ETH Price: $2,524.27 (-0.39%)

Transaction Decoder

Block:
17219987 at May-09-2023 02:40:35 AM +UTC
Transaction Fee:
0.015797212316723526 ETH $39.88
Gas Used:
208,743 Gas / 75.677806282 Gwei

Account State Difference:

  Address   Before After State Difference Code
0x1353fd9d...B906DE4E8
0x41B9FAEE...1b950aEED
114.207391783591372676 Eth
Nonce: 852
114.19159457127464915 Eth
Nonce: 853
0.015797212316723526
0x68d0F6d1...D9D6EEc2E
(builder0x69)
1.293932856719448401 Eth1.293953731019448401 Eth0.0000208743
0x8ABdb5c4...BEc416a24
0xFab34fD3...64B42B845

Execution Trace

DiamondPassRedeemer.purchase( purchases= )
  • DiamondExhibitionRegularPass.redeem( sender=0x41B9FAEEd6CAFF639D1DA708A9899821b950aEED, tokenId=303 )
  • DiamondExhibition.handleSale( to=0x41B9FAEEd6CAFF639D1DA708A9899821b950aEED, num=1, data=0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001 )
    • GenArt721CoreV3_Engine_Flex_PROOF.mint_Ecf( _to=0x68d0F6d1d99Bb830E17fFaA8aDB5BbeD9D6EEc2E, _projectId=6, _by=0x68d0F6d1d99Bb830E17fFaA8aDB5BbeD9D6EEc2E ) => ( _tokenId=6000288 )
      • 0x8abdb5c40bb6593550ff0027d15d8b6bec416a24.b628171a( )
        • GenArt721CoreV3_Engine_Flex_PROOF.setTokenHash_8PT( _tokenId=6000288, _hashSeed=69F592F70544EAC77BF473F21ADBF2BB3D8A7D378EA0381E2EA76E245677A866 )
          File 1 of 4: DiamondPassRedeemer
          // SPDX-License-Identifier: MIT
          // Copyright (c) 2021 the ethier authors (github.com/divergencetech/ethier)
          pragma solidity >=0.8.0 <0.9.0;
          import {IRedeemableToken} from "proof/redemption/interfaces/IRedeemableToken.sol";
          import {DiamondExhibition} from "../exhibition/DiamondExhibition.sol";
          import {DiamondExhibitionRegularPass} from "../passes/DiamondExhibitionRegularPass.sol";
          import {DiamondExhibitionChoicePass} from "../passes/DiamondExhibitionChoicePass.sol";
          import {DiamondExhibitionSeller} from "./DiamondExhibitionSeller.sol";
          /**
           * @notice Seller that redeems a Diamond Pass (either a Day One or Regular pass) for a piece in the Diamond Exhibition.
           */
          contract DiamondPassRedeemer is DiamondExhibitionSeller {
              /**
               * @notice Emitted when the callback to the `IRedeemableToken` contract fails.
               */
              error RedeemableCallbackFailed(IRedeemableToken token, bytes reason);
              /**
               * @notice Emitted when the `IRedeemableToken` contract is not a valid pass.
               */
              error InvalidRedeemableToken(IRedeemableToken token);
              /**
               * @notice The regular Diamond Exhibition pass.
               */
              DiamondExhibitionRegularPass public immutable regularPass;
              /**
               * @notice The Day One Diamond Exhibition pass.
               */
              DiamondExhibitionChoicePass public immutable dayOnePass;
              constructor(
                  DiamondExhibition exhibition,
                  DiamondExhibitionRegularPass regularPass_,
                  DiamondExhibitionChoicePass dayOnePass_
              ) DiamondExhibitionSeller(exhibition) {
                  dayOnePass = dayOnePass_;
                  regularPass = regularPass_;
              }
              /**
               * @notice Encodes a purchase.
               * @param redeemable The `IRedeemableToken` contract to redeem.
               * @param passId The tokenId of the pass to redeem.
               * @param projectId The project ID to purchase.
               */
              struct Purchase {
                  IRedeemableToken redeemable;
                  uint256 passId;
                  uint8 projectId;
              }
              /**
               * @notice Redeems the given passes and purchases pieces in the Diamond Exhibition.
               */
              function purchase(Purchase[] calldata purchases) external {
                  uint8[] memory projectIds = new uint8[](purchases.length);
                  for (uint256 i = 0; i < purchases.length; ++i) {
                      if (purchases[i].redeemable != regularPass && purchases[i].redeemable != dayOnePass) {
                          revert InvalidRedeemableToken(purchases[i].redeemable);
                      }
                      try purchases[i].redeemable.redeem(msg.sender, purchases[i].passId) {}
                      catch (bytes memory reason) {
                          revert RedeemableCallbackFailed(purchases[i].redeemable, reason);
                      }
                      projectIds[i] = purchases[i].projectId;
                  }
                  _purchase(msg.sender, projectIds);
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity ^0.8.0;
          /**
           * @notice Interface for a redeemable Voucher token preventing double spending
           * through internal book-keeping (e.g. burning the token, token property, etc.).
           * @dev Voucher tokens are intendent to be redeemed through a redeemer contract.
           */
          interface IRedeemableToken {
              /**
               * @notice Thrown if the redemption caller is not allowed to spend a given
               * voucher.
               */
              error RedeemerCallerNotAllowedToSpendVoucher(address sender, uint256 tokenId);
              /**
               * @notice Interface through which a `IRedeemer` contract informs the
               * voucher about its redemption.
               * @param sender The address that initiate the redemption on the
               * redeemer contract.
               * @param tokenId The voucher token to be redeemed.
               * @dev This function MUST be called by redeemer contracts.
               * @dev MUST revert with `RedeemerNotApproved` if the calling redeemer
               * contract is not approved to spend this voucher.
               * @dev MUST revert with `RedeemerCallerNotAllowedToSpendVoucher` if
               * sender is not allowed to spend tokenId.
               */
              function redeem(address sender, uint256 tokenId) external;
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity ^0.8.15;
          import {Strings} from "openzeppelin-contracts/utils/Strings.sol";
          import {GenArt721CoreV3_Engine_Flex_PROOF} from "artblocks-contracts/GenArt721CoreV3_Engine_Flex_PROOF.sol";
          import {ERC721A, ERC721ACommon, BaseTokenURI, ERC721ACommonBaseTokenURI} from "ethier/erc721/BaseTokenURI.sol";
          import {OperatorFilterOS} from "ethier/erc721/OperatorFilterOS.sol";
          import {artblocksTokenID} from "proof/artblocks/TokenIDMapping.sol";
          import {IGenArt721CoreContractV3_Mintable} from "proof/artblocks/IGenArt721CoreContractV3_Mintable.sol";
          import {SellableERC721ACommon} from "proof/sellers/sellable/SellableERC721ACommon.sol";
          import {ProjectsConfig} from "./ProjectsConfig.sol";
          import {TokenInfoManager} from "./TokenInfoManager.sol";
          /**
           * @notice Library for encoding and decoding purchase data for the Diamond Exhibition sellers.
           */
          library DiamondExhibitionLib {
              function encodePurchaseData(uint8[] memory projectIds) internal pure returns (bytes memory) {
                  return abi.encode(projectIds);
              }
              function dencodePurchaseData(bytes memory data) internal pure returns (uint8[] memory) {
                  return abi.decode(data, (uint8[]));
              }
          }
          /**
           * @notice Diamond Exhibition
           * @author David Huber (@cxkoda)
           * @custom:reviewer Arran Schlosberg (@divergencearran)
           */
          contract DiamondExhibition is
              ERC721ACommonBaseTokenURI,
              OperatorFilterOS,
              SellableERC721ACommon,
              ProjectsConfig,
              TokenInfoManager
          {
              // =================================================================================================================
              //                          Errors
              // =================================================================================================================
              /**
               * @notice Thrown if the number of requested purchases exceeds the number of remaining tokens.
               */
              error ExceedingMaxTotalSupply(uint256 num, uint256 numLeft);
              /**
               * @notice Thrown if a user attempts to purchase tokens from an exhausted project.
               */
              error ProjectExhausted(uint8 projectId);
              /**
               * @notice Thrown if a user attempts to purchase tokens from an invalid project.
               */
              error InvalidProject(uint8 projectId);
              // =================================================================================================================
              //                          Constants
              // =================================================================================================================
              /**
               * @notice The ArtBlocks engine flex contract.
               */
              GenArt721CoreV3_Engine_Flex_PROOF public immutable flex;
              /**
               * @notice The ArtBlocks engine flex contract or a minter multiplexer.
               */
              IGenArt721CoreContractV3_Mintable public immutable flexMintGateway;
              /**
               * @notice The maximum total number of tokens that can be minted.
               * @dev This is intentionally not a compile-time constant for the sake of testing.
               */
              uint256 public immutable maxTotalSupply;
              // =========================================================================
              //                          Storage
              // =================================================================================================================
              /**
               * @notice The number of tokens minted per project.
               */
              uint16[NUM_PROJECTS] internal _numPurchasedPerProject;
              // =================================================================================================================
              //                          Storage
              // =================================================================================================================
              struct ConstructorParams {
                  address admin;
                  address steerer;
                  address payable secondaryReceiver;
                  GenArt721CoreV3_Engine_Flex_PROOF flex;
                  IGenArt721CoreContractV3_Mintable flexMintGateway;
              }
              constructor(ConstructorParams memory params)
                  ERC721ACommon(params.admin, params.steerer, "Diamond Exhibition", "DIAMOND", params.secondaryReceiver, 500)
                  BaseTokenURI("https://metadata.proof.xyz/diamond-exhibition/")
              {
                  uint256 total;
                  uint256[NUM_PROJECTS] memory maxNumPerProject_ = _maxNumPerProject();
                  for (uint256 i = 0; i < NUM_PROJECTS; i++) {
                      total += maxNumPerProject_[i];
                  }
                  maxTotalSupply = total;
                  flex = params.flex;
                  flexMintGateway = params.flexMintGateway;
              }
              // =================================================================================================================
              //                          Selling
              // =================================================================================================================
              /**
               * @notice Assigns a project to a token.
               * @dev Mints from the associated ArtBlocks project if the project is a longform project.
               */
              function _assignProject(uint256 tokenId, uint8 projectId, uint256[NUM_PROJECTS] memory maxNumPerProject_)
                  internal
              {
                  if (projectId >= NUM_PROJECTS) {
                      revert InvalidProject(projectId);
                  }
                  uint16 numPurchased = _numPurchasedPerProject[projectId];
                  if (numPurchased >= maxNumPerProject_[projectId]) {
                      revert ProjectExhausted(projectId);
                  }
                  _numPurchasedPerProject[projectId] = numPurchased + 1;
                  if (_isLongformProject(projectId)) {
                      flexMintGateway.mint_Ecf(address(this), _artblocksProjectId(projectId), address(this));
                  }
                  _setTokenInfo(tokenId, projectId, numPurchased /* edition */ );
              }
              /**
               * @inheritdoc SellableERC721ACommon
               */
              function _handleSale(address to, uint64 num, bytes calldata data) internal virtual override {
                  if (num + _totalMinted() > maxTotalSupply) {
                      revert ExceedingMaxTotalSupply(num, maxTotalSupply - _totalMinted());
                  }
                  uint8[] memory projectIds = DiamondExhibitionLib.dencodePurchaseData(data);
                  assert(projectIds.length == num);
                  uint256 tokenId = _nextTokenId();
                  uint256[NUM_PROJECTS] memory maxNumPerProject_ = _maxNumPerProject();
                  for (uint256 i = 0; i < num; ++i) {
                      _assignProject(tokenId++, projectIds[i], maxNumPerProject_);
                  }
                  SellableERC721ACommon._handleSale(to, num, data);
              }
              /**
               * @inheritdoc ERC721A
               */
              function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
                  TokenInfo memory info = _tokenInfo(tokenId);
                  if (projectType(info.projectId) == ProjectType.Curated) {
                      return string.concat(_baseURI(), Strings.toString(tokenId));
                  }
                  return flex.tokenURI(artblocksTokenID(_artblocksProjectId(info.projectId), info.edition));
              }
              /**
               * @notice Returns all tokenIds for a given project.
               * @dev Intended for front-end consumption and not optimised for gas.
               */
              function tokenIdsByProjectId(uint8 projectId) external view returns (uint256[] memory) {
                  uint256[] memory tokenIds = new uint256[](_numPurchasedPerProject[projectId]);
                  uint256 cursor;
                  uint256 supply = totalSupply();
                  for (uint256 tokenId = 0; tokenId < supply; ++tokenId) {
                      if (_tokenInfo(tokenId).projectId == projectId) {
                          tokenIds[cursor++] = tokenId;
                      }
                  }
                  return tokenIds;
              }
              /**
               * @notice Returns the number of tokens purchased for each project.
               * @dev Intended for front-end consumption and not optimised for gas.
               */
              function numPurchasedPerProject() external view returns (uint16[NUM_PROJECTS] memory) {
                  return _numPurchasedPerProject;
              }
              // =================================================================================================================
              //                          Inheritance resolution
              // =================================================================================================================
              /**
               * @notice Helper function that returns true if the token belongs to a longform project.
               */
              function _isLongformToken(uint256 tokenId) internal view virtual returns (bool) {
                  return _isLongformProject(_tokenInfo(tokenId).projectId);
              }
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(ERC721ACommon, ERC721ACommonBaseTokenURI, SellableERC721ACommon)
                  returns (bool)
              {
                  return ERC721ACommonBaseTokenURI.supportsInterface(interfaceId);
              }
              function _baseURI() internal view virtual override(ERC721A, ERC721ACommonBaseTokenURI) returns (string memory) {
                  return ERC721ACommonBaseTokenURI._baseURI();
              }
              function setApprovalForAll(address operator, bool approved) public virtual override(ERC721A, OperatorFilterOS) {
                  ERC721A.setApprovalForAll(operator, approved);
              }
              function approve(address operator, uint256 tokenId) public payable virtual override(ERC721A, OperatorFilterOS) {
                  if (_isLongformToken(tokenId)) {
                      ERC721A.approve(operator, tokenId);
                  } else {
                      OperatorFilterOS.approve(operator, tokenId);
                  }
              }
              function transferFrom(address from, address to, uint256 tokenId)
                  public
                  payable
                  virtual
                  override(ERC721A, OperatorFilterOS)
              {
                  if (_isLongformToken(tokenId)) {
                      ERC721A.transferFrom(from, to, tokenId);
                  } else {
                      OperatorFilterOS.transferFrom(from, to, tokenId);
                  }
              }
              function safeTransferFrom(address from, address to, uint256 tokenId)
                  public
                  payable
                  virtual
                  override(ERC721A, OperatorFilterOS)
              {
                  if (_isLongformToken(tokenId)) {
                      ERC721A.safeTransferFrom(from, to, tokenId);
                  } else {
                      OperatorFilterOS.safeTransferFrom(from, to, tokenId);
                  }
              }
              function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data)
                  public
                  payable
                  virtual
                  override(ERC721A, OperatorFilterOS)
              {
                  if (_isLongformToken(tokenId)) {
                      ERC721A.safeTransferFrom(from, to, tokenId, data);
                  } else {
                      OperatorFilterOS.safeTransferFrom(from, to, tokenId, data);
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity ^0.8.15;
          import {ERC721ACommon, BaseTokenURI} from "ethier/erc721/BaseTokenURI.sol";
          import {SellableRedeemableRestrictableERC721} from "./SellableRedeemableRestrictableERC721.sol";
          /**
           * @title Diamond Exhibition: Regular Pass
           * @notice A token claimable by all diamond nested Moonbirds that did not receive a Day One Pass, redeemable for
           * diamond exhibition artworks.
           */
          contract DiamondExhibitionRegularPass is SellableRedeemableRestrictableERC721 {
              constructor(address admin, address steerer, address payable secondaryReceiver)
                  ERC721ACommon(admin, steerer, "Diamond Exhibition: Regular Pass", "REGULAR", secondaryReceiver, 500)
                  BaseTokenURI("https://metadata.proof.xyz/diamond-exhibition-pass/regular/")
              {}
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity ^0.8.15;
          import {ERC721ACommon, BaseTokenURI} from "ethier/erc721/BaseTokenURI.sol";
          import {RoleGatedFreeOfCharge} from "proof/sellers/presets/RoleGatedFreeOfCharge.sol";
          import {SellableRedeemableRestrictableERC721} from "./SellableRedeemableRestrictableERC721.sol";
          /**
           * @title Diamond Exhibition: Choice Pass
           * @notice A redeemable token airdropped to all day-1 nested Moonbirds that allows mints with preferences from the diamond exhibition.
           */
          contract DiamondExhibitionChoicePass is SellableRedeemableRestrictableERC721 {
              /**
               * @notice The seller handling the airdrop.
               */
              RoleGatedFreeOfCharge public airdropper;
              constructor(address admin, address steerer, address payable secondaryReceiver, uint64 numDayOneBirds)
                  ERC721ACommon(admin, steerer, "Diamond Exhibition: Day One Pass", "DAY1PASS", secondaryReceiver, 500)
                  BaseTokenURI("https://metadata.proof.xyz/diamond-exhibition-pass/day-one/")
              {
                  airdropper = new RoleGatedFreeOfCharge(admin , steerer, this, numDayOneBirds);
                  _grantRole(AUTHORISED_SELLER_ROLE, address(airdropper));
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright (c) 2021 the ethier authors (github.com/divergencetech/ethier)
          pragma solidity >=0.8.0 <0.9.0;
          import {SellableCallbacker} from "proof/sellers/base/SellableCallbacker.sol";
          import {Seller} from "proof/sellers/base/Seller.sol";
          import {DiamondExhibitionLib, DiamondExhibition} from "../exhibition/DiamondExhibition.sol";
          /**
           * @notice Seller module to purchase Diamond Exhibition tokens.
           */
          abstract contract DiamondExhibitionSeller is Seller, SellableCallbacker {
              constructor(DiamondExhibition exhibition) SellableCallbacker(exhibition) {}
              /**
               * @notice Convenience function for inheriting sellers. Purchases tokens of given project IDs free-of-charge.
               */
              function _purchase(address to, uint8[] memory projectIds) internal {
                  _purchase(
                      to, uint64(projectIds.length), /* total cost */ 0, DiamondExhibitionLib.encodePurchaseData(projectIds)
                  );
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)
          pragma solidity ^0.8.0;
          import "./math/Math.sol";
          /**
           * @dev String operations.
           */
          library Strings {
              bytes16 private constant _SYMBOLS = "0123456789abcdef";
              uint8 private constant _ADDRESS_LENGTH = 20;
              /**
               * @dev Converts a `uint256` to its ASCII `string` decimal representation.
               */
              function toString(uint256 value) internal pure returns (string memory) {
                  unchecked {
                      uint256 length = Math.log10(value) + 1;
                      string memory buffer = new string(length);
                      uint256 ptr;
                      /// @solidity memory-safe-assembly
                      assembly {
                          ptr := add(buffer, add(32, length))
                      }
                      while (true) {
                          ptr--;
                          /// @solidity memory-safe-assembly
                          assembly {
                              mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                          }
                          value /= 10;
                          if (value == 0) break;
                      }
                      return buffer;
                  }
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
               */
              function toHexString(uint256 value) internal pure returns (string memory) {
                  unchecked {
                      return toHexString(value, Math.log256(value) + 1);
                  }
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
               */
              function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                  bytes memory buffer = new bytes(2 * length + 2);
                  buffer[0] = "0";
                  buffer[1] = "x";
                  for (uint256 i = 2 * length + 1; i > 1; --i) {
                      buffer[i] = _SYMBOLS[value & 0xf];
                      value >>= 4;
                  }
                  require(value == 0, "Strings: hex length insufficient");
                  return string(buffer);
              }
              /**
               * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
               */
              function toHexString(address addr) internal pure returns (string memory) {
                  return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
              }
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          pragma solidity 0.8.17;
          // Created By: Art Blocks Inc.
          import "./interfaces/0.8.x/IRandomizerV2.sol";
          import "./interfaces/0.8.x/IAdminACLV0.sol";
          import "./interfaces/0.8.x/IEngineRegistryV0.sol";
          import "./interfaces/0.8.x/IGenArt721CoreContractV3_Engine_Flex.sol";
          import "./interfaces/0.8.x/IDependencyRegistryCompatibleV0.sol";
          import "./interfaces/0.8.x/IManifold.sol";
          import "@openzeppelin-4.7/contracts/access/Ownable.sol";
          import "./libs/0.8.x/ERC721_PackedHashSeed.sol";
          import "./libs/0.8.x/BytecodeStorage.sol";
          import "./libs/0.8.x/Bytes32Strings.sol";
          /**
           * @title Art Blocks Engine Flex ERC-721 core contract, V3.
           * @author Art Blocks Inc.
           * @notice Privileged Roles and Ownership:
           * This contract is designed to be managed, with progressively limited powers
           * as a project progresses from active to locked.
           * Privileged roles and abilities are controlled by the admin ACL contract and
           * artists. Both of these roles hold extensive power and can arbitrarily
           * control and modify portions of projects, dependent upon project state. After
           * a project is locked, important project metadata fields are locked including
           * the project name, artist name, and script and display details. Edition size
           * can never be increased.
           * Care must be taken to ensure that the admin ACL contract and artist
           * addresses are secure behind a multi-sig or other access control mechanism.
           * ----------------------------------------------------------------------------
           * The following functions are restricted to the Admin ACL contract:
           * - updateArtblocksDependencyRegistryAddress
           * - updateProviderSalesAddresses
           * - updateProviderPrimarySalesPercentages (up to 100%)
           * - updateProviderSecondarySalesBPS (up to 100%)
           * - updateMinterContract
           * - updateRandomizerAddress
           * - toggleProjectIsActive
           * - addProject
           * - forbidNewProjects (forever forbidding new projects)
           * - updateDefaultBaseURI (used to initialize new project base URIs)
           * - updateIPFSGateway
           * - updateArweaveGateway
           * ----------------------------------------------------------------------------
           * The following functions are restricted to either the the Artist address or
           * the Admin ACL contract, only when the project is not locked:
           * - updateProjectName
           * - updateProjectArtistName
           * - updateProjectLicense
           * - Change project script via addProjectScript, updateProjectScript,
           *   and removeProjectLastScript
           * - updateProjectScriptType
           * - updateProjectAspectRatio
           * ----------------------------------------------------------------------------
           * The following functions are restricted to only the Artist or Admin ACL
           * contract of a valid project ID:
           * - proposeArtistPaymentAddressesAndSplits (Note that this has to be accepted
           *   by adminAcceptArtistAddressesAndSplits to take effect, which is restricted
           *   to the Admin ACL contract, or the artist if the core contract owner has
           *   renounced ownership. Also note that a proposal will be automatically
           *   accepted if the artist only proposes changed payee percentages without
           *   modifying any payee addresses, or is only removing payee addresses, or
           *   if the global config `autoApproveArtistSplitProposals` is set to `true`.)
           * - toggleProjectIsPaused (note the artist can still mint while paused)
           * - updateProjectSecondaryMarketRoyaltyPercentage (up to
           *   ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE percent)
           * - updateProjectWebsite
           * - updateProjectMaxInvocations (to a number greater than or equal to the
           *   current number of invocations, and less than current project maximum
           *   invocations)
           * - updateProjectBaseURI (controlling the base URI for tokens in the project)
           * ----------------------------------------------------------------------------
           * The following function is restricted to either the Admin ACL contract, or
           * the Artist address if the core contract owner has renounced ownership:
           * - adminAcceptArtistAddressesAndSplits
           * - updateProjectArtistAddress (owner ultimately controlling the project and
           *   its and-on revenue, unless owner has renounced ownership)
           * ----------------------------------------------------------------------------
           * The following function is restricted to the artist when a project is
           * unlocked, and only callable by Admin ACL contract when a project is locked:
           * - updateProjectDescription
           * ----------------------------------------------------------------------------
           * The following functions for managing external asset dependencies are restricted
           * to projects with external asset dependencies that are unlocked:
           * - lockProjectExternalAssetDependencies
           * - updateProjectExternalAssetDependency
           * - removeProjectExternalAssetDependency
           * - addProjectExternalAssetDependency
           * ----------------------------------------------------------------------------
           * The following function is restricted to owner calling directly:
           * - transferOwnership
           * - renounceOwnership
           * ----------------------------------------------------------------------------
           * The following configuration variables are set at time of contract deployment,
           * and not modifiable thereafter (immutable after the point of deployment):
           * - (bool) autoApproveArtistSplitProposals
           * ----------------------------------------------------------------------------
           * Additional admin and artist privileged roles may be described on minters,
           * registries, and other contracts that may interact with this core contract.
           */
          contract GenArt721CoreV3_Engine_Flex_PROOF is
              ERC721_PackedHashSeed,
              Ownable,
              IDependencyRegistryCompatibleV0,
              IManifold,
              IGenArt721CoreContractV3_Engine_Flex
          {
              using BytecodeStorage for string;
              using BytecodeStorage for address;
              using Bytes32Strings for bytes32;
              uint256 constant ONE_HUNDRED = 100;
              uint256 constant ONE_MILLION = 1_000_000;
              uint24 constant ONE_MILLION_UINT24 = 1_000_000;
              uint256 constant FOUR_WEEKS_IN_SECONDS = 2_419_200;
              uint8 constant AT_CHARACTER_CODE = uint8(bytes1("@")); // 0x40
              // numeric constants
              uint256 constant MAX_PROVIDER_SECONDARY_SALES_BPS = 10000; // 10_000 BPS = 100%
              uint256 constant ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE = 95; // 95%
              // This contract emits generic events that contain fields that indicate
              // which parameter has been updated. This is sufficient for application
              // state management, while also simplifying the contract and indexing code.
              // This was done as an alternative to having custom events that emit what
              // field-values have changed for each event, given that changed values can
              // be introspected by indexers due to the design of this smart contract
              // exposing these state changes via publicly viewable fields.
              //
              // The following fields are used to indicate which contract-level parameter
              // has been updated in the `PlatformUpdated` event:
              bytes32 constant FIELD_NEXT_PROJECT_ID = "nextProjectId";
              bytes32 constant FIELD_NEW_PROJECTS_FORBIDDEN = "newProjectsForbidden";
              bytes32 constant FIELD_DEFAULT_BASE_URI = "defaultBaseURI";
              bytes32 constant FIELD_RANDOMIZER_ADDRESS = "randomizerAddress";
              bytes32 constant FIELD_ARTBLOCKS_DEPENDENCY_REGISTRY_ADDRESS =
                  "dependencyRegistryAddress";
              bytes32 constant FIELD_PROVIDER_SALES_ADDRESSES = "providerSalesAddresses";
              bytes32 constant FIELD_PROVIDER_PRIMARY_SALES_PERCENTAGES =
                  "providerPrimaryPercentages";
              bytes32 constant FIELD_PROVIDER_SECONDARY_SALES_BPS =
                  "providerSecondaryBPS";
              // The following fields are used to indicate which project-level parameter
              // has been updated in the `ProjectUpdated` event:
              bytes32 constant FIELD_PROJECT_COMPLETED = "completed";
              bytes32 constant FIELD_PROJECT_ACTIVE = "active";
              bytes32 constant FIELD_PROJECT_ARTIST_ADDRESS = "artistAddress";
              bytes32 constant FIELD_PROJECT_PAUSED = "paused";
              bytes32 constant FIELD_PROJECT_CREATED = "created";
              bytes32 constant FIELD_PROJECT_NAME = "name";
              bytes32 constant FIELD_PROJECT_ARTIST_NAME = "artistName";
              bytes32 constant FIELD_PROJECT_SECONDARY_MARKET_ROYALTY_PERCENTAGE =
                  "royaltyPercentage";
              bytes32 constant FIELD_PROJECT_DESCRIPTION = "description";
              bytes32 constant FIELD_PROJECT_WEBSITE = "website";
              bytes32 constant FIELD_PROJECT_LICENSE = "license";
              bytes32 constant FIELD_PROJECT_MAX_INVOCATIONS = "maxInvocations";
              bytes32 constant FIELD_PROJECT_SCRIPT = "script";
              bytes32 constant FIELD_PROJECT_SCRIPT_TYPE = "scriptType";
              bytes32 constant FIELD_PROJECT_ASPECT_RATIO = "aspectRatio";
              bytes32 constant FIELD_PROJECT_BASE_URI = "baseURI";
              /// Dependency registry managed by Art Blocks
              address public artblocksDependencyRegistryAddress;
              /// current randomizer contract
              IRandomizerV2 public randomizerContract;
              /// append-only array of all randomizer contract addresses ever used by
              /// this contract
              address[] private _historicalRandomizerAddresses;
              /// admin ACL contract
              IAdminACLV0 public adminACLContract;
              struct Project {
                  uint24 invocations;
                  uint24 maxInvocations;
                  uint24 scriptCount;
                  // max uint64 ~= 1.8e19 sec ~= 570 billion years
                  uint64 completedTimestamp;
                  bool active;
                  bool paused;
                  string name;
                  string artist;
                  string description;
                  string website;
                  string license;
                  string projectBaseURI;
                  bytes32 scriptTypeAndVersion;
                  string aspectRatio;
                  // mapping from script index to address storing script in bytecode
                  mapping(uint256 => address) scriptBytecodeAddresses;
                  bool externalAssetDependenciesLocked;
                  uint24 externalAssetDependencyCount;
                  mapping(uint256 => ExternalAssetDependency) externalAssetDependencies;
              }
              mapping(uint256 => Project) projects;
              string public preferredIPFSGateway;
              string public preferredArweaveGateway;
              /// packed struct containing project financial information
              struct ProjectFinance {
                  address payable additionalPayeePrimarySales;
                  // packed uint: max of 95, max uint8 = 255
                  uint8 secondaryMarketRoyaltyPercentage;
                  address payable additionalPayeeSecondarySales;
                  // packed uint: max of 100, max uint8 = 255
                  uint8 additionalPayeeSecondarySalesPercentage;
                  address payable artistAddress;
                  // packed uint: max of 100, max uint8 = 255
                  uint8 additionalPayeePrimarySalesPercentage;
              }
              // Project financials mapping
              mapping(uint256 => ProjectFinance) projectIdToFinancials;
              /// hash of artist's proposed payment updates to be approved by admin
              mapping(uint256 => bytes32) public proposedArtistAddressesAndSplitsHash;
              /// The render provider payment address for all primary sales revenues
              /// (packed)
              address payable public renderProviderPrimarySalesAddress;
              /// Percentage of primary sales revenue allocated to the render provider
              /// (packed)
              // packed uint: max of 100, max uint8 = 255
              uint8 private _renderProviderPrimarySalesPercentage = 10;
              /// The platform provider payment address for all primary sales revenues
              /// (packed)
              address payable public platformProviderPrimarySalesAddress;
              /// Percentage of primary sales revenue allocated to the platform provider
              /// (packed)
              // packed uint: max of 100, max uint8 = 255
              uint8 private _platformProviderPrimarySalesPercentage = 10;
              /// The render provider payment address for all secondary sales royalty
              /// revenues
              address payable public renderProviderSecondarySalesAddress;
              /// Basis Points of secondary sales royalties allocated to the
              /// render provider
              uint256 public renderProviderSecondarySalesBPS = 250;
              /// The platform provider payment address for all secondary sales royalty
              /// revenues
              address payable public platformProviderSecondarySalesAddress;
              /// Basis Points of secondary sales royalties allocated to the
              /// platform provider
              uint256 public platformProviderSecondarySalesBPS = 250;
              /// single minter allowed for this core contract
              address public minterContract;
              /// starting (initial) project ID on this contract
              uint256 public immutable startingProjectId;
              /// next project ID to be created
              uint248 private _nextProjectId;
              /// bool indicating if adding new projects is forbidden;
              /// default behavior is to allow new projects
              bool public newProjectsForbidden;
              /// configuration variable (determined at time of deployment)
              /// that determines whether or not admin approval^ should be required
              /// to accept artist address change proposals, or if these proposals
              /// should always auto-approve, as determined by the business process
              /// requirements of the Engine partner using this contract.
              ///
              /// ^does not apply in the case where contract-ownership itself is revoked
              bool public immutable autoApproveArtistSplitProposals;
              /// version & type of this core contract
              bytes32 constant CORE_VERSION = "v3.1.2";
              function coreVersion() external pure returns (string memory) {
                  return CORE_VERSION.toString();
              }
              bytes32 constant CORE_TYPE = "GenArt721CoreV3_Engine_Flex";
              function coreType() external pure returns (string memory) {
                  return CORE_TYPE.toString();
              }
              /// default base URI to initialize all new project projectBaseURI values to
              string public defaultBaseURI;
              function _onlyUnlockedProjectExternalAssetDependencies(
                  uint256 _projectId
              ) internal view {
                  require(
                      !projects[_projectId].externalAssetDependenciesLocked,
                      "External dependencies locked"
                  );
              }
              function _onlyNonZeroAddress(address _address) internal pure {
                  require(_address != address(0), "Must input non-zero address");
              }
              function _onlyNonEmptyString(string memory _string) internal pure {
                  require(bytes(_string).length != 0, "Must input non-empty string");
              }
              function _onlyValidTokenId(uint256 _tokenId) internal view {
                  require(_exists(_tokenId), "Token ID does not exist");
              }
              function _onlyValidProjectId(uint256 _projectId) internal view {
                  require(
                      (_projectId >= startingProjectId) && (_projectId < _nextProjectId),
                      "Project ID does not exist"
                  );
              }
              function _onlyUnlocked(uint256 _projectId) internal view {
                  // Note: calling `_projectUnlocked` enforces that the `_projectId`
                  //       passed in is valid.`
                  require(_projectUnlocked(_projectId), "Only if unlocked");
              }
              function _onlyAdminACL(bytes4 _selector) internal {
                  require(
                      adminACLAllowed(msg.sender, address(this), _selector),
                      "Only Admin ACL allowed"
                  );
              }
              function _onlyArtistOrAdminACL(
                  uint256 _projectId,
                  bytes4 _selector
              ) internal {
                  require(
                      msg.sender == projectIdToFinancials[_projectId].artistAddress ||
                          adminACLAllowed(msg.sender, address(this), _selector),
                      "Only artist or Admin ACL allowed"
                  );
              }
              /**
               * This modifier allows the artist of a project to call a function if the
               * owner of the contract has renounced ownership. This is to allow the
               * contract to continue to function if the owner decides to renounce
               * ownership.
               */
              function _onlyAdminACLOrRenouncedArtist(
                  uint256 _projectId,
                  bytes4 _selector
              ) internal {
                  require(
                      adminACLAllowed(msg.sender, address(this), _selector) ||
                          (owner() == address(0) &&
                              msg.sender ==
                              projectIdToFinancials[_projectId].artistAddress),
                      "Only Admin ACL allowed, or artist if owner has renounced"
                  );
              }
              /**
               * @notice Initializes contract.
               * @param _tokenName Name of token.
               * @param _tokenSymbol Token symbol.
               * @param _randomizerContract Randomizer contract.
               * @param _adminACLContract Address of admin access control contract, to be
               * set as contract owner.
               * @param _startingProjectId The initial next project ID.
               * @param _autoApproveArtistSplitProposals Whether or not to always
               * auto-approve proposed artist split updates.
               * @dev _startingProjectId should be set to a value much, much less than
               * max(uint248), but an explicit input type of `uint248` is used as it is
               * safer to cast up to `uint256` than it is to cast down for the purposes
               * of setting `_nextProjectId`.
               */
              constructor(
                  string memory _tokenName,
                  string memory _tokenSymbol,
                  address _renderProviderAddress,
                  address _platformProviderAddress,
                  address _randomizerContract,
                  address _adminACLContract,
                  uint248 _startingProjectId,
                  bool _autoApproveArtistSplitProposals,
                  address _engineRegistryContract
              ) ERC721_PackedHashSeed(_tokenName, _tokenSymbol) {
                  _onlyNonZeroAddress(_renderProviderAddress);
                  _onlyNonZeroAddress(_platformProviderAddress);
                  _onlyNonZeroAddress(_randomizerContract);
                  _onlyNonZeroAddress(_adminACLContract);
                  // setup immutable `autoApproveArtistSplitProposals` config
                  autoApproveArtistSplitProposals = _autoApproveArtistSplitProposals;
                  // record contracts starting project ID
                  // casting-up is safe
                  startingProjectId = uint256(_startingProjectId);
                  _updateProviderSalesAddresses(
                      _renderProviderAddress,
                      _renderProviderAddress,
                      _platformProviderAddress,
                      _platformProviderAddress
                  );
                  _updateRandomizerAddress(_randomizerContract);
                  // set AdminACL management contract as owner
                  _transferOwnership(_adminACLContract);
                  // initialize default base URI
                  _updateDefaultBaseURI(
                      string.concat(
                          "https://token.artblocks.io/",
                          toHexString(address(this)),
                          "/"
                      )
                  );
                  // initialize next project ID
                  _nextProjectId = _startingProjectId;
                  emit PlatformUpdated(FIELD_NEXT_PROJECT_ID);
                  // register contract as an Engine contract
                  IEngineRegistryV0(_engineRegistryContract).registerContract(
                      address(this),
                      CORE_VERSION,
                      CORE_TYPE
                  );
              }
              /**
               * @notice Updates preferredIPFSGateway to `_gateway`.
               */
              function updateIPFSGateway(string calldata _gateway) public {
                  _onlyAdminACL(this.updateIPFSGateway.selector);
                  preferredIPFSGateway = _gateway;
                  emit GatewayUpdated(ExternalAssetDependencyType.IPFS, _gateway);
              }
              /**
               * @notice Updates preferredArweaveGateway to `_gateway`.
               */
              function updateArweaveGateway(string calldata _gateway) public {
                  _onlyAdminACL(this.updateArweaveGateway.selector);
                  preferredArweaveGateway = _gateway;
                  emit GatewayUpdated(ExternalAssetDependencyType.ARWEAVE, _gateway);
              }
              /**
               * @notice Locks external asset dependencies for project `_projectId`.
               */
              function lockProjectExternalAssetDependencies(uint256 _projectId) external {
                  _onlyUnlockedProjectExternalAssetDependencies(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.lockProjectExternalAssetDependencies.selector
                  );
                  projects[_projectId].externalAssetDependenciesLocked = true;
                  emit ProjectExternalAssetDependenciesLocked(_projectId);
              }
              /**
               * @notice Updates external asset dependency for project `_projectId`.
               * @param _projectId Project to be updated.
               * @param _index Asset index.
               * @param _cidOrData Asset cid (Content identifier) or data string to be translated into bytecode.
               * @param _dependencyType Asset dependency type.
               *  0 - IPFS
               *  1 - ARWEAVE
               *  2 - ONCHAIN
               */
              function updateProjectExternalAssetDependency(
                  uint256 _projectId,
                  uint256 _index,
                  string memory _cidOrData,
                  ExternalAssetDependencyType _dependencyType
              ) external {
                  _onlyUnlockedProjectExternalAssetDependencies(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.updateProjectExternalAssetDependency.selector
                  );
                  uint24 assetCount = projects[_projectId].externalAssetDependencyCount;
                  require(_index < assetCount, "Asset index out of range");
                  ExternalAssetDependency storage _oldDependency = projects[_projectId]
                      .externalAssetDependencies[_index];
                  ExternalAssetDependencyType _oldDependencyType = _oldDependency
                      .dependencyType;
                  projects[_projectId]
                      .externalAssetDependencies[_index]
                      .dependencyType = _dependencyType;
                  // if the incoming dependency type is onchain, we need to write the data to bytecode
                  if (_dependencyType == ExternalAssetDependencyType.ONCHAIN) {
                      if (_oldDependencyType != ExternalAssetDependencyType.ONCHAIN) {
                          // we only need to set the cid to an empty string if we are replacing an offchain asset
                          // an onchain asset will already have an empty cid
                          projects[_projectId].externalAssetDependencies[_index].cid = "";
                      }
                      projects[_projectId]
                          .externalAssetDependencies[_index]
                          .bytecodeAddress = _cidOrData.writeToBytecode();
                      // we don't want to emit data, so we emit the cid as an empty string
                      _cidOrData = "";
                  } else {
                      projects[_projectId]
                          .externalAssetDependencies[_index]
                          .cid = _cidOrData;
                  }
                  emit ExternalAssetDependencyUpdated(
                      _projectId,
                      _index,
                      _cidOrData,
                      _dependencyType,
                      assetCount
                  );
              }
              /**
               * @notice Removes external asset dependency for project `_projectId` at index `_index`.
               * Removal is done by swapping the element to be removed with the last element in the array, then deleting this last element.
               * Assets with indices higher than `_index` can have their indices adjusted as a result of this operation.
               * @param _projectId Project to be updated.
               * @param _index Asset index
               */
              function removeProjectExternalAssetDependency(
                  uint256 _projectId,
                  uint256 _index
              ) external {
                  _onlyUnlockedProjectExternalAssetDependencies(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.removeProjectExternalAssetDependency.selector
                  );
                  uint24 assetCount = projects[_projectId].externalAssetDependencyCount;
                  require(_index < assetCount, "Asset index out of range");
                  uint24 lastElementIndex = assetCount - 1;
                  // copy last element to index of element to be removed
                  projects[_projectId].externalAssetDependencies[_index] = projects[
                      _projectId
                  ].externalAssetDependencies[lastElementIndex];
                  delete projects[_projectId].externalAssetDependencies[lastElementIndex];
                  projects[_projectId].externalAssetDependencyCount = lastElementIndex;
                  emit ExternalAssetDependencyRemoved(_projectId, _index);
              }
              /**
               * @notice Adds external asset dependency for project `_projectId`.
               * @param _projectId Project to be updated.
               * @param _cidOrData Asset cid (Content identifier) or data string to be translated into bytecode.
               * @param _dependencyType Asset dependency type.
               *  0 - IPFS
               *  1 - ARWEAVE
               *  2 - ONCHAIN
               */
              function addProjectExternalAssetDependency(
                  uint256 _projectId,
                  string memory _cidOrData,
                  ExternalAssetDependencyType _dependencyType
              ) external {
                  _onlyUnlockedProjectExternalAssetDependencies(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.addProjectExternalAssetDependency.selector
                  );
                  uint24 assetCount = projects[_projectId].externalAssetDependencyCount;
                  address _bytecodeAddress = address(0);
                  // if the incoming dependency type is onchain, we need to write the data to bytecode
                  if (_dependencyType == ExternalAssetDependencyType.ONCHAIN) {
                      _bytecodeAddress = _cidOrData.writeToBytecode();
                      // we don't want to emit data, so we emit the cid as an empty string
                      _cidOrData = "";
                  }
                  ExternalAssetDependency memory asset = ExternalAssetDependency({
                      cid: _cidOrData,
                      dependencyType: _dependencyType,
                      bytecodeAddress: _bytecodeAddress
                  });
                  projects[_projectId].externalAssetDependencies[assetCount] = asset;
                  projects[_projectId].externalAssetDependencyCount = assetCount + 1;
                  emit ExternalAssetDependencyUpdated(
                      _projectId,
                      assetCount,
                      _cidOrData,
                      _dependencyType,
                      assetCount + 1
                  );
              }
              /**
               * @notice Mints a token from project `_projectId` and sets the
               * token's owner to `_to`. Hash may or may not be assigned to the token
               * during the mint transaction, depending on the randomizer contract.
               * @param _to Address to be the minted token's owner.
               * @param _projectId Project ID to mint a token on.
               * @param _by Purchaser of minted token.
               * @return _tokenId The ID of the minted token.
               * @dev sender must be the allowed minterContract
               * @dev name of function is optimized for gas usage
               */
              function mint_Ecf(
                  address _to,
                  uint256 _projectId,
                  address _by
              ) external returns (uint256 _tokenId) {
                  // CHECKS
                  require(msg.sender == minterContract, "Must mint from minter contract");
                  Project storage project = projects[_projectId];
                  // load invocations into memory
                  uint24 invocationsBefore = project.invocations;
                  uint24 invocationsAfter;
                  unchecked {
                      // invocationsBefore guaranteed <= maxInvocations <= 1_000_000,
                      // 1_000_000 << max uint24, so no possible overflow
                      invocationsAfter = invocationsBefore + 1;
                  }
                  uint24 maxInvocations = project.maxInvocations;
                  require(
                      invocationsBefore < maxInvocations,
                      "Must not exceed max invocations"
                  );
                  require(
                      project.active ||
                          _by == projectIdToFinancials[_projectId].artistAddress,
                      "Project must exist and be active"
                  );
                  require(
                      !project.paused ||
                          _by == projectIdToFinancials[_projectId].artistAddress,
                      "Purchases are paused."
                  );
                  // EFFECTS
                  // increment project's invocations
                  project.invocations = invocationsAfter;
                  uint256 thisTokenId;
                  unchecked {
                      // invocationsBefore is uint24 << max uint256. In production use,
                      // _projectId * ONE_MILLION must be << max uint256, otherwise
                      // tokenIdToProjectId function become invalid.
                      // Therefore, no risk of overflow
                      thisTokenId = (_projectId * ONE_MILLION) + invocationsBefore;
                  }
                  // mark project as completed if hit max invocations
                  if (invocationsAfter == maxInvocations) {
                      _completeProject(_projectId);
                  }
                  // INTERACTIONS
                  _mint(_to, thisTokenId);
                  // token hash is updated by the randomizer contract on V3
                  randomizerContract.assignTokenHash(thisTokenId);
                  // Do not need to also log `projectId` in event, as the `projectId` for
                  // a given token can be derived from the `tokenId` with:
                  //   projectId = tokenId / 1_000_000
                  emit Mint(_to, thisTokenId);
                  return thisTokenId;
              }
              /**
               * @notice Sets the hash seed for a given token ID `_tokenId`.
               * May only be called by the current randomizer contract.
               * May only be called for tokens that have not already been assigned a
               * non-zero hash.
               * @param _tokenId Token ID to set the hash for.
               * @param _hashSeed Hash seed to set for the token ID. Only last 12 bytes
               * will be used.
               * @dev gas-optimized function name because called during mint sequence
               * @dev if a separate event is required when the token hash is set, e.g.
               * for indexing purposes, it must be emitted by the randomizer. This is to
               * minimize gas when minting.
               */
              function setTokenHash_8PT(uint256 _tokenId, bytes32 _hashSeed) external {
                  _onlyValidTokenId(_tokenId);
                  OwnerAndHashSeed storage ownerAndHashSeed = _ownersAndHashSeeds[
                      _tokenId
                  ];
                  require(
                      msg.sender == address(randomizerContract),
                      "Only randomizer may set"
                  );
                  require(
                      ownerAndHashSeed.hashSeed == bytes12(0),
                      "Token hash already set"
                  );
                  require(_hashSeed != bytes12(0), "No zero hash seed");
                  ownerAndHashSeed.hashSeed = bytes12(_hashSeed);
              }
              /**
               * @notice Allows owner (AdminACL) to revoke ownership of the contract.
               * Note that the contract is intended to continue to function after the
               * owner renounces ownership, but no new projects will be able to be added.
               * Renouncing ownership will leave the contract without an owner,
               * thereby removing any functionality that is only available to the
               * owner/AdminACL contract. The same is true for any dependent contracts
               * that also integrate with the owner/AdminACL contract (e.g. potentially
               * minter suite contracts, registry contracts, etc.).
               * After renouncing ownership, artists will be in control of updates to
               * their payment addresses and splits (see modifier
               * onlyAdminACLOrRenouncedArtist`).
               * While there is no currently intended reason to call this method based on
               * typical Engine partner business practices, this method exists to allow
               * artists to continue to maintain the limited set of contract
               * functionality that exists post-project-lock in an environment in which
               * there is no longer an admin maintaining this smart contract.
               * @dev This function is intended to be called directly by the AdminACL,
               * not by an address allowed by the AdminACL contract.
               */
              function renounceOwnership() public override onlyOwner {
                  // broadcast that new projects are no longer allowed (if not already)
                  _forbidNewProjects();
                  // renounce ownership viw Ownable
                  Ownable.renounceOwnership();
              }
              /**
               * @notice Updates reference to Art Blocks Dependency Registry contract.
               * @param _artblocksDependencyRegistryAddress Address of new Dependency
               * Registry.
               */
              function updateArtblocksDependencyRegistryAddress(
                  address _artblocksDependencyRegistryAddress
              ) external {
                  _onlyAdminACL(this.updateArtblocksDependencyRegistryAddress.selector);
                  _onlyNonZeroAddress(_artblocksDependencyRegistryAddress);
                  artblocksDependencyRegistryAddress = _artblocksDependencyRegistryAddress;
                  emit PlatformUpdated(FIELD_ARTBLOCKS_DEPENDENCY_REGISTRY_ADDRESS);
              }
              /**
               * @notice Updates sales addresses for the platform and render providers to
               * the input parameters.
               * @param _renderProviderPrimarySalesAddress Address of new primary sales
               * payment address.
               * @param _renderProviderSecondarySalesAddress Address of new secondary sales
               * payment address.
               * @param _platformProviderPrimarySalesAddress Address of new primary sales
               * payment address.
               * @param _platformProviderSecondarySalesAddress Address of new secondary sales
               * payment address.
               */
              function updateProviderSalesAddresses(
                  address payable _renderProviderPrimarySalesAddress,
                  address payable _renderProviderSecondarySalesAddress,
                  address payable _platformProviderPrimarySalesAddress,
                  address payable _platformProviderSecondarySalesAddress
              ) external {
                  _onlyAdminACL(this.updateProviderSalesAddresses.selector);
                  _onlyNonZeroAddress(_renderProviderPrimarySalesAddress);
                  _onlyNonZeroAddress(_renderProviderSecondarySalesAddress);
                  _onlyNonZeroAddress(_platformProviderPrimarySalesAddress);
                  _onlyNonZeroAddress(_platformProviderSecondarySalesAddress);
                  _updateProviderSalesAddresses(
                      _renderProviderPrimarySalesAddress,
                      _renderProviderSecondarySalesAddress,
                      _platformProviderPrimarySalesAddress,
                      _platformProviderSecondarySalesAddress
                  );
              }
              /**
               * @notice Updates the render and platform provider primary sales revenue percentage to
               * the provided inputs.
               * @param renderProviderPrimarySalesPercentage_ New primary sales revenue % for the render provider
               * @param platformProviderPrimarySalesPercentage_ New primary sales revenue % for the platform provider
               * percentage.
               */
              function updateProviderPrimarySalesPercentages(
                  uint256 renderProviderPrimarySalesPercentage_,
                  uint256 platformProviderPrimarySalesPercentage_
              ) external {
                  _onlyAdminACL(this.updateProviderPrimarySalesPercentages.selector);
                  // Validate that the sum of the proposed %s, does not exceed 100%.
                  require(
                      (renderProviderPrimarySalesPercentage_ +
                          platformProviderPrimarySalesPercentage_) <= ONE_HUNDRED,
                      "Max sum of ONE_HUNDRED %"
                  );
                  // Casting to `uint8` here is safe due check above, which does not allow
                  // overflow as of solidity version ^0.8.0.
                  _renderProviderPrimarySalesPercentage = uint8(
                      renderProviderPrimarySalesPercentage_
                  );
                  _platformProviderPrimarySalesPercentage = uint8(
                      platformProviderPrimarySalesPercentage_
                  );
                  emit PlatformUpdated(FIELD_PROVIDER_PRIMARY_SALES_PERCENTAGES);
              }
              /**
               * @notice Updates render and platform provider secondary sales royalty Basis Points to
               * the provided inputs.
               * @param _renderProviderSecondarySalesBPS New secondary sales royalty Basis
               * points.
               * @param _platformProviderSecondarySalesBPS New secondary sales royalty Basis
               * points.
               * @dev Due to secondary royalties being ultimately enforced via social
               * consensus, no hard upper limit is imposed on the BPS value, other than
               * <= 100% royalty, which would not make mathematical sense. Realistically,
               * changing this value is expected to either never occur, or be a rare
               * occurrence.
               */
              function updateProviderSecondarySalesBPS(
                  uint256 _renderProviderSecondarySalesBPS,
                  uint256 _platformProviderSecondarySalesBPS
              ) external {
                  _onlyAdminACL(this.updateProviderSecondarySalesBPS.selector);
                  // Validate that the sum of the proposed provider BPS, does not exceed 10_000 BPS.
                  require(
                      (_renderProviderSecondarySalesBPS +
                          _platformProviderSecondarySalesBPS) <=
                          MAX_PROVIDER_SECONDARY_SALES_BPS,
                      "Over max sum of BPS"
                  );
                  renderProviderSecondarySalesBPS = _renderProviderSecondarySalesBPS;
                  platformProviderSecondarySalesBPS = _platformProviderSecondarySalesBPS;
                  emit PlatformUpdated(FIELD_PROVIDER_SECONDARY_SALES_BPS);
              }
              /**
               * @notice Updates minter to `_address`.
               * @param _address Address of new minter.
               */
              function updateMinterContract(address _address) external {
                  _onlyAdminACL(this.updateMinterContract.selector);
                  _onlyNonZeroAddress(_address);
                  minterContract = _address;
                  emit MinterUpdated(_address);
              }
              /**
               * @notice Updates randomizer to `_randomizerAddress`.
               * @param _randomizerAddress Address of new randomizer.
               */
              function updateRandomizerAddress(address _randomizerAddress) external {
                  _onlyAdminACL(this.updateRandomizerAddress.selector);
                  _onlyNonZeroAddress(_randomizerAddress);
                  _updateRandomizerAddress(_randomizerAddress);
              }
              /**
               * @notice Toggles project `_projectId` as active/inactive.
               * @param _projectId Project ID to be toggled.
               */
              function toggleProjectIsActive(uint256 _projectId) external {
                  _onlyAdminACL(this.toggleProjectIsActive.selector);
                  _onlyValidProjectId(_projectId);
                  projects[_projectId].active = !projects[_projectId].active;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_ACTIVE);
              }
              /**
               * @notice Artist proposes updated set of artist address, additional payee
               * addresses, and percentage splits for project `_projectId`. Addresses and
               * percentages do not have to all be changed, but they must all be defined
               * as a complete set.
               * Note that if the artist is only proposing a change to the payee percentage
               * splits, without modifying the payee addresses, the proposal will be
               * automatically approved and the new splits will become active immediately.
               * Automatic approval will also be granted if the artist is only removing
               * additional payee addresses, without adding any new ones.
               * Also note that if `autoApproveArtistSplitProposals` is true, proposals
               * will always be auto-approved, regardless of what is being changed.
               * Also note that if the artist is proposing sending funds to the zero
               * address, this function will revert and the proposal will not be created.
               * @param _projectId Project ID.
               * @param _artistAddress Artist address that controls the project, and may
               * receive payments.
               * @param _additionalPayeePrimarySales Address that may receive a
               * percentage split of the artist's primary sales revenue.
               * @param _additionalPayeePrimarySalesPercentage Percent of artist's
               * portion of primary sale revenue that will be split to address
               * `_additionalPayeePrimarySales`.
               * @param _additionalPayeeSecondarySales Address that may receive a percentage
               * split of the secondary sales royalties.
               * @param _additionalPayeeSecondarySalesPercentage Percent of artist's portion
               * of secondary sale royalties that will be split to address
               * `_additionalPayeeSecondarySales`.
               * @dev `_artistAddress` must be a valid address (non-zero-address), but it
               * is intentionally allowable for `_additionalPayee{Primary,Secondaary}Sales`
               * and their associated percentages to be zero'd out by the controlling artist.
               */
              function proposeArtistPaymentAddressesAndSplits(
                  uint256 _projectId,
                  address payable _artistAddress,
                  address payable _additionalPayeePrimarySales,
                  uint256 _additionalPayeePrimarySalesPercentage,
                  address payable _additionalPayeeSecondarySales,
                  uint256 _additionalPayeeSecondarySalesPercentage
              ) external {
                  _onlyValidProjectId(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.proposeArtistPaymentAddressesAndSplits.selector
                  );
                  _onlyNonZeroAddress(_artistAddress);
                  ProjectFinance storage projectFinance = projectIdToFinancials[
                      _projectId
                  ];
                  // checks
                  require(
                      _additionalPayeePrimarySalesPercentage <= ONE_HUNDRED &&
                          _additionalPayeeSecondarySalesPercentage <= ONE_HUNDRED,
                      "Max of 100%"
                  );
                  require(
                      _additionalPayeePrimarySalesPercentage == 0 ||
                          _additionalPayeePrimarySales != address(0),
                      "Primary payee is zero address"
                  );
                  require(
                      _additionalPayeeSecondarySalesPercentage == 0 ||
                          _additionalPayeeSecondarySales != address(0),
                      "Secondary payee is zero address"
                  );
                  // effects
                  // emit event for off-chain indexing
                  // note: always emit a proposal event, even in the pathway of
                  // automatic approval, to simplify indexing expectations
                  emit ProposedArtistAddressesAndSplits(
                      _projectId,
                      _artistAddress,
                      _additionalPayeePrimarySales,
                      _additionalPayeePrimarySalesPercentage,
                      _additionalPayeeSecondarySales,
                      _additionalPayeeSecondarySalesPercentage
                  );
                  // automatically accept if no proposed addresses modifications, or if
                  // the proposal only removes payee addresses, or if contract is set to
                  // always auto-approve.
                  // store proposal hash on-chain, only if not automatic accept
                  bool automaticAccept = autoApproveArtistSplitProposals;
                  if (!automaticAccept) {
                      // block scope to avoid stack too deep error
                      bool artistUnchanged = _artistAddress ==
                          projectFinance.artistAddress;
                      bool additionalPrimaryUnchangedOrRemoved = (_additionalPayeePrimarySales ==
                              projectFinance.additionalPayeePrimarySales) ||
                              (_additionalPayeePrimarySales == address(0));
                      bool additionalSecondaryUnchangedOrRemoved = (_additionalPayeeSecondarySales ==
                              projectFinance.additionalPayeeSecondarySales) ||
                              (_additionalPayeeSecondarySales == address(0));
                      automaticAccept =
                          artistUnchanged &&
                          additionalPrimaryUnchangedOrRemoved &&
                          additionalSecondaryUnchangedOrRemoved;
                  }
                  if (automaticAccept) {
                      // clear any previously proposed values
                      proposedArtistAddressesAndSplitsHash[_projectId] = bytes32(0);
                      // update storage
                      // artist address can change during automatic accept if
                      // autoApproveArtistSplitProposals is true
                      projectFinance.artistAddress = _artistAddress;
                      projectFinance
                          .additionalPayeePrimarySales = _additionalPayeePrimarySales;
                      // safe to cast as uint8 as max is 100%, max uint8 is 255
                      projectFinance.additionalPayeePrimarySalesPercentage = uint8(
                          _additionalPayeePrimarySalesPercentage
                      );
                      projectFinance
                          .additionalPayeeSecondarySales = _additionalPayeeSecondarySales;
                      // safe to cast as uint8 as max is 100%, max uint8 is 255
                      projectFinance.additionalPayeeSecondarySalesPercentage = uint8(
                          _additionalPayeeSecondarySalesPercentage
                      );
                      // emit event for off-chain indexing
                      emit AcceptedArtistAddressesAndSplits(_projectId);
                  } else {
                      proposedArtistAddressesAndSplitsHash[_projectId] = keccak256(
                          abi.encode(
                              _artistAddress,
                              _additionalPayeePrimarySales,
                              _additionalPayeePrimarySalesPercentage,
                              _additionalPayeeSecondarySales,
                              _additionalPayeeSecondarySalesPercentage
                          )
                      );
                  }
              }
              /**
               * @notice Admin accepts a proposed set of updated artist address,
               * additional payee addresses, and percentage splits for project
               * `_projectId`. Addresses and percentages do not have to all be changed,
               * but they must all be defined as a complete set.
               * @param _projectId Project ID.
               * @param _artistAddress Artist address that controls the project, and may
               * receive payments.
               * @param _additionalPayeePrimarySales Address that may receive a
               * percentage split of the artist's primary sales revenue.
               * @param _additionalPayeePrimarySalesPercentage Percent of artist's
               * portion of primary sale revenue that will be split to address
               * `_additionalPayeePrimarySales`.
               * @param _additionalPayeeSecondarySales Address that may receive a percentage
               * split of the secondary sales royalties.
               * @param _additionalPayeeSecondarySalesPercentage Percent of artist's portion
               * of secondary sale royalties that will be split to address
               * `_additionalPayeeSecondarySales`.
               * @dev this must be called by the Admin ACL contract, and must only accept
               * the most recent proposed values for a given project (validated on-chain
               * by comparing the hash of the proposed and accepted values).
               * @dev `_artistAddress` must be a valid address (non-zero-address), but it
               * is intentionally allowable for `_additionalPayee{Primary,Secondaary}Sales`
               * and their associated percentages to be zero'd out by the controlling artist.
               */
              function adminAcceptArtistAddressesAndSplits(
                  uint256 _projectId,
                  address payable _artistAddress,
                  address payable _additionalPayeePrimarySales,
                  uint256 _additionalPayeePrimarySalesPercentage,
                  address payable _additionalPayeeSecondarySales,
                  uint256 _additionalPayeeSecondarySalesPercentage
              ) external {
                  _onlyValidProjectId(_projectId);
                  _onlyAdminACLOrRenouncedArtist(
                      _projectId,
                      this.adminAcceptArtistAddressesAndSplits.selector
                  );
                  _onlyNonZeroAddress(_artistAddress);
                  // checks
                  require(
                      proposedArtistAddressesAndSplitsHash[_projectId] ==
                          keccak256(
                              abi.encode(
                                  _artistAddress,
                                  _additionalPayeePrimarySales,
                                  _additionalPayeePrimarySalesPercentage,
                                  _additionalPayeeSecondarySales,
                                  _additionalPayeeSecondarySalesPercentage
                              )
                          ),
                      "Must match artist proposal"
                  );
                  // effects
                  ProjectFinance storage projectFinance = projectIdToFinancials[
                      _projectId
                  ];
                  projectFinance.artistAddress = _artistAddress;
                  projectFinance
                      .additionalPayeePrimarySales = _additionalPayeePrimarySales;
                  projectFinance.additionalPayeePrimarySalesPercentage = uint8(
                      _additionalPayeePrimarySalesPercentage
                  );
                  projectFinance
                      .additionalPayeeSecondarySales = _additionalPayeeSecondarySales;
                  projectFinance.additionalPayeeSecondarySalesPercentage = uint8(
                      _additionalPayeeSecondarySalesPercentage
                  );
                  // clear proposed values
                  proposedArtistAddressesAndSplitsHash[_projectId] = bytes32(0);
                  // emit event for off-chain indexing
                  emit AcceptedArtistAddressesAndSplits(_projectId);
              }
              /**
               * @notice Updates artist of project `_projectId` to `_artistAddress`.
               * This is to only be used in the event that the artist address is
               * compromised or sanctioned.
               * @param _projectId Project ID.
               * @param _artistAddress New artist address.
               */
              function updateProjectArtistAddress(
                  uint256 _projectId,
                  address payable _artistAddress
              ) external {
                  _onlyValidProjectId(_projectId);
                  _onlyAdminACLOrRenouncedArtist(
                      _projectId,
                      this.updateProjectArtistAddress.selector
                  );
                  _onlyNonZeroAddress(_artistAddress);
                  projectIdToFinancials[_projectId].artistAddress = _artistAddress;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_ARTIST_ADDRESS);
              }
              /**
               * @notice Toggles paused state of project `_projectId`.
               * @param _projectId Project ID to be toggled.
               */
              function toggleProjectIsPaused(uint256 _projectId) external {
                  _onlyValidProjectId(_projectId);
                  _onlyArtistOrAdminACL(_projectId, this.toggleProjectIsPaused.selector);
                  projects[_projectId].paused = !projects[_projectId].paused;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_PAUSED);
              }
              /**
               * @notice Adds new project `_projectName` by `_artistAddress`.
               * @param _projectName Project name.
               * @param _artistAddress Artist's address.
               * @dev token price now stored on minter
               */
              function addProject(
                  string memory _projectName,
                  address payable _artistAddress
              ) external {
                  _onlyAdminACL(this.addProject.selector);
                  _onlyNonEmptyString(_projectName);
                  _onlyNonZeroAddress(_artistAddress);
                  require(!newProjectsForbidden, "New projects forbidden");
                  uint256 projectId = _nextProjectId;
                  projectIdToFinancials[projectId].artistAddress = _artistAddress;
                  projects[projectId].name = _projectName;
                  projects[projectId].paused = true;
                  projects[projectId].maxInvocations = ONE_MILLION_UINT24;
                  projects[projectId].projectBaseURI = defaultBaseURI;
                  _nextProjectId = uint248(projectId) + 1;
                  emit ProjectUpdated(projectId, FIELD_PROJECT_CREATED);
              }
              /**
               * @notice Forever forbids new projects from being added to this contract.
               */
              function forbidNewProjects() external {
                  _onlyAdminACL(this.forbidNewProjects.selector);
                  require(!newProjectsForbidden, "Already forbidden");
                  _forbidNewProjects();
              }
              /**
               * @notice Updates name of project `_projectId` to be `_projectName`.
               * @param _projectId Project ID.
               * @param _projectName New project name.
               */
              function updateProjectName(
                  uint256 _projectId,
                  string memory _projectName
              ) external {
                  _onlyUnlocked(_projectId);
                  _onlyArtistOrAdminACL(_projectId, this.updateProjectName.selector);
                  _onlyNonEmptyString(_projectName);
                  projects[_projectId].name = _projectName;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_NAME);
              }
              /**
               * @notice Updates artist name for project `_projectId` to be
               * `_projectArtistName`.
               * @param _projectId Project ID.
               * @param _projectArtistName New artist name.
               */
              function updateProjectArtistName(
                  uint256 _projectId,
                  string memory _projectArtistName
              ) external {
                  _onlyUnlocked(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.updateProjectArtistName.selector
                  );
                  _onlyNonEmptyString(_projectArtistName);
                  projects[_projectId].artist = _projectArtistName;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_ARTIST_NAME);
              }
              /**
               * @notice Updates artist secondary market royalties for project
               * `_projectId` to be `_secondMarketRoyalty` percent.
               * This DOES NOT include the secondary market royalty percentages collected
               * by the issuing platform; it is only the total percentage of royalties
               * that will be split to artist and additionalSecondaryPayee.
               * @param _projectId Project ID.
               * @param _secondMarketRoyalty Percent of secondary sales revenue that will
               * be split to artist and additionalSecondaryPayee. This must be less than
               * or equal to ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE percent.
               */
              function updateProjectSecondaryMarketRoyaltyPercentage(
                  uint256 _projectId,
                  uint256 _secondMarketRoyalty
              ) external {
                  _onlyValidProjectId(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.updateProjectSecondaryMarketRoyaltyPercentage.selector
                  );
                  require(
                      _secondMarketRoyalty <= ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE,
                      "Over max percent"
                  );
                  projectIdToFinancials[_projectId]
                      .secondaryMarketRoyaltyPercentage = uint8(_secondMarketRoyalty);
                  emit ProjectUpdated(
                      _projectId,
                      FIELD_PROJECT_SECONDARY_MARKET_ROYALTY_PERCENTAGE
                  );
              }
              /**
               * @notice Updates description of project `_projectId`.
               * Only artist may call when unlocked, only admin may call when locked.
               * @param _projectId Project ID.
               * @param _projectDescription New project description.
               */
              function updateProjectDescription(
                  uint256 _projectId,
                  string memory _projectDescription
              ) external {
                  // checks
                  require(
                      _projectUnlocked(_projectId)
                          ? msg.sender == projectIdToFinancials[_projectId].artistAddress
                          : adminACLAllowed(
                              msg.sender,
                              address(this),
                              this.updateProjectDescription.selector
                          ),
                      "Only artist when unlocked, owner when locked"
                  );
                  // effects
                  projects[_projectId].description = _projectDescription;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_DESCRIPTION);
              }
              /**
               * @notice Updates website of project `_projectId` to be `_projectWebsite`.
               * @param _projectId Project ID.
               * @param _projectWebsite New project website.
               * @dev It is intentionally allowed for this to be set to the empty string.
               */
              function updateProjectWebsite(
                  uint256 _projectId,
                  string memory _projectWebsite
              ) external {
                  _onlyValidProjectId(_projectId);
                  _onlyArtistOrAdminACL(_projectId, this.updateProjectWebsite.selector);
                  projects[_projectId].website = _projectWebsite;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_WEBSITE);
              }
              /**
               * @notice Updates license for project `_projectId`.
               * @param _projectId Project ID.
               * @param _projectLicense New project license.
               */
              function updateProjectLicense(
                  uint256 _projectId,
                  string memory _projectLicense
              ) external {
                  _onlyUnlocked(_projectId);
                  _onlyArtistOrAdminACL(_projectId, this.updateProjectLicense.selector);
                  _onlyNonEmptyString(_projectLicense);
                  projects[_projectId].license = _projectLicense;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_LICENSE);
              }
              /**
               * @notice Updates maximum invocations for project `_projectId` to
               * `_maxInvocations`. Maximum invocations may only be decreased by the
               * artist, and must be greater than or equal to current invocations.
               * New projects are created with maximum invocations of 1 million by
               * default.
               * @param _projectId Project ID.
               * @param _maxInvocations New maximum invocations.
               */
              function updateProjectMaxInvocations(
                  uint256 _projectId,
                  uint24 _maxInvocations
              ) external {
                  _onlyValidProjectId(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.updateProjectMaxInvocations.selector
                  );
                  // CHECKS
                  Project storage project = projects[_projectId];
                  uint256 _invocations = project.invocations;
                  require(
                      (_maxInvocations < project.maxInvocations),
                      "Only maxInvocations decrease"
                  );
                  require(_maxInvocations >= _invocations, "Only gte invocations");
                  // EFFECTS
                  project.maxInvocations = _maxInvocations;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_MAX_INVOCATIONS);
                  // register completed timestamp if action completed the project
                  if (_maxInvocations == _invocations) {
                      _completeProject(_projectId);
                  }
              }
              /**
               * @notice Adds a script to project `_projectId`.
               * @param _projectId Project to be updated.
               * @param _script Script to be added. Required to be a non-empty string,
               * but no further validation is performed.
               */
              function addProjectScript(
                  uint256 _projectId,
                  string memory _script
              ) external {
                  _onlyUnlocked(_projectId);
                  _onlyArtistOrAdminACL(_projectId, this.addProjectScript.selector);
                  _onlyNonEmptyString(_script);
                  Project storage project = projects[_projectId];
                  // store script in contract bytecode
                  project.scriptBytecodeAddresses[project.scriptCount] = _script
                      .writeToBytecode();
                  project.scriptCount = project.scriptCount + 1;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT);
              }
              /**
               * @notice Updates script for project `_projectId` at script ID `_scriptId`.
               * @param _projectId Project to be updated.
               * @param _scriptId Script ID to be updated.
               * @param _script The updated script value. Required to be a non-empty
               *                string, but no further validation is performed.
               */
              function updateProjectScript(
                  uint256 _projectId,
                  uint256 _scriptId,
                  string memory _script
              ) external {
                  _onlyUnlocked(_projectId);
                  _onlyArtistOrAdminACL(_projectId, this.updateProjectScript.selector);
                  _onlyNonEmptyString(_script);
                  Project storage project = projects[_projectId];
                  require(_scriptId < project.scriptCount, "scriptId out of range");
                  // store script in contract bytecode, replacing reference address from
                  // the contract that no longer exists with the newly created one
                  project.scriptBytecodeAddresses[_scriptId] = _script.writeToBytecode();
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT);
              }
              /**
               * @notice Removes last script from project `_projectId`.
               * @param _projectId Project to be updated.
               */
              function removeProjectLastScript(uint256 _projectId) external {
                  _onlyUnlocked(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.removeProjectLastScript.selector
                  );
                  Project storage project = projects[_projectId];
                  require(project.scriptCount > 0, "No scripts to remove");
                  // delete reference to contract address that no longer exists
                  delete project.scriptBytecodeAddresses[project.scriptCount - 1];
                  unchecked {
                      project.scriptCount = project.scriptCount - 1;
                  }
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT);
              }
              /**
               * @notice Updates script type for project `_projectId`.
               * @param _projectId Project to be updated.
               * @param _scriptTypeAndVersion Script type and version e.g. "[email protected]",
               * as bytes32 encoded string.
               */
              function updateProjectScriptType(
                  uint256 _projectId,
                  bytes32 _scriptTypeAndVersion
              ) external {
                  _onlyUnlocked(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.updateProjectScriptType.selector
                  );
                  Project storage project = projects[_projectId];
                  // require exactly one @ symbol in _scriptTypeAndVersion
                  require(
                      _scriptTypeAndVersion.containsExactCharacterQty(
                          AT_CHARACTER_CODE,
                          uint8(1)
                      ),
                      "must contain exactly one @"
                  );
                  project.scriptTypeAndVersion = _scriptTypeAndVersion;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT_TYPE);
              }
              /**
               * @notice Updates project's aspect ratio.
               * @param _projectId Project to be updated.
               * @param _aspectRatio Aspect ratio to be set. Intended to be string in the
               * format of a decimal, e.g. "1" for square, "1.77777778" for 16:9, etc.,
               * allowing for a maximum of 10 digits and one (optional) decimal separator.
               */
              function updateProjectAspectRatio(
                  uint256 _projectId,
                  string memory _aspectRatio
              ) external {
                  _onlyUnlocked(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.updateProjectAspectRatio.selector
                  );
                  _onlyNonEmptyString(_aspectRatio);
                  // Perform more detailed input validation for aspect ratio.
                  bytes memory aspectRatioBytes = bytes(_aspectRatio);
                  uint256 bytesLength = aspectRatioBytes.length;
                  require(bytesLength <= 11, "Aspect ratio format too long");
                  bool hasSeenDecimalSeparator = false;
                  bool hasSeenNumber = false;
                  for (uint256 i; i < bytesLength; i++) {
                      bytes1 character = aspectRatioBytes[i];
                      // Allow as many #s as desired.
                      if (character >= 0x30 && character <= 0x39) {
                          // 9-0
                          // We need to ensure there is at least 1 `9-0` occurrence.
                          hasSeenNumber = true;
                          continue;
                      }
                      if (character == 0x2E) {
                          // .
                          // Allow no more than 1 `.` occurrence.
                          if (!hasSeenDecimalSeparator) {
                              hasSeenDecimalSeparator = true;
                              continue;
                          }
                      }
                      revert("Improperly formatted aspect ratio");
                  }
                  require(hasSeenNumber, "Aspect ratio has no numbers");
                  projects[_projectId].aspectRatio = _aspectRatio;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_ASPECT_RATIO);
              }
              /**
               * @notice Updates base URI for project `_projectId` to `_newBaseURI`.
               * This is the controlling base URI for all tokens in the project. The
               * contract-level defaultBaseURI is only used when initializing new
               * projects.
               * @param _projectId Project to be updated.
               * @param _newBaseURI New base URI.
               */
              function updateProjectBaseURI(
                  uint256 _projectId,
                  string memory _newBaseURI
              ) external {
                  _onlyValidProjectId(_projectId);
                  _onlyArtistOrAdminACL(_projectId, this.updateProjectBaseURI.selector);
                  _onlyNonEmptyString(_newBaseURI);
                  projects[_projectId].projectBaseURI = _newBaseURI;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_BASE_URI);
              }
              /**
               * @notice Updates default base URI to `_defaultBaseURI`. The
               * contract-level defaultBaseURI is only used when initializing new
               * projects. Token URIs are determined by their project's `projectBaseURI`.
               * @param _defaultBaseURI New default base URI.
               */
              function updateDefaultBaseURI(string memory _defaultBaseURI) external {
                  _onlyAdminACL(this.updateDefaultBaseURI.selector);
                  _onlyNonEmptyString(_defaultBaseURI);
                  _updateDefaultBaseURI(_defaultBaseURI);
              }
              /**
               * @notice Next project ID to be created on this contract.
               * @return uint256 Next project ID.
               */
              function nextProjectId() external view returns (uint256) {
                  return _nextProjectId;
              }
              /**
               * @notice Returns token hash for token ID `_tokenId`. Returns null if hash
               * has not been set.
               * @param _tokenId Token ID to be queried.
               * @return bytes32 Token hash.
               * @dev token hash is the keccak256 hash of the stored hash seed
               */
              function tokenIdToHash(uint256 _tokenId) external view returns (bytes32) {
                  bytes12 _hashSeed = _ownersAndHashSeeds[_tokenId].hashSeed;
                  if (_hashSeed == 0) {
                      return 0;
                  }
                  return keccak256(abi.encode(_hashSeed));
              }
              /**
               * @notice Returns token hash **seed** for token ID `_tokenId`. Returns
               * null if hash seed has not been set. The hash seed id the bytes12 value
               * which is hashed to produce the token hash.
               * @param _tokenId Token ID to be queried.
               * @return bytes12 Token hash seed.
               * @dev token hash seed is keccak256 hashed to give the token hash
               */
              function tokenIdToHashSeed(
                  uint256 _tokenId
              ) external view returns (bytes12) {
                  return _ownersAndHashSeeds[_tokenId].hashSeed;
              }
              /**
               * @notice View function returning the render provider portion of
               * primary sales, in percent.
               * @return uint256 The render provider portion of primary sales,
               * in percent.
               */
              function renderProviderPrimarySalesPercentage()
                  external
                  view
                  returns (uint256)
              {
                  return _renderProviderPrimarySalesPercentage;
              }
              /**
               * @notice View function returning the platform provider portion of
               * primary sales, in percent.
               * @return uint256 The platform provider portion of primary sales,
               * in percent.
               */
              function platformProviderPrimarySalesPercentage()
                  external
                  view
                  returns (uint256)
              {
                  return _platformProviderPrimarySalesPercentage;
              }
              /**
               * @notice View function returning Artist's address for project
               * `_projectId`.
               * @param _projectId Project ID to be queried.
               * @return address Artist's address.
               */
              function projectIdToArtistAddress(
                  uint256 _projectId
              ) external view returns (address payable) {
                  return projectIdToFinancials[_projectId].artistAddress;
              }
              /**
               * @notice View function returning Artist's secondary market royalty
               * percentage for project `_projectId`.
               * This does not include render/platform providers portions of secondary
               * market royalties.
               * @param _projectId Project ID to be queried.
               * @return uint256 Artist's secondary market royalty percentage.
               */
              function projectIdToSecondaryMarketRoyaltyPercentage(
                  uint256 _projectId
              ) external view returns (uint256) {
                  return
                      projectIdToFinancials[_projectId].secondaryMarketRoyaltyPercentage;
              }
              /**
               * @notice View function returning Artist's additional payee address for
               * primary sales, for project `_projectId`.
               * @param _projectId Project ID to be queried.
               * @return address Artist's additional payee address for primary sales.
               */
              function projectIdToAdditionalPayeePrimarySales(
                  uint256 _projectId
              ) external view returns (address payable) {
                  return projectIdToFinancials[_projectId].additionalPayeePrimarySales;
              }
              /**
               * @notice View function returning Artist's additional payee primary sales
               * percentage, for project `_projectId`.
               * @param _projectId Project ID to be queried.
               * @return uint256 Artist's additional payee primary sales percentage.
               */
              function projectIdToAdditionalPayeePrimarySalesPercentage(
                  uint256 _projectId
              ) external view returns (uint256) {
                  return
                      projectIdToFinancials[_projectId]
                          .additionalPayeePrimarySalesPercentage;
              }
              /**
               * @notice View function returning Artist's additional payee address for
               * secondary sales, for project `_projectId`.
               * @param _projectId Project ID to be queried.
               * @return address payable Artist's additional payee address for secondary
               * sales.
               */
              function projectIdToAdditionalPayeeSecondarySales(
                  uint256 _projectId
              ) external view returns (address payable) {
                  return projectIdToFinancials[_projectId].additionalPayeeSecondarySales;
              }
              /**
               * @notice View function returning Artist's additional payee secondary
               * sales percentage, for project `_projectId`.
               * @param _projectId Project ID to be queried.
               * @return uint256 Artist's additional payee secondary sales percentage.
               */
              function projectIdToAdditionalPayeeSecondarySalesPercentage(
                  uint256 _projectId
              ) external view returns (uint256) {
                  return
                      projectIdToFinancials[_projectId]
                          .additionalPayeeSecondarySalesPercentage;
              }
              /**
               * @notice Returns project details for project `_projectId`.
               * @param _projectId Project to be queried.
               * @return projectName Name of project
               * @return artist Artist of project
               * @return description Project description
               * @return website Project website
               * @return license Project license
               * @dev this function was named projectDetails prior to V3 core contract.
               */
              function projectDetails(
                  uint256 _projectId
              )
                  external
                  view
                  returns (
                      string memory projectName,
                      string memory artist,
                      string memory description,
                      string memory website,
                      string memory license
                  )
              {
                  Project storage project = projects[_projectId];
                  projectName = project.name;
                  artist = project.artist;
                  description = project.description;
                  website = project.website;
                  license = project.license;
              }
              /**
               * @notice Returns project state data for project `_projectId`.
               * @param _projectId Project to be queried
               * @return invocations Current number of invocations
               * @return maxInvocations Maximum allowed invocations
               * @return active Boolean representing if project is currently active
               * @return paused Boolean representing if project is paused
               * @return completedTimestamp zero if project not complete, otherwise
               * timestamp of project completion.
               * @return locked Boolean representing if project is locked
               * @dev price and currency info are located on minter contracts
               */
              function projectStateData(
                  uint256 _projectId
              )
                  external
                  view
                  returns (
                      uint256 invocations,
                      uint256 maxInvocations,
                      bool active,
                      bool paused,
                      uint256 completedTimestamp,
                      bool locked
                  )
              {
                  Project storage project = projects[_projectId];
                  invocations = project.invocations;
                  maxInvocations = project.maxInvocations;
                  active = project.active;
                  paused = project.paused;
                  completedTimestamp = project.completedTimestamp;
                  locked = !_projectUnlocked(_projectId);
              }
              /**
               * @notice Returns artist payment information for project `_projectId`.
               * @param _projectId Project to be queried
               * @return artistAddress Project Artist's address
               * @return additionalPayeePrimarySales Additional payee address for primary
               * sales
               * @return additionalPayeePrimarySalesPercentage Percentage of artist revenue
               * to be sent to the additional payee address for primary sales
               * @return additionalPayeeSecondarySales Additional payee address for secondary
               * sales royalties
               * @return additionalPayeeSecondarySalesPercentage Percentage of artist revenue
               * to be sent to the additional payee address for secondary sales royalties
               * @return secondaryMarketRoyaltyPercentage Royalty percentage to be sent to
               * combination of artist and additional payee. This does not include the
               * platform's percentage of secondary sales royalties, which is defined as
               * the sum of `renderProviderSecondarySalesBPS`
               * and `platformProviderSecondarySalesBPS`.
               */
              function projectArtistPaymentInfo(
                  uint256 _projectId
              )
                  external
                  view
                  returns (
                      address artistAddress,
                      address additionalPayeePrimarySales,
                      uint256 additionalPayeePrimarySalesPercentage,
                      address additionalPayeeSecondarySales,
                      uint256 additionalPayeeSecondarySalesPercentage,
                      uint256 secondaryMarketRoyaltyPercentage
                  )
              {
                  ProjectFinance storage projectFinance = projectIdToFinancials[
                      _projectId
                  ];
                  artistAddress = projectFinance.artistAddress;
                  additionalPayeePrimarySales = projectFinance
                      .additionalPayeePrimarySales;
                  additionalPayeePrimarySalesPercentage = projectFinance
                      .additionalPayeePrimarySalesPercentage;
                  additionalPayeeSecondarySales = projectFinance
                      .additionalPayeeSecondarySales;
                  additionalPayeeSecondarySalesPercentage = projectFinance
                      .additionalPayeeSecondarySalesPercentage;
                  secondaryMarketRoyaltyPercentage = projectFinance
                      .secondaryMarketRoyaltyPercentage;
              }
              /**
               * @notice Returns script information for project `_projectId`.
               * @param _projectId Project to be queried.
               * @return scriptTypeAndVersion Project's script type and version
               * (e.g. "p5js(atSymbol)1.0.0")
               * @return aspectRatio Aspect ratio of project (e.g. "1" for square,
               * "1.77777778" for 16:9, etc.)
               * @return scriptCount Count of scripts for project
               */
              function projectScriptDetails(
                  uint256 _projectId
              )
                  external
                  view
                  override(IGenArt721CoreContractV3_Base, IDependencyRegistryCompatibleV0)
                  returns (
                      string memory scriptTypeAndVersion,
                      string memory aspectRatio,
                      uint256 scriptCount
                  )
              {
                  Project storage project = projects[_projectId];
                  scriptTypeAndVersion = project.scriptTypeAndVersion.toString();
                  aspectRatio = project.aspectRatio;
                  scriptCount = project.scriptCount;
              }
              /**
               * @notice Returns address with bytecode containing project script for
               * project `_projectId` at script index `_index`.
               */
              function projectScriptBytecodeAddressByIndex(
                  uint256 _projectId,
                  uint256 _index
              ) external view returns (address) {
                  return projects[_projectId].scriptBytecodeAddresses[_index];
              }
              /**
               * @notice Returns script for project `_projectId` at script index `_index`.
               * @param _projectId Project to be queried.
               * @param _index Index of script to be queried.
               */
              function projectScriptByIndex(
                  uint256 _projectId,
                  uint256 _index
              ) external view returns (string memory) {
                  Project storage project = projects[_projectId];
                  // If trying to access an out-of-index script, return the empty string.
                  if (_index >= project.scriptCount) {
                      return "";
                  }
                  return project.scriptBytecodeAddresses[_index].readFromBytecode();
              }
              /**
               * @notice Returns base URI for project `_projectId`.
               * @param _projectId Project to be queried.
               * @return projectBaseURI Base URI for project
               */
              function projectURIInfo(
                  uint256 _projectId
              ) external view returns (string memory projectBaseURI) {
                  projectBaseURI = projects[_projectId].projectBaseURI;
              }
              /**
               * @notice Backwards-compatible (pre-V3) function returning if `_minter` is
               * minterContract.
               * @param _minter Address to be queried.
               * @return bool Boolean representing if `_minter` is minterContract.
               */
              function isMintWhitelisted(address _minter) external view returns (bool) {
                  return (minterContract == _minter);
              }
              /**
               * @notice Gets qty of randomizers in history of all randomizers used by
               * this core contract. If a randomizer is switched away from then back to,
               * it will show up in the history twice.
               * @return randomizerHistoryCount Count of randomizers in history
               */
              function numHistoricalRandomizers() external view returns (uint256) {
                  return _historicalRandomizerAddresses.length;
              }
              /**
               * @notice Gets address of randomizer at index `_index` in history of all
               * randomizers used by this core contract. Index is zero-based.
               * @param _index Historical index of randomizer to be queried.
               * @return randomizerAddress Address of randomizer at index `_index`.
               * @dev If a randomizer is switched away from and then switched back to, it
               * will show up in the history twice.
               */
              function getHistoricalRandomizerAt(
                  uint256 _index
              ) external view returns (address) {
                  require(
                      _index < _historicalRandomizerAddresses.length,
                      "Index out of bounds"
                  );
                  return _historicalRandomizerAddresses[_index];
              }
              /**
               * @notice Gets royalty Basis Points (BPS) for token ID `_tokenId`.
               * This conforms to the IManifold interface designated in the Royalty
               * Registry's RoyaltyEngineV1.sol contract.
               * ref: https://github.com/manifoldxyz/royalty-registry-solidity
               * @param _tokenId Token ID to be queried.
               * @return recipients Array of royalty payment recipients
               * @return bps Array of Basis Points (BPS) allocated to each recipient,
               * aligned by index.
               * @dev reverts if invalid _tokenId
               * @dev only returns recipients that have a non-zero BPS allocation
               */
              function getRoyalties(
                  uint256 _tokenId
              )
                  external
                  view
                  returns (address payable[] memory recipients, uint256[] memory bps)
              {
                  _onlyValidTokenId(_tokenId);
                  // initialize arrays with maximum potential length
                  recipients = new address payable[](4);
                  bps = new uint256[](4);
                  uint256 projectId = tokenIdToProjectId(_tokenId);
                  ProjectFinance storage projectFinance = projectIdToFinancials[
                      projectId
                  ];
                  // load values into memory
                  uint256 royaltyPercentageForArtistAndAdditional = projectFinance
                      .secondaryMarketRoyaltyPercentage;
                  uint256 additionalPayeePercentage = projectFinance
                      .additionalPayeeSecondarySalesPercentage;
                  // calculate BPS = percentage * 100
                  uint256 artistBPS = (ONE_HUNDRED - additionalPayeePercentage) *
                      royaltyPercentageForArtistAndAdditional;
                  uint256 additionalBPS = additionalPayeePercentage *
                      royaltyPercentageForArtistAndAdditional;
                  uint256 renderProviderBPS = renderProviderSecondarySalesBPS;
                  uint256 platformProviderBPS = platformProviderSecondarySalesBPS;
                  // populate arrays
                  uint256 payeeCount;
                  if (artistBPS > 0) {
                      recipients[payeeCount] = projectFinance.artistAddress;
                      bps[payeeCount++] = artistBPS;
                  }
                  if (additionalBPS > 0) {
                      recipients[payeeCount] = projectFinance
                          .additionalPayeeSecondarySales;
                      bps[payeeCount++] = additionalBPS;
                  }
                  if (renderProviderBPS > 0) {
                      recipients[payeeCount] = renderProviderSecondarySalesAddress;
                      bps[payeeCount++] = renderProviderBPS;
                  }
                  if (platformProviderBPS > 0) {
                      recipients[payeeCount] = platformProviderSecondarySalesAddress;
                      bps[payeeCount++] = platformProviderBPS;
                  }
                  // trim arrays if necessary
                  if (4 > payeeCount) {
                      assembly {
                          let decrease := sub(4, payeeCount)
                          mstore(recipients, sub(mload(recipients), decrease))
                          mstore(bps, sub(mload(bps), decrease))
                      }
                  }
                  return (recipients, bps);
              }
              /**
               * @notice View function that returns appropriate revenue splits between
               * different render provider, platform provider, Artist, and Artist's
               * additional primary sales payee given a sale price of `_price` on
               * project `_projectId`.
               * This always returns four revenue amounts and four addresses, but if a
               * revenue is zero for either Artist or additional payee, the corresponding
               * address returned will also be null (for gas optimization).
               * Does not account for refund if user overpays for a token (minter should
               * handle a refund of the difference, if appropriate).
               * Some minters may have alternative methods of splitting payments, in
               * which case they should implement their own payment splitting logic.
               * @param _projectId Project ID to be queried.
               * @param _price Sale price of token.
               * @return renderProviderRevenue_ amount of revenue to be sent to the
               * render provider
               * @return renderProviderAddress_ address to send render provider revenue to
               * @return platformProviderRevenue_ amount of revenue to be sent to the
               * platform provider
               * @return platformProviderAddress_ address to send platform provider revenue to
               * @return artistRevenue_ amount of revenue to be sent to Artist
               * @return artistAddress_ address to send Artist revenue to. Will be null
               * if no revenue is due to artist (gas optimization).
               * @return additionalPayeePrimaryRevenue_ amount of revenue to be sent to
               * additional payee for primary sales
               * @return additionalPayeePrimaryAddress_ address to send Artist's
               * additional payee for primary sales revenue to. Will be null if no
               * revenue is due to additional payee for primary sales (gas optimization).
               * @dev this always returns four addresses and four revenues, but if the
               * revenue is zero, the corresponding address will be address(0). It is up
               * to the contract performing the revenue split to handle this
               * appropriately.
               */
              function getPrimaryRevenueSplits(
                  uint256 _projectId,
                  uint256 _price
              )
                  external
                  view
                  returns (
                      uint256 renderProviderRevenue_,
                      address payable renderProviderAddress_,
                      uint256 platformProviderRevenue_,
                      address payable platformProviderAddress_,
                      uint256 artistRevenue_,
                      address payable artistAddress_,
                      uint256 additionalPayeePrimaryRevenue_,
                      address payable additionalPayeePrimaryAddress_
                  )
              {
                  ProjectFinance storage projectFinance = projectIdToFinancials[
                      _projectId
                  ];
                  // calculate revenues – this is a three-way split between the
                  // render provider, the platform provider, and the artist, and
                  // is safe to perform this given that in the case of loss of
                  // precision Solidity will round down.
                  uint256 projectFunds = _price;
                  renderProviderRevenue_ =
                      (_price * uint256(_renderProviderPrimarySalesPercentage)) /
                      ONE_HUNDRED;
                  // renderProviderRevenue_ percentage is always <=100, so guaranteed to never underflow
                  projectFunds -= renderProviderRevenue_;
                  platformProviderRevenue_ =
                      (_price * uint256(_platformProviderPrimarySalesPercentage)) /
                      ONE_HUNDRED;
                  // platformProviderRevenue_ percentage is always <=100, so guaranteed to never underflow
                  projectFunds -= platformProviderRevenue_;
                  additionalPayeePrimaryRevenue_ =
                      (projectFunds *
                          projectFinance.additionalPayeePrimarySalesPercentage) /
                      ONE_HUNDRED;
                  // projectIdToAdditionalPayeePrimarySalesPercentage is always
                  // <=100, so guaranteed to never underflow
                  artistRevenue_ = projectFunds - additionalPayeePrimaryRevenue_;
                  // set addresses from storage
                  renderProviderAddress_ = renderProviderPrimarySalesAddress;
                  platformProviderAddress_ = platformProviderPrimarySalesAddress;
                  if (artistRevenue_ > 0) {
                      artistAddress_ = projectFinance.artistAddress;
                  }
                  if (additionalPayeePrimaryRevenue_ > 0) {
                      additionalPayeePrimaryAddress_ = projectFinance
                          .additionalPayeePrimarySales;
                  }
              }
              /**
               * @notice Returns external asset dependency for project `_projectId` at index `_index`.
               * If the dependencyType is ONCHAIN, the `data` field will contain the extrated bytecode data and `cid`
               * will be an empty string. Conversly, for any other dependencyType, the `data` field will be an empty string
               * and the `bytecodeAddress` will point to the zero address.
               */
              function projectExternalAssetDependencyByIndex(
                  uint256 _projectId,
                  uint256 _index
              ) external view returns (ExternalAssetDependencyWithData memory) {
                  ExternalAssetDependency storage _dependency = projects[_projectId]
                      .externalAssetDependencies[_index];
                  address _bytecodeAddress = _dependency.bytecodeAddress;
                  return
                      ExternalAssetDependencyWithData({
                          dependencyType: _dependency.dependencyType,
                          cid: _dependency.cid,
                          bytecodeAddress: _bytecodeAddress,
                          data: (_dependency.dependencyType ==
                              ExternalAssetDependencyType.ONCHAIN)
                              ? _bytecodeAddress.readFromBytecode()
                              : ""
                      });
              }
              /**
               * @notice Returns external asset dependency count for project `_projectId` at index `_index`.
               */
              function projectExternalAssetDependencyCount(
                  uint256 _projectId
              ) external view returns (uint256) {
                  return uint256(projects[_projectId].externalAssetDependencyCount);
              }
              /**
               * @notice Backwards-compatible (pre-V3) getter returning contract admin
               * @return address Address of contract admin (same as owner)
               */
              function admin() external view returns (address) {
                  return owner();
              }
              /**
               * @notice Gets the project ID for a given `_tokenId`.
               * @param _tokenId Token ID to be queried.
               * @return _projectId Project ID for given `_tokenId`.
               */
              function tokenIdToProjectId(
                  uint256 _tokenId
              ) public pure returns (uint256 _projectId) {
                  return _tokenId / ONE_MILLION;
              }
              /**
               * @notice Convenience function that returns whether `_sender` is allowed
               * to call function with selector `_selector` on contract `_contract`, as
               * determined by this contract's current Admin ACL contract. Expected use
               * cases include minter contracts checking if caller is allowed to call
               * admin-gated functions on minter contracts.
               * @param _sender Address of the sender calling function with selector
               * `_selector` on contract `_contract`.
               * @param _contract Address of the contract being called by `_sender`.
               * @param _selector Function selector of the function being called by
               * `_sender`.
               * @return bool Whether `_sender` is allowed to call function with selector
               * `_selector` on contract `_contract`.
               * @dev assumes the Admin ACL contract is the owner of this contract, which
               * is expected to always be true.
               * @dev adminACLContract is expected to either be null address (if owner
               * has renounced ownership), or conform to IAdminACLV0 interface. Check for
               * null address first to avoid revert when admin has renounced ownership.
               */
              function adminACLAllowed(
                  address _sender,
                  address _contract,
                  bytes4 _selector
              ) public returns (bool) {
                  return
                      owner() != address(0) &&
                      adminACLContract.allowed(_sender, _contract, _selector);
              }
              /**
               * @notice Returns contract owner. Set to deployer's address by default on
               * contract deployment.
               * @return address Address of contract owner.
               * @dev ref: https://docs.openzeppelin.com/contracts/4.x/api/access#Ownable
               * @dev owner role was called `admin` prior to V3 core contract
               */
              function owner()
                  public
                  view
                  override(Ownable, IGenArt721CoreContractV3_Base)
                  returns (address)
              {
                  return Ownable.owner();
              }
              /**
               * @notice Gets token URI for token ID `_tokenId`.
               * @param _tokenId Token ID to be queried.
               * @return string URI of token ID `_tokenId`.
               * @dev token URIs are the concatenation of the project base URI and the
               * token ID.
               */
              function tokenURI(
                  uint256 _tokenId
              ) public view override returns (string memory) {
                  _onlyValidTokenId(_tokenId);
                  string memory _projectBaseURI = projects[tokenIdToProjectId(_tokenId)]
                      .projectBaseURI;
                  return string.concat(_projectBaseURI, toString(_tokenId));
              }
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(
                  bytes4 interfaceId
              ) public view virtual override returns (bool) {
                  return
                      interfaceId == type(IManifold).interfaceId ||
                      super.supportsInterface(interfaceId);
              }
              /**
               * @notice Forbids new projects from being created
               * @dev only performs operation and emits event if contract is not already
               * forbidding new projects.
               */
              function _forbidNewProjects() internal {
                  if (!newProjectsForbidden) {
                      newProjectsForbidden = true;
                      emit PlatformUpdated(FIELD_NEW_PROJECTS_FORBIDDEN);
                  }
              }
              /**
               * @notice Transfers ownership of the contract to a new account (`newOwner`).
               * Internal function without access restriction.
               * @param newOwner New owner.
               * @dev owner role was called `admin` prior to V3 core contract.
               * @dev Overrides and wraps OpenZeppelin's _transferOwnership function to
               * also update adminACLContract for improved introspection.
               */
              function _transferOwnership(address newOwner) internal override {
                  Ownable._transferOwnership(newOwner);
                  adminACLContract = IAdminACLV0(newOwner);
              }
              /**
               * @notice Updates sales addresses for the platform and render providers to
               * the input parameters.
               * @param _renderProviderPrimarySalesAddress Address of new primary sales
               * payment address.
               * @param _renderProviderSecondarySalesAddress Address of new secondary sales
               * payment address.
               * @param _platformProviderPrimarySalesAddress Address of new primary sales
               * payment address.
               * @param _platformProviderSecondarySalesAddress Address of new secondary sales
               * payment address.
               * @dev Note that this method does not check that the input address is
               * not `address(0)`, as it is expected that callers of this method should
               * perform input validation where applicable.
               */
              function _updateProviderSalesAddresses(
                  address _renderProviderPrimarySalesAddress,
                  address _renderProviderSecondarySalesAddress,
                  address _platformProviderPrimarySalesAddress,
                  address _platformProviderSecondarySalesAddress
              ) internal {
                  platformProviderPrimarySalesAddress = payable(
                      _platformProviderPrimarySalesAddress
                  );
                  platformProviderSecondarySalesAddress = payable(
                      _platformProviderSecondarySalesAddress
                  );
                  renderProviderPrimarySalesAddress = payable(
                      _renderProviderPrimarySalesAddress
                  );
                  renderProviderSecondarySalesAddress = payable(
                      _renderProviderSecondarySalesAddress
                  );
                  emit PlatformUpdated(FIELD_PROVIDER_SALES_ADDRESSES);
              }
              /**
               * @notice Updates randomizer address to `_randomizerAddress`.
               * @param _randomizerAddress New randomizer address.
               * @dev Note that this method does not check that the input address is
               * not `address(0)`, as it is expected that callers of this method should
               * perform input validation where applicable.
               */
              function _updateRandomizerAddress(address _randomizerAddress) internal {
                  randomizerContract = IRandomizerV2(_randomizerAddress);
                  // populate historical randomizer array
                  _historicalRandomizerAddresses.push(_randomizerAddress);
                  emit PlatformUpdated(FIELD_RANDOMIZER_ADDRESS);
              }
              /**
               * @notice Updates default base URI to `_defaultBaseURI`.
               * When new projects are added, their `projectBaseURI` is automatically
               * initialized to `_defaultBaseURI`.
               * @param _defaultBaseURI New default base URI.
               * @dev Note that this method does not check that the input string is not
               * the empty string, as it is expected that callers of this method should
               * perform input validation where applicable.
               */
              function _updateDefaultBaseURI(string memory _defaultBaseURI) internal {
                  defaultBaseURI = _defaultBaseURI;
                  emit PlatformUpdated(FIELD_DEFAULT_BASE_URI);
              }
              /**
               * @notice Internal function to complete a project.
               * @param _projectId Project ID to be completed.
               */
              function _completeProject(uint256 _projectId) internal {
                  projects[_projectId].completedTimestamp = uint64(block.timestamp);
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_COMPLETED);
              }
              /**
               * @notice Internal function that returns whether a project is unlocked.
               * Projects automatically lock four weeks after they are completed.
               * Projects are considered completed when they have been invoked the
               * maximum number of times.
               * @param _projectId Project ID to be queried.
               * @return bool true if project is unlocked, false otherwise.
               * @dev This also enforces that the `_projectId` passed in is valid.
               */
              function _projectUnlocked(uint256 _projectId) internal view returns (bool) {
                  _onlyValidProjectId(_projectId);
                  uint256 projectCompletedTimestamp = projects[_projectId]
                      .completedTimestamp;
                  bool projectOpen = projectCompletedTimestamp == 0;
                  return
                      projectOpen ||
                      (block.timestamp - projectCompletedTimestamp <
                          FOUR_WEEKS_IN_SECONDS);
              }
              // strings library from OpenZeppelin, modified for no constants
              bytes16 private _HEX_SYMBOLS = "0123456789abcdef";
              uint8 private _ADDRESS_LENGTH = 20;
              /**
               * @dev Converts a `uint256` to its ASCII `string` decimal representation.
               */
              function toString(uint256 value) internal pure returns (string memory) {
                  // Inspired by OraclizeAPI's implementation - MIT licence
                  // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
                  if (value == 0) {
                      return "0";
                  }
                  uint256 temp = value;
                  uint256 digits;
                  while (temp != 0) {
                      digits++;
                      temp /= 10;
                  }
                  bytes memory buffer = new bytes(digits);
                  while (value != 0) {
                      digits -= 1;
                      buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
                      value /= 10;
                  }
                  return string(buffer);
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
               */
              function toHexString(
                  uint256 value,
                  uint256 length
              ) internal view returns (string memory) {
                  bytes memory buffer = new bytes(2 * length + 2);
                  buffer[0] = "0";
                  buffer[1] = "x";
                  for (uint256 i = 2 * length + 1; i > 1; --i) {
                      buffer[i] = _HEX_SYMBOLS[value & 0xf];
                      value >>= 4;
                  }
                  require(value == 0, "hex length insufficient");
                  return string(buffer);
              }
              /**
               * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
               */
              function toHexString(address addr) internal view returns (string memory) {
                  return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright (c) 2023 the ethier authors (github.com/divergencetech/ethier)
          pragma solidity >=0.8.0 <0.9.0;
          import {AccessControlEnumerable} from "../utils/AccessControlEnumerable.sol";
          import {ERC721A, ERC721ACommon} from "./ERC721ACommon.sol";
          /**
           * @notice ERC721 extension that implements a commonly used _baseURI() function
           * to return an URL prefix that can be set by the contract steerer.
           */
          contract BaseTokenURI is AccessControlEnumerable {
              /**
               * @notice Base token URI used as a prefix by tokenURI().
               */
              string private _baseTokenURI;
              constructor(string memory baseTokenURI_) {
                  _setBaseTokenURI(baseTokenURI_);
              }
              /**
               * @notice Sets the base token URI prefix.
               * @dev Only callable by the contract steerer.
               */
              function setBaseTokenURI(string memory baseTokenURI_)
                  public
                  onlyRole(DEFAULT_STEERING_ROLE)
              {
                  _setBaseTokenURI(baseTokenURI_);
              }
              /**
               * @notice Sets the base token URI prefix.
               */
              function _setBaseTokenURI(string memory baseTokenURI_) internal virtual {
                  _baseTokenURI = baseTokenURI_;
              }
              /**
               * @notice Returns the `baseTokenURI`.
               */
              function baseTokenURI() public view virtual returns (string memory) {
                  return _baseTokenURI;
              }
              /**
               * @notice Returns the base token URI * without any additional characters (e.g. a slash).
               */
              function _baseURI() internal view virtual returns (string memory) {
                  return _baseTokenURI;
              }
          }
          /**
           * @notice ERC721ACommon extension that adds BaseTokenURI.
           */
          abstract contract ERC721ACommonBaseTokenURI is ERC721ACommon, BaseTokenURI {
              /**
               * @notice Overrides supportsInterface as required by inheritance.
               */
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(ERC721ACommon, AccessControlEnumerable)
                  returns (bool)
              {
                  return
                      ERC721ACommon.supportsInterface(interfaceId) ||
                      AccessControlEnumerable.supportsInterface(interfaceId);
              }
              /**
               * @dev Inheritance resolution.
               */
              function _baseURI()
                  internal
                  view
                  virtual
                  override(ERC721A, BaseTokenURI)
                  returns (string memory)
              {
                  return BaseTokenURI._baseURI();
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright (c) 2023 the ethier authors (github.com/divergencetech/ethier)
          pragma solidity >=0.8.0 <0.9.0;
          import {Address} from "@openzeppelin/contracts/utils/Address.sol";
          import {DefaultOperatorFilterer} from "operator-filter-registry/src/DefaultOperatorFilterer.sol";
          import {ERC721A, ERC721ACommon} from "./ERC721ACommon.sol";
          /**
           * @notice ERC721ACommon extension that adds Opensea's operator filtering.
           */
          abstract contract OperatorFilterOS is ERC721ACommon, DefaultOperatorFilterer {
              using Address for address;
              /**
               * @notice Calling the operator filter registry with given calldata.
               * @dev The registry contract did not foresee role-based contract access
               * control -- only the contract itself, or its (EIP-173) owner is allowed to
               * change subscription settings. To work around this, we enforce
               * authorisation here and forward arbitrary calldata to the registry.
               * Use with care!
               */
              function callOperatorFilterRegistry(bytes calldata cdata)
                  external
                  onlyRole(DEFAULT_STEERING_ROLE)
                  returns (bytes memory)
              {
                  return address(OPERATOR_FILTER_REGISTRY).functionCall(cdata);
              }
              // =========================================================================
              //                           Operator filtering
              // =========================================================================
              function setApprovalForAll(address operator, bool approved)
                  public
                  virtual
                  override
                  onlyAllowedOperatorApproval(operator)
              {
                  super.setApprovalForAll(operator, approved);
              }
              function approve(address operator, uint256 tokenId)
                  public
                  payable
                  virtual
                  override
                  onlyAllowedOperatorApproval(operator)
              {
                  super.approve(operator, tokenId);
              }
              function transferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) public payable virtual override onlyAllowedOperator(from) {
                  super.transferFrom(from, to, tokenId);
              }
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) public payable virtual override onlyAllowedOperator(from) {
                  super.safeTransferFrom(from, to, tokenId);
              }
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory data
              ) public payable virtual override onlyAllowedOperator(from) {
                  super.safeTransferFrom(from, to, tokenId, data);
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 Proof Holdings Inc.
          pragma solidity ^0.8.0;
          uint256 constant AB_ENGINE_PROJECT_MULTIPLIER = 1_000_000;
          function artblocksTokenID(uint256 projectId, uint256 edition) pure returns (uint256) {
              return (projectId * AB_ENGINE_PROJECT_MULTIPLIER) + edition;
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 Proof Holdings Inc.
          pragma solidity >=0.8.0;
          /**
           * @notice IGenArt721CoreContractV3_Base minting interface
           */
          interface IGenArt721CoreContractV3_Mintable {
              function mint_Ecf(address to, uint256 projectId, address sender) external returns (uint256 _tokenId);
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity >=0.8.0 <0.9.0;
          import {ERC721ACommon} from "ethier/erc721/ERC721ACommon.sol";
          import {AccessControlEnumerable, BaseSellable} from "./BaseSellable.sol";
          /**
           * @notice Base contract for sellable ERC721ACommon tokens.
           */
          abstract contract SellableERC721ACommon is BaseSellable, ERC721ACommon {
              /**
               * @inheritdoc BaseSellable
               */
              function _handleSale(address to, uint64 num, bytes calldata) internal virtual override {
                  _mint(to, num);
              }
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(ERC721ACommon, AccessControlEnumerable)
                  returns (bool)
              {
                  return ERC721ACommon.supportsInterface(interfaceId) || AccessControlEnumerable.supportsInterface(interfaceId);
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 Proof Holdings Inc.
          pragma solidity >=0.8.17;
          /**
           * @notice Diamond Exhibition - Projects configuration.
           * @author David Huber (@cxkoda)
           * @custom:reviewer Arran Schlosberg (@divergencearran)
           */
          contract ProjectsConfig {
              /**
               * @notice The number of longform projects.
               */
              uint8 internal constant _NUM_LONGFORM_PROJECTS = 11;
              /**
               * @notice The number of pre-curated projects.
               */
              uint8 internal constant _NUM_CURATED_PROJECTS = 10;
              /**
               * @notice The total number of projects.
               */
              uint8 public constant NUM_PROJECTS = _NUM_LONGFORM_PROJECTS + _NUM_CURATED_PROJECTS;
              /**
               * @notice Returns the number of projects than can be minted per project.
               */
              function _maxNumPerProject() internal pure virtual returns (uint256[NUM_PROJECTS] memory sizes) {
                  return [
                      // Longform
                      uint256(600), // Impossible Distance
                      600, // cathedral study
                      600, // Deja Vu
                      800, // WaveShapes
                      1000, // Ephemeral Tides
                      600, // StackSlash
                      450, // Viridaria
                      1000, // Windwoven
                      256, // Memory Loss
                      1000, // The Collector's Room
                      1000, // Extrañezas
                      // Pre-curated
                      100, // Everydays: Group Effort
                      100, // Kid Heart
                      100, // BEHEADED (SELF PORTRAIT)
                      1127, // End Transmissions
                      77, // DES CHOSES™
                      100, // A Wintry Night in Chinatown
                      100, // Penthouse
                      200, // Hands of Umbra
                      100, // Solitaire
                      100 // Remnants of a Distant Dream
                  ];
              }
              /**
               * @notice Returns the number of projects than can be minted per project.
               */
              function maxNumPerProject() external pure returns (uint256[NUM_PROJECTS] memory) {
                  return _maxNumPerProject();
              }
              // =========================================================================
              //                          Project Types
              // =========================================================================
              /**
               * @notice The different types of projects.
               */
              enum ProjectType {
                  Longform,
                  Curated
              }
              /**
               * @notice Returns the project type for a given project ID.
               */
              function projectType(uint8 projectId) public pure returns (ProjectType) {
                  return projectId < _NUM_LONGFORM_PROJECTS ? ProjectType.Longform : ProjectType.Curated;
              }
              /**
               * @notice Returns true iff the project is a longform project.
               */
              function _isLongformProject(uint8 projectId) internal pure virtual returns (bool) {
                  return projectType(projectId) == ProjectType.Longform;
              }
              // =========================================================================
              //                          Artblocks
              // =========================================================================
              /**
               * @notice Returns the ArtBlocks engine project IDs for the longform projects.
               */
              function _artblocksProjectIds() internal pure virtual returns (uint8[_NUM_LONGFORM_PROJECTS] memory) {
                  return [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
              }
              /**
               * @notice Returns the ArtBlocks engine project IDs for the longform projects.
               */
              function artblocksProjectIds() external pure returns (uint8[_NUM_LONGFORM_PROJECTS] memory) {
                  return _artblocksProjectIds();
              }
              /**
               * @notice Returns the ArtBlocks engine project ID for a given project ID.
               * @dev Reverts if the project is not long-form.
               */
              function _artblocksProjectId(uint8 projectId) internal pure returns (uint256) {
                  assert(_isLongformProject(projectId));
                  return _artblocksProjectIds()[projectId];
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 Proof Holdings Inc.
          pragma solidity >=0.8.17;
          /**
           * @notice Token information module for Diamond Exhibition.
           * @author David Huber (@cxkoda)
           * @custom:reviewer Arran Schlosberg (@divergencearran)
           */
          contract TokenInfoManager {
              /**
               * @notice Encodes token information.
               * @param projectId the ID of the project associated with the token.
               * @param edition the edition of the token within the given project.
               */
              struct TokenInfo {
                  uint8 projectId;
                  uint16 edition;
              }
              /**
               * @notice Max numbers of tokens that this contract can store.
               * @dev This constant is intentionally very large so we never have to worry about it.
               */
              uint256 internal constant _NUM_MAX_TOKEN_INFO = (1 << 32);
              /**
               * @notice Stores token information.
               */
              TokenInfo[_NUM_MAX_TOKEN_INFO] private _infos;
              /**
               * @notice Returns the token information for the given token IDs.
               * @dev Intended for off-chain use only.
               */
              function tokenInfos(uint256[] calldata tokenIds) external view returns (TokenInfo[] memory) {
                  TokenInfo[] memory infos = new TokenInfo[](tokenIds.length);
                  for (uint256 i = 0; i < tokenIds.length; ++i) {
                      infos[i] = _tokenInfo(tokenIds[i]);
                  }
                  return infos;
              }
              /**
               * @notice Returns the token information for the given token ID.
               */
              function _tokenInfo(uint256 tokenId) internal view returns (TokenInfo memory) {
                  return _infos[tokenId];
              }
              /**
               * @notice Sets the token information for the given token ID.
               */
              function _setTokenInfo(uint256 tokenId, uint8 projectId, uint16 edition) internal {
                  _infos[tokenId] = TokenInfo({projectId: projectId, edition: edition});
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity ^0.8.15;
          import {ERC721A, ERC721ACommon, BaseTokenURI, ERC721ACommonBaseTokenURI} from "ethier/erc721/BaseTokenURI.sol";
          import {OperatorFilterOS} from "ethier/erc721/OperatorFilterOS.sol";
          import {
              TransferRestrictedRedeemableERC721ACommon,
              TransferRestriction
          } from "proof/redemption/voucher/TransferRestrictedRedeemableERC721ACommon.sol";
          import {SellableERC721ACommon} from "proof/sellers/sellable/SellableERC721ACommon.sol";
          /**
           * @notice A redeemable and sellable ERC721 token with operator filtering and transfer restrictions.
           * @dev The contract name should be sung to the tune of "Modern Major-General"
           */
          abstract contract SellableRedeemableRestrictableERC721 is
              ERC721ACommonBaseTokenURI,
              OperatorFilterOS,
              SellableERC721ACommon,
              TransferRestrictedRedeemableERC721ACommon
          {
              // =================================================================================================================
              //                          Inheritance Resolution
              // =================================================================================================================
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(ERC721ACommon, ERC721ACommonBaseTokenURI, SellableERC721ACommon, TransferRestrictedRedeemableERC721ACommon)
                  returns (bool)
              {
                  return TransferRestrictedRedeemableERC721ACommon.supportsInterface(interfaceId)
                      || SellableERC721ACommon.supportsInterface(interfaceId)
                      || ERC721ACommonBaseTokenURI.supportsInterface(interfaceId);
              }
              function _beforeTokenTransfers(address from, address to, uint256 startTokenId, uint256 quantity)
                  internal
                  virtual
                  override(ERC721ACommon, TransferRestrictedRedeemableERC721ACommon)
              {
                  TransferRestrictedRedeemableERC721ACommon._beforeTokenTransfers(from, to, startTokenId, quantity);
              }
              function _baseURI() internal view virtual override(ERC721A, ERC721ACommonBaseTokenURI) returns (string memory) {
                  return ERC721ACommonBaseTokenURI._baseURI();
              }
              function setApprovalForAll(address operator, bool approved) public virtual override(ERC721A, OperatorFilterOS) {
                  OperatorFilterOS.setApprovalForAll(operator, approved);
              }
              function approve(address operator, uint256 tokenId) public payable virtual override(ERC721A, OperatorFilterOS) {
                  OperatorFilterOS.approve(operator, tokenId);
              }
              function transferFrom(address from, address to, uint256 tokenId)
                  public
                  payable
                  virtual
                  override(ERC721A, OperatorFilterOS)
              {
                  OperatorFilterOS.transferFrom(from, to, tokenId);
              }
              function safeTransferFrom(address from, address to, uint256 tokenId)
                  public
                  payable
                  virtual
                  override(ERC721A, OperatorFilterOS)
              {
                  OperatorFilterOS.safeTransferFrom(from, to, tokenId);
              }
              function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data)
                  public
                  payable
                  virtual
                  override(ERC721A, OperatorFilterOS)
              {
                  OperatorFilterOS.safeTransferFrom(from, to, tokenId, data);
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright (c) 2021 the ethier authors (github.com/divergencetech/ethier)
          pragma solidity >=0.8.0 <0.9.0;
          import {ISellable, CallbackerWithAccessControl} from "./CallbackerWithAccessControl.sol";
          import {FixedSupply} from "../base/SupplyLimited.sol";
          /**
           * @notice Seller module that adds a role-gated free-of-charge purchase (e.g. to facilitate to owner mints).
           */
          contract RoleGatedFreeOfCharge is CallbackerWithAccessControl, FixedSupply {
              error WrongNumSoldAfterPurchase(uint256 actual, uint256 expected);
              constructor(address admin, address steerer, ISellable sellable_, uint64 numMaxSellable_)
                  CallbackerWithAccessControl(admin, steerer, sellable_)
                  FixedSupply(numMaxSellable_)
              {
                  // solhint-disable-line no-empty-blocks
              }
              /**
               * @notice Encodes a free-of-charge purchase.
               * @param to The address to receive the purchased items.
               * @param num The number of items to purchase.
               */
              struct Receiver {
                  address to;
                  uint64 num;
              }
              /**
               * @notice Purchases numbers of tokens for given addresses free of charge.
               */
              function _purchase(Receiver[] calldata receivers) internal {
                  for (uint256 idx = 0; idx < receivers.length; ++idx) {
                      _purchase(receivers[idx].to, receivers[idx].num, 0, "");
                  }
              }
              /**
               * @notice Purchases numbers of tokens for given addresses free of charge.
               */
              function purchase(Receiver[] calldata receivers) external onlyRole(DEFAULT_STEERING_ROLE) {
                  _purchase(receivers);
              }
              /**
               * @notice Purchases numbers of tokens for given addresses free of charge and checks if the number of sold tokens
               * matches the expected value.
               */
              function purchaseWithGuardRails(Receiver[] calldata receivers, uint256 expectedNumSoldAfter)
                  external
                  onlyRole(DEFAULT_STEERING_ROLE)
              {
                  _purchase(receivers);
                  if (numSold() != expectedNumSoldAfter) {
                      revert WrongNumSoldAfterPurchase(numSold(), expectedNumSoldAfter);
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity >=0.8.0 <0.9.0;
          import {ISellable} from "../interfaces/ISellable.sol";
          import {PurchaseExecuter} from "../interfaces/PurchaseExecuter.sol";
          /**
           * @notice Executes a purchase by calling the purchase interface of a `ISellable`  contract.
           */
          abstract contract SellableCallbacker is PurchaseExecuter {
              /**
               * @notice Emitted when the callback to the `ISellable` contract fails.
               */
              error CallbackFailed(bytes reason);
              /**
               * @notice The `ISellable` contract that will be called to execute the purchase.
               */
              ISellable public immutable sellable;
              constructor(ISellable sellable_) {
                  sellable = ISellable(sellable_);
              }
              /**
               * @notice Executes a purchase by calling the sale interface of a `ISellable` contract.
               */
              function _executePurchase(address to, uint64 num, uint256 cost, bytes memory data) internal virtual override {
                  try sellable.handleSale{value: cost}(to, num, data) {}
                  catch (bytes memory reason) {
                      // TODO(dave): the reason is empty if the above call runs OutOfFund. Explore ways to bubble this up more cleanly.
                      revert CallbackFailed(reason);
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity >=0.8.0 <0.9.0;
          import {ReentrancyGuard} from "openzeppelin-contracts/security/ReentrancyGuard.sol";
          import {PurchaseExecuter} from "../interfaces/PurchaseExecuter.sol";
          /**
           * @notice Abstract base contract for all `Seller`s.
           * @dev The intention of this contract is to provide an extensible base for various kinds of Seller modules that can be
           * flexibly composed to build more complex sellers - allowing effective code reuse.
           * Derived contracts are intended to implement their logic by overriding and extending the `_checkAndModifyPurchase` and
           * `_beforePurchase` hooks (calling the parent implementation(s) to compose logic). The former is intended to perform
           * manipulations and checks of the input data; the latter to update the internal state of the module.
           * Final sellers will compose these modules and expose an addition external purchase function for buyers.
           */
          abstract contract Seller is PurchaseExecuter, ReentrancyGuard {
              uint256 internal constant _UNDEFINED_COST = type(uint256).max;
              /**
               * @notice Internal function handling a given purchase, performing checks and input manipulations depending on the
               * logic in the hooks.
               * @param to The receiver of the purchase
               * @param num Number of requested purchases
               * @param externalTotalCost Total cost of the purchase
               * @dev This function is intended to be wrapped in an external method for final sellers. Since we cannot foresee
               * what logic will be implemented in the hooks, we added a reentrancy guard for safety.
               */
              function _purchase(address to, uint64 num, uint256 externalTotalCost, bytes memory data)
                  internal
                  virtual
                  nonReentrant
              {
                  uint256 totalCost;
                  (to, num, totalCost) = _checkAndModifyPurchase(to, num, externalTotalCost, data);
                  _beforePurchase(to, num, totalCost, data);
                  _executePurchase(to, num, totalCost, data);
              }
              // =================================================================================================================
              //                           Hooks
              // =================================================================================================================
              /**
               * @notice Hook that is called before handling a purchase (even before `_beforePurchase`)
               * @dev The intent of this hook is to manipulate the input data and perform  checks before actually handling the
               * purchase.
               * @param to The receiver of the purchase
               * @param num Number of requested purchases
               * @param totalCost Total cost of the purchase
               * @dev This function MUST return sensible values, since these will be used to perfom the purchase.
               */
              function _checkAndModifyPurchase(address to, uint64 num, uint256 totalCost, bytes memory)
                  internal
                  view
                  virtual
                  returns (address, uint64, uint256)
              {
                  return (to, num, totalCost);
              }
              /**
               * @notice Hook that is called before handling a purchase.
               * @dev The intent of this hook is to update the internal state of the seller (module) if necessary.
               * It is critical that the updates happen here and not in `_checkAndModifyPurchase` because only after calling that
               * function the purchase parameters can be considered fixed.
               */
              function _beforePurchase(address to, uint64 num, uint256 totalCost, bytes memory data) internal virtual {
                  // solhint-disable-line no-empty-blocks
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Standard math utilities missing in the Solidity language.
           */
          library Math {
              enum Rounding {
                  Down, // Toward negative infinity
                  Up, // Toward infinity
                  Zero // Toward zero
              }
              /**
               * @dev Returns the largest of two numbers.
               */
              function max(uint256 a, uint256 b) internal pure returns (uint256) {
                  return a > b ? a : b;
              }
              /**
               * @dev Returns the smallest of two numbers.
               */
              function min(uint256 a, uint256 b) internal pure returns (uint256) {
                  return a < b ? a : b;
              }
              /**
               * @dev Returns the average of two numbers. The result is rounded towards
               * zero.
               */
              function average(uint256 a, uint256 b) internal pure returns (uint256) {
                  // (a + b) / 2 can overflow.
                  return (a & b) + (a ^ b) / 2;
              }
              /**
               * @dev Returns the ceiling of the division of two numbers.
               *
               * This differs from standard division with `/` in that it rounds up instead
               * of rounding down.
               */
              function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
                  // (a + b - 1) / b can overflow on addition, so we distribute.
                  return a == 0 ? 0 : (a - 1) / b + 1;
              }
              /**
               * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
               * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
               * with further edits by Uniswap Labs also under MIT license.
               */
              function mulDiv(
                  uint256 x,
                  uint256 y,
                  uint256 denominator
              ) internal pure returns (uint256 result) {
                  unchecked {
                      // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                      // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                      // variables such that product = prod1 * 2^256 + prod0.
                      uint256 prod0; // Least significant 256 bits of the product
                      uint256 prod1; // Most significant 256 bits of the product
                      assembly {
                          let mm := mulmod(x, y, not(0))
                          prod0 := mul(x, y)
                          prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                      }
                      // Handle non-overflow cases, 256 by 256 division.
                      if (prod1 == 0) {
                          return prod0 / denominator;
                      }
                      // Make sure the result is less than 2^256. Also prevents denominator == 0.
                      require(denominator > prod1);
                      ///////////////////////////////////////////////
                      // 512 by 256 division.
                      ///////////////////////////////////////////////
                      // Make division exact by subtracting the remainder from [prod1 prod0].
                      uint256 remainder;
                      assembly {
                          // Compute remainder using mulmod.
                          remainder := mulmod(x, y, denominator)
                          // Subtract 256 bit number from 512 bit number.
                          prod1 := sub(prod1, gt(remainder, prod0))
                          prod0 := sub(prod0, remainder)
                      }
                      // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
                      // See https://cs.stackexchange.com/q/138556/92363.
                      // Does not overflow because the denominator cannot be zero at this stage in the function.
                      uint256 twos = denominator & (~denominator + 1);
                      assembly {
                          // Divide denominator by twos.
                          denominator := div(denominator, twos)
                          // Divide [prod1 prod0] by twos.
                          prod0 := div(prod0, twos)
                          // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                          twos := add(div(sub(0, twos), twos), 1)
                      }
                      // Shift in bits from prod1 into prod0.
                      prod0 |= prod1 * twos;
                      // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                      // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                      // four bits. That is, denominator * inv = 1 mod 2^4.
                      uint256 inverse = (3 * denominator) ^ 2;
                      // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
                      // in modular arithmetic, doubling the correct bits in each step.
                      inverse *= 2 - denominator * inverse; // inverse mod 2^8
                      inverse *= 2 - denominator * inverse; // inverse mod 2^16
                      inverse *= 2 - denominator * inverse; // inverse mod 2^32
                      inverse *= 2 - denominator * inverse; // inverse mod 2^64
                      inverse *= 2 - denominator * inverse; // inverse mod 2^128
                      inverse *= 2 - denominator * inverse; // inverse mod 2^256
                      // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                      // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                      // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                      // is no longer required.
                      result = prod0 * inverse;
                      return result;
                  }
              }
              /**
               * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
               */
              function mulDiv(
                  uint256 x,
                  uint256 y,
                  uint256 denominator,
                  Rounding rounding
              ) internal pure returns (uint256) {
                  uint256 result = mulDiv(x, y, denominator);
                  if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
                      result += 1;
                  }
                  return result;
              }
              /**
               * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
               *
               * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
               */
              function sqrt(uint256 a) internal pure returns (uint256) {
                  if (a == 0) {
                      return 0;
                  }
                  // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
                  //
                  // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
                  // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
                  //
                  // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
                  // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
                  // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
                  //
                  // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
                  uint256 result = 1 << (log2(a) >> 1);
                  // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
                  // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
                  // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
                  // into the expected uint128 result.
                  unchecked {
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      return min(result, a / result);
                  }
              }
              /**
               * @notice Calculates sqrt(a), following the selected rounding direction.
               */
              function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = sqrt(a);
                      return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
                  }
              }
              /**
               * @dev Return the log in base 2, rounded down, of a positive value.
               * Returns 0 if given 0.
               */
              function log2(uint256 value) internal pure returns (uint256) {
                  uint256 result = 0;
                  unchecked {
                      if (value >> 128 > 0) {
                          value >>= 128;
                          result += 128;
                      }
                      if (value >> 64 > 0) {
                          value >>= 64;
                          result += 64;
                      }
                      if (value >> 32 > 0) {
                          value >>= 32;
                          result += 32;
                      }
                      if (value >> 16 > 0) {
                          value >>= 16;
                          result += 16;
                      }
                      if (value >> 8 > 0) {
                          value >>= 8;
                          result += 8;
                      }
                      if (value >> 4 > 0) {
                          value >>= 4;
                          result += 4;
                      }
                      if (value >> 2 > 0) {
                          value >>= 2;
                          result += 2;
                      }
                      if (value >> 1 > 0) {
                          result += 1;
                      }
                  }
                  return result;
              }
              /**
               * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
               * Returns 0 if given 0.
               */
              function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = log2(value);
                      return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
                  }
              }
              /**
               * @dev Return the log in base 10, rounded down, of a positive value.
               * Returns 0 if given 0.
               */
              function log10(uint256 value) internal pure returns (uint256) {
                  uint256 result = 0;
                  unchecked {
                      if (value >= 10**64) {
                          value /= 10**64;
                          result += 64;
                      }
                      if (value >= 10**32) {
                          value /= 10**32;
                          result += 32;
                      }
                      if (value >= 10**16) {
                          value /= 10**16;
                          result += 16;
                      }
                      if (value >= 10**8) {
                          value /= 10**8;
                          result += 8;
                      }
                      if (value >= 10**4) {
                          value /= 10**4;
                          result += 4;
                      }
                      if (value >= 10**2) {
                          value /= 10**2;
                          result += 2;
                      }
                      if (value >= 10**1) {
                          result += 1;
                      }
                  }
                  return result;
              }
              /**
               * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
               * Returns 0 if given 0.
               */
              function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = log10(value);
                      return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
                  }
              }
              /**
               * @dev Return the log in base 256, rounded down, of a positive value.
               * Returns 0 if given 0.
               *
               * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
               */
              function log256(uint256 value) internal pure returns (uint256) {
                  uint256 result = 0;
                  unchecked {
                      if (value >> 128 > 0) {
                          value >>= 128;
                          result += 16;
                      }
                      if (value >> 64 > 0) {
                          value >>= 64;
                          result += 8;
                      }
                      if (value >> 32 > 0) {
                          value >>= 32;
                          result += 4;
                      }
                      if (value >> 16 > 0) {
                          value >>= 16;
                          result += 2;
                      }
                      if (value >> 8 > 0) {
                          result += 1;
                      }
                  }
                  return result;
              }
              /**
               * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
               * Returns 0 if given 0.
               */
              function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = log256(value);
                      return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
                  }
              }
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Creatd By: Art Blocks Inc.
          pragma solidity ^0.8.0;
          import "./IGenArt721CoreContractV3_Base.sol";
          interface IRandomizerV2 {
              // The core contract that may interact with this randomizer contract.
              function genArt721Core()
                  external
                  view
                  returns (IGenArt721CoreContractV3_Base);
              // When a core contract calls this, it can be assured that the randomizer
              // will set a bytes32 hash for tokenId `_tokenId` on the core contract.
              function assignTokenHash(uint256 _tokenId) external;
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.0;
          interface IAdminACLV0 {
              /**
               * @notice Token ID `_tokenId` minted to `_to`.
               * @param previousSuperAdmin The previous superAdmin address.
               * @param newSuperAdmin The new superAdmin address.
               * @param genArt721CoreAddressesToUpdate Array of genArt721Core
               * addresses to update to the new superAdmin, for indexing purposes only.
               */
              event SuperAdminTransferred(
                  address indexed previousSuperAdmin,
                  address indexed newSuperAdmin,
                  address[] genArt721CoreAddressesToUpdate
              );
              /// Type of the Admin ACL contract, e.g. "AdminACLV0"
              function AdminACLType() external view returns (string memory);
              /// super admin address
              function superAdmin() external view returns (address);
              /**
               * @notice Calls transferOwnership on other contract from this contract.
               * This is useful for updating to a new AdminACL contract.
               * @dev this function should be gated to only superAdmin-like addresses.
               */
              function transferOwnershipOn(
                  address _contract,
                  address _newAdminACL
              ) external;
              /**
               * @notice Calls renounceOwnership on other contract from this contract.
               * @dev this function should be gated to only superAdmin-like addresses.
               */
              function renounceOwnershipOn(address _contract) external;
              /**
               * @notice Checks if sender `_sender` is allowed to call function with selector
               * `_selector` on contract `_contract`.
               */
              function allowed(
                  address _sender,
                  address _contract,
                  bytes4 _selector
              ) external returns (bool);
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.17;
          interface IEngineRegistryV0 {
              /// ADDRESS
              /**
               * @notice contract has been registered as a contract that is powered by the Art Blocks Engine.
               */
              event ContractRegistered(
                  address indexed _contractAddress,
                  bytes32 _coreVersion,
                  bytes32 _coreType
              );
              /// ADDRESS
              /**
               * @notice contract has been unregistered as a contract that is powered by the Art Blocks Engine.
               */
              event ContractUnregistered(address indexed _contractAddress);
              /**
               * @notice Emits a `ContractRegistered` event with the provided information.
               * @dev this function should be gated to only deployer addresses.
               */
              function registerContract(
                  address _contractAddress,
                  bytes32 _coreVersion,
                  bytes32 _coreType
              ) external;
              /**
               * @notice Emits a `ContractUnregistered` event with the provided information, validating that the provided
               *         address was indeed previously registered.
               * @dev this function should be gated to only deployer addresses.
               */
              function unregisterContract(address _contractAddress) external;
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.0;
          import "./IAdminACLV0.sol";
          import "./IGenArt721CoreContractV3_Engine.sol";
          /**
           * @title This interface is intended to house interface items that are common
           * across all GenArt721CoreContractV3 Engine Flex and derivative implementations.
           * @author Art Blocks Inc.
           */
          interface IGenArt721CoreContractV3_Engine_Flex is
              IGenArt721CoreContractV3_Engine
          {
              /**
               * @notice When an external asset dependency is updated or added, this event is emitted.
               * @param _projectId The project ID of the project that was updated.
               * @param _index The index of the external asset dependency that was updated.
               * @param _cid The content ID of the external asset dependency. This is an empty string
               * if the dependency type is ONCHAIN.
               * @param _dependencyType The type of the external asset dependency.
               * @param _externalAssetDependencyCount The number of external asset dependencies.
               */
              event ExternalAssetDependencyUpdated(
                  uint256 indexed _projectId,
                  uint256 indexed _index,
                  string _cid,
                  ExternalAssetDependencyType _dependencyType,
                  uint24 _externalAssetDependencyCount
              );
              /**
               * @notice The project id `_projectId` has had an external asset dependency removed at index `_index`.
               */
              event ExternalAssetDependencyRemoved(
                  uint256 indexed _projectId,
                  uint256 indexed _index
              );
              /**
               * @notice The preferred gateway for dependency type `_dependencyType` has been updated to `_gatewayAddress`.
               */
              event GatewayUpdated(
                  ExternalAssetDependencyType indexed _dependencyType,
                  string _gatewayAddress
              );
              /**
               * @notice The project id `_projectId` has had all external asset dependencies locked.
               * @dev This is a one-way operation. Once locked, the external asset dependencies cannot be updated.
               */
              event ProjectExternalAssetDependenciesLocked(uint256 indexed _projectId);
              /**
               * @notice An external asset dependency type. Can be one of IPFS, ARWEAVE, or ONCHAIN.
               */
              enum ExternalAssetDependencyType {
                  IPFS,
                  ARWEAVE,
                  ONCHAIN
              }
              /**
               * @notice An external asset dependency. This is a struct that contains the CID of the dependency,
               * the type of the dependency, and the address of the bytecode for this dependency.
               */
              struct ExternalAssetDependency {
                  string cid;
                  ExternalAssetDependencyType dependencyType;
                  address bytecodeAddress;
              }
              /**
               * @notice An external asset dependency with data. This is a convenience struct that contains the CID of the dependency,
               * the type of the dependency, the address of the bytecode for this dependency, and the data retrieved from this bytecode address.
               */
              struct ExternalAssetDependencyWithData {
                  string cid;
                  ExternalAssetDependencyType dependencyType;
                  address bytecodeAddress;
                  string data;
              }
              // preferredIPFSGateway is a url string
              function preferredIPFSGateway() external view returns (string memory);
              // preferredArweaveGateway is a url string
              function preferredArweaveGateway() external view returns (string memory);
              // updates the preferred IPFS gateway
              function updateIPFSGateway(string calldata _gateway) external;
              // updates the preferred Arweave gateway
              function updateArweaveGateway(string calldata _gateway) external;
              // locks the external asset dependencies for a project
              function lockProjectExternalAssetDependencies(uint256 _projectId) external;
              // updates the external asset dependency for a project at a given index
              function updateProjectExternalAssetDependency(
                  uint256 _projectId,
                  uint256 _index,
                  string memory _cidOrData,
                  ExternalAssetDependencyType _dependencyType
              ) external;
              // adds an external asset dependency for a project
              function addProjectExternalAssetDependency(
                  uint256 _projectId,
                  string memory _cidOrData,
                  ExternalAssetDependencyType _dependencyType
              ) external;
              // removes an external asset dependency for a project at a given index
              function removeProjectExternalAssetDependency(
                  uint256 _projectId,
                  uint256 _index
              ) external;
              // getter function for project external asset dependencies
              function projectExternalAssetDependencyByIndex(
                  uint256 _projectId,
                  uint256 _index
              ) external view returns (ExternalAssetDependencyWithData memory);
              // getter function project external asset dependency count
              function projectExternalAssetDependencyCount(
                  uint256 _projectId
              ) external view returns (uint256);
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.17;
          interface IDependencyRegistryCompatibleV0 {
              /// Dependency registry managed by Art Blocks
              function artblocksDependencyRegistryAddress()
                  external
                  view
                  returns (address);
              /**
               * @notice Returns script information for project `_projectId`.
               * @param _projectId Project to be queried.
               * @return scriptTypeAndVersion Project's script type and version
               * (e.g. "p5js(atSymbol)1.0.0")
               * @return aspectRatio Aspect ratio of project (e.g. "1" for square,
               * "1.77777778" for 16:9, etc.)
               * @return scriptCount Count of scripts for project
               */
              function projectScriptDetails(
                  uint256 _projectId
              )
                  external
                  view
                  returns (
                      string memory scriptTypeAndVersion,
                      string memory aspectRatio,
                      uint256 scriptCount
                  );
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          /// @dev Royalty Registry interface, used to support the Royalty Registry.
          /// @dev Source: https://github.com/manifoldxyz/royalty-registry-solidity/blob/main/contracts/specs/IManifold.sol
          /// @author: manifold.xyz
          /**
           * @dev Royalty interface for creator core classes
           */
          interface IManifold {
              /**
               * @dev Get royalites of a token.  Returns list of receivers and basisPoints
               *
               *  bytes4(keccak256('getRoyalties(uint256)')) == 0xbb3bafd6
               *
               *  => 0xbb3bafd6 = 0xbb3bafd6
               */
              function getRoyalties(
                  uint256 tokenId
              ) external view returns (address payable[] memory, uint256[] memory);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
          pragma solidity ^0.8.0;
          import "../utils/Context.sol";
          /**
           * @dev Contract module which provides a basic access control mechanism, where
           * there is an account (an owner) that can be granted exclusive access to
           * specific functions.
           *
           * By default, the owner account will be the one that deploys the contract. This
           * can later be changed with {transferOwnership}.
           *
           * This module is used through inheritance. It will make available the modifier
           * `onlyOwner`, which can be applied to your functions to restrict their use to
           * the owner.
           */
          abstract contract Ownable is Context {
              address private _owner;
              event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
              /**
               * @dev Initializes the contract setting the deployer as the initial owner.
               */
              constructor() {
                  _transferOwnership(_msgSender());
              }
              /**
               * @dev Throws if called by any account other than the owner.
               */
              modifier onlyOwner() {
                  _checkOwner();
                  _;
              }
              /**
               * @dev Returns the address of the current owner.
               */
              function owner() public view virtual returns (address) {
                  return _owner;
              }
              /**
               * @dev Throws if the sender is not the owner.
               */
              function _checkOwner() internal view virtual {
                  require(owner() == _msgSender(), "Ownable: caller is not the owner");
              }
              /**
               * @dev Leaves the contract without owner. It will not be possible to call
               * `onlyOwner` functions anymore. Can only be called by the current owner.
               *
               * NOTE: Renouncing ownership will leave the contract without an owner,
               * thereby removing any functionality that is only available to the owner.
               */
              function renounceOwnership() public virtual onlyOwner {
                  _transferOwnership(address(0));
              }
              /**
               * @dev Transfers ownership of the contract to a new account (`newOwner`).
               * Can only be called by the current owner.
               */
              function transferOwnership(address newOwner) public virtual onlyOwner {
                  require(newOwner != address(0), "Ownable: new owner is the zero address");
                  _transferOwnership(newOwner);
              }
              /**
               * @dev Transfers ownership of the contract to a new account (`newOwner`).
               * Internal function without access restriction.
               */
              function _transferOwnership(address newOwner) internal virtual {
                  address oldOwner = _owner;
                  _owner = newOwner;
                  emit OwnershipTransferred(oldOwner, newOwner);
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/ERC721.sol)
          pragma solidity ^0.8.0;
          import "@openzeppelin-4.7/contracts/token/ERC721/IERC721.sol";
          import "@openzeppelin-4.7/contracts/token/ERC721/IERC721Receiver.sol";
          import "@openzeppelin-4.7/contracts/token/ERC721/extensions/IERC721Metadata.sol";
          import "@openzeppelin-4.7/contracts/utils/Address.sol";
          import "@openzeppelin-4.7/contracts/utils/Context.sol";
          import "@openzeppelin-4.7/contracts/utils/Strings.sol";
          import "@openzeppelin-4.7/contracts/utils/introspection/ERC165.sol";
          /**
           * @dev Forked version of the OpenZeppelin v4.7.1 ERC721 contract. Utilizes a
           * struct to pack owner and hash seed into a single storage slot.
           * ---------------------
           * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
           * the Metadata extension, but not including the Enumerable extension, which is available separately as
           * {ERC721Enumerable}.
           */
          contract ERC721_PackedHashSeed is Context, ERC165, IERC721, IERC721Metadata {
              using Address for address;
              using Strings for uint256;
              // Token name
              string private _name;
              // Token symbol
              string private _symbol;
              /// struct to pack a token owner and hash seed into same storage slot
              struct OwnerAndHashSeed {
                  // 20 bytes for address of token's owner
                  address owner;
                  // remaining 12 bytes allocated to token hash seed
                  bytes12 hashSeed;
              }
              /// mapping of token ID to OwnerAndHashSeed
              /// @dev visibility internal so inheriting contracts can access
              mapping(uint256 => OwnerAndHashSeed) internal _ownersAndHashSeeds;
              // Mapping owner address to token count
              mapping(address => uint256) private _balances;
              // Mapping from token ID to approved address
              mapping(uint256 => address) private _tokenApprovals;
              // Mapping from owner to operator approvals
              mapping(address => mapping(address => bool)) private _operatorApprovals;
              /**
               * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
               */
              constructor(string memory name_, string memory symbol_) {
                  _name = name_;
                  _symbol = symbol_;
              }
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(
                  bytes4 interfaceId
              ) public view virtual override(ERC165, IERC165) returns (bool) {
                  return
                      interfaceId == type(IERC721).interfaceId ||
                      interfaceId == type(IERC721Metadata).interfaceId ||
                      super.supportsInterface(interfaceId);
              }
              /**
               * @dev See {IERC721-balanceOf}.
               */
              function balanceOf(
                  address owner
              ) public view virtual override returns (uint256) {
                  require(
                      owner != address(0),
                      "ERC721: address zero is not a valid owner"
                  );
                  return _balances[owner];
              }
              /**
               * @dev See {IERC721-ownerOf}.
               */
              function ownerOf(
                  uint256 tokenId
              ) public view virtual override returns (address) {
                  address owner = _ownersAndHashSeeds[tokenId].owner;
                  require(owner != address(0), "ERC721: invalid token ID");
                  return owner;
              }
              /**
               * @dev See {IERC721Metadata-name}.
               */
              function name() public view virtual override returns (string memory) {
                  return _name;
              }
              /**
               * @dev See {IERC721Metadata-symbol}.
               */
              function symbol() public view virtual override returns (string memory) {
                  return _symbol;
              }
              /**
               * @dev See {IERC721Metadata-tokenURI}.
               */
              function tokenURI(
                  uint256 tokenId
              ) public view virtual override returns (string memory) {
                  _requireMinted(tokenId);
                  string memory baseURI = _baseURI();
                  return
                      bytes(baseURI).length > 0
                          ? string(abi.encodePacked(baseURI, tokenId.toString()))
                          : "";
              }
              /**
               * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
               * token will be the concatenation of the `baseURI` and the `tokenId`. Empty
               * by default, can be overridden in child contracts.
               */
              function _baseURI() internal view virtual returns (string memory) {
                  return "";
              }
              /**
               * @dev See {IERC721-approve}.
               */
              function approve(address to, uint256 tokenId) public virtual override {
                  address owner = ERC721_PackedHashSeed.ownerOf(tokenId);
                  require(to != owner, "ERC721: approval to current owner");
                  require(
                      _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
                      "ERC721: approve caller is not token owner nor approved for all"
                  );
                  _approve(to, tokenId);
              }
              /**
               * @dev See {IERC721-getApproved}.
               */
              function getApproved(
                  uint256 tokenId
              ) public view virtual override returns (address) {
                  _requireMinted(tokenId);
                  return _tokenApprovals[tokenId];
              }
              /**
               * @dev See {IERC721-setApprovalForAll}.
               */
              function setApprovalForAll(
                  address operator,
                  bool approved
              ) public virtual override {
                  _setApprovalForAll(_msgSender(), operator, approved);
              }
              /**
               * @dev See {IERC721-isApprovedForAll}.
               */
              function isApprovedForAll(
                  address owner,
                  address operator
              ) public view virtual override returns (bool) {
                  return _operatorApprovals[owner][operator];
              }
              /**
               * @dev See {IERC721-transferFrom}.
               */
              function transferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) public virtual override {
                  //solhint-disable-next-line max-line-length
                  require(
                      _isApprovedOrOwner(_msgSender(), tokenId),
                      "ERC721: caller is not token owner nor approved"
                  );
                  _transfer(from, to, tokenId);
              }
              /**
               * @dev See {IERC721-safeTransferFrom}.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) public virtual override {
                  safeTransferFrom(from, to, tokenId, "");
              }
              /**
               * @dev See {IERC721-safeTransferFrom}.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory data
              ) public virtual override {
                  require(
                      _isApprovedOrOwner(_msgSender(), tokenId),
                      "ERC721: caller is not token owner nor approved"
                  );
                  _safeTransfer(from, to, tokenId, data);
              }
              /**
               * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
               * are aware of the ERC721 protocol to prevent tokens from being forever locked.
               *
               * `data` is additional data, it has no specified format and it is sent in call to `to`.
               *
               * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
               * implement alternative mechanisms to perform token transfer, such as signature-based.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must exist and be owned by `from`.
               * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function _safeTransfer(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory data
              ) internal virtual {
                  _transfer(from, to, tokenId);
                  require(
                      _checkOnERC721Received(from, to, tokenId, data),
                      "ERC721: transfer to non ERC721Receiver implementer"
                  );
              }
              /**
               * @dev Returns whether `tokenId` exists.
               *
               * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
               *
               * Tokens start existing when they are minted (`_mint`),
               * and stop existing when they are burned (`_burn`).
               */
              function _exists(uint256 tokenId) internal view virtual returns (bool) {
                  return _ownersAndHashSeeds[tokenId].owner != address(0);
              }
              /**
               * @dev Returns whether `spender` is allowed to manage `tokenId`.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               */
              function _isApprovedOrOwner(
                  address spender,
                  uint256 tokenId
              ) internal view virtual returns (bool) {
                  address owner = ERC721_PackedHashSeed.ownerOf(tokenId);
                  return (spender == owner ||
                      isApprovedForAll(owner, spender) ||
                      getApproved(tokenId) == spender);
              }
              /**
               * @dev Safely mints `tokenId` and transfers it to `to`.
               *
               * Requirements:
               *
               * - `tokenId` must not exist.
               * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function _safeMint(address to, uint256 tokenId) internal virtual {
                  _safeMint(to, tokenId, "");
              }
              /**
               * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
               * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
               */
              function _safeMint(
                  address to,
                  uint256 tokenId,
                  bytes memory data
              ) internal virtual {
                  _mint(to, tokenId);
                  require(
                      _checkOnERC721Received(address(0), to, tokenId, data),
                      "ERC721: transfer to non ERC721Receiver implementer"
                  );
              }
              /**
               * @dev Mints `tokenId` and transfers it to `to`.
               *
               * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
               *
               * Requirements:
               *
               * - `tokenId` must not exist.
               * - `to` cannot be the zero address.
               *
               * Emits a {Transfer} event.
               */
              function _mint(address to, uint256 tokenId) internal virtual {
                  require(to != address(0), "ERC721: mint to the zero address");
                  require(!_exists(tokenId), "ERC721: token already minted");
                  _beforeTokenTransfer(address(0), to, tokenId);
                  _balances[to] += 1;
                  _ownersAndHashSeeds[tokenId].owner = to;
                  emit Transfer(address(0), to, tokenId);
                  _afterTokenTransfer(address(0), to, tokenId);
              }
              /**
               * @dev Destroys `tokenId`.
               * The approval is cleared when the token is burned.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               *
               * Emits a {Transfer} event.
               */
              function _burn(uint256 tokenId) internal virtual {
                  address owner = ERC721_PackedHashSeed.ownerOf(tokenId);
                  _beforeTokenTransfer(owner, address(0), tokenId);
                  // Clear approvals
                  _approve(address(0), tokenId);
                  _balances[owner] -= 1;
                  delete _ownersAndHashSeeds[tokenId].owner;
                  emit Transfer(owner, address(0), tokenId);
                  _afterTokenTransfer(owner, address(0), tokenId);
              }
              /**
               * @dev Transfers `tokenId` from `from` to `to`.
               *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
               *
               * Requirements:
               *
               * - `to` cannot be the zero address.
               * - `tokenId` token must be owned by `from`.
               *
               * Emits a {Transfer} event.
               */
              function _transfer(
                  address from,
                  address to,
                  uint256 tokenId
              ) internal virtual {
                  require(
                      ERC721_PackedHashSeed.ownerOf(tokenId) == from,
                      "ERC721: transfer from incorrect owner"
                  );
                  require(to != address(0), "ERC721: transfer to the zero address");
                  _beforeTokenTransfer(from, to, tokenId);
                  // Clear approvals from the previous owner
                  _approve(address(0), tokenId);
                  _balances[from] -= 1;
                  _balances[to] += 1;
                  _ownersAndHashSeeds[tokenId].owner = to;
                  emit Transfer(from, to, tokenId);
                  _afterTokenTransfer(from, to, tokenId);
              }
              /**
               * @dev Approve `to` to operate on `tokenId`
               *
               * Emits an {Approval} event.
               */
              function _approve(address to, uint256 tokenId) internal virtual {
                  _tokenApprovals[tokenId] = to;
                  emit Approval(ERC721_PackedHashSeed.ownerOf(tokenId), to, tokenId);
              }
              /**
               * @dev Approve `operator` to operate on all of `owner` tokens
               *
               * Emits an {ApprovalForAll} event.
               */
              function _setApprovalForAll(
                  address owner,
                  address operator,
                  bool approved
              ) internal virtual {
                  require(owner != operator, "ERC721: approve to caller");
                  _operatorApprovals[owner][operator] = approved;
                  emit ApprovalForAll(owner, operator, approved);
              }
              /**
               * @dev Reverts if the `tokenId` has not been minted yet.
               */
              function _requireMinted(uint256 tokenId) internal view virtual {
                  require(_exists(tokenId), "ERC721: invalid token ID");
              }
              /**
               * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
               * The call is not executed if the target address is not a contract.
               *
               * @param from address representing the previous owner of the given token ID
               * @param to target address that will receive the tokens
               * @param tokenId uint256 ID of the token to be transferred
               * @param data bytes optional data to send along with the call
               * @return bool whether the call correctly returned the expected magic value
               */
              function _checkOnERC721Received(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory data
              ) private returns (bool) {
                  if (to.isContract()) {
                      try
                          IERC721Receiver(to).onERC721Received(
                              _msgSender(),
                              from,
                              tokenId,
                              data
                          )
                      returns (bytes4 retval) {
                          return retval == IERC721Receiver.onERC721Received.selector;
                      } catch (bytes memory reason) {
                          if (reason.length == 0) {
                              revert(
                                  "ERC721: transfer to non ERC721Receiver implementer"
                              );
                          } else {
                              /// @solidity memory-safe-assembly
                              assembly {
                                  revert(add(32, reason), mload(reason))
                              }
                          }
                      }
                  } else {
                      return true;
                  }
              }
              /**
               * @dev Hook that is called before any token transfer. This includes minting
               * and burning.
               *
               * 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, ``from``'s `tokenId` will be burned.
               * - `from` and `to` are never both zero.
               *
               * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
               */
              function _beforeTokenTransfer(
                  address from,
                  address to,
                  uint256 tokenId
              ) internal virtual {}
              /**
               * @dev Hook that is called after any transfer of tokens. This includes
               * minting and burning.
               *
               * Calling conditions:
               *
               * - when `from` and `to` are both non-zero.
               * - `from` and `to` are never both zero.
               *
               * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
               */
              function _afterTokenTransfer(
                  address from,
                  address to,
                  uint256 tokenId
              ) internal virtual {}
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.0;
          /**
           * @title Art Blocks Script Storage Library
           * @notice Utilize contract bytecode as persistant storage for large chunks of script string data.
           *
           * @author Art Blocks Inc.
           * @author Modified from 0xSequence (https://github.com/0xsequence/sstore2/blob/master/contracts/SSTORE2.sol)
           * @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SSTORE2.sol)
           *
           * @dev Compared to the above two rerferenced libraries, this contracts-as-storage implementation makes a few
           *      notably different design decisions:
           *      - uses the `string` data type for input/output on reads, rather than speaking in bytes directly
           *      - exposes "delete" functionality, allowing no-longer-used storage to be purged from chain state
           *      - stores the "writer" address (library user) in the deployed contract bytes, which is useful for both:
           *         a) providing necessary information for safe deletion; and
           *         b) allowing this to be introspected on-chain
           *      Also, given that much of this library is written in assembly, this library makes use of a slightly
           *      different convention (when compared to the rest of the Art Blocks smart contract repo) around
           *      pre-defining return values in some cases in order to simplify need to directly memory manage these
           *      return values.
           */
          library BytecodeStorage {
              //---------------------------------------------------------------------------------------------------------------//
              // Starting Index | Size | Ending Index | Description                                                            //
              //---------------------------------------------------------------------------------------------------------------//
              // 0              | N/A  | 0            |                                                                        //
              // 0              | 72   | 72           | the bytes of the gated-cleanup-logic allowing for `selfdestruct`ion    //
              // 72             | 32   | 104          | the 32 bytes for storing the deploying contract's (0-padded) address   //
              //---------------------------------------------------------------------------------------------------------------//
              // Define the offset for where the "logic bytes" end, and the "data bytes" begin. Note that this is a manually
              // calculated value, and must be updated if the above table is changed. It is expected that tests will fail
              // loudly if these values are not updated in-step with eachother.
              uint256 internal constant DATA_OFFSET = 104;
              uint256 internal constant ADDRESS_OFFSET = 72;
              /*//////////////////////////////////////////////////////////////
                                     WRITE LOGIC
              //////////////////////////////////////////////////////////////*/
              /**
               * @notice Write a string to contract bytecode
               * @param _data string to be written to contract. No input validation is performed on this parameter.
               * @return address_ address of deployed contract with bytecode containing concat(gated-cleanup-logic, address, data)
               */
              function writeToBytecode(
                  string memory _data
              ) internal returns (address address_) {
                  // prefix bytecode with
                  bytes memory creationCode = abi.encodePacked(
                      //---------------------------------------------------------------------------------------------------------------//
                      // Opcode  | Opcode + Arguments  | Description  | Stack View                                                     //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (0) creation code returns all code in the contract except for the first 11 (0B in hex) bytes, as these 11
                      //     bytes are the creation code itself which we do not want to store in the deployed storage contract result
                      //---------------------------------------------------------------------------------------------------------------//
                      // 0x60    |  0x60_0B            | PUSH1 11     | codeOffset                                                     //
                      // 0x59    |  0x59               | MSIZE        | 0 codeOffset                                                   //
                      // 0x81    |  0x81               | DUP2         | codeOffset 0 codeOffset                                        //
                      // 0x38    |  0x38               | CODESIZE     | codeSize codeOffset 0 codeOffset                               //
                      // 0x03    |  0x03               | SUB          | (codeSize - codeOffset) 0 codeOffset                           //
                      // 0x80    |  0x80               | DUP          | (codeSize - codeOffset) (codeSize - codeOffset) 0 codeOffset   //
                      // 0x92    |  0x92               | SWAP3        | codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset)   //
                      // 0x59    |  0x59               | MSIZE        | 0 codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset) //
                      // 0x39    |  0x39               | CODECOPY     | 0 (codeSize - codeOffset)                                      //
                      // 0xf3    |  0xf3               | RETURN       |                                                                //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (11 bytes)
                      hex"60_0B_59_81_38_03_80_92_59_39_F3",
                      //---------------------------------------------------------------------------------------------------------------//
                      // Opcode  | Opcode + Arguments  | Description  | Stack View                                                     //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (1a) conditional logic for determing purge-gate (only the bytecode contract deployer can `selfdestruct`)
                      //---------------------------------------------------------------------------------------------------------------//
                      // 0x60    |  0x60_20            | PUSH1 32           | 32                                                       //
                      // 0x60    |  0x60_48            | PUSH1 72 (*)       | contractOffset 32                                        //
                      // 0x60    |  0x60_00            | PUSH1 0            | 0 contractOffset 32                                      //
                      // 0x39    |  0x39               | CODECOPY           |                                                          //
                      // 0x60    |  0x60_00            | PUSH1 0            | 0                                                        //
                      // 0x51    |  0x51               | MLOAD              | byteDeployerAddress                                      //
                      // 0x33    |  0x33               | CALLER             | msg.sender byteDeployerAddress                           //
                      // 0x14    |  0x14               | EQ                 | (msg.sender == byteDeployerAddress)                      //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (12 bytes: 0-11 in deployed contract)
                      hex"60_20_60_48_60_00_39_60_00_51_33_14",
                      //---------------------------------------------------------------------------------------------------------------//
                      // (1b) load up the destination jump address for `(2a) calldata length check` logic, jump or raise `invalid` op-code
                      //---------------------------------------------------------------------------------------------------------------//
                      // 0x60    |  0x60_10            | PUSH1 16 (^)       | jumpDestination (msg.sender == byteDeployerAddress)      //
                      // 0x57    |  0x57               | JUMPI              |                                                          //
                      // 0xFE    |  0xFE               | INVALID            |                                                          //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (4 bytes: 12-15 in deployed contract)
                      hex"60_10_57_FE",
                      //---------------------------------------------------------------------------------------------------------------//
                      // (2a) conditional logic for determing purge-gate (only if calldata length is 1 byte)
                      //---------------------------------------------------------------------------------------------------------------//
                      // 0x5B    |  0x5B               | JUMPDEST (16)      |                                                          //
                      // 0x60    |  0x60_01            | PUSH1 1            | 1                                                        //
                      // 0x36    |  0x36               | CALLDATASIZE       | calldataSize 1                                           //
                      // 0x14    |  0x14               | EQ                 | (calldataSize == 1)                                      //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (5 bytes: 16-20 in deployed contract)
                      hex"5B_60_01_36_14",
                      //---------------------------------------------------------------------------------------------------------------//
                      // (2b) load up the destination jump address for `(3a) calldata value check` logic, jump or raise `invalid` op-code
                      //---------------------------------------------------------------------------------------------------------------//
                      // 0x60    |  0x60_19            | PUSH1 25 (^)       | jumpDestination (calldataSize == 1)                      //
                      // 0x57    |  0x57               | JUMPI              |                                                          //
                      // 0xFE    |  0xFE               | INVALID            |                                                          //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (4 bytes: 21-24 in deployed contract)
                      hex"60_19_57_FE",
                      //---------------------------------------------------------------------------------------------------------------//
                      // (3a) conditional logic for determing purge-gate (only if calldata is `0xFF`)
                      //---------------------------------------------------------------------------------------------------------------//
                      // 0x5B    |  0x5B               | JUMPDEST (25)      |                                                          //
                      // 0x60    |  0x60_00            | PUSH1 0            | 0                                                        //
                      // 0x35    |  0x35               | CALLDATALOAD       | calldata                                                 //
                      // 0x7F    |  0x7F_FF_00_..._00  | PUSH32 0xFF00...00 | 0xFF0...00 calldata                                      //
                      // 0x14    |  0x14               | EQ                 | (0xFF00...00 == calldata)                                //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (4 bytes: 25-28 in deployed contract)
                      hex"5B_60_00_35",
                      // (33 bytes: 29-61 in deployed contract)
                      hex"7F_FF_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00",
                      // (1 byte: 62 in deployed contract)
                      hex"14",
                      //---------------------------------------------------------------------------------------------------------------//
                      // (3b) load up the destination jump address for actual purging (4), jump or raise `invalid` op-code
                      //---------------------------------------------------------------------------------------------------------------//
                      // 0x60    |  0x60_43            | PUSH1 67 (^)       | jumpDestination (0xFF00...00 == calldata)                //
                      // 0x57    |  0x57               | JUMPI              |                                                          //
                      // 0xFE    |  0xFE               | INVALID            |                                                          //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (4 bytes: 63-66 in deployed contract)
                      hex"60_43_57_FE",
                      //---------------------------------------------------------------------------------------------------------------//
                      // (4) perform actual purging
                      //---------------------------------------------------------------------------------------------------------------//
                      // 0x5B    |  0x5B               | JUMPDEST (67)      |                                                          //
                      // 0x60    |  0x60_00            | PUSH1 0            | 0                                                        //
                      // 0x51    |  0x51               | MLOAD              | byteDeployerAddress                                      //
                      // 0xFF    |  0xFF               | SELFDESTRUCT       |                                                          //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (5 bytes: 67-71 in deployed contract)
                      hex"5B_60_00_51_FF",
                      //---------------------------------------------------------------------------------------------------------------//
                      // (*) Note: this value must be adjusted if selfdestruct purge logic is adjusted, to refer to the correct start  //
                      //           offset for where the `msg.sender` address was stored in deployed bytecode.                          //
                      //                                                                                                               //
                      // (^) Note: this value must be adjusted if portions of the selfdestruct purge logic are adjusted.               //
                      //---------------------------------------------------------------------------------------------------------------//
                      //
                      // store the deploying-contract's address (to be used to gate and call `selfdestruct`),
                      // with expected 0-padding to fit a 20-byte address into a 30-byte slot.
                      //
                      // note: it is important that this address is the executing contract's address
                      //      (the address that represents the client-application smart contract of this library)
                      //      which means that it is the responsibility of the client-application smart contract
                      //      to determine how deletes are gated (or if they are exposed at all) as it is only
                      //      this contract that will be able to call `purgeBytecode` as the `CALLER` that is
                      //      checked above (op-code 0x33).
                      hex"00_00_00_00_00_00_00_00_00_00_00_00", // left-pad 20-byte address with 12 0x00 bytes
                      address(this),
                      // uploaded data (stored as bytecode) comes last
                      _data
                  );
                  assembly {
                      // deploy a new contract with the generated creation code.
                      // start 32 bytes into creationCode to avoid copying the byte length.
                      address_ := create(0, add(creationCode, 0x20), mload(creationCode))
                  }
                  // address must be non-zero if contract was deployed successfully
                  require(address_ != address(0), "ContractAsStorage: Write Error");
              }
              /*//////////////////////////////////////////////////////////////
                                         READ LOGIC
              //////////////////////////////////////////////////////////////*/
              /**
               * @notice Read a string from contract bytecode
               * @param _address address of deployed contract with bytecode containing concat(gated-cleanup-logic, address, data)
               * @return data string read from contract bytecode
               */
              function readFromBytecode(
                  address _address
              ) internal view returns (string memory data) {
                  // get the size of the bytecode
                  uint256 bytecodeSize = _bytecodeSizeAt(_address);
                  // handle case where address contains code < DATA_OFFSET
                  // note: the first check here also captures the case where
                  //       (bytecodeSize == 0) implicitly, but we add the second check of
                  //       (bytecodeSize == 0) as a fall-through that will never execute
                  //       unless `DATA_OFFSET` is set to 0 at some point.
                  if ((bytecodeSize < DATA_OFFSET) || (bytecodeSize == 0)) {
                      revert("ContractAsStorage: Read Error");
                  }
                  // handle case where address contains code >= DATA_OFFSET
                  // decrement by DATA_OFFSET to account for purge logic
                  uint256 size;
                  unchecked {
                      size = bytecodeSize - DATA_OFFSET;
                  }
                  assembly {
                      // allocate free memory
                      data := mload(0x40)
                      // update free memory pointer
                      // use and(x, not(0x1f) as cheaper equivalent to sub(x, mod(x, 0x20)).
                      // adding 0x1f to size + logic above ensures the free memory pointer
                      // remains word-aligned, following the Solidity convention.
                      mstore(0x40, add(data, and(add(add(size, 0x20), 0x1f), not(0x1f))))
                      // store length of data in first 32 bytes
                      mstore(data, size)
                      // copy code to memory, excluding the gated-cleanup-logic and address
                      extcodecopy(_address, add(data, 0x20), DATA_OFFSET, size)
                  }
              }
              /**
               * @notice Get address for deployer for given contract bytecode
               * @param _address address of deployed contract with bytecode containing concat(gated-cleanup-logic, address, data)
               * @return writerAddress address read from contract bytecode
               */
              function getWriterAddressForBytecode(
                  address _address
              ) internal view returns (address) {
                  // get the size of the data
                  uint256 bytecodeSize = _bytecodeSizeAt(_address);
                  // handle case where address contains code < DATA_OFFSET
                  // note: the first check here also captures the case where
                  //       (bytecodeSize == 0) implicitly, but we add the second check of
                  //       (bytecodeSize == 0) as a fall-through that will never execute
                  //       unless `DATA_OFFSET` is set to 0 at some point.
                  if ((bytecodeSize < DATA_OFFSET) || (bytecodeSize == 0)) {
                      revert("ContractAsStorage: Read Error");
                  }
                  assembly {
                      // allocate free memory
                      let writerAddress := mload(0x40)
                      // shift free memory pointer by one slot
                      mstore(0x40, add(mload(0x40), 0x20))
                      // copy the 32-byte address of the data contract writer to memory
                      // note: this relies on the assumption noted at the top-level of
                      //       this file that the storage layout for the deployed
                      //       contracts-as-storage contract looks like:
                      //       | gated-cleanup-logic | deployer-address (padded) | data |
                      extcodecopy(
                          _address,
                          writerAddress,
                          ADDRESS_OFFSET,
                          0x20 // full 32-bytes, as address is expected to be zero-padded
                      )
                      return(
                          writerAddress,
                          0x20 // return size is entire slot, as it is zero-padded
                      )
                  }
              }
              /*//////////////////////////////////////////////////////////////
                                        DELETE LOGIC
              //////////////////////////////////////////////////////////////*/
              /**
               * @notice Purge contract bytecode for cleanup purposes
               * note: Although this does reduce usage of Ethereum state, it does not reduce the gas costs of removal
               * transactions. We believe this is the best behavior at the time of writing, and do not expect this to
               * result in any breaking changes in the future. All current proposals to change the self-destruct opcode
               * are backwards compatible, but may result in not removing the bytecode from the blockchain state. This
               * implementation is compatible with that architecture, as it does not rely on the bytecode being removed
               * from the blockchain state (as opposed to using a CREATE2 style opcode when creating bytecode contracts,
               * which could be used in a way that may rely on the bytecode being removed from the blockchain state,
               * e.g. replacing a contract at a given deployed address).
               * @param _address address of deployed contract with bytecode containing concat(gated-cleanup-logic, address, data)
               * @dev This contract is only callable by the address of the contract that originally deployed the bytecode
               *      being purged. If this method is called by any other address, it will revert with the `INVALID` op-code.
               *      Additionally, for security purposes, the contract must be called with calldata `0xFF` to ensure that
               *      the `selfdestruct` op-code is intentionally being invoked, otherwise the `INVALID` op-code will be raised.
               */
              function purgeBytecode(address _address) internal {
                  // deployed bytecode (above) handles all logic for purging state, so no
                  // call data is expected to be passed along to perform data purge
                  (bool success /* `data` not needed */, ) = _address.call(hex"FF");
                  if (!success) {
                      revert("ContractAsStorage: Delete Error");
                  }
              }
              /*//////////////////////////////////////////////////////////////
                                    INTERNAL HELPER LOGIC
              //////////////////////////////////////////////////////////////*/
              /**
                  @notice Returns the size of the bytecode at address `_address`
                  @param _address address that may or may not contain bytecode
                  @return size size of the bytecode code at `_address`
              */
              function _bytecodeSizeAt(
                  address _address
              ) private view returns (uint256 size) {
                  assembly {
                      size := extcodesize(_address)
                  }
              }
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          // Inspired by: https://ethereum.stackexchange.com/a/123950/103422
          pragma solidity ^0.8.0;
          /**
           * @dev Operations on bytes32 data type, dealing with conversion to string.
           */
          library Bytes32Strings {
              /**
               * @dev Intended to convert a `bytes32`-encoded string literal to `string`.
               * Trims zero padding to arrive at original string literal.
               */
              function toString(
                  bytes32 source
              ) internal pure returns (string memory result) {
                  uint8 length = 0;
                  while (source[length] != 0 && length < 32) {
                      length++;
                  }
                  assembly {
                      // free memory pointer
                      result := mload(0x40)
                      // update free memory pointer to new "memory end"
                      // (offset is 64-bytes: 32 for length, 32 for data)
                      mstore(0x40, add(result, 0x40))
                      // store length in first 32-byte memory slot
                      mstore(result, length)
                      // write actual data in second 32-byte memory slot
                      mstore(add(result, 0x20), source)
                  }
              }
              /**
               * @dev Intended to check if a `bytes32`-encoded string contains a given
               * character with UTF-8 character code `utf8CharCode exactly `targetQty`
               * times. Does not support searching for multi-byte characters, only
               * characters with UTF-8 character codes < 0x80.
               */
              function containsExactCharacterQty(
                  bytes32 source,
                  uint8 utf8CharCode,
                  uint8 targetQty
              ) internal pure returns (bool) {
                  uint8 _occurrences = 0;
                  uint8 i;
                  for (i = 0; i < 32; ) {
                      uint8 _charCode = uint8(source[i]);
                      // if not a null byte, or a multi-byte UTF-8 character, check match
                      if (_charCode != 0 && _charCode < 0x80) {
                          if (_charCode == utf8CharCode) {
                              unchecked {
                                  // no risk of overflow since max 32 iterations < max uin8=255
                                  ++_occurrences;
                              }
                          }
                      }
                      unchecked {
                          // no risk of overflow since max 32 iterations < max uin8=255
                          ++i;
                      }
                  }
                  return _occurrences == targetQty;
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright (c) 2023 the ethier authors (github.com/divergencetech/ethier)
          pragma solidity >=0.8.0 <0.9.0;
          import {AccessControlEnumerable as ACE} from "@openzeppelin/contracts/access/AccessControlEnumerable.sol";
          contract AccessControlEnumerable is ACE {
              /// @notice The default role intended to perform access-restricted actions.
              /// @dev We are using this instead of DEFAULT_ADMIN_ROLE because the latter
              /// is intended to grant/revoke roles and will be secured differently.
              bytes32 public constant DEFAULT_STEERING_ROLE =
                  keccak256("DEFAULT_STEERING_ROLE");
              /// @dev Overrides supportsInterface so that inheriting contracts can
              /// reference this contract instead of OZ's version for further overrides.
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(ACE)
                  returns (bool)
              {
                  return ACE.supportsInterface(interfaceId);
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright (c) 2022 the ethier authors (github.com/divergencetech/ethier)
          pragma solidity >=0.8.0 <0.9.0;
          import {ERC721A} from "erc721a/contracts/ERC721A.sol";
          import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol";
          import {AccessControlEnumerable} from "../utils/AccessControlEnumerable.sol";
          import {AccessControlPausable} from "../utils/AccessControlPausable.sol";
          import {ERC4906} from "./ERC4906.sol";
          /**
          @notice An ERC721A contract with common functionality:
           - Pausable with toggling functions exposed to Owner only
           - ERC2981 royalties
           */
          contract ERC721ACommon is ERC721A, AccessControlPausable, ERC2981, ERC4906 {
              constructor(
                  address admin,
                  address steerer,
                  string memory name,
                  string memory symbol,
                  address payable royaltyReciever,
                  uint96 royaltyBasisPoints
              ) ERC721A(name, symbol) {
                  _setDefaultRoyalty(royaltyReciever, royaltyBasisPoints);
                  _grantRole(DEFAULT_ADMIN_ROLE, admin);
                  _grantRole(DEFAULT_STEERING_ROLE, steerer);
              }
              /// @notice Requires that the token exists.
              modifier tokenExists(uint256 tokenId) {
                  require(ERC721A._exists(tokenId), "ERC721ACommon: Token doesn't exist");
                  _;
              }
              /// @notice Requires that msg.sender owns or is approved for the token.
              modifier onlyApprovedOrOwner(uint256 tokenId) {
                  require(
                      _ownershipOf(tokenId).addr == _msgSender() ||
                          getApproved(tokenId) == _msgSender(),
                      "ERC721ACommon: Not approved nor owner"
                  );
                  _;
              }
              function _beforeTokenTransfers(
                  address from,
                  address to,
                  uint256 startTokenId,
                  uint256 quantity
              ) internal virtual override {
                  require(!paused(), "ERC721ACommon: paused");
                  super._beforeTokenTransfers(from, to, startTokenId, quantity);
              }
              /// @notice Overrides supportsInterface as required by inheritance.
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(ERC721A, AccessControlEnumerable, ERC2981, ERC4906)
                  returns (bool)
              {
                  return
                      ERC721A.supportsInterface(interfaceId) ||
                      ERC2981.supportsInterface(interfaceId) ||
                      AccessControlEnumerable.supportsInterface(interfaceId) ||
                      ERC4906.supportsInterface(interfaceId);
              }
              /// @notice Sets the royalty receiver and percentage (in units of basis
              /// points = 0.01%).
              function setDefaultRoyalty(address receiver, uint96 basisPoints)
                  public
                  virtual
                  onlyRole(DEFAULT_STEERING_ROLE)
              {
                  _setDefaultRoyalty(receiver, basisPoints);
              }
              function emitMetadataUpdateForAll()
                  external
                  onlyRole(DEFAULT_STEERING_ROLE)
              {
                  // EIP4906 is unfortunately quite vague on whether the `toTokenId` in
                  // the following event is included or not. We hence use `totalSupply()`
                  // to ensure that the last actual `tokenId` is included in any case.
                  _refreshMetadata(0, totalSupply());
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)
          pragma solidity ^0.8.1;
          /**
           * @dev Collection of functions related to the address type
           */
          library Address {
              /**
               * @dev Returns true if `account` is a contract.
               *
               * [IMPORTANT]
               * ====
               * It is unsafe to assume that an address for which this function returns
               * false is an externally-owned account (EOA) and not a contract.
               *
               * Among others, `isContract` will return false for the following
               * types of addresses:
               *
               *  - an externally-owned account
               *  - a contract in construction
               *  - an address where a contract will be created
               *  - an address where a contract lived, but was destroyed
               * ====
               *
               * [IMPORTANT]
               * ====
               * You shouldn't rely on `isContract` to protect against flash loan attacks!
               *
               * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
               * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
               * constructor.
               * ====
               */
              function isContract(address account) internal view returns (bool) {
                  // This method relies on extcodesize/address.code.length, which returns 0
                  // for contracts in construction, since the code is only stored at the end
                  // of the constructor execution.
                  return account.code.length > 0;
              }
              /**
               * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
               * `recipient`, forwarding all available gas and reverting on errors.
               *
               * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
               * of certain opcodes, possibly making contracts go over the 2300 gas limit
               * imposed by `transfer`, making them unable to receive funds via
               * `transfer`. {sendValue} removes this limitation.
               *
               * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
               *
               * IMPORTANT: because control is transferred to `recipient`, care must be
               * taken to not create reentrancy vulnerabilities. Consider using
               * {ReentrancyGuard} or the
               * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
               */
              function sendValue(address payable recipient, uint256 amount) internal {
                  require(address(this).balance >= amount, "Address: insufficient balance");
                  (bool success, ) = recipient.call{value: amount}("");
                  require(success, "Address: unable to send value, recipient may have reverted");
              }
              /**
               * @dev Performs a Solidity function call using a low level `call`. A
               * plain `call` is an unsafe replacement for a function call: use this
               * function instead.
               *
               * If `target` reverts with a revert reason, it is bubbled up by this
               * function (like regular Solidity function calls).
               *
               * Returns the raw returned data. To convert to the expected return value,
               * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
               *
               * Requirements:
               *
               * - `target` must be a contract.
               * - calling `target` with `data` must not revert.
               *
               * _Available since v3.1._
               */
              function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, 0, "Address: low-level call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
               * `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, 0, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but also transferring `value` wei to `target`.
               *
               * Requirements:
               *
               * - the calling contract must have an ETH balance of at least `value`.
               * - the called Solidity function must be `payable`.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
              }
              /**
               * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
               * with `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  require(address(this).balance >= value, "Address: insufficient balance for call");
                  (bool success, bytes memory returndata) = target.call{value: value}(data);
                  return verifyCallResultFromTarget(target, success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                  return functionStaticCall(target, data, "Address: low-level static call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal view returns (bytes memory) {
                  (bool success, bytes memory returndata) = target.staticcall(data);
                  return verifyCallResultFromTarget(target, success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a delegate call.
               *
               * _Available since v3.4._
               */
              function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionDelegateCall(target, data, "Address: low-level delegate call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a delegate call.
               *
               * _Available since v3.4._
               */
              function functionDelegateCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  (bool success, bytes memory returndata) = target.delegatecall(data);
                  return verifyCallResultFromTarget(target, success, returndata, errorMessage);
              }
              /**
               * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
               * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
               *
               * _Available since v4.8._
               */
              function verifyCallResultFromTarget(
                  address target,
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal view returns (bytes memory) {
                  if (success) {
                      if (returndata.length == 0) {
                          // only check isContract if the call was successful and the return data is empty
                          // otherwise we already know that it was a contract
                          require(isContract(target), "Address: call to non-contract");
                      }
                      return returndata;
                  } else {
                      _revert(returndata, errorMessage);
                  }
              }
              /**
               * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
               * revert reason or using the provided one.
               *
               * _Available since v4.3._
               */
              function verifyCallResult(
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal pure returns (bytes memory) {
                  if (success) {
                      return returndata;
                  } else {
                      _revert(returndata, errorMessage);
                  }
              }
              function _revert(bytes memory returndata, string memory errorMessage) private pure {
                  // Look for revert reason and bubble it up if present
                  if (returndata.length > 0) {
                      // The easiest way to bubble the revert reason is using memory via assembly
                      /// @solidity memory-safe-assembly
                      assembly {
                          let returndata_size := mload(returndata)
                          revert(add(32, returndata), returndata_size)
                      }
                  } else {
                      revert(errorMessage);
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.13;
          import {OperatorFilterer} from "./OperatorFilterer.sol";
          import {CANONICAL_CORI_SUBSCRIPTION} from "./lib/Constants.sol";
          /**
           * @title  DefaultOperatorFilterer
           * @notice Inherits from OperatorFilterer and automatically subscribes to the default OpenSea subscription.
           * @dev    Please note that if your token contract does not provide an owner with EIP-173, it must provide
           *         administration methods on the contract itself to interact with the registry otherwise the subscription
           *         will be locked to the options set during construction.
           */
          abstract contract DefaultOperatorFilterer is OperatorFilterer {
              /// @dev The constructor that is called when the contract is being deployed.
              constructor() OperatorFilterer(CANONICAL_CORI_SUBSCRIPTION, true) {}
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity >=0.8.0 <0.9.0;
          import {AccessControlEnumerable} from "ethier/utils/AccessControlEnumerable.sol";
          import {ISellable} from "../interfaces/ISellable.sol";
          /**
           * @notice A base contract for selling content via authorised sellers.
           */
          abstract contract BaseSellable is ISellable, AccessControlEnumerable {
              /**
               * @notice Authorised sellers.
               */
              bytes32 public constant AUTHORISED_SELLER_ROLE = keccak256("AUTHORISED_SELLER_ROLE");
              /**
               * @notice A role that cannot be granted or revoked.
               * @dev Used to lock in members of the `AUTHORISED_SELLER_ROLE` role.
               */
              bytes32 private constant _NOOP_ROLE = keccak256("NOOP_ROLE");
              constructor() {
                  _setRoleAdmin(AUTHORISED_SELLER_ROLE, DEFAULT_STEERING_ROLE);
                  _setRoleAdmin(_NOOP_ROLE, _NOOP_ROLE);
              }
              /**
               * @notice Handles the sale of sellable content via an authorised seller.
               * @dev Delegates the implementation to `_handleSale`.
               */
              function handleSale(address to, uint64 num, bytes calldata data)
                  external
                  payable
                  onlyRole(AUTHORISED_SELLER_ROLE)
              {
                  _handleSale(to, num, data);
              }
              /**
               * @notice Handles the sale of sellable content.
               */
              function _handleSale(address to, uint64 num, bytes calldata data) internal virtual;
              /**
               * @notice Locks the `AUTHORISED_SELLER_ROLE` role.
               */
              function lockSellers() external onlyRole(DEFAULT_STEERING_ROLE) {
                  _lockSellers();
              }
              /**
               * @notice Locks the `AUTHORISED_SELLER_ROLE` role.
               */
              function _lockSellers() internal {
                  _setRoleAdmin(AUTHORISED_SELLER_ROLE, _NOOP_ROLE);
              }
              /**
               * @notice Revokes approval for all sellers.
               */
              function _revokeAllSellers() internal {
                  uint256 num = getRoleMemberCount(AUTHORISED_SELLER_ROLE);
                  for (uint256 i = 0; i < num; i++) {
                      // Akin to a popFront
                      address seller = getRoleMember(AUTHORISED_SELLER_ROLE, 0);
                      _revokeRole(AUTHORISED_SELLER_ROLE, seller);
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity ^0.8.16;
          import {ERC721A, ERC721ACommon} from "ethier/erc721/ERC721ACommon.sol";
          import {RedeemableERC721ACommon} from "./RedeemableERC721ACommon.sol";
          import {
              TransferRestriction,
              ERC721ATransferRestrictedBase,
              ERC721ATransferRestricted
          } from "../restricted/ERC721ATransferRestricted.sol";
          /**
           * @notice An ERC721 token intended to act as freely tradeable voucher.
           * @dev This is mainly a convenience wrapper.
           */
          abstract contract TransferRestrictedRedeemableERC721ACommon is RedeemableERC721ACommon, ERC721ATransferRestricted {
              /**
               * @notice Overrides supportsInterface as required by inheritance.
               */
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(RedeemableERC721ACommon, ERC721ACommon)
                  returns (bool)
              {
                  return RedeemableERC721ACommon.supportsInterface(interfaceId);
              }
              /**
               * @inheritdoc ERC721ATransferRestrictedBase
               */
              function _beforeTokenTransfers(address from, address to, uint256 startTokenId, uint256 quantity)
                  internal
                  virtual
                  override(ERC721ATransferRestrictedBase, ERC721ACommon)
              {
                  ERC721ATransferRestrictedBase._beforeTokenTransfers(from, to, startTokenId, quantity);
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity ^0.8.16;
          import {AccessControlEnumerable} from "ethier/utils/AccessControlEnumerable.sol";
          import {ISellable, SellableCallbacker} from "../base/SellableCallbacker.sol";
          /**
           * @notice Base contract for seller presets that call back to a sellable contract.
           */
          contract AccessControlled is AccessControlEnumerable {
              constructor(address admin, address steerer) {
                  _grantRole(DEFAULT_ADMIN_ROLE, admin);
                  _grantRole(DEFAULT_STEERING_ROLE, steerer);
              }
          }
          /**
           * @notice Base contract for seller presets that call back to a sellable contract.
           */
          contract CallbackerWithAccessControl is SellableCallbacker, AccessControlled {
              constructor(address admin, address steerer, ISellable sellable_)
                  SellableCallbacker(sellable_)
                  AccessControlled(admin, steerer)
              {}
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity >=0.8.0 <0.9.0;
          import {Seller} from "./Seller.sol";
          /**
           * @notice Seller module that adds an upper limit on the number of sellable items.
           */
          abstract contract SupplyLimited is Seller {
              error SupplyLimitExceeded(uint64 numRequested, uint64 numLeft);
              /**
               * @notice The number of tokens that have already been sold by the seller.
               */
              uint64 private _numSold;
              /**
               * @notice Returns the total number of items sold by this contract.
               */
              function numSold() public view returns (uint64) {
                  return _numSold;
              }
              /**
               * @notice Returns the total number of items sold by this contract.
               */
              function maxNumSellable() public view virtual returns (uint64);
              // -------------------------------------------------------------------------
              //
              //  Internals
              //
              // -------------------------------------------------------------------------
              /**
               * @notice Checks if the number of requested purchases is below the limit
               * given by the inventory.
               * @dev Reverts otherwise.
               */
              function _checkAndModifyPurchase(address to, uint64 num, uint256 cost, bytes memory data)
                  internal
                  view
                  virtual
                  override(Seller)
                  returns (address, uint64, uint256)
              {
                  (to, num, cost) = Seller._checkAndModifyPurchase(to, num, cost, data);
                  uint64 numLeft = maxNumSellable() - numSold();
                  if (num > numLeft) {
                      revert SupplyLimitExceeded(num, numLeft);
                  }
                  return (to, num, cost);
              }
              /**
               * @notice Updating the total number of sold tokens.
               */
              function _beforePurchase(address to, uint64 num, uint256 cost, bytes memory data)
                  internal
                  virtual
                  override(Seller)
              {
                  Seller._beforePurchase(to, num, cost, data);
                  _numSold += num;
              }
          }
          /**
           * @notice Seller module with a supply limit that is fixed at deployment.
           */
          abstract contract FixedSupply is SupplyLimited {
              /**
               * @notice The maximum number of sellable items.
               */
              uint64 private immutable _maxNumSellable;
              constructor(uint64 maxNumSellable_) {
                  _maxNumSellable = maxNumSellable_;
              }
              /**
               * @inheritdoc SupplyLimited
               */
              function maxNumSellable() public view virtual override returns (uint64) {
                  return _maxNumSellable;
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity >=0.8.0 <0.9.0;
          /**
           * @notice Basic interface for a contract providing sellable content.
           */
          interface ISellable {
              /**
               * @notice Handles the sale of sellable content.
               * @dev This is usually only callable by Sellers.
               */
              function handleSale(address to, uint64 num, bytes calldata data) external payable;
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity >=0.8.0 <0.9.0;
          /**
           * @notice Interface to execute purchases in `Seller`s.
           * @dev This executes the final purchase. This can be anything from minting ERC721 tokens to transfering funds, etc.
           */
          abstract contract PurchaseExecuter {
              function _executePurchase(address to, uint64 num, uint256 cost, bytes memory data) internal virtual;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (security/ReentrancyGuard.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Contract module that helps prevent reentrant calls to a function.
           *
           * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
           * available, which can be applied to functions to make sure there are no nested
           * (reentrant) calls to them.
           *
           * Note that because there is a single `nonReentrant` guard, functions marked as
           * `nonReentrant` may not call one another. This can be worked around by making
           * those functions `private`, and then adding `external` `nonReentrant` entry
           * points to them.
           *
           * TIP: If you would like to learn more about reentrancy and alternative ways
           * to protect against it, check out our blog post
           * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
           */
          abstract contract ReentrancyGuard {
              // Booleans are more expensive than uint256 or any type that takes up a full
              // word because each write operation emits an extra SLOAD to first read the
              // slot's contents, replace the bits taken up by the boolean, and then write
              // back. This is the compiler's defense against contract upgrades and
              // pointer aliasing, and it cannot be disabled.
              // The values being non-zero value makes deployment a bit more expensive,
              // but in exchange the refund on every call to nonReentrant will be lower in
              // amount. Since refunds are capped to a percentage of the total
              // transaction's gas, it is best to keep them low in cases like this one, to
              // increase the likelihood of the full refund coming into effect.
              uint256 private constant _NOT_ENTERED = 1;
              uint256 private constant _ENTERED = 2;
              uint256 private _status;
              constructor() {
                  _status = _NOT_ENTERED;
              }
              /**
               * @dev Prevents a contract from calling itself, directly or indirectly.
               * Calling a `nonReentrant` function from another `nonReentrant`
               * function is not supported. It is possible to prevent this from happening
               * by making the `nonReentrant` function external, and making it call a
               * `private` function that does the actual work.
               */
              modifier nonReentrant() {
                  _nonReentrantBefore();
                  _;
                  _nonReentrantAfter();
              }
              function _nonReentrantBefore() private {
                  // On the first call to nonReentrant, _status will be _NOT_ENTERED
                  require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
                  // Any calls to nonReentrant after this point will fail
                  _status = _ENTERED;
              }
              function _nonReentrantAfter() private {
                  // By storing the original value once again, a refund is triggered (see
                  // https://eips.ethereum.org/EIPS/eip-2200)
                  _status = _NOT_ENTERED;
              }
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.0;
          import "./IAdminACLV0.sol";
          /// use the Royalty Registry's IManifold interface for token royalties
          import "./IManifold.sol";
          /**
           * @title This interface is intended to house interface items that are common
           * across all GenArt721CoreContractV3 flagship and derivative implementations.
           * This interface extends the IManifold royalty interface in order to
           * add support the Royalty Registry by default.
           * @author Art Blocks Inc.
           */
          interface IGenArt721CoreContractV3_Base is IManifold {
              /**
               * @notice Token ID `_tokenId` minted to `_to`.
               */
              event Mint(address indexed _to, uint256 indexed _tokenId);
              /**
               * @notice currentMinter updated to `_currentMinter`.
               * @dev Implemented starting with V3 core
               */
              event MinterUpdated(address indexed _currentMinter);
              /**
               * @notice Platform updated on bytes32-encoded field `_field`.
               */
              event PlatformUpdated(bytes32 indexed _field);
              /**
               * @notice Project ID `_projectId` updated on bytes32-encoded field
               * `_update`.
               */
              event ProjectUpdated(uint256 indexed _projectId, bytes32 indexed _update);
              event ProposedArtistAddressesAndSplits(
                  uint256 indexed _projectId,
                  address _artistAddress,
                  address _additionalPayeePrimarySales,
                  uint256 _additionalPayeePrimarySalesPercentage,
                  address _additionalPayeeSecondarySales,
                  uint256 _additionalPayeeSecondarySalesPercentage
              );
              event AcceptedArtistAddressesAndSplits(uint256 indexed _projectId);
              // version and type of the core contract
              // coreVersion is a string of the form "0.x.y"
              function coreVersion() external view returns (string memory);
              // coreType is a string of the form "GenArt721CoreV3"
              function coreType() external view returns (string memory);
              // owner (pre-V3 was named admin) of contract
              // this is expected to be an Admin ACL contract for V3
              function owner() external view returns (address);
              // Admin ACL contract for V3, will be at the address owner()
              function adminACLContract() external returns (IAdminACLV0);
              // backwards-compatible (pre-V3) admin - equal to owner()
              function admin() external view returns (address);
              /**
               * Function determining if _sender is allowed to call function with
               * selector _selector on contract `_contract`. Intended to be used with
               * peripheral contracts such as minters, as well as internally by the
               * core contract itself.
               */
              function adminACLAllowed(
                  address _sender,
                  address _contract,
                  bytes4 _selector
              ) external returns (bool);
              // getter function of public variable
              function nextProjectId() external view returns (uint256);
              // getter function of public mapping
              function tokenIdToProjectId(
                  uint256 tokenId
              ) external view returns (uint256 projectId);
              // @dev this is not available in V0
              function isMintWhitelisted(address minter) external view returns (bool);
              function projectIdToArtistAddress(
                  uint256 _projectId
              ) external view returns (address payable);
              function projectIdToAdditionalPayeePrimarySales(
                  uint256 _projectId
              ) external view returns (address payable);
              function projectIdToAdditionalPayeePrimarySalesPercentage(
                  uint256 _projectId
              ) external view returns (uint256);
              function projectIdToSecondaryMarketRoyaltyPercentage(
                  uint256 _projectId
              ) external view returns (uint256);
              function projectURIInfo(
                  uint256 _projectId
              ) external view returns (string memory projectBaseURI);
              // @dev new function in V3
              function projectStateData(
                  uint256 _projectId
              )
                  external
                  view
                  returns (
                      uint256 invocations,
                      uint256 maxInvocations,
                      bool active,
                      bool paused,
                      uint256 completedTimestamp,
                      bool locked
                  );
              function projectDetails(
                  uint256 _projectId
              )
                  external
                  view
                  returns (
                      string memory projectName,
                      string memory artist,
                      string memory description,
                      string memory website,
                      string memory license
                  );
              function projectScriptDetails(
                  uint256 _projectId
              )
                  external
                  view
                  returns (
                      string memory scriptTypeAndVersion,
                      string memory aspectRatio,
                      uint256 scriptCount
                  );
              function projectScriptByIndex(
                  uint256 _projectId,
                  uint256 _index
              ) external view returns (string memory);
              function tokenIdToHash(uint256 _tokenId) external view returns (bytes32);
              // function to set a token's hash (must be guarded)
              function setTokenHash_8PT(uint256 _tokenId, bytes32 _hash) external;
              // @dev gas-optimized signature in V3 for `mint`
              function mint_Ecf(
                  address _to,
                  uint256 _projectId,
                  address _by
              ) external returns (uint256 tokenId);
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.0;
          import "./IAdminACLV0.sol";
          import "./IGenArt721CoreContractV3_Base.sol";
          interface IGenArt721CoreContractV3_Engine is IGenArt721CoreContractV3_Base {
              // @dev new function in V3
              function getPrimaryRevenueSplits(
                  uint256 _projectId,
                  uint256 _price
              )
                  external
                  view
                  returns (
                      uint256 renderProviderRevenue_,
                      address payable renderProviderAddress_,
                      uint256 platformProviderRevenue_,
                      address payable platformProviderAddress_,
                      uint256 artistRevenue_,
                      address payable artistAddress_,
                      uint256 additionalPayeePrimaryRevenue_,
                      address payable additionalPayeePrimaryAddress_
                  );
              // @dev The render provider primary sales payment address
              function renderProviderPrimarySalesAddress()
                  external
                  view
                  returns (address payable);
              // @dev The platform provider primary sales payment address
              function platformProviderPrimarySalesAddress()
                  external
                  view
                  returns (address payable);
              // @dev Percentage of primary sales allocated to the render provider
              function renderProviderPrimarySalesPercentage()
                  external
                  view
                  returns (uint256);
              // @dev Percentage of primary sales allocated to the platform provider
              function platformProviderPrimarySalesPercentage()
                  external
                  view
                  returns (uint256);
              // @dev The render provider secondary sales royalties payment address
              function renderProviderSecondarySalesAddress()
                  external
                  view
                  returns (address payable);
              // @dev The platform provider secondary sales royalties payment address
              function platformProviderSecondarySalesAddress()
                  external
                  view
                  returns (address payable);
              // @dev Basis points of secondary sales allocated to the render provider
              function renderProviderSecondarySalesBPS() external view returns (uint256);
              // @dev Basis points of secondary sales allocated to the platform provider
              function platformProviderSecondarySalesBPS()
                  external
                  view
                  returns (uint256);
              // function to read the hash-seed for a given tokenId
              function tokenIdToHashSeed(
                  uint256 _tokenId
              ) external view returns (bytes12);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Provides information about the current execution context, including the
           * sender of the transaction and its data. While these are generally available
           * via msg.sender and msg.data, they should not be accessed in such a direct
           * manner, since when dealing with meta-transactions the account sending and
           * paying for execution may not be the actual sender (as far as an application
           * is concerned).
           *
           * This contract is only required for intermediate, library-like contracts.
           */
          abstract contract Context {
              function _msgSender() internal view virtual returns (address) {
                  return msg.sender;
              }
              function _msgData() internal view virtual returns (bytes calldata) {
                  return msg.data;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/IERC721.sol)
          pragma solidity ^0.8.0;
          import "../../utils/introspection/IERC165.sol";
          /**
           * @dev Required interface of an ERC721 compliant contract.
           */
          interface IERC721 is IERC165 {
              /**
               * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
               */
              event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
              /**
               * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
               */
              event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
              /**
               * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
               */
              event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
              /**
               * @dev Returns the number of tokens in ``owner``'s account.
               */
              function balanceOf(address owner) external view returns (uint256 balance);
              /**
               * @dev Returns the owner of the `tokenId` token.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               */
              function ownerOf(uint256 tokenId) external view returns (address owner);
              /**
               * @dev Safely transfers `tokenId` token from `from` to `to`.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must exist and be owned by `from`.
               * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
               * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes calldata data
              ) external;
              /**
               * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
               * are aware of the ERC721 protocol to prevent tokens from being forever locked.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must exist and be owned by `from`.
               * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
               * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) external;
              /**
               * @dev Transfers `tokenId` token from `from` to `to`.
               *
               * WARNING: 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);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)
          pragma solidity ^0.8.0;
          /**
           * @title ERC721 token receiver interface
           * @dev Interface for any contract that wants to support safeTransfers
           * from ERC721 asset contracts.
           */
          interface IERC721Receiver {
              /**
               * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
               * by `operator` from `from`, this function is called.
               *
               * It must return its Solidity selector to confirm the token transfer.
               * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
               *
               * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
               */
              function onERC721Received(
                  address operator,
                  address from,
                  uint256 tokenId,
                  bytes calldata data
              ) external returns (bytes4);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)
          pragma solidity ^0.8.0;
          import "../IERC721.sol";
          /**
           * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
           * @dev See https://eips.ethereum.org/EIPS/eip-721
           */
          interface IERC721Metadata is IERC721 {
              /**
               * @dev Returns the token collection name.
               */
              function name() external view returns (string memory);
              /**
               * @dev Returns the token collection symbol.
               */
              function symbol() external view returns (string memory);
              /**
               * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
               */
              function tokenURI(uint256 tokenId) external view returns (string memory);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol)
          pragma solidity ^0.8.1;
          /**
           * @dev Collection of functions related to the address type
           */
          library Address {
              /**
               * @dev Returns true if `account` is a contract.
               *
               * [IMPORTANT]
               * ====
               * It is unsafe to assume that an address for which this function returns
               * false is an externally-owned account (EOA) and not a contract.
               *
               * Among others, `isContract` will return false for the following
               * types of addresses:
               *
               *  - an externally-owned account
               *  - a contract in construction
               *  - an address where a contract will be created
               *  - an address where a contract lived, but was destroyed
               * ====
               *
               * [IMPORTANT]
               * ====
               * You shouldn't rely on `isContract` to protect against flash loan attacks!
               *
               * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
               * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
               * constructor.
               * ====
               */
              function isContract(address account) internal view returns (bool) {
                  // This method relies on extcodesize/address.code.length, which returns 0
                  // for contracts in construction, since the code is only stored at the end
                  // of the constructor execution.
                  return account.code.length > 0;
              }
              /**
               * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
               * `recipient`, forwarding all available gas and reverting on errors.
               *
               * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
               * of certain opcodes, possibly making contracts go over the 2300 gas limit
               * imposed by `transfer`, making them unable to receive funds via
               * `transfer`. {sendValue} removes this limitation.
               *
               * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
               *
               * IMPORTANT: because control is transferred to `recipient`, care must be
               * taken to not create reentrancy vulnerabilities. Consider using
               * {ReentrancyGuard} or the
               * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
               */
              function sendValue(address payable recipient, uint256 amount) internal {
                  require(address(this).balance >= amount, "Address: insufficient balance");
                  (bool success, ) = recipient.call{value: amount}("");
                  require(success, "Address: unable to send value, recipient may have reverted");
              }
              /**
               * @dev Performs a Solidity function call using a low level `call`. A
               * plain `call` is an unsafe replacement for a function call: use this
               * function instead.
               *
               * If `target` reverts with a revert reason, it is bubbled up by this
               * function (like regular Solidity function calls).
               *
               * Returns the raw returned data. To convert to the expected return value,
               * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
               *
               * Requirements:
               *
               * - `target` must be a contract.
               * - calling `target` with `data` must not revert.
               *
               * _Available since v3.1._
               */
              function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionCall(target, data, "Address: low-level call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
               * `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, 0, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but also transferring `value` wei to `target`.
               *
               * Requirements:
               *
               * - the calling contract must have an ETH balance of at least `value`.
               * - the called Solidity function must be `payable`.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
              }
              /**
               * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
               * with `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  require(address(this).balance >= value, "Address: insufficient balance for call");
                  require(isContract(target), "Address: call to non-contract");
                  (bool success, bytes memory returndata) = target.call{value: value}(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                  return functionStaticCall(target, data, "Address: low-level static call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal view returns (bytes memory) {
                  require(isContract(target), "Address: static call to non-contract");
                  (bool success, bytes memory returndata) = target.staticcall(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a delegate call.
               *
               * _Available since v3.4._
               */
              function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionDelegateCall(target, data, "Address: low-level delegate call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a delegate call.
               *
               * _Available since v3.4._
               */
              function functionDelegateCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  require(isContract(target), "Address: delegate call to non-contract");
                  (bool success, bytes memory returndata) = target.delegatecall(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
               * revert reason 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 {
                      // Look for revert reason and bubble it up if present
                      if (returndata.length > 0) {
                          // The easiest way to bubble the revert reason is using memory via assembly
                          /// @solidity memory-safe-assembly
                          assembly {
                              let returndata_size := mload(returndata)
                              revert(add(32, returndata), returndata_size)
                          }
                      } else {
                          revert(errorMessage);
                      }
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev String operations.
           */
          library Strings {
              bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
              uint8 private constant _ADDRESS_LENGTH = 20;
              /**
               * @dev Converts a `uint256` to its ASCII `string` decimal representation.
               */
              function toString(uint256 value) internal pure returns (string memory) {
                  // Inspired by OraclizeAPI's implementation - MIT licence
                  // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
                  if (value == 0) {
                      return "0";
                  }
                  uint256 temp = value;
                  uint256 digits;
                  while (temp != 0) {
                      digits++;
                      temp /= 10;
                  }
                  bytes memory buffer = new bytes(digits);
                  while (value != 0) {
                      digits -= 1;
                      buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
                      value /= 10;
                  }
                  return string(buffer);
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
               */
              function toHexString(uint256 value) internal pure returns (string memory) {
                  if (value == 0) {
                      return "0x00";
                  }
                  uint256 temp = value;
                  uint256 length = 0;
                  while (temp != 0) {
                      length++;
                      temp >>= 8;
                  }
                  return toHexString(value, length);
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
               */
              function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                  bytes memory buffer = new bytes(2 * length + 2);
                  buffer[0] = "0";
                  buffer[1] = "x";
                  for (uint256 i = 2 * length + 1; i > 1; --i) {
                      buffer[i] = _HEX_SYMBOLS[value & 0xf];
                      value >>= 4;
                  }
                  require(value == 0, "Strings: hex length insufficient");
                  return string(buffer);
              }
              /**
               * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
               */
              function toHexString(address addr) internal pure returns (string memory) {
                  return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
          pragma solidity ^0.8.0;
          import "./IERC165.sol";
          /**
           * @dev Implementation of the {IERC165} interface.
           *
           * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
           * for the additional interface id that will be supported. For example:
           *
           * ```solidity
           * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
           *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
           * }
           * ```
           *
           * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
           */
          abstract contract ERC165 is IERC165 {
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                  return interfaceId == type(IERC165).interfaceId;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControlEnumerable.sol)
          pragma solidity ^0.8.0;
          import "./IAccessControlEnumerable.sol";
          import "./AccessControl.sol";
          import "../utils/structs/EnumerableSet.sol";
          /**
           * @dev Extension of {AccessControl} that allows enumerating the members of each role.
           */
          abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {
              using EnumerableSet for EnumerableSet.AddressSet;
              mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers;
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                  return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);
              }
              /**
               * @dev Returns one of the accounts that have `role`. `index` must be a
               * value between 0 and {getRoleMemberCount}, non-inclusive.
               *
               * Role bearers are not sorted in any particular way, and their ordering may
               * change at any point.
               *
               * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
               * you perform all queries on the same block. See the following
               * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
               * for more information.
               */
              function getRoleMember(bytes32 role, uint256 index) public view virtual override returns (address) {
                  return _roleMembers[role].at(index);
              }
              /**
               * @dev Returns the number of accounts that have `role`. Can be used
               * together with {getRoleMember} to enumerate all bearers of a role.
               */
              function getRoleMemberCount(bytes32 role) public view virtual override returns (uint256) {
                  return _roleMembers[role].length();
              }
              /**
               * @dev Overload {_grantRole} to track enumerable memberships
               */
              function _grantRole(bytes32 role, address account) internal virtual override {
                  super._grantRole(role, account);
                  _roleMembers[role].add(account);
              }
              /**
               * @dev Overload {_revokeRole} to track enumerable memberships
               */
              function _revokeRole(bytes32 role, address account) internal virtual override {
                  super._revokeRole(role, account);
                  _roleMembers[role].remove(account);
              }
          }
          // SPDX-License-Identifier: MIT
          // ERC721A Contracts v4.2.3
          // Creator: Chiru Labs
          pragma solidity ^0.8.4;
          import './IERC721A.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 ERC721A is IERC721A {
              // 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
              // =============================================================
              constructor(string memory name_, string memory symbol_) {
                  _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 payable 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 payable virtual override {
                  uint256 prevOwnershipPacked = _packedOwnershipOf(tokenId);
                  if (address(uint160(prevOwnershipPacked)) != from) revert TransferFromIncorrectOwner();
                  (uint256 approvedAddressSlot, address approvedAddress) = _getApprovedSlotAndAddress(tokenId);
                  // The nested ifs save around 20+ gas over a compound boolean condition.
                  if (!_isSenderApprovedOrOwner(approvedAddress, from, _msgSenderERC721A()))
                      if (!isApprovedForAll(from, _msgSenderERC721A())) revert TransferCallerNotOwnerNorApproved();
                  if (to == address(0)) revert TransferToZeroAddress();
                  _beforeTokenTransfers(from, to, tokenId, 1);
                  // Clear approvals from the previous owner.
                  assembly {
                      if approvedAddress {
                          // This is equivalent to `delete _tokenApprovals[tokenId]`.
                          sstore(approvedAddressSlot, 0)
                      }
                  }
                  // Underflow of the sender's balance is impossible because we check for
                  // ownership above and the recipient's balance can't realistically overflow.
                  // Counter overflow is incredibly unrealistic as `tokenId` would have to be 2**256.
                  unchecked {
                      // We can directly increment and decrement the balances.
                      --_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 payable virtual override {
                  safeTransferFrom(from, to, tokenId, '');
              }
              /**
               * @dev Safely transfers `tokenId` token from `from` to `to`.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must exist and be owned by `from`.
               * - If the caller is not `from`, it must be approved to move this token
               * by either {approve} or {setApprovalForAll}.
               * - If `to` refers to a smart contract, it must implement
               * {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory _data
              ) public payable virtual override {
                  transferFrom(from, to, tokenId);
                  if (to.code.length != 0)
                      if (!_checkContractOnERC721Received(from, to, tokenId, _data)) {
                          revert TransferToNonERC721ReceiverImplementer();
                      }
              }
              /**
               * @dev Hook that is called before a set of serially-ordered token IDs
               * are about to be transferred. This includes minting.
               * And also called before burning one token.
               *
               * `startTokenId` - the first token ID to be transferred.
               * `quantity` - the amount to be transferred.
               *
               * Calling conditions:
               *
               * - When `from` and `to` are both non-zero, `from`'s `tokenId` will be
               * transferred to `to`.
               * - When `from` is zero, `tokenId` will be minted for `to`.
               * - When `to` is zero, `tokenId` will be burned by `from`.
               * - `from` and `to` are never both zero.
               */
              function _beforeTokenTransfers(
                  address from,
                  address to,
                  uint256 startTokenId,
                  uint256 quantity
              ) internal virtual {}
              /**
               * @dev Hook that is called after a set of serially-ordered token IDs
               * have been transferred. This includes minting.
               * And also called after one token has been burned.
               *
               * `startTokenId` - the first token ID to be transferred.
               * `quantity` - the amount to be transferred.
               *
               * Calling conditions:
               *
               * - When `from` and `to` are both non-zero, `from`'s `tokenId` has been
               * transferred to `to`.
               * - When `from` is zero, `tokenId` has been minted for `to`.
               * - When `to` is zero, `tokenId` has been burned by `from`.
               * - `from` and `to` are never both zero.
               */
              function _afterTokenTransfers(
                  address from,
                  address to,
                  uint256 startTokenId,
                  uint256 quantity
              ) internal virtual {}
              /**
               * @dev Private function to invoke {IERC721Receiver-onERC721Received} on a target contract.
               *
               * `from` - Previous owner of the given token ID.
               * `to` - Target address that will receive the token.
               * `tokenId` - Token ID to be transferred.
               * `_data` - Optional data to send along with the call.
               *
               * Returns whether the call correctly returned the expected magic value.
               */
              function _checkContractOnERC721Received(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory _data
              ) private returns (bool) {
                  try ERC721A__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 (last updated v4.7.0) (token/common/ERC2981.sol)
          pragma solidity ^0.8.0;
          import "../../interfaces/IERC2981.sol";
          import "../../utils/introspection/ERC165.sol";
          /**
           * @dev Implementation of the NFT Royalty Standard, a standardized way to retrieve royalty payment information.
           *
           * Royalty information can be specified globally for all token ids via {_setDefaultRoyalty}, and/or individually for
           * specific token ids via {_setTokenRoyalty}. The latter takes precedence over the first.
           *
           * Royalty is specified as a fraction of sale price. {_feeDenominator} is overridable but defaults to 10000, meaning the
           * fee is specified in basis points by default.
           *
           * IMPORTANT: ERC-2981 only specifies a way to signal royalty information and does not enforce its payment. See
           * https://eips.ethereum.org/EIPS/eip-2981#optional-royalty-payments[Rationale] in the EIP. Marketplaces are expected to
           * voluntarily pay royalties together with sales, but note that this standard is not yet widely supported.
           *
           * _Available since v4.5._
           */
          abstract contract ERC2981 is IERC2981, ERC165 {
              struct RoyaltyInfo {
                  address receiver;
                  uint96 royaltyFraction;
              }
              RoyaltyInfo private _defaultRoyaltyInfo;
              mapping(uint256 => RoyaltyInfo) private _tokenRoyaltyInfo;
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) {
                  return interfaceId == type(IERC2981).interfaceId || super.supportsInterface(interfaceId);
              }
              /**
               * @inheritdoc IERC2981
               */
              function royaltyInfo(uint256 _tokenId, uint256 _salePrice) public view virtual override returns (address, uint256) {
                  RoyaltyInfo memory royalty = _tokenRoyaltyInfo[_tokenId];
                  if (royalty.receiver == address(0)) {
                      royalty = _defaultRoyaltyInfo;
                  }
                  uint256 royaltyAmount = (_salePrice * royalty.royaltyFraction) / _feeDenominator();
                  return (royalty.receiver, royaltyAmount);
              }
              /**
               * @dev The denominator with which to interpret the fee set in {_setTokenRoyalty} and {_setDefaultRoyalty} as a
               * fraction of the sale price. Defaults to 10000 so fees are expressed in basis points, but may be customized by an
               * override.
               */
              function _feeDenominator() internal pure virtual returns (uint96) {
                  return 10000;
              }
              /**
               * @dev Sets the royalty information that all ids in this contract will default to.
               *
               * Requirements:
               *
               * - `receiver` cannot be the zero address.
               * - `feeNumerator` cannot be greater than the fee denominator.
               */
              function _setDefaultRoyalty(address receiver, uint96 feeNumerator) internal virtual {
                  require(feeNumerator <= _feeDenominator(), "ERC2981: royalty fee will exceed salePrice");
                  require(receiver != address(0), "ERC2981: invalid receiver");
                  _defaultRoyaltyInfo = RoyaltyInfo(receiver, feeNumerator);
              }
              /**
               * @dev Removes default royalty information.
               */
              function _deleteDefaultRoyalty() internal virtual {
                  delete _defaultRoyaltyInfo;
              }
              /**
               * @dev Sets the royalty information for a specific token id, overriding the global default.
               *
               * Requirements:
               *
               * - `receiver` cannot be the zero address.
               * - `feeNumerator` cannot be greater than the fee denominator.
               */
              function _setTokenRoyalty(
                  uint256 tokenId,
                  address receiver,
                  uint96 feeNumerator
              ) internal virtual {
                  require(feeNumerator <= _feeDenominator(), "ERC2981: royalty fee will exceed salePrice");
                  require(receiver != address(0), "ERC2981: Invalid parameters");
                  _tokenRoyaltyInfo[tokenId] = RoyaltyInfo(receiver, feeNumerator);
              }
              /**
               * @dev Resets royalty information for the token id back to the global default.
               */
              function _resetTokenRoyalty(uint256 tokenId) internal virtual {
                  delete _tokenRoyaltyInfo[tokenId];
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright (c) 2021 the ethier authors (github.com/divergencetech/ethier)
          pragma solidity >=0.8.0 <0.9.0;
          import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol";
          import {AccessControlEnumerable} from "./AccessControlEnumerable.sol";
          /// @notice A Pausable contract that can only be toggled by a member of the
          /// STEERING role.
          contract AccessControlPausable is AccessControlEnumerable, Pausable {
              /// @notice Pauses the contract.
              function pause() public onlyRole(DEFAULT_STEERING_ROLE) {
                  Pausable._pause();
              }
              /// @notice Unpauses the contract.
              function unpause() public onlyRole(DEFAULT_STEERING_ROLE) {
                  Pausable._unpause();
              }
          }
          // SPDX-License-Identifier: CC0-1.0
          pragma solidity ^0.8.0;
          import {IERC165, ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
          interface IERC4906Events {
              /// @dev This event emits when the metadata of a token is changed.
              /// So that the third-party platforms such as NFT market could
              /// timely update the images and related attributes of the NFT.
              event MetadataUpdate(uint256 _tokenId);
              /// @dev This event emits when the metadata of a range of tokens is changed.
              /// So that the third-party platforms such as NFT market could
              /// timely update the images and related attributes of the NFTs.
              event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId);
          }
          /// @title EIP-721 Metadata Update Extension
          // solhint-disable-next-line no-empty-blocks
          interface IERC4906 is IERC165, IERC4906Events {
          }
          contract ERC4906 is IERC4906, ERC165 {
              function _refreshMetadata(uint256 tokenId) internal {
                  emit MetadataUpdate(tokenId);
              }
              function _refreshMetadata(uint256 fromTokenId, uint256 toTokenId) internal {
                  emit BatchMetadataUpdate(fromTokenId, toTokenId);
              }
              /// @dev See {IERC165-supportsInterface}.
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(ERC165, IERC165)
                  returns (bool)
              {
                  return
                      interfaceId == bytes4(0x49064906) ||
                      ERC165.supportsInterface(interfaceId);
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.13;
          import {IOperatorFilterRegistry} from "./IOperatorFilterRegistry.sol";
          import {CANONICAL_OPERATOR_FILTER_REGISTRY_ADDRESS} from "./lib/Constants.sol";
          /**
           * @title  OperatorFilterer
           * @notice Abstract contract whose constructor automatically registers and optionally subscribes to or copies another
           *         registrant's entries in the OperatorFilterRegistry.
           * @dev    This smart contract is meant to be inherited by token contracts so they can use the following:
           *         - `onlyAllowedOperator` modifier for `transferFrom` and `safeTransferFrom` methods.
           *         - `onlyAllowedOperatorApproval` modifier for `approve` and `setApprovalForAll` methods.
           *         Please note that if your token contract does not provide an owner with EIP-173, it must provide
           *         administration methods on the contract itself to interact with the registry otherwise the subscription
           *         will be locked to the options set during construction.
           */
          abstract contract OperatorFilterer {
              /// @dev Emitted when an operator is not allowed.
              error OperatorNotAllowed(address operator);
              IOperatorFilterRegistry public constant OPERATOR_FILTER_REGISTRY =
                  IOperatorFilterRegistry(CANONICAL_OPERATOR_FILTER_REGISTRY_ADDRESS);
              /// @dev The constructor that is called when the contract is being deployed.
              constructor(address subscriptionOrRegistrantToCopy, bool subscribe) {
                  // If an inheriting token contract is deployed to a network without the registry deployed, the modifier
                  // will not revert, but the contract will need to be registered with the registry once it is deployed in
                  // order for the modifier to filter addresses.
                  if (address(OPERATOR_FILTER_REGISTRY).code.length > 0) {
                      if (subscribe) {
                          OPERATOR_FILTER_REGISTRY.registerAndSubscribe(address(this), subscriptionOrRegistrantToCopy);
                      } else {
                          if (subscriptionOrRegistrantToCopy != address(0)) {
                              OPERATOR_FILTER_REGISTRY.registerAndCopyEntries(address(this), subscriptionOrRegistrantToCopy);
                          } else {
                              OPERATOR_FILTER_REGISTRY.register(address(this));
                          }
                      }
                  }
              }
              /**
               * @dev A helper function to check if an operator is allowed.
               */
              modifier onlyAllowedOperator(address from) virtual {
                  // Allow spending tokens from addresses with balance
                  // Note that this still allows listings and marketplaces with escrow to transfer tokens if transferred
                  // from an EOA.
                  if (from != msg.sender) {
                      _checkFilterOperator(msg.sender);
                  }
                  _;
              }
              /**
               * @dev A helper function to check if an operator approval is allowed.
               */
              modifier onlyAllowedOperatorApproval(address operator) virtual {
                  _checkFilterOperator(operator);
                  _;
              }
              /**
               * @dev A helper function to check if an operator is allowed.
               */
              function _checkFilterOperator(address operator) internal view virtual {
                  // Check registry code length to facilitate testing in environments without a deployed registry.
                  if (address(OPERATOR_FILTER_REGISTRY).code.length > 0) {
                      // under normal circumstances, this function will revert rather than return false, but inheriting contracts
                      // may specify their own OperatorFilterRegistry implementations, which may behave differently
                      if (!OPERATOR_FILTER_REGISTRY.isOperatorAllowed(address(this), operator)) {
                          revert OperatorNotAllowed(operator);
                      }
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.13;
          address constant CANONICAL_OPERATOR_FILTER_REGISTRY_ADDRESS = 0x000000000000AAeB6D7670E522A718067333cd4E;
          address constant CANONICAL_CORI_SUBSCRIPTION = 0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6;
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity ^0.8.16;
          import {ERC721ACommon} from "ethier/erc721/ERC721ACommon.sol";
          import {BaseRedeemableToken} from "./BaseRedeemableToken.sol";
          /**
           * @notice An ERC721 token intended to act as freely tradeable voucher.
           * @dev This is mainly a convenience wrapper.
           */
          abstract contract RedeemableERC721ACommon is BaseRedeemableToken, ERC721ACommon {
              // =========================================================================
              //                           Internals
              // =========================================================================
              /**
               * @notice Allows spending if the sender is the owner of or approved for
               * transfers of a given token.
               */
              function _isSenderAllowedToSpend(address sender, uint256 tokenId)
                  internal
                  view
                  virtual
                  override
                  returns (bool result)
              {
                  address tokenOwner = ownerOf(tokenId);
                  return (sender == tokenOwner) || isApprovedForAll(tokenOwner, sender) || (sender == getApproved(tokenId));
              }
              /**
               * @notice Redeeming a voucher token burns it.
               */
              function _doRedeem(address, uint256 tokenId) internal virtual override {
                  _burn(tokenId);
              }
              /**
               * @notice Overrides supportsInterface as required by inheritance.
               */
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(BaseRedeemableToken, ERC721ACommon)
                  returns (bool)
              {
                  return BaseRedeemableToken.supportsInterface(interfaceId) || ERC721ACommon.supportsInterface(interfaceId);
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.16 <0.9.0;
          import {ERC721ATransferRestrictedBase, TransferRestriction} from "./ERC721ATransferRestrictedBase.sol";
          /**
           * @notice Extension of ERC721 transfer restrictions with manual restriction
           * setter.
           */
          abstract contract ERC721ATransferRestricted is ERC721ATransferRestrictedBase {
              // =========================================================================
              //                           Error
              // =========================================================================
              error TransferRestrictionLocked();
              error TransferRestrictionCheckFailed(TransferRestriction want);
              // =========================================================================
              //                           Storage
              // =========================================================================
              /**
               * @notice The current restrictions.
               */
              TransferRestriction private _transferRestriction;
              /**
               * @notice Flag to lock in the current transfer restriction.
               */
              bool private _locked;
              // =========================================================================
              //                           Steering
              // =========================================================================
              /**
               * @notice Sets the transfer restrictions.
               */
              function _setTransferRestriction(TransferRestriction restriction) internal virtual {
                  _transferRestriction = restriction;
              }
              /**
               * @notice Sets the transfer restrictions.
               * @dev Only callable by a contract steerer.
               */
              function setTransferRestriction(TransferRestriction restriction) external onlyRole(DEFAULT_STEERING_ROLE) {
                  if (_locked) {
                      revert TransferRestrictionLocked();
                  }
                  _setTransferRestriction(restriction);
              }
              /**
               * @notice Locks the current transfer restrictions.
               * @dev Only callable by a contract steerer.
               * @param restriction must match the current transfer restriction as
               * additional security measure.
               */
              function lockTransferRestriction(TransferRestriction restriction) external onlyRole(DEFAULT_STEERING_ROLE) {
                  if (restriction != _transferRestriction) {
                      revert TransferRestrictionCheckFailed(_transferRestriction);
                  }
                  _locked = true;
              }
              /**
               * @notice Returns the stored transfer restrictions.
               */
              function transferRestriction() public view virtual override returns (TransferRestriction) {
                  return _transferRestriction;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Interface of the ERC165 standard, as defined in the
           * https://eips.ethereum.org/EIPS/eip-165[EIP].
           *
           * Implementers can declare support of contract interfaces, which can then be
           * queried by others ({ERC165Checker}).
           *
           * For an implementation, see {ERC165}.
           */
          interface IERC165 {
              /**
               * @dev Returns true if this contract implements the interface defined by
               * `interfaceId`. See the corresponding
               * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
               * to learn more about how these ids are created.
               *
               * This function call must use less than 30 000 gas.
               */
              function supportsInterface(bytes4 interfaceId) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol)
          pragma solidity ^0.8.0;
          import "./IAccessControl.sol";
          /**
           * @dev External interface of AccessControlEnumerable declared to support ERC165 detection.
           */
          interface IAccessControlEnumerable is IAccessControl {
              /**
               * @dev Returns one of the accounts that have `role`. `index` must be a
               * value between 0 and {getRoleMemberCount}, non-inclusive.
               *
               * Role bearers are not sorted in any particular way, and their ordering may
               * change at any point.
               *
               * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
               * you perform all queries on the same block. See the following
               * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
               * for more information.
               */
              function getRoleMember(bytes32 role, uint256 index) external view returns (address);
              /**
               * @dev Returns the number of accounts that have `role`. Can be used
               * together with {getRoleMember} to enumerate all bearers of a role.
               */
              function getRoleMemberCount(bytes32 role) external view returns (uint256);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (access/AccessControl.sol)
          pragma solidity ^0.8.0;
          import "./IAccessControl.sol";
          import "../utils/Context.sol";
          import "../utils/Strings.sol";
          import "../utils/introspection/ERC165.sol";
          /**
           * @dev Contract module that allows children to implement role-based access
           * control mechanisms. This is a lightweight version that doesn't allow enumerating role
           * members except through off-chain means by accessing the contract event logs. Some
           * applications may benefit from on-chain enumerability, for those cases see
           * {AccessControlEnumerable}.
           *
           * Roles are referred to by their `bytes32` identifier. These should be exposed
           * in the external API and be unique. The best way to achieve this is by
           * using `public constant` hash digests:
           *
           * ```
           * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
           * ```
           *
           * Roles can be used to represent a set of permissions. To restrict access to a
           * function call, use {hasRole}:
           *
           * ```
           * function foo() public {
           *     require(hasRole(MY_ROLE, msg.sender));
           *     ...
           * }
           * ```
           *
           * Roles can be granted and revoked dynamically via the {grantRole} and
           * {revokeRole} functions. Each role has an associated admin role, and only
           * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
           *
           * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
           * that only accounts with this role will be able to grant or revoke other
           * roles. More complex role relationships can be created by using
           * {_setRoleAdmin}.
           *
           * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
           * grant and revoke this role. Extra precautions should be taken to secure
           * accounts that have been granted it.
           */
          abstract contract AccessControl is Context, IAccessControl, ERC165 {
              struct RoleData {
                  mapping(address => bool) members;
                  bytes32 adminRole;
              }
              mapping(bytes32 => RoleData) private _roles;
              bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
              /**
               * @dev Modifier that checks that an account has a specific role. Reverts
               * with a standardized message including the required role.
               *
               * The format of the revert reason is given by the following regular expression:
               *
               *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
               *
               * _Available since v4.1._
               */
              modifier onlyRole(bytes32 role) {
                  _checkRole(role);
                  _;
              }
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                  return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
              }
              /**
               * @dev Returns `true` if `account` has been granted `role`.
               */
              function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
                  return _roles[role].members[account];
              }
              /**
               * @dev Revert with a standard message if `_msgSender()` is missing `role`.
               * Overriding this function changes the behavior of the {onlyRole} modifier.
               *
               * Format of the revert message is described in {_checkRole}.
               *
               * _Available since v4.6._
               */
              function _checkRole(bytes32 role) internal view virtual {
                  _checkRole(role, _msgSender());
              }
              /**
               * @dev Revert with a standard message if `account` is missing `role`.
               *
               * The format of the revert reason is given by the following regular expression:
               *
               *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
               */
              function _checkRole(bytes32 role, address account) internal view virtual {
                  if (!hasRole(role, account)) {
                      revert(
                          string(
                              abi.encodePacked(
                                  "AccessControl: account ",
                                  Strings.toHexString(account),
                                  " is missing role ",
                                  Strings.toHexString(uint256(role), 32)
                              )
                          )
                      );
                  }
              }
              /**
               * @dev Returns the admin role that controls `role`. See {grantRole} and
               * {revokeRole}.
               *
               * To change a role's admin, use {_setRoleAdmin}.
               */
              function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
                  return _roles[role].adminRole;
              }
              /**
               * @dev Grants `role` to `account`.
               *
               * If `account` had not been already granted `role`, emits a {RoleGranted}
               * event.
               *
               * Requirements:
               *
               * - the caller must have ``role``'s admin role.
               *
               * May emit a {RoleGranted} event.
               */
              function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
                  _grantRole(role, account);
              }
              /**
               * @dev Revokes `role` from `account`.
               *
               * If `account` had been granted `role`, emits a {RoleRevoked} event.
               *
               * Requirements:
               *
               * - the caller must have ``role``'s admin role.
               *
               * May emit a {RoleRevoked} event.
               */
              function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
                  _revokeRole(role, account);
              }
              /**
               * @dev Revokes `role` from the calling account.
               *
               * Roles are often managed via {grantRole} and {revokeRole}: this function's
               * purpose is to provide a mechanism for accounts to lose their privileges
               * if they are compromised (such as when a trusted device is misplaced).
               *
               * If the calling account had been revoked `role`, emits a {RoleRevoked}
               * event.
               *
               * Requirements:
               *
               * - the caller must be `account`.
               *
               * May emit a {RoleRevoked} event.
               */
              function renounceRole(bytes32 role, address account) public virtual override {
                  require(account == _msgSender(), "AccessControl: can only renounce roles for self");
                  _revokeRole(role, account);
              }
              /**
               * @dev Grants `role` to `account`.
               *
               * If `account` had not been already granted `role`, emits a {RoleGranted}
               * event. Note that unlike {grantRole}, this function doesn't perform any
               * checks on the calling account.
               *
               * May emit a {RoleGranted} event.
               *
               * [WARNING]
               * ====
               * This function should only be called from the constructor when setting
               * up the initial roles for the system.
               *
               * Using this function in any other way is effectively circumventing the admin
               * system imposed by {AccessControl}.
               * ====
               *
               * NOTE: This function is deprecated in favor of {_grantRole}.
               */
              function _setupRole(bytes32 role, address account) internal virtual {
                  _grantRole(role, account);
              }
              /**
               * @dev Sets `adminRole` as ``role``'s admin role.
               *
               * Emits a {RoleAdminChanged} event.
               */
              function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
                  bytes32 previousAdminRole = getRoleAdmin(role);
                  _roles[role].adminRole = adminRole;
                  emit RoleAdminChanged(role, previousAdminRole, adminRole);
              }
              /**
               * @dev Grants `role` to `account`.
               *
               * Internal function without access restriction.
               *
               * May emit a {RoleGranted} event.
               */
              function _grantRole(bytes32 role, address account) internal virtual {
                  if (!hasRole(role, account)) {
                      _roles[role].members[account] = true;
                      emit RoleGranted(role, account, _msgSender());
                  }
              }
              /**
               * @dev Revokes `role` from `account`.
               *
               * Internal function without access restriction.
               *
               * May emit a {RoleRevoked} event.
               */
              function _revokeRole(bytes32 role, address account) internal virtual {
                  if (hasRole(role, account)) {
                      _roles[role].members[account] = false;
                      emit RoleRevoked(role, account, _msgSender());
                  }
              }
          }
          // 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
          // ERC721A Contracts v4.2.3
          // 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 payable;
              /**
               * @dev Equivalent to `safeTransferFrom(from, to, tokenId, '')`.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) external payable;
              /**
               * @dev Transfers `tokenId` from `from` to `to`.
               *
               * WARNING: Usage of this method is discouraged, use {safeTransferFrom}
               * whenever possible.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must be owned by `from`.
               * - If the caller is not `from`, it must be approved to move this token
               * by either {approve} or {setApprovalForAll}.
               *
               * Emits a {Transfer} event.
               */
              function transferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) external payable;
              /**
               * @dev Gives permission to `to` to transfer `tokenId` token to another account.
               * The approval is cleared when the token is transferred.
               *
               * Only a single account can be approved at a time, so approving the
               * zero address clears previous approvals.
               *
               * Requirements:
               *
               * - The caller must own the token or be an approved operator.
               * - `tokenId` must exist.
               *
               * Emits an {Approval} event.
               */
              function approve(address to, uint256 tokenId) external payable;
              /**
               * @dev Approve or remove `operator` as an operator for the caller.
               * Operators can call {transferFrom} or {safeTransferFrom}
               * for any token owned by the caller.
               *
               * Requirements:
               *
               * - The `operator` cannot be the caller.
               *
               * Emits an {ApprovalForAll} event.
               */
              function setApprovalForAll(address operator, bool _approved) external;
              /**
               * @dev Returns the account approved for `tokenId` token.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               */
              function getApproved(uint256 tokenId) external view returns (address operator);
              /**
               * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
               *
               * See {setApprovalForAll}.
               */
              function isApprovedForAll(address owner, address operator) external view returns (bool);
              // =============================================================
              //                        IERC721Metadata
              // =============================================================
              /**
               * @dev Returns the token collection name.
               */
              function name() external view returns (string memory);
              /**
               * @dev Returns the token collection symbol.
               */
              function symbol() external view returns (string memory);
              /**
               * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
               */
              function tokenURI(uint256 tokenId) external view returns (string memory);
              // =============================================================
              //                           IERC2309
              // =============================================================
              /**
               * @dev Emitted when tokens in `fromTokenId` to `toTokenId`
               * (inclusive) is transferred from `from` to `to`, as defined in the
               * [ERC2309](https://eips.ethereum.org/EIPS/eip-2309) standard.
               *
               * See {_mintERC2309} for more details.
               */
              event ConsecutiveTransfer(uint256 indexed fromTokenId, uint256 toTokenId, address indexed from, address indexed to);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.6.0) (interfaces/IERC2981.sol)
          pragma solidity ^0.8.0;
          import "../utils/introspection/IERC165.sol";
          /**
           * @dev Interface for the NFT Royalty Standard.
           *
           * A standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal
           * support for royalty payments across all NFT marketplaces and ecosystem participants.
           *
           * _Available since v4.5._
           */
          interface IERC2981 is IERC165 {
              /**
               * @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of
               * exchange. The royalty amount is denominated and should be paid in that same unit of exchange.
               */
              function royaltyInfo(uint256 tokenId, uint256 salePrice)
                  external
                  view
                  returns (address receiver, uint256 royaltyAmount);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
          pragma solidity ^0.8.0;
          import "./IERC165.sol";
          /**
           * @dev Implementation of the {IERC165} interface.
           *
           * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
           * for the additional interface id that will be supported. For example:
           *
           * ```solidity
           * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
           *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
           * }
           * ```
           *
           * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
           */
          abstract contract ERC165 is IERC165 {
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                  return interfaceId == type(IERC165).interfaceId;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
          pragma solidity ^0.8.0;
          import "../utils/Context.sol";
          /**
           * @dev Contract module which allows children to implement an emergency stop
           * mechanism that can be triggered by an authorized account.
           *
           * This module is used through inheritance. It will make available the
           * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
           * the functions of your contract. Note that they will not be pausable by
           * simply including this module, only once the modifiers are put in place.
           */
          abstract contract Pausable is Context {
              /**
               * @dev Emitted when the pause is triggered by `account`.
               */
              event Paused(address account);
              /**
               * @dev Emitted when the pause is lifted by `account`.
               */
              event Unpaused(address account);
              bool private _paused;
              /**
               * @dev Initializes the contract in unpaused state.
               */
              constructor() {
                  _paused = false;
              }
              /**
               * @dev Modifier to make a function callable only when the contract is not paused.
               *
               * Requirements:
               *
               * - The contract must not be paused.
               */
              modifier whenNotPaused() {
                  _requireNotPaused();
                  _;
              }
              /**
               * @dev Modifier to make a function callable only when the contract is paused.
               *
               * Requirements:
               *
               * - The contract must be paused.
               */
              modifier whenPaused() {
                  _requirePaused();
                  _;
              }
              /**
               * @dev Returns true if the contract is paused, and false otherwise.
               */
              function paused() public view virtual returns (bool) {
                  return _paused;
              }
              /**
               * @dev Throws if the contract is paused.
               */
              function _requireNotPaused() internal view virtual {
                  require(!paused(), "Pausable: paused");
              }
              /**
               * @dev Throws if the contract is not paused.
               */
              function _requirePaused() internal view virtual {
                  require(paused(), "Pausable: not paused");
              }
              /**
               * @dev Triggers stopped state.
               *
               * Requirements:
               *
               * - The contract must not be paused.
               */
              function _pause() internal virtual whenNotPaused {
                  _paused = true;
                  emit Paused(_msgSender());
              }
              /**
               * @dev Returns to normal state.
               *
               * Requirements:
               *
               * - The contract must be paused.
               */
              function _unpause() internal virtual whenPaused {
                  _paused = false;
                  emit Unpaused(_msgSender());
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.13;
          interface IOperatorFilterRegistry {
              /**
               * @notice Returns true if operator is not filtered for a given token, either by address or codeHash. Also returns
               *         true if supplied registrant address is not registered.
               */
              function isOperatorAllowed(address registrant, address operator) external view returns (bool);
              /**
               * @notice Registers an address with the registry. May be called by address itself or by EIP-173 owner.
               */
              function register(address registrant) external;
              /**
               * @notice Registers an address with the registry and "subscribes" to another address's filtered operators and codeHashes.
               */
              function registerAndSubscribe(address registrant, address subscription) external;
              /**
               * @notice Registers an address with the registry and copies the filtered operators and codeHashes from another
               *         address without subscribing.
               */
              function registerAndCopyEntries(address registrant, address registrantToCopy) external;
              /**
               * @notice Unregisters an address with the registry and removes its subscription. May be called by address itself or by EIP-173 owner.
               *         Note that this does not remove any filtered addresses or codeHashes.
               *         Also note that any subscriptions to this registrant will still be active and follow the existing filtered addresses and codehashes.
               */
              function unregister(address addr) external;
              /**
               * @notice Update an operator address for a registered address - when filtered is true, the operator is filtered.
               */
              function updateOperator(address registrant, address operator, bool filtered) external;
              /**
               * @notice Update multiple operators for a registered address - when filtered is true, the operators will be filtered. Reverts on duplicates.
               */
              function updateOperators(address registrant, address[] calldata operators, bool filtered) external;
              /**
               * @notice Update a codeHash for a registered address - when filtered is true, the codeHash is filtered.
               */
              function updateCodeHash(address registrant, bytes32 codehash, bool filtered) external;
              /**
               * @notice Update multiple codeHashes for a registered address - when filtered is true, the codeHashes will be filtered. Reverts on duplicates.
               */
              function updateCodeHashes(address registrant, bytes32[] calldata codeHashes, bool filtered) external;
              /**
               * @notice Subscribe an address to another registrant's filtered operators and codeHashes. Will remove previous
               *         subscription if present.
               *         Note that accounts with subscriptions may go on to subscribe to other accounts - in this case,
               *         subscriptions will not be forwarded. Instead the former subscription's existing entries will still be
               *         used.
               */
              function subscribe(address registrant, address registrantToSubscribe) external;
              /**
               * @notice Unsubscribe an address from its current subscribed registrant, and optionally copy its filtered operators and codeHashes.
               */
              function unsubscribe(address registrant, bool copyExistingEntries) external;
              /**
               * @notice Get the subscription address of a given registrant, if any.
               */
              function subscriptionOf(address addr) external returns (address registrant);
              /**
               * @notice Get the set of addresses subscribed to a given registrant.
               *         Note that order is not guaranteed as updates are made.
               */
              function subscribers(address registrant) external returns (address[] memory);
              /**
               * @notice Get the subscriber at a given index in the set of addresses subscribed to a given registrant.
               *         Note that order is not guaranteed as updates are made.
               */
              function subscriberAt(address registrant, uint256 index) external returns (address);
              /**
               * @notice Copy filtered operators and codeHashes from a different registrantToCopy to addr.
               */
              function copyEntriesOf(address registrant, address registrantToCopy) external;
              /**
               * @notice Returns true if operator is filtered by a given address or its subscription.
               */
              function isOperatorFiltered(address registrant, address operator) external returns (bool);
              /**
               * @notice Returns true if the hash of an address's code is filtered by a given address or its subscription.
               */
              function isCodeHashOfFiltered(address registrant, address operatorWithCode) external returns (bool);
              /**
               * @notice Returns true if a codeHash is filtered by a given address or its subscription.
               */
              function isCodeHashFiltered(address registrant, bytes32 codeHash) external returns (bool);
              /**
               * @notice Returns a list of filtered operators for a given address or its subscription.
               */
              function filteredOperators(address addr) external returns (address[] memory);
              /**
               * @notice Returns the set of filtered codeHashes for a given address or its subscription.
               *         Note that order is not guaranteed as updates are made.
               */
              function filteredCodeHashes(address addr) external returns (bytes32[] memory);
              /**
               * @notice Returns the filtered operator at the given index of the set of filtered operators for a given address or
               *         its subscription.
               *         Note that order is not guaranteed as updates are made.
               */
              function filteredOperatorAt(address registrant, uint256 index) external returns (address);
              /**
               * @notice Returns the filtered codeHash at the given index of the list of filtered codeHashes for a given address or
               *         its subscription.
               *         Note that order is not guaranteed as updates are made.
               */
              function filteredCodeHashAt(address registrant, uint256 index) external returns (bytes32);
              /**
               * @notice Returns true if an address has registered
               */
              function isRegistered(address addr) external returns (bool);
              /**
               * @dev Convenience method to compute the code hash of an arbitrary contract
               */
              function codeHashOf(address addr) external returns (bytes32);
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity ^0.8.16;
          import {AccessControlEnumerable} from "ethier/utils/AccessControlEnumerable.sol";
          import {IRedeemableToken} from "../interfaces/IRedeemableToken.sol";
          /**
           * @notice Base implementation of a voucher token with approvable redeemer contracts.
           */
          abstract contract BaseRedeemableToken is IRedeemableToken, AccessControlEnumerable {
              /**
               * @notice Authorised redeemers.
               */
              bytes32 public constant REDEEMER_ROLE = keccak256("REDEEMER_ROLE");
              /**
               * @notice A role that cannot be granted or revoked.
               * @dev Used to lock in members of the `REDEEMER_ROLE` role.
               */
              bytes32 private constant _NOOP_ROLE = keccak256("NOOP_ROLE");
              constructor() {
                  _setRoleAdmin(REDEEMER_ROLE, DEFAULT_STEERING_ROLE);
                  _setRoleAdmin(_NOOP_ROLE, _NOOP_ROLE);
              }
              // =========================================================================
              //                           Redemption
              // =========================================================================
              /**
               * @notice Redeems a voucher token with given tokenId.
               * @dev Can only be called by approved redeemer contracts.
               * @dev Reverts if `sender` is not the owner of or approved to transfer the token.
               */
              function redeem(address sender, uint256 tokenId) external onlyRole(REDEEMER_ROLE) {
                  if (!_isSenderAllowedToSpend(sender, tokenId)) {
                      revert IRedeemableToken.RedeemerCallerNotAllowedToSpendVoucher(sender, tokenId);
                  }
                  _doRedeem(sender, tokenId);
              }
              // =========================================================================
              //                           Internal hooks
              // =========================================================================
              /**
               * @notice Hook called by `redeem` to check if the sender is allowed to
               * spend a given token (e.g. if it is the owner or transfer approved).
               */
              function _isSenderAllowedToSpend(address sender, uint256 tokenId) internal view virtual returns (bool result);
              /**
               * @notice Hook called by `redeem` to preform the redemption of a voucher
               * token (e.g. burn).
               */
              function _doRedeem(address sender, uint256 tokenId) internal virtual;
              // =========================================================================
              //                           Steering
              // =========================================================================
              /**
               * @notice Locks the `REDEEMER_ROLE` role.
               */
              function lockRedeemers() external onlyRole(DEFAULT_STEERING_ROLE) {
                  _lockRedeemers();
              }
              /**
               * @notice Locks the `REDEEMER_ROLE` role.
               */
              function _lockRedeemers() internal {
                  _setRoleAdmin(REDEEMER_ROLE, _NOOP_ROLE);
              }
              /**
               * @notice Revokes approval for all redeemers.
               */
              function _revokeAllRedeemers() internal {
                  uint256 num = getRoleMemberCount(REDEEMER_ROLE);
                  for (uint256 i = 0; i < num; i++) {
                      // Akin to a popFront
                      address redeemer = getRoleMember(REDEEMER_ROLE, 0);
                      _revokeRole(REDEEMER_ROLE, redeemer);
                  }
              }
              /**
               * @notice Overrides supportsInterface as required by inheritance.
               */
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(AccessControlEnumerable)
                  returns (bool)
              {
                  return interfaceId == type(IRedeemableToken).interfaceId || super.supportsInterface(interfaceId);
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity >=0.8.16 <0.9.0;
          import {ERC721ACommon} from "ethier/erc721/ERC721ACommon.sol";
          /**
           * @notice Possible transfer restrictions.
           */
          enum TransferRestriction {
              None,
              OnlyMint,
              OnlyBurn,
              Frozen
          }
          /**
           * @notice Implements restrictions for ERC721 transfers.
           * @dev This is intended to facilitate a soft expiry for voucher tokens, having an intermediate stage that still allows
           * voucher to be redeemed but not traded before closing all activity indefinitely.
           * @dev The activation of restrictions is left to the extending contract.
           */
          abstract contract ERC721ATransferRestrictedBase is ERC721ACommon {
              // =========================================================================
              //                           Errors
              // =========================================================================
              /**
               * @notice Thrown if an action is disallowed by the current transfer
               * restriction.
               */
              error DisallowedByTransferRestriction(TransferRestriction);
              // =========================================================================
              //                           Transfer Restriction
              // =========================================================================
              /**
               * @notice Returns the current transfer restriction.
               * @dev Hook to be implemented by the consuming contract (e.g. manual
               * setter, time based, etc.)
               */
              function transferRestriction() public view virtual returns (TransferRestriction);
              // =========================================================================
              //                           Internals
              // =========================================================================
              /**
               * @notice Blocks transfers depending on the current restrictions.
               */
              function _beforeTokenTransfers(address from, address to, uint256 startTokenId, uint256 quantity)
                  internal
                  virtual
                  override
              {
                  super._beforeTokenTransfers(from, to, startTokenId, quantity);
                  TransferRestriction restriction = transferRestriction();
                  if (restriction == TransferRestriction.None) {
                      return;
                  }
                  if (restriction == TransferRestriction.OnlyMint && from == address(0)) {
                      return;
                  }
                  if (restriction == TransferRestriction.OnlyBurn && to == address(0)) {
                      return;
                  }
                  revert DisallowedByTransferRestriction(restriction);
              }
          }
          // 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 v4.4.1 (utils/Context.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Provides information about the current execution context, including the
           * sender of the transaction and its data. While these are generally available
           * via msg.sender and msg.data, they should not be accessed in such a direct
           * manner, since when dealing with meta-transactions the account sending and
           * paying for execution may not be the actual sender (as far as an application
           * is concerned).
           *
           * This contract is only required for intermediate, library-like contracts.
           */
          abstract contract Context {
              function _msgSender() internal view virtual returns (address) {
                  return msg.sender;
              }
              function _msgData() internal view virtual returns (bytes calldata) {
                  return msg.data;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/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);
          }
          

          File 2 of 4: DiamondExhibitionRegularPass
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity ^0.8.15;
          import {ERC721ACommon, BaseTokenURI} from "ethier/erc721/BaseTokenURI.sol";
          import {SellableRedeemableRestrictableERC721} from "./SellableRedeemableRestrictableERC721.sol";
          /**
           * @title Diamond Exhibition: Regular Pass
           * @notice A token claimable by all diamond nested Moonbirds that did not receive a Day One Pass, redeemable for
           * diamond exhibition artworks.
           */
          contract DiamondExhibitionRegularPass is SellableRedeemableRestrictableERC721 {
              constructor(address admin, address steerer, address payable secondaryReceiver)
                  ERC721ACommon(admin, steerer, "Diamond Exhibition: Regular Pass", "REGULAR", secondaryReceiver, 500)
                  BaseTokenURI("https://metadata.proof.xyz/diamond-exhibition-pass/regular/")
              {}
          }
          // SPDX-License-Identifier: MIT
          // Copyright (c) 2023 the ethier authors (github.com/divergencetech/ethier)
          pragma solidity >=0.8.0 <0.9.0;
          import {AccessControlEnumerable} from "../utils/AccessControlEnumerable.sol";
          import {ERC721A, ERC721ACommon} from "./ERC721ACommon.sol";
          /**
           * @notice ERC721 extension that implements a commonly used _baseURI() function
           * to return an URL prefix that can be set by the contract steerer.
           */
          contract BaseTokenURI is AccessControlEnumerable {
              /**
               * @notice Base token URI used as a prefix by tokenURI().
               */
              string private _baseTokenURI;
              constructor(string memory baseTokenURI_) {
                  _setBaseTokenURI(baseTokenURI_);
              }
              /**
               * @notice Sets the base token URI prefix.
               * @dev Only callable by the contract steerer.
               */
              function setBaseTokenURI(string memory baseTokenURI_)
                  public
                  onlyRole(DEFAULT_STEERING_ROLE)
              {
                  _setBaseTokenURI(baseTokenURI_);
              }
              /**
               * @notice Sets the base token URI prefix.
               */
              function _setBaseTokenURI(string memory baseTokenURI_) internal virtual {
                  _baseTokenURI = baseTokenURI_;
              }
              /**
               * @notice Returns the `baseTokenURI`.
               */
              function baseTokenURI() public view virtual returns (string memory) {
                  return _baseTokenURI;
              }
              /**
               * @notice Returns the base token URI * without any additional characters (e.g. a slash).
               */
              function _baseURI() internal view virtual returns (string memory) {
                  return _baseTokenURI;
              }
          }
          /**
           * @notice ERC721ACommon extension that adds BaseTokenURI.
           */
          abstract contract ERC721ACommonBaseTokenURI is ERC721ACommon, BaseTokenURI {
              /**
               * @notice Overrides supportsInterface as required by inheritance.
               */
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(ERC721ACommon, AccessControlEnumerable)
                  returns (bool)
              {
                  return
                      ERC721ACommon.supportsInterface(interfaceId) ||
                      AccessControlEnumerable.supportsInterface(interfaceId);
              }
              /**
               * @dev Inheritance resolution.
               */
              function _baseURI()
                  internal
                  view
                  virtual
                  override(ERC721A, BaseTokenURI)
                  returns (string memory)
              {
                  return BaseTokenURI._baseURI();
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity ^0.8.15;
          import {ERC721A, ERC721ACommon, BaseTokenURI, ERC721ACommonBaseTokenURI} from "ethier/erc721/BaseTokenURI.sol";
          import {OperatorFilterOS} from "ethier/erc721/OperatorFilterOS.sol";
          import {
              TransferRestrictedRedeemableERC721ACommon,
              TransferRestriction
          } from "proof/redemption/voucher/TransferRestrictedRedeemableERC721ACommon.sol";
          import {SellableERC721ACommon} from "proof/sellers/sellable/SellableERC721ACommon.sol";
          /**
           * @notice A redeemable and sellable ERC721 token with operator filtering and transfer restrictions.
           * @dev The contract name should be sung to the tune of "Modern Major-General"
           */
          abstract contract SellableRedeemableRestrictableERC721 is
              ERC721ACommonBaseTokenURI,
              OperatorFilterOS,
              SellableERC721ACommon,
              TransferRestrictedRedeemableERC721ACommon
          {
              // =================================================================================================================
              //                          Inheritance Resolution
              // =================================================================================================================
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(ERC721ACommon, ERC721ACommonBaseTokenURI, SellableERC721ACommon, TransferRestrictedRedeemableERC721ACommon)
                  returns (bool)
              {
                  return TransferRestrictedRedeemableERC721ACommon.supportsInterface(interfaceId)
                      || SellableERC721ACommon.supportsInterface(interfaceId)
                      || ERC721ACommonBaseTokenURI.supportsInterface(interfaceId);
              }
              function _beforeTokenTransfers(address from, address to, uint256 startTokenId, uint256 quantity)
                  internal
                  virtual
                  override(ERC721ACommon, TransferRestrictedRedeemableERC721ACommon)
              {
                  TransferRestrictedRedeemableERC721ACommon._beforeTokenTransfers(from, to, startTokenId, quantity);
              }
              function _baseURI() internal view virtual override(ERC721A, ERC721ACommonBaseTokenURI) returns (string memory) {
                  return ERC721ACommonBaseTokenURI._baseURI();
              }
              function setApprovalForAll(address operator, bool approved) public virtual override(ERC721A, OperatorFilterOS) {
                  OperatorFilterOS.setApprovalForAll(operator, approved);
              }
              function approve(address operator, uint256 tokenId) public payable virtual override(ERC721A, OperatorFilterOS) {
                  OperatorFilterOS.approve(operator, tokenId);
              }
              function transferFrom(address from, address to, uint256 tokenId)
                  public
                  payable
                  virtual
                  override(ERC721A, OperatorFilterOS)
              {
                  OperatorFilterOS.transferFrom(from, to, tokenId);
              }
              function safeTransferFrom(address from, address to, uint256 tokenId)
                  public
                  payable
                  virtual
                  override(ERC721A, OperatorFilterOS)
              {
                  OperatorFilterOS.safeTransferFrom(from, to, tokenId);
              }
              function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data)
                  public
                  payable
                  virtual
                  override(ERC721A, OperatorFilterOS)
              {
                  OperatorFilterOS.safeTransferFrom(from, to, tokenId, data);
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright (c) 2023 the ethier authors (github.com/divergencetech/ethier)
          pragma solidity >=0.8.0 <0.9.0;
          import {AccessControlEnumerable as ACE} from "@openzeppelin/contracts/access/AccessControlEnumerable.sol";
          contract AccessControlEnumerable is ACE {
              /// @notice The default role intended to perform access-restricted actions.
              /// @dev We are using this instead of DEFAULT_ADMIN_ROLE because the latter
              /// is intended to grant/revoke roles and will be secured differently.
              bytes32 public constant DEFAULT_STEERING_ROLE =
                  keccak256("DEFAULT_STEERING_ROLE");
              /// @dev Overrides supportsInterface so that inheriting contracts can
              /// reference this contract instead of OZ's version for further overrides.
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(ACE)
                  returns (bool)
              {
                  return ACE.supportsInterface(interfaceId);
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright (c) 2022 the ethier authors (github.com/divergencetech/ethier)
          pragma solidity >=0.8.0 <0.9.0;
          import {ERC721A} from "erc721a/contracts/ERC721A.sol";
          import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol";
          import {AccessControlEnumerable} from "../utils/AccessControlEnumerable.sol";
          import {AccessControlPausable} from "../utils/AccessControlPausable.sol";
          import {ERC4906} from "./ERC4906.sol";
          /**
          @notice An ERC721A contract with common functionality:
           - Pausable with toggling functions exposed to Owner only
           - ERC2981 royalties
           */
          contract ERC721ACommon is ERC721A, AccessControlPausable, ERC2981, ERC4906 {
              constructor(
                  address admin,
                  address steerer,
                  string memory name,
                  string memory symbol,
                  address payable royaltyReciever,
                  uint96 royaltyBasisPoints
              ) ERC721A(name, symbol) {
                  _setDefaultRoyalty(royaltyReciever, royaltyBasisPoints);
                  _grantRole(DEFAULT_ADMIN_ROLE, admin);
                  _grantRole(DEFAULT_STEERING_ROLE, steerer);
              }
              /// @notice Requires that the token exists.
              modifier tokenExists(uint256 tokenId) {
                  require(ERC721A._exists(tokenId), "ERC721ACommon: Token doesn't exist");
                  _;
              }
              /// @notice Requires that msg.sender owns or is approved for the token.
              modifier onlyApprovedOrOwner(uint256 tokenId) {
                  require(
                      _ownershipOf(tokenId).addr == _msgSender() ||
                          getApproved(tokenId) == _msgSender(),
                      "ERC721ACommon: Not approved nor owner"
                  );
                  _;
              }
              function _beforeTokenTransfers(
                  address from,
                  address to,
                  uint256 startTokenId,
                  uint256 quantity
              ) internal virtual override {
                  require(!paused(), "ERC721ACommon: paused");
                  super._beforeTokenTransfers(from, to, startTokenId, quantity);
              }
              /// @notice Overrides supportsInterface as required by inheritance.
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(ERC721A, AccessControlEnumerable, ERC2981, ERC4906)
                  returns (bool)
              {
                  return
                      ERC721A.supportsInterface(interfaceId) ||
                      ERC2981.supportsInterface(interfaceId) ||
                      AccessControlEnumerable.supportsInterface(interfaceId) ||
                      ERC4906.supportsInterface(interfaceId);
              }
              /// @notice Sets the royalty receiver and percentage (in units of basis
              /// points = 0.01%).
              function setDefaultRoyalty(address receiver, uint96 basisPoints)
                  public
                  virtual
                  onlyRole(DEFAULT_STEERING_ROLE)
              {
                  _setDefaultRoyalty(receiver, basisPoints);
              }
              function emitMetadataUpdateForAll()
                  external
                  onlyRole(DEFAULT_STEERING_ROLE)
              {
                  // EIP4906 is unfortunately quite vague on whether the `toTokenId` in
                  // the following event is included or not. We hence use `totalSupply()`
                  // to ensure that the last actual `tokenId` is included in any case.
                  _refreshMetadata(0, totalSupply());
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright (c) 2023 the ethier authors (github.com/divergencetech/ethier)
          pragma solidity >=0.8.0 <0.9.0;
          import {Address} from "@openzeppelin/contracts/utils/Address.sol";
          import {DefaultOperatorFilterer} from "operator-filter-registry/src/DefaultOperatorFilterer.sol";
          import {ERC721A, ERC721ACommon} from "./ERC721ACommon.sol";
          /**
           * @notice ERC721ACommon extension that adds Opensea's operator filtering.
           */
          abstract contract OperatorFilterOS is ERC721ACommon, DefaultOperatorFilterer {
              using Address for address;
              /**
               * @notice Calling the operator filter registry with given calldata.
               * @dev The registry contract did not foresee role-based contract access
               * control -- only the contract itself, or its (EIP-173) owner is allowed to
               * change subscription settings. To work around this, we enforce
               * authorisation here and forward arbitrary calldata to the registry.
               * Use with care!
               */
              function callOperatorFilterRegistry(bytes calldata cdata)
                  external
                  onlyRole(DEFAULT_STEERING_ROLE)
                  returns (bytes memory)
              {
                  return address(OPERATOR_FILTER_REGISTRY).functionCall(cdata);
              }
              // =========================================================================
              //                           Operator filtering
              // =========================================================================
              function setApprovalForAll(address operator, bool approved)
                  public
                  virtual
                  override
                  onlyAllowedOperatorApproval(operator)
              {
                  super.setApprovalForAll(operator, approved);
              }
              function approve(address operator, uint256 tokenId)
                  public
                  payable
                  virtual
                  override
                  onlyAllowedOperatorApproval(operator)
              {
                  super.approve(operator, tokenId);
              }
              function transferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) public payable virtual override onlyAllowedOperator(from) {
                  super.transferFrom(from, to, tokenId);
              }
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) public payable virtual override onlyAllowedOperator(from) {
                  super.safeTransferFrom(from, to, tokenId);
              }
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory data
              ) public payable virtual override onlyAllowedOperator(from) {
                  super.safeTransferFrom(from, to, tokenId, data);
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity ^0.8.16;
          import {ERC721A, ERC721ACommon} from "ethier/erc721/ERC721ACommon.sol";
          import {RedeemableERC721ACommon} from "./RedeemableERC721ACommon.sol";
          import {
              TransferRestriction,
              ERC721ATransferRestrictedBase,
              ERC721ATransferRestricted
          } from "../restricted/ERC721ATransferRestricted.sol";
          /**
           * @notice An ERC721 token intended to act as freely tradeable voucher.
           * @dev This is mainly a convenience wrapper.
           */
          abstract contract TransferRestrictedRedeemableERC721ACommon is RedeemableERC721ACommon, ERC721ATransferRestricted {
              /**
               * @notice Overrides supportsInterface as required by inheritance.
               */
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(RedeemableERC721ACommon, ERC721ACommon)
                  returns (bool)
              {
                  return RedeemableERC721ACommon.supportsInterface(interfaceId);
              }
              /**
               * @inheritdoc ERC721ATransferRestrictedBase
               */
              function _beforeTokenTransfers(address from, address to, uint256 startTokenId, uint256 quantity)
                  internal
                  virtual
                  override(ERC721ATransferRestrictedBase, ERC721ACommon)
              {
                  ERC721ATransferRestrictedBase._beforeTokenTransfers(from, to, startTokenId, quantity);
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity >=0.8.0 <0.9.0;
          import {ERC721ACommon} from "ethier/erc721/ERC721ACommon.sol";
          import {AccessControlEnumerable, BaseSellable} from "./BaseSellable.sol";
          /**
           * @notice Base contract for sellable ERC721ACommon tokens.
           */
          abstract contract SellableERC721ACommon is BaseSellable, ERC721ACommon {
              /**
               * @inheritdoc BaseSellable
               */
              function _handleSale(address to, uint64 num, bytes calldata) internal virtual override {
                  _mint(to, num);
              }
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(ERC721ACommon, AccessControlEnumerable)
                  returns (bool)
              {
                  return ERC721ACommon.supportsInterface(interfaceId) || AccessControlEnumerable.supportsInterface(interfaceId);
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControlEnumerable.sol)
          pragma solidity ^0.8.0;
          import "./IAccessControlEnumerable.sol";
          import "./AccessControl.sol";
          import "../utils/structs/EnumerableSet.sol";
          /**
           * @dev Extension of {AccessControl} that allows enumerating the members of each role.
           */
          abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {
              using EnumerableSet for EnumerableSet.AddressSet;
              mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers;
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                  return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);
              }
              /**
               * @dev Returns one of the accounts that have `role`. `index` must be a
               * value between 0 and {getRoleMemberCount}, non-inclusive.
               *
               * Role bearers are not sorted in any particular way, and their ordering may
               * change at any point.
               *
               * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
               * you perform all queries on the same block. See the following
               * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
               * for more information.
               */
              function getRoleMember(bytes32 role, uint256 index) public view virtual override returns (address) {
                  return _roleMembers[role].at(index);
              }
              /**
               * @dev Returns the number of accounts that have `role`. Can be used
               * together with {getRoleMember} to enumerate all bearers of a role.
               */
              function getRoleMemberCount(bytes32 role) public view virtual override returns (uint256) {
                  return _roleMembers[role].length();
              }
              /**
               * @dev Overload {_grantRole} to track enumerable memberships
               */
              function _grantRole(bytes32 role, address account) internal virtual override {
                  super._grantRole(role, account);
                  _roleMembers[role].add(account);
              }
              /**
               * @dev Overload {_revokeRole} to track enumerable memberships
               */
              function _revokeRole(bytes32 role, address account) internal virtual override {
                  super._revokeRole(role, account);
                  _roleMembers[role].remove(account);
              }
          }
          // SPDX-License-Identifier: MIT
          // ERC721A Contracts v4.2.3
          // Creator: Chiru Labs
          pragma solidity ^0.8.4;
          import './IERC721A.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 ERC721A is IERC721A {
              // 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
              // =============================================================
              constructor(string memory name_, string memory symbol_) {
                  _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 payable 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 payable virtual override {
                  uint256 prevOwnershipPacked = _packedOwnershipOf(tokenId);
                  if (address(uint160(prevOwnershipPacked)) != from) revert TransferFromIncorrectOwner();
                  (uint256 approvedAddressSlot, address approvedAddress) = _getApprovedSlotAndAddress(tokenId);
                  // The nested ifs save around 20+ gas over a compound boolean condition.
                  if (!_isSenderApprovedOrOwner(approvedAddress, from, _msgSenderERC721A()))
                      if (!isApprovedForAll(from, _msgSenderERC721A())) revert TransferCallerNotOwnerNorApproved();
                  if (to == address(0)) revert TransferToZeroAddress();
                  _beforeTokenTransfers(from, to, tokenId, 1);
                  // Clear approvals from the previous owner.
                  assembly {
                      if approvedAddress {
                          // This is equivalent to `delete _tokenApprovals[tokenId]`.
                          sstore(approvedAddressSlot, 0)
                      }
                  }
                  // Underflow of the sender's balance is impossible because we check for
                  // ownership above and the recipient's balance can't realistically overflow.
                  // Counter overflow is incredibly unrealistic as `tokenId` would have to be 2**256.
                  unchecked {
                      // We can directly increment and decrement the balances.
                      --_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 payable virtual override {
                  safeTransferFrom(from, to, tokenId, '');
              }
              /**
               * @dev Safely transfers `tokenId` token from `from` to `to`.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must exist and be owned by `from`.
               * - If the caller is not `from`, it must be approved to move this token
               * by either {approve} or {setApprovalForAll}.
               * - If `to` refers to a smart contract, it must implement
               * {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory _data
              ) public payable virtual override {
                  transferFrom(from, to, tokenId);
                  if (to.code.length != 0)
                      if (!_checkContractOnERC721Received(from, to, tokenId, _data)) {
                          revert TransferToNonERC721ReceiverImplementer();
                      }
              }
              /**
               * @dev Hook that is called before a set of serially-ordered token IDs
               * are about to be transferred. This includes minting.
               * And also called before burning one token.
               *
               * `startTokenId` - the first token ID to be transferred.
               * `quantity` - the amount to be transferred.
               *
               * Calling conditions:
               *
               * - When `from` and `to` are both non-zero, `from`'s `tokenId` will be
               * transferred to `to`.
               * - When `from` is zero, `tokenId` will be minted for `to`.
               * - When `to` is zero, `tokenId` will be burned by `from`.
               * - `from` and `to` are never both zero.
               */
              function _beforeTokenTransfers(
                  address from,
                  address to,
                  uint256 startTokenId,
                  uint256 quantity
              ) internal virtual {}
              /**
               * @dev Hook that is called after a set of serially-ordered token IDs
               * have been transferred. This includes minting.
               * And also called after one token has been burned.
               *
               * `startTokenId` - the first token ID to be transferred.
               * `quantity` - the amount to be transferred.
               *
               * Calling conditions:
               *
               * - When `from` and `to` are both non-zero, `from`'s `tokenId` has been
               * transferred to `to`.
               * - When `from` is zero, `tokenId` has been minted for `to`.
               * - When `to` is zero, `tokenId` has been burned by `from`.
               * - `from` and `to` are never both zero.
               */
              function _afterTokenTransfers(
                  address from,
                  address to,
                  uint256 startTokenId,
                  uint256 quantity
              ) internal virtual {}
              /**
               * @dev Private function to invoke {IERC721Receiver-onERC721Received} on a target contract.
               *
               * `from` - Previous owner of the given token ID.
               * `to` - Target address that will receive the token.
               * `tokenId` - Token ID to be transferred.
               * `_data` - Optional data to send along with the call.
               *
               * Returns whether the call correctly returned the expected magic value.
               */
              function _checkContractOnERC721Received(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory _data
              ) private returns (bool) {
                  try ERC721A__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 (last updated v4.7.0) (token/common/ERC2981.sol)
          pragma solidity ^0.8.0;
          import "../../interfaces/IERC2981.sol";
          import "../../utils/introspection/ERC165.sol";
          /**
           * @dev Implementation of the NFT Royalty Standard, a standardized way to retrieve royalty payment information.
           *
           * Royalty information can be specified globally for all token ids via {_setDefaultRoyalty}, and/or individually for
           * specific token ids via {_setTokenRoyalty}. The latter takes precedence over the first.
           *
           * Royalty is specified as a fraction of sale price. {_feeDenominator} is overridable but defaults to 10000, meaning the
           * fee is specified in basis points by default.
           *
           * IMPORTANT: ERC-2981 only specifies a way to signal royalty information and does not enforce its payment. See
           * https://eips.ethereum.org/EIPS/eip-2981#optional-royalty-payments[Rationale] in the EIP. Marketplaces are expected to
           * voluntarily pay royalties together with sales, but note that this standard is not yet widely supported.
           *
           * _Available since v4.5._
           */
          abstract contract ERC2981 is IERC2981, ERC165 {
              struct RoyaltyInfo {
                  address receiver;
                  uint96 royaltyFraction;
              }
              RoyaltyInfo private _defaultRoyaltyInfo;
              mapping(uint256 => RoyaltyInfo) private _tokenRoyaltyInfo;
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) {
                  return interfaceId == type(IERC2981).interfaceId || super.supportsInterface(interfaceId);
              }
              /**
               * @inheritdoc IERC2981
               */
              function royaltyInfo(uint256 _tokenId, uint256 _salePrice) public view virtual override returns (address, uint256) {
                  RoyaltyInfo memory royalty = _tokenRoyaltyInfo[_tokenId];
                  if (royalty.receiver == address(0)) {
                      royalty = _defaultRoyaltyInfo;
                  }
                  uint256 royaltyAmount = (_salePrice * royalty.royaltyFraction) / _feeDenominator();
                  return (royalty.receiver, royaltyAmount);
              }
              /**
               * @dev The denominator with which to interpret the fee set in {_setTokenRoyalty} and {_setDefaultRoyalty} as a
               * fraction of the sale price. Defaults to 10000 so fees are expressed in basis points, but may be customized by an
               * override.
               */
              function _feeDenominator() internal pure virtual returns (uint96) {
                  return 10000;
              }
              /**
               * @dev Sets the royalty information that all ids in this contract will default to.
               *
               * Requirements:
               *
               * - `receiver` cannot be the zero address.
               * - `feeNumerator` cannot be greater than the fee denominator.
               */
              function _setDefaultRoyalty(address receiver, uint96 feeNumerator) internal virtual {
                  require(feeNumerator <= _feeDenominator(), "ERC2981: royalty fee will exceed salePrice");
                  require(receiver != address(0), "ERC2981: invalid receiver");
                  _defaultRoyaltyInfo = RoyaltyInfo(receiver, feeNumerator);
              }
              /**
               * @dev Removes default royalty information.
               */
              function _deleteDefaultRoyalty() internal virtual {
                  delete _defaultRoyaltyInfo;
              }
              /**
               * @dev Sets the royalty information for a specific token id, overriding the global default.
               *
               * Requirements:
               *
               * - `receiver` cannot be the zero address.
               * - `feeNumerator` cannot be greater than the fee denominator.
               */
              function _setTokenRoyalty(
                  uint256 tokenId,
                  address receiver,
                  uint96 feeNumerator
              ) internal virtual {
                  require(feeNumerator <= _feeDenominator(), "ERC2981: royalty fee will exceed salePrice");
                  require(receiver != address(0), "ERC2981: Invalid parameters");
                  _tokenRoyaltyInfo[tokenId] = RoyaltyInfo(receiver, feeNumerator);
              }
              /**
               * @dev Resets royalty information for the token id back to the global default.
               */
              function _resetTokenRoyalty(uint256 tokenId) internal virtual {
                  delete _tokenRoyaltyInfo[tokenId];
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright (c) 2021 the ethier authors (github.com/divergencetech/ethier)
          pragma solidity >=0.8.0 <0.9.0;
          import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol";
          import {AccessControlEnumerable} from "./AccessControlEnumerable.sol";
          /// @notice A Pausable contract that can only be toggled by a member of the
          /// STEERING role.
          contract AccessControlPausable is AccessControlEnumerable, Pausable {
              /// @notice Pauses the contract.
              function pause() public onlyRole(DEFAULT_STEERING_ROLE) {
                  Pausable._pause();
              }
              /// @notice Unpauses the contract.
              function unpause() public onlyRole(DEFAULT_STEERING_ROLE) {
                  Pausable._unpause();
              }
          }
          // SPDX-License-Identifier: CC0-1.0
          pragma solidity ^0.8.0;
          import {IERC165, ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
          interface IERC4906Events {
              /// @dev This event emits when the metadata of a token is changed.
              /// So that the third-party platforms such as NFT market could
              /// timely update the images and related attributes of the NFT.
              event MetadataUpdate(uint256 _tokenId);
              /// @dev This event emits when the metadata of a range of tokens is changed.
              /// So that the third-party platforms such as NFT market could
              /// timely update the images and related attributes of the NFTs.
              event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId);
          }
          /// @title EIP-721 Metadata Update Extension
          // solhint-disable-next-line no-empty-blocks
          interface IERC4906 is IERC165, IERC4906Events {
          }
          contract ERC4906 is IERC4906, ERC165 {
              function _refreshMetadata(uint256 tokenId) internal {
                  emit MetadataUpdate(tokenId);
              }
              function _refreshMetadata(uint256 fromTokenId, uint256 toTokenId) internal {
                  emit BatchMetadataUpdate(fromTokenId, toTokenId);
              }
              /// @dev See {IERC165-supportsInterface}.
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(ERC165, IERC165)
                  returns (bool)
              {
                  return
                      interfaceId == bytes4(0x49064906) ||
                      ERC165.supportsInterface(interfaceId);
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)
          pragma solidity ^0.8.1;
          /**
           * @dev Collection of functions related to the address type
           */
          library Address {
              /**
               * @dev Returns true if `account` is a contract.
               *
               * [IMPORTANT]
               * ====
               * It is unsafe to assume that an address for which this function returns
               * false is an externally-owned account (EOA) and not a contract.
               *
               * Among others, `isContract` will return false for the following
               * types of addresses:
               *
               *  - an externally-owned account
               *  - a contract in construction
               *  - an address where a contract will be created
               *  - an address where a contract lived, but was destroyed
               * ====
               *
               * [IMPORTANT]
               * ====
               * You shouldn't rely on `isContract` to protect against flash loan attacks!
               *
               * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
               * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
               * constructor.
               * ====
               */
              function isContract(address account) internal view returns (bool) {
                  // This method relies on extcodesize/address.code.length, which returns 0
                  // for contracts in construction, since the code is only stored at the end
                  // of the constructor execution.
                  return account.code.length > 0;
              }
              /**
               * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
               * `recipient`, forwarding all available gas and reverting on errors.
               *
               * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
               * of certain opcodes, possibly making contracts go over the 2300 gas limit
               * imposed by `transfer`, making them unable to receive funds via
               * `transfer`. {sendValue} removes this limitation.
               *
               * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
               *
               * IMPORTANT: because control is transferred to `recipient`, care must be
               * taken to not create reentrancy vulnerabilities. Consider using
               * {ReentrancyGuard} or the
               * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
               */
              function sendValue(address payable recipient, uint256 amount) internal {
                  require(address(this).balance >= amount, "Address: insufficient balance");
                  (bool success, ) = recipient.call{value: amount}("");
                  require(success, "Address: unable to send value, recipient may have reverted");
              }
              /**
               * @dev Performs a Solidity function call using a low level `call`. A
               * plain `call` is an unsafe replacement for a function call: use this
               * function instead.
               *
               * If `target` reverts with a revert reason, it is bubbled up by this
               * function (like regular Solidity function calls).
               *
               * Returns the raw returned data. To convert to the expected return value,
               * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
               *
               * Requirements:
               *
               * - `target` must be a contract.
               * - calling `target` with `data` must not revert.
               *
               * _Available since v3.1._
               */
              function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, 0, "Address: low-level call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
               * `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, 0, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but also transferring `value` wei to `target`.
               *
               * Requirements:
               *
               * - the calling contract must have an ETH balance of at least `value`.
               * - the called Solidity function must be `payable`.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
              }
              /**
               * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
               * with `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  require(address(this).balance >= value, "Address: insufficient balance for call");
                  (bool success, bytes memory returndata) = target.call{value: value}(data);
                  return verifyCallResultFromTarget(target, success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                  return functionStaticCall(target, data, "Address: low-level static call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal view returns (bytes memory) {
                  (bool success, bytes memory returndata) = target.staticcall(data);
                  return verifyCallResultFromTarget(target, success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a delegate call.
               *
               * _Available since v3.4._
               */
              function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionDelegateCall(target, data, "Address: low-level delegate call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a delegate call.
               *
               * _Available since v3.4._
               */
              function functionDelegateCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  (bool success, bytes memory returndata) = target.delegatecall(data);
                  return verifyCallResultFromTarget(target, success, returndata, errorMessage);
              }
              /**
               * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
               * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
               *
               * _Available since v4.8._
               */
              function verifyCallResultFromTarget(
                  address target,
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal view returns (bytes memory) {
                  if (success) {
                      if (returndata.length == 0) {
                          // only check isContract if the call was successful and the return data is empty
                          // otherwise we already know that it was a contract
                          require(isContract(target), "Address: call to non-contract");
                      }
                      return returndata;
                  } else {
                      _revert(returndata, errorMessage);
                  }
              }
              /**
               * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
               * revert reason or using the provided one.
               *
               * _Available since v4.3._
               */
              function verifyCallResult(
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal pure returns (bytes memory) {
                  if (success) {
                      return returndata;
                  } else {
                      _revert(returndata, errorMessage);
                  }
              }
              function _revert(bytes memory returndata, string memory errorMessage) private pure {
                  // Look for revert reason and bubble it up if present
                  if (returndata.length > 0) {
                      // The easiest way to bubble the revert reason is using memory via assembly
                      /// @solidity memory-safe-assembly
                      assembly {
                          let returndata_size := mload(returndata)
                          revert(add(32, returndata), returndata_size)
                      }
                  } else {
                      revert(errorMessage);
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.13;
          import {OperatorFilterer} from "./OperatorFilterer.sol";
          import {CANONICAL_CORI_SUBSCRIPTION} from "./lib/Constants.sol";
          /**
           * @title  DefaultOperatorFilterer
           * @notice Inherits from OperatorFilterer and automatically subscribes to the default OpenSea subscription.
           * @dev    Please note that if your token contract does not provide an owner with EIP-173, it must provide
           *         administration methods on the contract itself to interact with the registry otherwise the subscription
           *         will be locked to the options set during construction.
           */
          abstract contract DefaultOperatorFilterer is OperatorFilterer {
              /// @dev The constructor that is called when the contract is being deployed.
              constructor() OperatorFilterer(CANONICAL_CORI_SUBSCRIPTION, true) {}
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity ^0.8.16;
          import {ERC721ACommon} from "ethier/erc721/ERC721ACommon.sol";
          import {BaseRedeemableToken} from "./BaseRedeemableToken.sol";
          /**
           * @notice An ERC721 token intended to act as freely tradeable voucher.
           * @dev This is mainly a convenience wrapper.
           */
          abstract contract RedeemableERC721ACommon is BaseRedeemableToken, ERC721ACommon {
              // =========================================================================
              //                           Internals
              // =========================================================================
              /**
               * @notice Allows spending if the sender is the owner of or approved for
               * transfers of a given token.
               */
              function _isSenderAllowedToSpend(address sender, uint256 tokenId)
                  internal
                  view
                  virtual
                  override
                  returns (bool result)
              {
                  address tokenOwner = ownerOf(tokenId);
                  return (sender == tokenOwner) || isApprovedForAll(tokenOwner, sender) || (sender == getApproved(tokenId));
              }
              /**
               * @notice Redeeming a voucher token burns it.
               */
              function _doRedeem(address, uint256 tokenId) internal virtual override {
                  _burn(tokenId);
              }
              /**
               * @notice Overrides supportsInterface as required by inheritance.
               */
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(BaseRedeemableToken, ERC721ACommon)
                  returns (bool)
              {
                  return BaseRedeemableToken.supportsInterface(interfaceId) || ERC721ACommon.supportsInterface(interfaceId);
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.16 <0.9.0;
          import {ERC721ATransferRestrictedBase, TransferRestriction} from "./ERC721ATransferRestrictedBase.sol";
          /**
           * @notice Extension of ERC721 transfer restrictions with manual restriction
           * setter.
           */
          abstract contract ERC721ATransferRestricted is ERC721ATransferRestrictedBase {
              // =========================================================================
              //                           Error
              // =========================================================================
              error TransferRestrictionLocked();
              error TransferRestrictionCheckFailed(TransferRestriction want);
              // =========================================================================
              //                           Storage
              // =========================================================================
              /**
               * @notice The current restrictions.
               */
              TransferRestriction private _transferRestriction;
              /**
               * @notice Flag to lock in the current transfer restriction.
               */
              bool private _locked;
              // =========================================================================
              //                           Steering
              // =========================================================================
              /**
               * @notice Sets the transfer restrictions.
               */
              function _setTransferRestriction(TransferRestriction restriction) internal virtual {
                  _transferRestriction = restriction;
              }
              /**
               * @notice Sets the transfer restrictions.
               * @dev Only callable by a contract steerer.
               */
              function setTransferRestriction(TransferRestriction restriction) external onlyRole(DEFAULT_STEERING_ROLE) {
                  if (_locked) {
                      revert TransferRestrictionLocked();
                  }
                  _setTransferRestriction(restriction);
              }
              /**
               * @notice Locks the current transfer restrictions.
               * @dev Only callable by a contract steerer.
               * @param restriction must match the current transfer restriction as
               * additional security measure.
               */
              function lockTransferRestriction(TransferRestriction restriction) external onlyRole(DEFAULT_STEERING_ROLE) {
                  if (restriction != _transferRestriction) {
                      revert TransferRestrictionCheckFailed(_transferRestriction);
                  }
                  _locked = true;
              }
              /**
               * @notice Returns the stored transfer restrictions.
               */
              function transferRestriction() public view virtual override returns (TransferRestriction) {
                  return _transferRestriction;
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity >=0.8.0 <0.9.0;
          import {AccessControlEnumerable} from "ethier/utils/AccessControlEnumerable.sol";
          import {ISellable} from "../interfaces/ISellable.sol";
          /**
           * @notice A base contract for selling content via authorised sellers.
           */
          abstract contract BaseSellable is ISellable, AccessControlEnumerable {
              /**
               * @notice Authorised sellers.
               */
              bytes32 public constant AUTHORISED_SELLER_ROLE = keccak256("AUTHORISED_SELLER_ROLE");
              /**
               * @notice A role that cannot be granted or revoked.
               * @dev Used to lock in members of the `AUTHORISED_SELLER_ROLE` role.
               */
              bytes32 private constant _NOOP_ROLE = keccak256("NOOP_ROLE");
              constructor() {
                  _setRoleAdmin(AUTHORISED_SELLER_ROLE, DEFAULT_STEERING_ROLE);
                  _setRoleAdmin(_NOOP_ROLE, _NOOP_ROLE);
              }
              /**
               * @notice Handles the sale of sellable content via an authorised seller.
               * @dev Delegates the implementation to `_handleSale`.
               */
              function handleSale(address to, uint64 num, bytes calldata data)
                  external
                  payable
                  onlyRole(AUTHORISED_SELLER_ROLE)
              {
                  _handleSale(to, num, data);
              }
              /**
               * @notice Handles the sale of sellable content.
               */
              function _handleSale(address to, uint64 num, bytes calldata data) internal virtual;
              /**
               * @notice Locks the `AUTHORISED_SELLER_ROLE` role.
               */
              function lockSellers() external onlyRole(DEFAULT_STEERING_ROLE) {
                  _lockSellers();
              }
              /**
               * @notice Locks the `AUTHORISED_SELLER_ROLE` role.
               */
              function _lockSellers() internal {
                  _setRoleAdmin(AUTHORISED_SELLER_ROLE, _NOOP_ROLE);
              }
              /**
               * @notice Revokes approval for all sellers.
               */
              function _revokeAllSellers() internal {
                  uint256 num = getRoleMemberCount(AUTHORISED_SELLER_ROLE);
                  for (uint256 i = 0; i < num; i++) {
                      // Akin to a popFront
                      address seller = getRoleMember(AUTHORISED_SELLER_ROLE, 0);
                      _revokeRole(AUTHORISED_SELLER_ROLE, seller);
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol)
          pragma solidity ^0.8.0;
          import "./IAccessControl.sol";
          /**
           * @dev External interface of AccessControlEnumerable declared to support ERC165 detection.
           */
          interface IAccessControlEnumerable is IAccessControl {
              /**
               * @dev Returns one of the accounts that have `role`. `index` must be a
               * value between 0 and {getRoleMemberCount}, non-inclusive.
               *
               * Role bearers are not sorted in any particular way, and their ordering may
               * change at any point.
               *
               * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
               * you perform all queries on the same block. See the following
               * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
               * for more information.
               */
              function getRoleMember(bytes32 role, uint256 index) external view returns (address);
              /**
               * @dev Returns the number of accounts that have `role`. Can be used
               * together with {getRoleMember} to enumerate all bearers of a role.
               */
              function getRoleMemberCount(bytes32 role) external view returns (uint256);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (access/AccessControl.sol)
          pragma solidity ^0.8.0;
          import "./IAccessControl.sol";
          import "../utils/Context.sol";
          import "../utils/Strings.sol";
          import "../utils/introspection/ERC165.sol";
          /**
           * @dev Contract module that allows children to implement role-based access
           * control mechanisms. This is a lightweight version that doesn't allow enumerating role
           * members except through off-chain means by accessing the contract event logs. Some
           * applications may benefit from on-chain enumerability, for those cases see
           * {AccessControlEnumerable}.
           *
           * Roles are referred to by their `bytes32` identifier. These should be exposed
           * in the external API and be unique. The best way to achieve this is by
           * using `public constant` hash digests:
           *
           * ```
           * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
           * ```
           *
           * Roles can be used to represent a set of permissions. To restrict access to a
           * function call, use {hasRole}:
           *
           * ```
           * function foo() public {
           *     require(hasRole(MY_ROLE, msg.sender));
           *     ...
           * }
           * ```
           *
           * Roles can be granted and revoked dynamically via the {grantRole} and
           * {revokeRole} functions. Each role has an associated admin role, and only
           * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
           *
           * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
           * that only accounts with this role will be able to grant or revoke other
           * roles. More complex role relationships can be created by using
           * {_setRoleAdmin}.
           *
           * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
           * grant and revoke this role. Extra precautions should be taken to secure
           * accounts that have been granted it.
           */
          abstract contract AccessControl is Context, IAccessControl, ERC165 {
              struct RoleData {
                  mapping(address => bool) members;
                  bytes32 adminRole;
              }
              mapping(bytes32 => RoleData) private _roles;
              bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
              /**
               * @dev Modifier that checks that an account has a specific role. Reverts
               * with a standardized message including the required role.
               *
               * The format of the revert reason is given by the following regular expression:
               *
               *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
               *
               * _Available since v4.1._
               */
              modifier onlyRole(bytes32 role) {
                  _checkRole(role);
                  _;
              }
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                  return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
              }
              /**
               * @dev Returns `true` if `account` has been granted `role`.
               */
              function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
                  return _roles[role].members[account];
              }
              /**
               * @dev Revert with a standard message if `_msgSender()` is missing `role`.
               * Overriding this function changes the behavior of the {onlyRole} modifier.
               *
               * Format of the revert message is described in {_checkRole}.
               *
               * _Available since v4.6._
               */
              function _checkRole(bytes32 role) internal view virtual {
                  _checkRole(role, _msgSender());
              }
              /**
               * @dev Revert with a standard message if `account` is missing `role`.
               *
               * The format of the revert reason is given by the following regular expression:
               *
               *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
               */
              function _checkRole(bytes32 role, address account) internal view virtual {
                  if (!hasRole(role, account)) {
                      revert(
                          string(
                              abi.encodePacked(
                                  "AccessControl: account ",
                                  Strings.toHexString(account),
                                  " is missing role ",
                                  Strings.toHexString(uint256(role), 32)
                              )
                          )
                      );
                  }
              }
              /**
               * @dev Returns the admin role that controls `role`. See {grantRole} and
               * {revokeRole}.
               *
               * To change a role's admin, use {_setRoleAdmin}.
               */
              function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
                  return _roles[role].adminRole;
              }
              /**
               * @dev Grants `role` to `account`.
               *
               * If `account` had not been already granted `role`, emits a {RoleGranted}
               * event.
               *
               * Requirements:
               *
               * - the caller must have ``role``'s admin role.
               *
               * May emit a {RoleGranted} event.
               */
              function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
                  _grantRole(role, account);
              }
              /**
               * @dev Revokes `role` from `account`.
               *
               * If `account` had been granted `role`, emits a {RoleRevoked} event.
               *
               * Requirements:
               *
               * - the caller must have ``role``'s admin role.
               *
               * May emit a {RoleRevoked} event.
               */
              function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
                  _revokeRole(role, account);
              }
              /**
               * @dev Revokes `role` from the calling account.
               *
               * Roles are often managed via {grantRole} and {revokeRole}: this function's
               * purpose is to provide a mechanism for accounts to lose their privileges
               * if they are compromised (such as when a trusted device is misplaced).
               *
               * If the calling account had been revoked `role`, emits a {RoleRevoked}
               * event.
               *
               * Requirements:
               *
               * - the caller must be `account`.
               *
               * May emit a {RoleRevoked} event.
               */
              function renounceRole(bytes32 role, address account) public virtual override {
                  require(account == _msgSender(), "AccessControl: can only renounce roles for self");
                  _revokeRole(role, account);
              }
              /**
               * @dev Grants `role` to `account`.
               *
               * If `account` had not been already granted `role`, emits a {RoleGranted}
               * event. Note that unlike {grantRole}, this function doesn't perform any
               * checks on the calling account.
               *
               * May emit a {RoleGranted} event.
               *
               * [WARNING]
               * ====
               * This function should only be called from the constructor when setting
               * up the initial roles for the system.
               *
               * Using this function in any other way is effectively circumventing the admin
               * system imposed by {AccessControl}.
               * ====
               *
               * NOTE: This function is deprecated in favor of {_grantRole}.
               */
              function _setupRole(bytes32 role, address account) internal virtual {
                  _grantRole(role, account);
              }
              /**
               * @dev Sets `adminRole` as ``role``'s admin role.
               *
               * Emits a {RoleAdminChanged} event.
               */
              function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
                  bytes32 previousAdminRole = getRoleAdmin(role);
                  _roles[role].adminRole = adminRole;
                  emit RoleAdminChanged(role, previousAdminRole, adminRole);
              }
              /**
               * @dev Grants `role` to `account`.
               *
               * Internal function without access restriction.
               *
               * May emit a {RoleGranted} event.
               */
              function _grantRole(bytes32 role, address account) internal virtual {
                  if (!hasRole(role, account)) {
                      _roles[role].members[account] = true;
                      emit RoleGranted(role, account, _msgSender());
                  }
              }
              /**
               * @dev Revokes `role` from `account`.
               *
               * Internal function without access restriction.
               *
               * May emit a {RoleRevoked} event.
               */
              function _revokeRole(bytes32 role, address account) internal virtual {
                  if (hasRole(role, account)) {
                      _roles[role].members[account] = false;
                      emit RoleRevoked(role, account, _msgSender());
                  }
              }
          }
          // 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
          // ERC721A Contracts v4.2.3
          // 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 payable;
              /**
               * @dev Equivalent to `safeTransferFrom(from, to, tokenId, '')`.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) external payable;
              /**
               * @dev Transfers `tokenId` from `from` to `to`.
               *
               * WARNING: Usage of this method is discouraged, use {safeTransferFrom}
               * whenever possible.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must be owned by `from`.
               * - If the caller is not `from`, it must be approved to move this token
               * by either {approve} or {setApprovalForAll}.
               *
               * Emits a {Transfer} event.
               */
              function transferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) external payable;
              /**
               * @dev Gives permission to `to` to transfer `tokenId` token to another account.
               * The approval is cleared when the token is transferred.
               *
               * Only a single account can be approved at a time, so approving the
               * zero address clears previous approvals.
               *
               * Requirements:
               *
               * - The caller must own the token or be an approved operator.
               * - `tokenId` must exist.
               *
               * Emits an {Approval} event.
               */
              function approve(address to, uint256 tokenId) external payable;
              /**
               * @dev Approve or remove `operator` as an operator for the caller.
               * Operators can call {transferFrom} or {safeTransferFrom}
               * for any token owned by the caller.
               *
               * Requirements:
               *
               * - The `operator` cannot be the caller.
               *
               * Emits an {ApprovalForAll} event.
               */
              function setApprovalForAll(address operator, bool _approved) external;
              /**
               * @dev Returns the account approved for `tokenId` token.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               */
              function getApproved(uint256 tokenId) external view returns (address operator);
              /**
               * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
               *
               * See {setApprovalForAll}.
               */
              function isApprovedForAll(address owner, address operator) external view returns (bool);
              // =============================================================
              //                        IERC721Metadata
              // =============================================================
              /**
               * @dev Returns the token collection name.
               */
              function name() external view returns (string memory);
              /**
               * @dev Returns the token collection symbol.
               */
              function symbol() external view returns (string memory);
              /**
               * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
               */
              function tokenURI(uint256 tokenId) external view returns (string memory);
              // =============================================================
              //                           IERC2309
              // =============================================================
              /**
               * @dev Emitted when tokens in `fromTokenId` to `toTokenId`
               * (inclusive) is transferred from `from` to `to`, as defined in the
               * [ERC2309](https://eips.ethereum.org/EIPS/eip-2309) standard.
               *
               * See {_mintERC2309} for more details.
               */
              event ConsecutiveTransfer(uint256 indexed fromTokenId, uint256 toTokenId, address indexed from, address indexed to);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.6.0) (interfaces/IERC2981.sol)
          pragma solidity ^0.8.0;
          import "../utils/introspection/IERC165.sol";
          /**
           * @dev Interface for the NFT Royalty Standard.
           *
           * A standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal
           * support for royalty payments across all NFT marketplaces and ecosystem participants.
           *
           * _Available since v4.5._
           */
          interface IERC2981 is IERC165 {
              /**
               * @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of
               * exchange. The royalty amount is denominated and should be paid in that same unit of exchange.
               */
              function royaltyInfo(uint256 tokenId, uint256 salePrice)
                  external
                  view
                  returns (address receiver, uint256 royaltyAmount);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
          pragma solidity ^0.8.0;
          import "./IERC165.sol";
          /**
           * @dev Implementation of the {IERC165} interface.
           *
           * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
           * for the additional interface id that will be supported. For example:
           *
           * ```solidity
           * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
           *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
           * }
           * ```
           *
           * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
           */
          abstract contract ERC165 is IERC165 {
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                  return interfaceId == type(IERC165).interfaceId;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
          pragma solidity ^0.8.0;
          import "../utils/Context.sol";
          /**
           * @dev Contract module which allows children to implement an emergency stop
           * mechanism that can be triggered by an authorized account.
           *
           * This module is used through inheritance. It will make available the
           * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
           * the functions of your contract. Note that they will not be pausable by
           * simply including this module, only once the modifiers are put in place.
           */
          abstract contract Pausable is Context {
              /**
               * @dev Emitted when the pause is triggered by `account`.
               */
              event Paused(address account);
              /**
               * @dev Emitted when the pause is lifted by `account`.
               */
              event Unpaused(address account);
              bool private _paused;
              /**
               * @dev Initializes the contract in unpaused state.
               */
              constructor() {
                  _paused = false;
              }
              /**
               * @dev Modifier to make a function callable only when the contract is not paused.
               *
               * Requirements:
               *
               * - The contract must not be paused.
               */
              modifier whenNotPaused() {
                  _requireNotPaused();
                  _;
              }
              /**
               * @dev Modifier to make a function callable only when the contract is paused.
               *
               * Requirements:
               *
               * - The contract must be paused.
               */
              modifier whenPaused() {
                  _requirePaused();
                  _;
              }
              /**
               * @dev Returns true if the contract is paused, and false otherwise.
               */
              function paused() public view virtual returns (bool) {
                  return _paused;
              }
              /**
               * @dev Throws if the contract is paused.
               */
              function _requireNotPaused() internal view virtual {
                  require(!paused(), "Pausable: paused");
              }
              /**
               * @dev Throws if the contract is not paused.
               */
              function _requirePaused() internal view virtual {
                  require(paused(), "Pausable: not paused");
              }
              /**
               * @dev Triggers stopped state.
               *
               * Requirements:
               *
               * - The contract must not be paused.
               */
              function _pause() internal virtual whenNotPaused {
                  _paused = true;
                  emit Paused(_msgSender());
              }
              /**
               * @dev Returns to normal state.
               *
               * Requirements:
               *
               * - The contract must be paused.
               */
              function _unpause() internal virtual whenPaused {
                  _paused = false;
                  emit Unpaused(_msgSender());
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.13;
          import {IOperatorFilterRegistry} from "./IOperatorFilterRegistry.sol";
          import {CANONICAL_OPERATOR_FILTER_REGISTRY_ADDRESS} from "./lib/Constants.sol";
          /**
           * @title  OperatorFilterer
           * @notice Abstract contract whose constructor automatically registers and optionally subscribes to or copies another
           *         registrant's entries in the OperatorFilterRegistry.
           * @dev    This smart contract is meant to be inherited by token contracts so they can use the following:
           *         - `onlyAllowedOperator` modifier for `transferFrom` and `safeTransferFrom` methods.
           *         - `onlyAllowedOperatorApproval` modifier for `approve` and `setApprovalForAll` methods.
           *         Please note that if your token contract does not provide an owner with EIP-173, it must provide
           *         administration methods on the contract itself to interact with the registry otherwise the subscription
           *         will be locked to the options set during construction.
           */
          abstract contract OperatorFilterer {
              /// @dev Emitted when an operator is not allowed.
              error OperatorNotAllowed(address operator);
              IOperatorFilterRegistry public constant OPERATOR_FILTER_REGISTRY =
                  IOperatorFilterRegistry(CANONICAL_OPERATOR_FILTER_REGISTRY_ADDRESS);
              /// @dev The constructor that is called when the contract is being deployed.
              constructor(address subscriptionOrRegistrantToCopy, bool subscribe) {
                  // If an inheriting token contract is deployed to a network without the registry deployed, the modifier
                  // will not revert, but the contract will need to be registered with the registry once it is deployed in
                  // order for the modifier to filter addresses.
                  if (address(OPERATOR_FILTER_REGISTRY).code.length > 0) {
                      if (subscribe) {
                          OPERATOR_FILTER_REGISTRY.registerAndSubscribe(address(this), subscriptionOrRegistrantToCopy);
                      } else {
                          if (subscriptionOrRegistrantToCopy != address(0)) {
                              OPERATOR_FILTER_REGISTRY.registerAndCopyEntries(address(this), subscriptionOrRegistrantToCopy);
                          } else {
                              OPERATOR_FILTER_REGISTRY.register(address(this));
                          }
                      }
                  }
              }
              /**
               * @dev A helper function to check if an operator is allowed.
               */
              modifier onlyAllowedOperator(address from) virtual {
                  // Allow spending tokens from addresses with balance
                  // Note that this still allows listings and marketplaces with escrow to transfer tokens if transferred
                  // from an EOA.
                  if (from != msg.sender) {
                      _checkFilterOperator(msg.sender);
                  }
                  _;
              }
              /**
               * @dev A helper function to check if an operator approval is allowed.
               */
              modifier onlyAllowedOperatorApproval(address operator) virtual {
                  _checkFilterOperator(operator);
                  _;
              }
              /**
               * @dev A helper function to check if an operator is allowed.
               */
              function _checkFilterOperator(address operator) internal view virtual {
                  // Check registry code length to facilitate testing in environments without a deployed registry.
                  if (address(OPERATOR_FILTER_REGISTRY).code.length > 0) {
                      // under normal circumstances, this function will revert rather than return false, but inheriting contracts
                      // may specify their own OperatorFilterRegistry implementations, which may behave differently
                      if (!OPERATOR_FILTER_REGISTRY.isOperatorAllowed(address(this), operator)) {
                          revert OperatorNotAllowed(operator);
                      }
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.13;
          address constant CANONICAL_OPERATOR_FILTER_REGISTRY_ADDRESS = 0x000000000000AAeB6D7670E522A718067333cd4E;
          address constant CANONICAL_CORI_SUBSCRIPTION = 0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6;
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity ^0.8.16;
          import {AccessControlEnumerable} from "ethier/utils/AccessControlEnumerable.sol";
          import {IRedeemableToken} from "../interfaces/IRedeemableToken.sol";
          /**
           * @notice Base implementation of a voucher token with approvable redeemer contracts.
           */
          abstract contract BaseRedeemableToken is IRedeemableToken, AccessControlEnumerable {
              /**
               * @notice Authorised redeemers.
               */
              bytes32 public constant REDEEMER_ROLE = keccak256("REDEEMER_ROLE");
              /**
               * @notice A role that cannot be granted or revoked.
               * @dev Used to lock in members of the `REDEEMER_ROLE` role.
               */
              bytes32 private constant _NOOP_ROLE = keccak256("NOOP_ROLE");
              constructor() {
                  _setRoleAdmin(REDEEMER_ROLE, DEFAULT_STEERING_ROLE);
                  _setRoleAdmin(_NOOP_ROLE, _NOOP_ROLE);
              }
              // =========================================================================
              //                           Redemption
              // =========================================================================
              /**
               * @notice Redeems a voucher token with given tokenId.
               * @dev Can only be called by approved redeemer contracts.
               * @dev Reverts if `sender` is not the owner of or approved to transfer the token.
               */
              function redeem(address sender, uint256 tokenId) external onlyRole(REDEEMER_ROLE) {
                  if (!_isSenderAllowedToSpend(sender, tokenId)) {
                      revert IRedeemableToken.RedeemerCallerNotAllowedToSpendVoucher(sender, tokenId);
                  }
                  _doRedeem(sender, tokenId);
              }
              // =========================================================================
              //                           Internal hooks
              // =========================================================================
              /**
               * @notice Hook called by `redeem` to check if the sender is allowed to
               * spend a given token (e.g. if it is the owner or transfer approved).
               */
              function _isSenderAllowedToSpend(address sender, uint256 tokenId) internal view virtual returns (bool result);
              /**
               * @notice Hook called by `redeem` to preform the redemption of a voucher
               * token (e.g. burn).
               */
              function _doRedeem(address sender, uint256 tokenId) internal virtual;
              // =========================================================================
              //                           Steering
              // =========================================================================
              /**
               * @notice Locks the `REDEEMER_ROLE` role.
               */
              function lockRedeemers() external onlyRole(DEFAULT_STEERING_ROLE) {
                  _lockRedeemers();
              }
              /**
               * @notice Locks the `REDEEMER_ROLE` role.
               */
              function _lockRedeemers() internal {
                  _setRoleAdmin(REDEEMER_ROLE, _NOOP_ROLE);
              }
              /**
               * @notice Revokes approval for all redeemers.
               */
              function _revokeAllRedeemers() internal {
                  uint256 num = getRoleMemberCount(REDEEMER_ROLE);
                  for (uint256 i = 0; i < num; i++) {
                      // Akin to a popFront
                      address redeemer = getRoleMember(REDEEMER_ROLE, 0);
                      _revokeRole(REDEEMER_ROLE, redeemer);
                  }
              }
              /**
               * @notice Overrides supportsInterface as required by inheritance.
               */
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(AccessControlEnumerable)
                  returns (bool)
              {
                  return interfaceId == type(IRedeemableToken).interfaceId || super.supportsInterface(interfaceId);
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity >=0.8.16 <0.9.0;
          import {ERC721ACommon} from "ethier/erc721/ERC721ACommon.sol";
          /**
           * @notice Possible transfer restrictions.
           */
          enum TransferRestriction {
              None,
              OnlyMint,
              OnlyBurn,
              Frozen
          }
          /**
           * @notice Implements restrictions for ERC721 transfers.
           * @dev This is intended to facilitate a soft expiry for voucher tokens, having an intermediate stage that still allows
           * voucher to be redeemed but not traded before closing all activity indefinitely.
           * @dev The activation of restrictions is left to the extending contract.
           */
          abstract contract ERC721ATransferRestrictedBase is ERC721ACommon {
              // =========================================================================
              //                           Errors
              // =========================================================================
              /**
               * @notice Thrown if an action is disallowed by the current transfer
               * restriction.
               */
              error DisallowedByTransferRestriction(TransferRestriction);
              // =========================================================================
              //                           Transfer Restriction
              // =========================================================================
              /**
               * @notice Returns the current transfer restriction.
               * @dev Hook to be implemented by the consuming contract (e.g. manual
               * setter, time based, etc.)
               */
              function transferRestriction() public view virtual returns (TransferRestriction);
              // =========================================================================
              //                           Internals
              // =========================================================================
              /**
               * @notice Blocks transfers depending on the current restrictions.
               */
              function _beforeTokenTransfers(address from, address to, uint256 startTokenId, uint256 quantity)
                  internal
                  virtual
                  override
              {
                  super._beforeTokenTransfers(from, to, startTokenId, quantity);
                  TransferRestriction restriction = transferRestriction();
                  if (restriction == TransferRestriction.None) {
                      return;
                  }
                  if (restriction == TransferRestriction.OnlyMint && from == address(0)) {
                      return;
                  }
                  if (restriction == TransferRestriction.OnlyBurn && to == address(0)) {
                      return;
                  }
                  revert DisallowedByTransferRestriction(restriction);
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity >=0.8.0 <0.9.0;
          /**
           * @notice Basic interface for a contract providing sellable content.
           */
          interface ISellable {
              /**
               * @notice Handles the sale of sellable content.
               * @dev This is usually only callable by Sellers.
               */
              function handleSale(address to, uint64 num, bytes calldata data) external payable;
          }
          // 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 v4.4.1 (utils/Context.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Provides information about the current execution context, including the
           * sender of the transaction and its data. While these are generally available
           * via msg.sender and msg.data, they should not be accessed in such a direct
           * manner, since when dealing with meta-transactions the account sending and
           * paying for execution may not be the actual sender (as far as an application
           * is concerned).
           *
           * This contract is only required for intermediate, library-like contracts.
           */
          abstract contract Context {
              function _msgSender() internal view virtual returns (address) {
                  return msg.sender;
              }
              function _msgData() internal view virtual returns (bytes calldata) {
                  return msg.data;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)
          pragma solidity ^0.8.0;
          import "./math/Math.sol";
          /**
           * @dev String operations.
           */
          library Strings {
              bytes16 private constant _SYMBOLS = "0123456789abcdef";
              uint8 private constant _ADDRESS_LENGTH = 20;
              /**
               * @dev Converts a `uint256` to its ASCII `string` decimal representation.
               */
              function toString(uint256 value) internal pure returns (string memory) {
                  unchecked {
                      uint256 length = Math.log10(value) + 1;
                      string memory buffer = new string(length);
                      uint256 ptr;
                      /// @solidity memory-safe-assembly
                      assembly {
                          ptr := add(buffer, add(32, length))
                      }
                      while (true) {
                          ptr--;
                          /// @solidity memory-safe-assembly
                          assembly {
                              mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                          }
                          value /= 10;
                          if (value == 0) break;
                      }
                      return buffer;
                  }
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
               */
              function toHexString(uint256 value) internal pure returns (string memory) {
                  unchecked {
                      return toHexString(value, Math.log256(value) + 1);
                  }
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
               */
              function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                  bytes memory buffer = new bytes(2 * length + 2);
                  buffer[0] = "0";
                  buffer[1] = "x";
                  for (uint256 i = 2 * length + 1; i > 1; --i) {
                      buffer[i] = _SYMBOLS[value & 0xf];
                      value >>= 4;
                  }
                  require(value == 0, "Strings: hex length insufficient");
                  return string(buffer);
              }
              /**
               * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
               */
              function toHexString(address addr) internal pure returns (string memory) {
                  return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts 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.13;
          interface IOperatorFilterRegistry {
              /**
               * @notice Returns true if operator is not filtered for a given token, either by address or codeHash. Also returns
               *         true if supplied registrant address is not registered.
               */
              function isOperatorAllowed(address registrant, address operator) external view returns (bool);
              /**
               * @notice Registers an address with the registry. May be called by address itself or by EIP-173 owner.
               */
              function register(address registrant) external;
              /**
               * @notice Registers an address with the registry and "subscribes" to another address's filtered operators and codeHashes.
               */
              function registerAndSubscribe(address registrant, address subscription) external;
              /**
               * @notice Registers an address with the registry and copies the filtered operators and codeHashes from another
               *         address without subscribing.
               */
              function registerAndCopyEntries(address registrant, address registrantToCopy) external;
              /**
               * @notice Unregisters an address with the registry and removes its subscription. May be called by address itself or by EIP-173 owner.
               *         Note that this does not remove any filtered addresses or codeHashes.
               *         Also note that any subscriptions to this registrant will still be active and follow the existing filtered addresses and codehashes.
               */
              function unregister(address addr) external;
              /**
               * @notice Update an operator address for a registered address - when filtered is true, the operator is filtered.
               */
              function updateOperator(address registrant, address operator, bool filtered) external;
              /**
               * @notice Update multiple operators for a registered address - when filtered is true, the operators will be filtered. Reverts on duplicates.
               */
              function updateOperators(address registrant, address[] calldata operators, bool filtered) external;
              /**
               * @notice Update a codeHash for a registered address - when filtered is true, the codeHash is filtered.
               */
              function updateCodeHash(address registrant, bytes32 codehash, bool filtered) external;
              /**
               * @notice Update multiple codeHashes for a registered address - when filtered is true, the codeHashes will be filtered. Reverts on duplicates.
               */
              function updateCodeHashes(address registrant, bytes32[] calldata codeHashes, bool filtered) external;
              /**
               * @notice Subscribe an address to another registrant's filtered operators and codeHashes. Will remove previous
               *         subscription if present.
               *         Note that accounts with subscriptions may go on to subscribe to other accounts - in this case,
               *         subscriptions will not be forwarded. Instead the former subscription's existing entries will still be
               *         used.
               */
              function subscribe(address registrant, address registrantToSubscribe) external;
              /**
               * @notice Unsubscribe an address from its current subscribed registrant, and optionally copy its filtered operators and codeHashes.
               */
              function unsubscribe(address registrant, bool copyExistingEntries) external;
              /**
               * @notice Get the subscription address of a given registrant, if any.
               */
              function subscriptionOf(address addr) external returns (address registrant);
              /**
               * @notice Get the set of addresses subscribed to a given registrant.
               *         Note that order is not guaranteed as updates are made.
               */
              function subscribers(address registrant) external returns (address[] memory);
              /**
               * @notice Get the subscriber at a given index in the set of addresses subscribed to a given registrant.
               *         Note that order is not guaranteed as updates are made.
               */
              function subscriberAt(address registrant, uint256 index) external returns (address);
              /**
               * @notice Copy filtered operators and codeHashes from a different registrantToCopy to addr.
               */
              function copyEntriesOf(address registrant, address registrantToCopy) external;
              /**
               * @notice Returns true if operator is filtered by a given address or its subscription.
               */
              function isOperatorFiltered(address registrant, address operator) external returns (bool);
              /**
               * @notice Returns true if the hash of an address's code is filtered by a given address or its subscription.
               */
              function isCodeHashOfFiltered(address registrant, address operatorWithCode) external returns (bool);
              /**
               * @notice Returns true if a codeHash is filtered by a given address or its subscription.
               */
              function isCodeHashFiltered(address registrant, bytes32 codeHash) external returns (bool);
              /**
               * @notice Returns a list of filtered operators for a given address or its subscription.
               */
              function filteredOperators(address addr) external returns (address[] memory);
              /**
               * @notice Returns the set of filtered codeHashes for a given address or its subscription.
               *         Note that order is not guaranteed as updates are made.
               */
              function filteredCodeHashes(address addr) external returns (bytes32[] memory);
              /**
               * @notice Returns the filtered operator at the given index of the set of filtered operators for a given address or
               *         its subscription.
               *         Note that order is not guaranteed as updates are made.
               */
              function filteredOperatorAt(address registrant, uint256 index) external returns (address);
              /**
               * @notice Returns the filtered codeHash at the given index of the list of filtered codeHashes for a given address or
               *         its subscription.
               *         Note that order is not guaranteed as updates are made.
               */
              function filteredCodeHashAt(address registrant, uint256 index) external returns (bytes32);
              /**
               * @notice Returns true if an address has registered
               */
              function isRegistered(address addr) external returns (bool);
              /**
               * @dev Convenience method to compute the code hash of an arbitrary contract
               */
              function codeHashOf(address addr) external returns (bytes32);
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity ^0.8.0;
          /**
           * @notice Interface for a redeemable Voucher token preventing double spending
           * through internal book-keeping (e.g. burning the token, token property, etc.).
           * @dev Voucher tokens are intendent to be redeemed through a redeemer contract.
           */
          interface IRedeemableToken {
              /**
               * @notice Thrown if the redemption caller is not allowed to spend a given
               * voucher.
               */
              error RedeemerCallerNotAllowedToSpendVoucher(address sender, uint256 tokenId);
              /**
               * @notice Interface through which a `IRedeemer` contract informs the
               * voucher about its redemption.
               * @param sender The address that initiate the redemption on the
               * redeemer contract.
               * @param tokenId The voucher token to be redeemed.
               * @dev This function MUST be called by redeemer contracts.
               * @dev MUST revert with `RedeemerNotApproved` if the calling redeemer
               * contract is not approved to spend this voucher.
               * @dev MUST revert with `RedeemerCallerNotAllowedToSpendVoucher` if
               * sender is not allowed to spend tokenId.
               */
              function redeem(address sender, uint256 tokenId) external;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Standard math utilities missing in the Solidity language.
           */
          library Math {
              enum Rounding {
                  Down, // Toward negative infinity
                  Up, // Toward infinity
                  Zero // Toward zero
              }
              /**
               * @dev Returns the largest of two numbers.
               */
              function max(uint256 a, uint256 b) internal pure returns (uint256) {
                  return a > b ? a : b;
              }
              /**
               * @dev Returns the smallest of two numbers.
               */
              function min(uint256 a, uint256 b) internal pure returns (uint256) {
                  return a < b ? a : b;
              }
              /**
               * @dev Returns the average of two numbers. The result is rounded towards
               * zero.
               */
              function average(uint256 a, uint256 b) internal pure returns (uint256) {
                  // (a + b) / 2 can overflow.
                  return (a & b) + (a ^ b) / 2;
              }
              /**
               * @dev Returns the ceiling of the division of two numbers.
               *
               * This differs from standard division with `/` in that it rounds up instead
               * of rounding down.
               */
              function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
                  // (a + b - 1) / b can overflow on addition, so we distribute.
                  return a == 0 ? 0 : (a - 1) / b + 1;
              }
              /**
               * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
               * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
               * with further edits by Uniswap Labs also under MIT license.
               */
              function mulDiv(
                  uint256 x,
                  uint256 y,
                  uint256 denominator
              ) internal pure returns (uint256 result) {
                  unchecked {
                      // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                      // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                      // variables such that product = prod1 * 2^256 + prod0.
                      uint256 prod0; // Least significant 256 bits of the product
                      uint256 prod1; // Most significant 256 bits of the product
                      assembly {
                          let mm := mulmod(x, y, not(0))
                          prod0 := mul(x, y)
                          prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                      }
                      // Handle non-overflow cases, 256 by 256 division.
                      if (prod1 == 0) {
                          return prod0 / denominator;
                      }
                      // Make sure the result is less than 2^256. Also prevents denominator == 0.
                      require(denominator > prod1);
                      ///////////////////////////////////////////////
                      // 512 by 256 division.
                      ///////////////////////////////////////////////
                      // Make division exact by subtracting the remainder from [prod1 prod0].
                      uint256 remainder;
                      assembly {
                          // Compute remainder using mulmod.
                          remainder := mulmod(x, y, denominator)
                          // Subtract 256 bit number from 512 bit number.
                          prod1 := sub(prod1, gt(remainder, prod0))
                          prod0 := sub(prod0, remainder)
                      }
                      // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
                      // See https://cs.stackexchange.com/q/138556/92363.
                      // Does not overflow because the denominator cannot be zero at this stage in the function.
                      uint256 twos = denominator & (~denominator + 1);
                      assembly {
                          // Divide denominator by twos.
                          denominator := div(denominator, twos)
                          // Divide [prod1 prod0] by twos.
                          prod0 := div(prod0, twos)
                          // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                          twos := add(div(sub(0, twos), twos), 1)
                      }
                      // Shift in bits from prod1 into prod0.
                      prod0 |= prod1 * twos;
                      // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                      // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                      // four bits. That is, denominator * inv = 1 mod 2^4.
                      uint256 inverse = (3 * denominator) ^ 2;
                      // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
                      // in modular arithmetic, doubling the correct bits in each step.
                      inverse *= 2 - denominator * inverse; // inverse mod 2^8
                      inverse *= 2 - denominator * inverse; // inverse mod 2^16
                      inverse *= 2 - denominator * inverse; // inverse mod 2^32
                      inverse *= 2 - denominator * inverse; // inverse mod 2^64
                      inverse *= 2 - denominator * inverse; // inverse mod 2^128
                      inverse *= 2 - denominator * inverse; // inverse mod 2^256
                      // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                      // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                      // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                      // is no longer required.
                      result = prod0 * inverse;
                      return result;
                  }
              }
              /**
               * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
               */
              function mulDiv(
                  uint256 x,
                  uint256 y,
                  uint256 denominator,
                  Rounding rounding
              ) internal pure returns (uint256) {
                  uint256 result = mulDiv(x, y, denominator);
                  if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
                      result += 1;
                  }
                  return result;
              }
              /**
               * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
               *
               * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
               */
              function sqrt(uint256 a) internal pure returns (uint256) {
                  if (a == 0) {
                      return 0;
                  }
                  // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
                  //
                  // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
                  // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
                  //
                  // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
                  // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
                  // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
                  //
                  // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
                  uint256 result = 1 << (log2(a) >> 1);
                  // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
                  // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
                  // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
                  // into the expected uint128 result.
                  unchecked {
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      return min(result, a / result);
                  }
              }
              /**
               * @notice Calculates sqrt(a), following the selected rounding direction.
               */
              function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = sqrt(a);
                      return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
                  }
              }
              /**
               * @dev Return the log in base 2, rounded down, of a positive value.
               * Returns 0 if given 0.
               */
              function log2(uint256 value) internal pure returns (uint256) {
                  uint256 result = 0;
                  unchecked {
                      if (value >> 128 > 0) {
                          value >>= 128;
                          result += 128;
                      }
                      if (value >> 64 > 0) {
                          value >>= 64;
                          result += 64;
                      }
                      if (value >> 32 > 0) {
                          value >>= 32;
                          result += 32;
                      }
                      if (value >> 16 > 0) {
                          value >>= 16;
                          result += 16;
                      }
                      if (value >> 8 > 0) {
                          value >>= 8;
                          result += 8;
                      }
                      if (value >> 4 > 0) {
                          value >>= 4;
                          result += 4;
                      }
                      if (value >> 2 > 0) {
                          value >>= 2;
                          result += 2;
                      }
                      if (value >> 1 > 0) {
                          result += 1;
                      }
                  }
                  return result;
              }
              /**
               * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
               * Returns 0 if given 0.
               */
              function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = log2(value);
                      return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
                  }
              }
              /**
               * @dev Return the log in base 10, rounded down, of a positive value.
               * Returns 0 if given 0.
               */
              function log10(uint256 value) internal pure returns (uint256) {
                  uint256 result = 0;
                  unchecked {
                      if (value >= 10**64) {
                          value /= 10**64;
                          result += 64;
                      }
                      if (value >= 10**32) {
                          value /= 10**32;
                          result += 32;
                      }
                      if (value >= 10**16) {
                          value /= 10**16;
                          result += 16;
                      }
                      if (value >= 10**8) {
                          value /= 10**8;
                          result += 8;
                      }
                      if (value >= 10**4) {
                          value /= 10**4;
                          result += 4;
                      }
                      if (value >= 10**2) {
                          value /= 10**2;
                          result += 2;
                      }
                      if (value >= 10**1) {
                          result += 1;
                      }
                  }
                  return result;
              }
              /**
               * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
               * Returns 0 if given 0.
               */
              function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = log10(value);
                      return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
                  }
              }
              /**
               * @dev Return the log in base 256, rounded down, of a positive value.
               * Returns 0 if given 0.
               *
               * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
               */
              function log256(uint256 value) internal pure returns (uint256) {
                  uint256 result = 0;
                  unchecked {
                      if (value >> 128 > 0) {
                          value >>= 128;
                          result += 16;
                      }
                      if (value >> 64 > 0) {
                          value >>= 64;
                          result += 8;
                      }
                      if (value >> 32 > 0) {
                          value >>= 32;
                          result += 4;
                      }
                      if (value >> 16 > 0) {
                          value >>= 16;
                          result += 2;
                      }
                      if (value >> 8 > 0) {
                          result += 1;
                      }
                  }
                  return result;
              }
              /**
               * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
               * Returns 0 if given 0.
               */
              function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = log256(value);
                      return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
                  }
              }
          }
          

          File 3 of 4: GenArt721CoreV3_Engine_Flex_PROOF
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
          pragma solidity ^0.8.0;
          import "../utils/Context.sol";
          /**
           * @dev Contract module which provides a basic access control mechanism, where
           * there is an account (an owner) that can be granted exclusive access to
           * specific functions.
           *
           * By default, the owner account will be the one that deploys the contract. This
           * can later be changed with {transferOwnership}.
           *
           * This module is used through inheritance. It will make available the modifier
           * `onlyOwner`, which can be applied to your functions to restrict their use to
           * the owner.
           */
          abstract contract Ownable is Context {
              address private _owner;
              event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
              /**
               * @dev Initializes the contract setting the deployer as the initial owner.
               */
              constructor() {
                  _transferOwnership(_msgSender());
              }
              /**
               * @dev Throws if called by any account other than the owner.
               */
              modifier onlyOwner() {
                  _checkOwner();
                  _;
              }
              /**
               * @dev Returns the address of the current owner.
               */
              function owner() public view virtual returns (address) {
                  return _owner;
              }
              /**
               * @dev Throws if the sender is not the owner.
               */
              function _checkOwner() internal view virtual {
                  require(owner() == _msgSender(), "Ownable: caller is not the owner");
              }
              /**
               * @dev Leaves the contract without owner. It will not be possible to call
               * `onlyOwner` functions anymore. Can only be called by the current owner.
               *
               * NOTE: Renouncing ownership will leave the contract without an owner,
               * thereby removing any functionality that is only available to the owner.
               */
              function renounceOwnership() public virtual onlyOwner {
                  _transferOwnership(address(0));
              }
              /**
               * @dev Transfers ownership of the contract to a new account (`newOwner`).
               * Can only be called by the current owner.
               */
              function transferOwnership(address newOwner) public virtual onlyOwner {
                  require(newOwner != address(0), "Ownable: new owner is the zero address");
                  _transferOwnership(newOwner);
              }
              /**
               * @dev Transfers ownership of the contract to a new account (`newOwner`).
               * Internal function without access restriction.
               */
              function _transferOwnership(address newOwner) internal virtual {
                  address oldOwner = _owner;
                  _owner = newOwner;
                  emit OwnershipTransferred(oldOwner, newOwner);
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)
          pragma solidity ^0.8.0;
          import "../IERC721.sol";
          /**
           * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
           * @dev See https://eips.ethereum.org/EIPS/eip-721
           */
          interface IERC721Metadata is IERC721 {
              /**
               * @dev Returns the token collection name.
               */
              function name() external view returns (string memory);
              /**
               * @dev Returns the token collection symbol.
               */
              function symbol() external view returns (string memory);
              /**
               * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
               */
              function tokenURI(uint256 tokenId) external view returns (string memory);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/IERC721.sol)
          pragma solidity ^0.8.0;
          import "../../utils/introspection/IERC165.sol";
          /**
           * @dev Required interface of an ERC721 compliant contract.
           */
          interface IERC721 is IERC165 {
              /**
               * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
               */
              event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
              /**
               * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
               */
              event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
              /**
               * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
               */
              event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
              /**
               * @dev Returns the number of tokens in ``owner``'s account.
               */
              function balanceOf(address owner) external view returns (uint256 balance);
              /**
               * @dev Returns the owner of the `tokenId` token.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               */
              function ownerOf(uint256 tokenId) external view returns (address owner);
              /**
               * @dev Safely transfers `tokenId` token from `from` to `to`.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must exist and be owned by `from`.
               * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
               * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes calldata data
              ) external;
              /**
               * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
               * are aware of the ERC721 protocol to prevent tokens from being forever locked.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must exist and be owned by `from`.
               * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
               * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) external;
              /**
               * @dev Transfers `tokenId` token from `from` to `to`.
               *
               * WARNING: 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);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)
          pragma solidity ^0.8.0;
          /**
           * @title ERC721 token receiver interface
           * @dev Interface for any contract that wants to support safeTransfers
           * from ERC721 asset contracts.
           */
          interface IERC721Receiver {
              /**
               * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
               * by `operator` from `from`, this function is called.
               *
               * It must return its Solidity selector to confirm the token transfer.
               * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
               *
               * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
               */
              function onERC721Received(
                  address operator,
                  address from,
                  uint256 tokenId,
                  bytes calldata data
              ) external returns (bytes4);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol)
          pragma solidity ^0.8.1;
          /**
           * @dev Collection of functions related to the address type
           */
          library Address {
              /**
               * @dev Returns true if `account` is a contract.
               *
               * [IMPORTANT]
               * ====
               * It is unsafe to assume that an address for which this function returns
               * false is an externally-owned account (EOA) and not a contract.
               *
               * Among others, `isContract` will return false for the following
               * types of addresses:
               *
               *  - an externally-owned account
               *  - a contract in construction
               *  - an address where a contract will be created
               *  - an address where a contract lived, but was destroyed
               * ====
               *
               * [IMPORTANT]
               * ====
               * You shouldn't rely on `isContract` to protect against flash loan attacks!
               *
               * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
               * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
               * constructor.
               * ====
               */
              function isContract(address account) internal view returns (bool) {
                  // This method relies on extcodesize/address.code.length, which returns 0
                  // for contracts in construction, since the code is only stored at the end
                  // of the constructor execution.
                  return account.code.length > 0;
              }
              /**
               * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
               * `recipient`, forwarding all available gas and reverting on errors.
               *
               * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
               * of certain opcodes, possibly making contracts go over the 2300 gas limit
               * imposed by `transfer`, making them unable to receive funds via
               * `transfer`. {sendValue} removes this limitation.
               *
               * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
               *
               * IMPORTANT: because control is transferred to `recipient`, care must be
               * taken to not create reentrancy vulnerabilities. Consider using
               * {ReentrancyGuard} or the
               * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
               */
              function sendValue(address payable recipient, uint256 amount) internal {
                  require(address(this).balance >= amount, "Address: insufficient balance");
                  (bool success, ) = recipient.call{value: amount}("");
                  require(success, "Address: unable to send value, recipient may have reverted");
              }
              /**
               * @dev Performs a Solidity function call using a low level `call`. A
               * plain `call` is an unsafe replacement for a function call: use this
               * function instead.
               *
               * If `target` reverts with a revert reason, it is bubbled up by this
               * function (like regular Solidity function calls).
               *
               * Returns the raw returned data. To convert to the expected return value,
               * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
               *
               * Requirements:
               *
               * - `target` must be a contract.
               * - calling `target` with `data` must not revert.
               *
               * _Available since v3.1._
               */
              function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionCall(target, data, "Address: low-level call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
               * `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, 0, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but also transferring `value` wei to `target`.
               *
               * Requirements:
               *
               * - the calling contract must have an ETH balance of at least `value`.
               * - the called Solidity function must be `payable`.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
              }
              /**
               * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
               * with `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  require(address(this).balance >= value, "Address: insufficient balance for call");
                  require(isContract(target), "Address: call to non-contract");
                  (bool success, bytes memory returndata) = target.call{value: value}(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                  return functionStaticCall(target, data, "Address: low-level static call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal view returns (bytes memory) {
                  require(isContract(target), "Address: static call to non-contract");
                  (bool success, bytes memory returndata) = target.staticcall(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a delegate call.
               *
               * _Available since v3.4._
               */
              function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionDelegateCall(target, data, "Address: low-level delegate call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a delegate call.
               *
               * _Available since v3.4._
               */
              function functionDelegateCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  require(isContract(target), "Address: delegate call to non-contract");
                  (bool success, bytes memory returndata) = target.delegatecall(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
               * revert reason 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 {
                      // Look for revert reason and bubble it up if present
                      if (returndata.length > 0) {
                          // The easiest way to bubble the revert reason is using memory via assembly
                          /// @solidity memory-safe-assembly
                          assembly {
                              let returndata_size := mload(returndata)
                              revert(add(32, returndata), returndata_size)
                          }
                      } else {
                          revert(errorMessage);
                      }
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Provides information about the current execution context, including the
           * sender of the transaction and its data. While these are generally available
           * via msg.sender and msg.data, they should not be accessed in such a direct
           * manner, since when dealing with meta-transactions the account sending and
           * paying for execution may not be the actual sender (as far as an application
           * is concerned).
           *
           * This contract is only required for intermediate, library-like contracts.
           */
          abstract contract Context {
              function _msgSender() internal view virtual returns (address) {
                  return msg.sender;
              }
              function _msgData() internal view virtual returns (bytes calldata) {
                  return msg.data;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
          pragma solidity ^0.8.0;
          import "./IERC165.sol";
          /**
           * @dev Implementation of the {IERC165} interface.
           *
           * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
           * for the additional interface id that will be supported. For example:
           *
           * ```solidity
           * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
           *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
           * }
           * ```
           *
           * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
           */
          abstract contract ERC165 is IERC165 {
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                  return interfaceId == type(IERC165).interfaceId;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Interface of the ERC165 standard, as defined in the
           * https://eips.ethereum.org/EIPS/eip-165[EIP].
           *
           * Implementers can declare support of contract interfaces, which can then be
           * queried by others ({ERC165Checker}).
           *
           * For an implementation, see {ERC165}.
           */
          interface IERC165 {
              /**
               * @dev Returns true if this contract implements the interface defined by
               * `interfaceId`. See the corresponding
               * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
               * to learn more about how these ids are created.
               *
               * This function call must use less than 30 000 gas.
               */
              function supportsInterface(bytes4 interfaceId) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev String operations.
           */
          library Strings {
              bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
              uint8 private constant _ADDRESS_LENGTH = 20;
              /**
               * @dev Converts a `uint256` to its ASCII `string` decimal representation.
               */
              function toString(uint256 value) internal pure returns (string memory) {
                  // Inspired by OraclizeAPI's implementation - MIT licence
                  // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
                  if (value == 0) {
                      return "0";
                  }
                  uint256 temp = value;
                  uint256 digits;
                  while (temp != 0) {
                      digits++;
                      temp /= 10;
                  }
                  bytes memory buffer = new bytes(digits);
                  while (value != 0) {
                      digits -= 1;
                      buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
                      value /= 10;
                  }
                  return string(buffer);
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
               */
              function toHexString(uint256 value) internal pure returns (string memory) {
                  if (value == 0) {
                      return "0x00";
                  }
                  uint256 temp = value;
                  uint256 length = 0;
                  while (temp != 0) {
                      length++;
                      temp >>= 8;
                  }
                  return toHexString(value, length);
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
               */
              function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                  bytes memory buffer = new bytes(2 * length + 2);
                  buffer[0] = "0";
                  buffer[1] = "x";
                  for (uint256 i = 2 * length + 1; i > 1; --i) {
                      buffer[i] = _HEX_SYMBOLS[value & 0xf];
                      value >>= 4;
                  }
                  require(value == 0, "Strings: hex length insufficient");
                  return string(buffer);
              }
              /**
               * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
               */
              function toHexString(address addr) internal pure returns (string memory) {
                  return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
              }
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          pragma solidity 0.8.17;
          // Created By: Art Blocks Inc.
          import "../../../interfaces/0.8.x/IRandomizerV2.sol";
          import "../../../interfaces/0.8.x/IAdminACLV0.sol";
          import "../../../interfaces/0.8.x/IEngineRegistryV0.sol";
          import "../../../interfaces/0.8.x/IGenArt721CoreContractV3_Engine_Flex.sol";
          import "../../../interfaces/0.8.x/IDependencyRegistryCompatibleV0.sol";
          import "../../../interfaces/0.8.x/IManifold.sol";
          import "@openzeppelin-4.7/contracts/access/Ownable.sol";
          import "../../../libs/0.8.x/ERC721_PackedHashSeed.sol";
          import "../../../libs/0.8.x/BytecodeStorage.sol";
          import "../../../libs/0.8.x/Bytes32Strings.sol";
          /**
           * @title Art Blocks Engine Flex ERC-721 core contract, V3.
           * @author Art Blocks Inc.
           * @notice Privileged Roles and Ownership:
           * This contract is designed to be managed, with progressively limited powers
           * as a project progresses from active to locked.
           * Privileged roles and abilities are controlled by the admin ACL contract and
           * artists. Both of these roles hold extensive power and can arbitrarily
           * control and modify portions of projects, dependent upon project state. After
           * a project is locked, important project metadata fields are locked including
           * the project name, artist name, and script and display details. Edition size
           * can never be increased.
           * Care must be taken to ensure that the admin ACL contract and artist
           * addresses are secure behind a multi-sig or other access control mechanism.
           * ----------------------------------------------------------------------------
           * The following functions are restricted to the Admin ACL contract:
           * - updateArtblocksDependencyRegistryAddress
           * - updateProviderSalesAddresses
           * - updateProviderPrimarySalesPercentages (up to 100%)
           * - updateProviderSecondarySalesBPS (up to 100%)
           * - updateMinterContract
           * - updateRandomizerAddress
           * - toggleProjectIsActive
           * - addProject
           * - forbidNewProjects (forever forbidding new projects)
           * - updateDefaultBaseURI (used to initialize new project base URIs)
           * - updateIPFSGateway
           * - updateArweaveGateway
           * ----------------------------------------------------------------------------
           * The following functions are restricted to either the the Artist address or
           * the Admin ACL contract, only when the project is not locked:
           * - updateProjectName
           * - updateProjectArtistName
           * - updateProjectLicense
           * - Change project script via addProjectScript, updateProjectScript,
           *   and removeProjectLastScript
           * - updateProjectScriptType
           * - updateProjectAspectRatio
           * ----------------------------------------------------------------------------
           * The following functions are restricted to only the Artist or Admin ACL
           * contract of a valid project ID:
           * - proposeArtistPaymentAddressesAndSplits (Note that this has to be accepted
           *   by adminAcceptArtistAddressesAndSplits to take effect, which is restricted
           *   to the Admin ACL contract, or the artist if the core contract owner has
           *   renounced ownership. Also note that a proposal will be automatically
           *   accepted if the artist only proposes changed payee percentages without
           *   modifying any payee addresses, or is only removing payee addresses, or
           *   if the global config `autoApproveArtistSplitProposals` is set to `true`.)
           * - toggleProjectIsPaused (note the artist can still mint while paused)
           * - updateProjectSecondaryMarketRoyaltyPercentage (up to
           *   ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE percent)
           * - updateProjectWebsite
           * - updateProjectMaxInvocations (to a number greater than or equal to the
           *   current number of invocations, and less than current project maximum
           *   invocations)
           * - updateProjectBaseURI (controlling the base URI for tokens in the project)
           * ----------------------------------------------------------------------------
           * The following function is restricted to either the Admin ACL contract, or
           * the Artist address if the core contract owner has renounced ownership:
           * - adminAcceptArtistAddressesAndSplits
           * - updateProjectArtistAddress (owner ultimately controlling the project and
           *   its and-on revenue, unless owner has renounced ownership)
           * ----------------------------------------------------------------------------
           * The following function is restricted to the artist when a project is
           * unlocked, and only callable by Admin ACL contract when a project is locked:
           * - updateProjectDescription
           * ----------------------------------------------------------------------------
           * The following functions for managing external asset dependencies are restricted
           * to projects with external asset dependencies that are unlocked:
           * - lockProjectExternalAssetDependencies
           * - updateProjectExternalAssetDependency
           * - removeProjectExternalAssetDependency
           * - addProjectExternalAssetDependency
           * ----------------------------------------------------------------------------
           * The following function is restricted to owner calling directly:
           * - transferOwnership
           * - renounceOwnership
           * ----------------------------------------------------------------------------
           * The following configuration variables are set at time of contract deployment,
           * and not modifiable thereafter (immutable after the point of deployment):
           * - (bool) autoApproveArtistSplitProposals
           * ----------------------------------------------------------------------------
           * Additional admin and artist privileged roles may be described on minters,
           * registries, and other contracts that may interact with this core contract.
           */
          contract GenArt721CoreV3_Engine_Flex_PROOF is
              ERC721_PackedHashSeed,
              Ownable,
              IDependencyRegistryCompatibleV0,
              IManifold,
              IGenArt721CoreContractV3_Engine_Flex
          {
              using BytecodeStorage for string;
              using BytecodeStorage for address;
              using Bytes32Strings for bytes32;
              uint256 constant ONE_HUNDRED = 100;
              uint256 constant ONE_MILLION = 1_000_000;
              uint24 constant ONE_MILLION_UINT24 = 1_000_000;
              uint256 constant FOUR_WEEKS_IN_SECONDS = 2_419_200;
              uint8 constant AT_CHARACTER_CODE = uint8(bytes1("@")); // 0x40
              // numeric constants
              uint256 constant MAX_PROVIDER_SECONDARY_SALES_BPS = 10000; // 10_000 BPS = 100%
              uint256 constant ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE = 95; // 95%
              // This contract emits generic events that contain fields that indicate
              // which parameter has been updated. This is sufficient for application
              // state management, while also simplifying the contract and indexing code.
              // This was done as an alternative to having custom events that emit what
              // field-values have changed for each event, given that changed values can
              // be introspected by indexers due to the design of this smart contract
              // exposing these state changes via publicly viewable fields.
              //
              // The following fields are used to indicate which contract-level parameter
              // has been updated in the `PlatformUpdated` event:
              bytes32 constant FIELD_NEXT_PROJECT_ID = "nextProjectId";
              bytes32 constant FIELD_NEW_PROJECTS_FORBIDDEN = "newProjectsForbidden";
              bytes32 constant FIELD_DEFAULT_BASE_URI = "defaultBaseURI";
              bytes32 constant FIELD_RANDOMIZER_ADDRESS = "randomizerAddress";
              bytes32 constant FIELD_ARTBLOCKS_DEPENDENCY_REGISTRY_ADDRESS =
                  "dependencyRegistryAddress";
              bytes32 constant FIELD_PROVIDER_SALES_ADDRESSES = "providerSalesAddresses";
              bytes32 constant FIELD_PROVIDER_PRIMARY_SALES_PERCENTAGES =
                  "providerPrimaryPercentages";
              bytes32 constant FIELD_PROVIDER_SECONDARY_SALES_BPS =
                  "providerSecondaryBPS";
              // The following fields are used to indicate which project-level parameter
              // has been updated in the `ProjectUpdated` event:
              bytes32 constant FIELD_PROJECT_COMPLETED = "completed";
              bytes32 constant FIELD_PROJECT_ACTIVE = "active";
              bytes32 constant FIELD_PROJECT_ARTIST_ADDRESS = "artistAddress";
              bytes32 constant FIELD_PROJECT_PAUSED = "paused";
              bytes32 constant FIELD_PROJECT_CREATED = "created";
              bytes32 constant FIELD_PROJECT_NAME = "name";
              bytes32 constant FIELD_PROJECT_ARTIST_NAME = "artistName";
              bytes32 constant FIELD_PROJECT_SECONDARY_MARKET_ROYALTY_PERCENTAGE =
                  "royaltyPercentage";
              bytes32 constant FIELD_PROJECT_DESCRIPTION = "description";
              bytes32 constant FIELD_PROJECT_WEBSITE = "website";
              bytes32 constant FIELD_PROJECT_LICENSE = "license";
              bytes32 constant FIELD_PROJECT_MAX_INVOCATIONS = "maxInvocations";
              bytes32 constant FIELD_PROJECT_SCRIPT = "script";
              bytes32 constant FIELD_PROJECT_SCRIPT_TYPE = "scriptType";
              bytes32 constant FIELD_PROJECT_ASPECT_RATIO = "aspectRatio";
              bytes32 constant FIELD_PROJECT_BASE_URI = "baseURI";
              /// Dependency registry managed by Art Blocks
              address public artblocksDependencyRegistryAddress;
              /// current randomizer contract
              IRandomizerV2 public randomizerContract;
              /// append-only array of all randomizer contract addresses ever used by
              /// this contract
              address[] private _historicalRandomizerAddresses;
              /// admin ACL contract
              IAdminACLV0 public adminACLContract;
              struct Project {
                  uint24 invocations;
                  uint24 maxInvocations;
                  uint24 scriptCount;
                  // max uint64 ~= 1.8e19 sec ~= 570 billion years
                  uint64 completedTimestamp;
                  bool active;
                  bool paused;
                  string name;
                  string artist;
                  string description;
                  string website;
                  string license;
                  string projectBaseURI;
                  bytes32 scriptTypeAndVersion;
                  string aspectRatio;
                  // mapping from script index to address storing script in bytecode
                  mapping(uint256 => address) scriptBytecodeAddresses;
                  bool externalAssetDependenciesLocked;
                  uint24 externalAssetDependencyCount;
                  mapping(uint256 => ExternalAssetDependency) externalAssetDependencies;
              }
              mapping(uint256 => Project) projects;
              string public preferredIPFSGateway;
              string public preferredArweaveGateway;
              /// packed struct containing project financial information
              struct ProjectFinance {
                  address payable additionalPayeePrimarySales;
                  // packed uint: max of 95, max uint8 = 255
                  uint8 secondaryMarketRoyaltyPercentage;
                  address payable additionalPayeeSecondarySales;
                  // packed uint: max of 100, max uint8 = 255
                  uint8 additionalPayeeSecondarySalesPercentage;
                  address payable artistAddress;
                  // packed uint: max of 100, max uint8 = 255
                  uint8 additionalPayeePrimarySalesPercentage;
              }
              // Project financials mapping
              mapping(uint256 => ProjectFinance) projectIdToFinancials;
              /// hash of artist's proposed payment updates to be approved by admin
              mapping(uint256 => bytes32) public proposedArtistAddressesAndSplitsHash;
              /// The render provider payment address for all primary sales revenues
              /// (packed)
              address payable public renderProviderPrimarySalesAddress;
              /// Percentage of primary sales revenue allocated to the render provider
              /// (packed)
              // packed uint: max of 100, max uint8 = 255
              uint8 private _renderProviderPrimarySalesPercentage = 10;
              /// The platform provider payment address for all primary sales revenues
              /// (packed)
              address payable public platformProviderPrimarySalesAddress;
              /// Percentage of primary sales revenue allocated to the platform provider
              /// (packed)
              // packed uint: max of 100, max uint8 = 255
              uint8 private _platformProviderPrimarySalesPercentage = 10;
              /// The render provider payment address for all secondary sales royalty
              /// revenues
              address payable public renderProviderSecondarySalesAddress;
              /// Basis Points of secondary sales royalties allocated to the
              /// render provider
              uint256 public renderProviderSecondarySalesBPS = 250;
              /// The platform provider payment address for all secondary sales royalty
              /// revenues
              address payable public platformProviderSecondarySalesAddress;
              /// Basis Points of secondary sales royalties allocated to the
              /// platform provider
              uint256 public platformProviderSecondarySalesBPS = 250;
              /// single minter allowed for this core contract
              address public minterContract;
              /// starting (initial) project ID on this contract
              uint256 public immutable startingProjectId;
              /// next project ID to be created
              uint248 private _nextProjectId;
              /// bool indicating if adding new projects is forbidden;
              /// default behavior is to allow new projects
              bool public newProjectsForbidden;
              /// configuration variable (determined at time of deployment)
              /// that determines whether or not admin approval^ should be required
              /// to accept artist address change proposals, or if these proposals
              /// should always auto-approve, as determined by the business process
              /// requirements of the Engine partner using this contract.
              ///
              /// ^does not apply in the case where contract-ownership itself is revoked
              bool public immutable autoApproveArtistSplitProposals;
              /// version & type of this core contract
              bytes32 constant CORE_VERSION = "v3.1.2";
              function coreVersion() external pure returns (string memory) {
                  return CORE_VERSION.toString();
              }
              bytes32 constant CORE_TYPE = "GenArt721CoreV3_Engine_Flex";
              function coreType() external pure returns (string memory) {
                  return CORE_TYPE.toString();
              }
              /// default base URI to initialize all new project projectBaseURI values to
              string public defaultBaseURI;
              function _onlyUnlockedProjectExternalAssetDependencies(
                  uint256 _projectId
              ) internal view {
                  require(
                      !projects[_projectId].externalAssetDependenciesLocked,
                      "External dependencies locked"
                  );
              }
              function _onlyNonZeroAddress(address _address) internal pure {
                  require(_address != address(0), "Must input non-zero address");
              }
              function _onlyNonEmptyString(string memory _string) internal pure {
                  require(bytes(_string).length != 0, "Must input non-empty string");
              }
              function _onlyValidTokenId(uint256 _tokenId) internal view {
                  require(_exists(_tokenId), "Token ID does not exist");
              }
              function _onlyValidProjectId(uint256 _projectId) internal view {
                  require(
                      (_projectId >= startingProjectId) && (_projectId < _nextProjectId),
                      "Project ID does not exist"
                  );
              }
              function _onlyUnlocked(uint256 _projectId) internal view {
                  // Note: calling `_projectUnlocked` enforces that the `_projectId`
                  //       passed in is valid.`
                  require(_projectUnlocked(_projectId), "Only if unlocked");
              }
              function _onlyAdminACL(bytes4 _selector) internal {
                  require(
                      adminACLAllowed(msg.sender, address(this), _selector),
                      "Only Admin ACL allowed"
                  );
              }
              function _onlyArtistOrAdminACL(
                  uint256 _projectId,
                  bytes4 _selector
              ) internal {
                  require(
                      msg.sender == projectIdToFinancials[_projectId].artistAddress ||
                          adminACLAllowed(msg.sender, address(this), _selector),
                      "Only artist or Admin ACL allowed"
                  );
              }
              /**
               * This modifier allows the artist of a project to call a function if the
               * owner of the contract has renounced ownership. This is to allow the
               * contract to continue to function if the owner decides to renounce
               * ownership.
               */
              function _onlyAdminACLOrRenouncedArtist(
                  uint256 _projectId,
                  bytes4 _selector
              ) internal {
                  require(
                      adminACLAllowed(msg.sender, address(this), _selector) ||
                          (owner() == address(0) &&
                              msg.sender ==
                              projectIdToFinancials[_projectId].artistAddress),
                      "Only Admin ACL allowed, or artist if owner has renounced"
                  );
              }
              /**
               * @notice Initializes contract.
               * @param _tokenName Name of token.
               * @param _tokenSymbol Token symbol.
               * @param _randomizerContract Randomizer contract.
               * @param _adminACLContract Address of admin access control contract, to be
               * set as contract owner.
               * @param _startingProjectId The initial next project ID.
               * @param _autoApproveArtistSplitProposals Whether or not to always
               * auto-approve proposed artist split updates.
               * @dev _startingProjectId should be set to a value much, much less than
               * max(uint248), but an explicit input type of `uint248` is used as it is
               * safer to cast up to `uint256` than it is to cast down for the purposes
               * of setting `_nextProjectId`.
               */
              constructor(
                  string memory _tokenName,
                  string memory _tokenSymbol,
                  address _renderProviderAddress,
                  address _platformProviderAddress,
                  address _randomizerContract,
                  address _adminACLContract,
                  uint248 _startingProjectId,
                  bool _autoApproveArtistSplitProposals,
                  address _engineRegistryContract
              ) ERC721_PackedHashSeed(_tokenName, _tokenSymbol) {
                  _onlyNonZeroAddress(_renderProviderAddress);
                  _onlyNonZeroAddress(_platformProviderAddress);
                  _onlyNonZeroAddress(_randomizerContract);
                  _onlyNonZeroAddress(_adminACLContract);
                  // setup immutable `autoApproveArtistSplitProposals` config
                  autoApproveArtistSplitProposals = _autoApproveArtistSplitProposals;
                  // record contracts starting project ID
                  // casting-up is safe
                  startingProjectId = uint256(_startingProjectId);
                  _updateProviderSalesAddresses(
                      _renderProviderAddress,
                      _renderProviderAddress,
                      _platformProviderAddress,
                      _platformProviderAddress
                  );
                  _updateRandomizerAddress(_randomizerContract);
                  // set AdminACL management contract as owner
                  _transferOwnership(_adminACLContract);
                  // initialize default base URI
                  _updateDefaultBaseURI(
                      string.concat(
                          "https://token.artblocks.io/",
                          toHexString(address(this)),
                          "/"
                      )
                  );
                  // initialize next project ID
                  _nextProjectId = _startingProjectId;
                  emit PlatformUpdated(FIELD_NEXT_PROJECT_ID);
                  // register contract as an Engine contract
                  IEngineRegistryV0(_engineRegistryContract).registerContract(
                      address(this),
                      CORE_VERSION,
                      CORE_TYPE
                  );
              }
              /**
               * @notice Updates preferredIPFSGateway to `_gateway`.
               */
              function updateIPFSGateway(string calldata _gateway) public {
                  _onlyAdminACL(this.updateIPFSGateway.selector);
                  preferredIPFSGateway = _gateway;
                  emit GatewayUpdated(ExternalAssetDependencyType.IPFS, _gateway);
              }
              /**
               * @notice Updates preferredArweaveGateway to `_gateway`.
               */
              function updateArweaveGateway(string calldata _gateway) public {
                  _onlyAdminACL(this.updateArweaveGateway.selector);
                  preferredArweaveGateway = _gateway;
                  emit GatewayUpdated(ExternalAssetDependencyType.ARWEAVE, _gateway);
              }
              /**
               * @notice Locks external asset dependencies for project `_projectId`.
               */
              function lockProjectExternalAssetDependencies(uint256 _projectId) external {
                  _onlyUnlockedProjectExternalAssetDependencies(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.lockProjectExternalAssetDependencies.selector
                  );
                  projects[_projectId].externalAssetDependenciesLocked = true;
                  emit ProjectExternalAssetDependenciesLocked(_projectId);
              }
              /**
               * @notice Updates external asset dependency for project `_projectId`.
               * @param _projectId Project to be updated.
               * @param _index Asset index.
               * @param _cidOrData Asset cid (Content identifier) or data string to be translated into bytecode.
               * @param _dependencyType Asset dependency type.
               *  0 - IPFS
               *  1 - ARWEAVE
               *  2 - ONCHAIN
               */
              function updateProjectExternalAssetDependency(
                  uint256 _projectId,
                  uint256 _index,
                  string memory _cidOrData,
                  ExternalAssetDependencyType _dependencyType
              ) external {
                  _onlyUnlockedProjectExternalAssetDependencies(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.updateProjectExternalAssetDependency.selector
                  );
                  uint24 assetCount = projects[_projectId].externalAssetDependencyCount;
                  require(_index < assetCount, "Asset index out of range");
                  ExternalAssetDependency storage _oldDependency = projects[_projectId]
                      .externalAssetDependencies[_index];
                  ExternalAssetDependencyType _oldDependencyType = _oldDependency
                      .dependencyType;
                  projects[_projectId]
                      .externalAssetDependencies[_index]
                      .dependencyType = _dependencyType;
                  // if the incoming dependency type is onchain, we need to write the data to bytecode
                  if (_dependencyType == ExternalAssetDependencyType.ONCHAIN) {
                      if (_oldDependencyType != ExternalAssetDependencyType.ONCHAIN) {
                          // we only need to set the cid to an empty string if we are replacing an offchain asset
                          // an onchain asset will already have an empty cid
                          projects[_projectId].externalAssetDependencies[_index].cid = "";
                      }
                      projects[_projectId]
                          .externalAssetDependencies[_index]
                          .bytecodeAddress = _cidOrData.writeToBytecode();
                      // we don't want to emit data, so we emit the cid as an empty string
                      _cidOrData = "";
                  } else {
                      projects[_projectId]
                          .externalAssetDependencies[_index]
                          .cid = _cidOrData;
                  }
                  emit ExternalAssetDependencyUpdated(
                      _projectId,
                      _index,
                      _cidOrData,
                      _dependencyType,
                      assetCount
                  );
              }
              /**
               * @notice Removes external asset dependency for project `_projectId` at index `_index`.
               * Removal is done by swapping the element to be removed with the last element in the array, then deleting this last element.
               * Assets with indices higher than `_index` can have their indices adjusted as a result of this operation.
               * @param _projectId Project to be updated.
               * @param _index Asset index
               */
              function removeProjectExternalAssetDependency(
                  uint256 _projectId,
                  uint256 _index
              ) external {
                  _onlyUnlockedProjectExternalAssetDependencies(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.removeProjectExternalAssetDependency.selector
                  );
                  uint24 assetCount = projects[_projectId].externalAssetDependencyCount;
                  require(_index < assetCount, "Asset index out of range");
                  uint24 lastElementIndex = assetCount - 1;
                  // copy last element to index of element to be removed
                  projects[_projectId].externalAssetDependencies[_index] = projects[
                      _projectId
                  ].externalAssetDependencies[lastElementIndex];
                  delete projects[_projectId].externalAssetDependencies[lastElementIndex];
                  projects[_projectId].externalAssetDependencyCount = lastElementIndex;
                  emit ExternalAssetDependencyRemoved(_projectId, _index);
              }
              /**
               * @notice Adds external asset dependency for project `_projectId`.
               * @param _projectId Project to be updated.
               * @param _cidOrData Asset cid (Content identifier) or data string to be translated into bytecode.
               * @param _dependencyType Asset dependency type.
               *  0 - IPFS
               *  1 - ARWEAVE
               *  2 - ONCHAIN
               */
              function addProjectExternalAssetDependency(
                  uint256 _projectId,
                  string memory _cidOrData,
                  ExternalAssetDependencyType _dependencyType
              ) external {
                  _onlyUnlockedProjectExternalAssetDependencies(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.addProjectExternalAssetDependency.selector
                  );
                  uint24 assetCount = projects[_projectId].externalAssetDependencyCount;
                  address _bytecodeAddress = address(0);
                  // if the incoming dependency type is onchain, we need to write the data to bytecode
                  if (_dependencyType == ExternalAssetDependencyType.ONCHAIN) {
                      _bytecodeAddress = _cidOrData.writeToBytecode();
                      // we don't want to emit data, so we emit the cid as an empty string
                      _cidOrData = "";
                  }
                  ExternalAssetDependency memory asset = ExternalAssetDependency({
                      cid: _cidOrData,
                      dependencyType: _dependencyType,
                      bytecodeAddress: _bytecodeAddress
                  });
                  projects[_projectId].externalAssetDependencies[assetCount] = asset;
                  projects[_projectId].externalAssetDependencyCount = assetCount + 1;
                  emit ExternalAssetDependencyUpdated(
                      _projectId,
                      assetCount,
                      _cidOrData,
                      _dependencyType,
                      assetCount + 1
                  );
              }
              /**
               * @notice Mints a token from project `_projectId` and sets the
               * token's owner to `_to`. Hash may or may not be assigned to the token
               * during the mint transaction, depending on the randomizer contract.
               * @param _to Address to be the minted token's owner.
               * @param _projectId Project ID to mint a token on.
               * @param _by Purchaser of minted token.
               * @return _tokenId The ID of the minted token.
               * @dev sender must be the allowed minterContract
               * @dev name of function is optimized for gas usage
               */
              function mint_Ecf(
                  address _to,
                  uint256 _projectId,
                  address _by
              ) external returns (uint256 _tokenId) {
                  // CHECKS
                  require(msg.sender == minterContract, "Must mint from minter contract");
                  Project storage project = projects[_projectId];
                  // load invocations into memory
                  uint24 invocationsBefore = project.invocations;
                  uint24 invocationsAfter;
                  unchecked {
                      // invocationsBefore guaranteed <= maxInvocations <= 1_000_000,
                      // 1_000_000 << max uint24, so no possible overflow
                      invocationsAfter = invocationsBefore + 1;
                  }
                  uint24 maxInvocations = project.maxInvocations;
                  require(
                      invocationsBefore < maxInvocations,
                      "Must not exceed max invocations"
                  );
                  require(
                      project.active ||
                          _by == projectIdToFinancials[_projectId].artistAddress,
                      "Project must exist and be active"
                  );
                  require(
                      !project.paused ||
                          _by == projectIdToFinancials[_projectId].artistAddress,
                      "Purchases are paused."
                  );
                  // EFFECTS
                  // increment project's invocations
                  project.invocations = invocationsAfter;
                  uint256 thisTokenId;
                  unchecked {
                      // invocationsBefore is uint24 << max uint256. In production use,
                      // _projectId * ONE_MILLION must be << max uint256, otherwise
                      // tokenIdToProjectId function become invalid.
                      // Therefore, no risk of overflow
                      thisTokenId = (_projectId * ONE_MILLION) + invocationsBefore;
                  }
                  // mark project as completed if hit max invocations
                  if (invocationsAfter == maxInvocations) {
                      _completeProject(_projectId);
                  }
                  // INTERACTIONS
                  _mint(_to, thisTokenId);
                  // token hash is updated by the randomizer contract on V3
                  randomizerContract.assignTokenHash(thisTokenId);
                  // Do not need to also log `projectId` in event, as the `projectId` for
                  // a given token can be derived from the `tokenId` with:
                  //   projectId = tokenId / 1_000_000
                  emit Mint(_to, thisTokenId);
                  return thisTokenId;
              }
              /**
               * @notice Sets the hash seed for a given token ID `_tokenId`.
               * May only be called by the current randomizer contract.
               * May only be called for tokens that have not already been assigned a
               * non-zero hash.
               * @param _tokenId Token ID to set the hash for.
               * @param _hashSeed Hash seed to set for the token ID. Only last 12 bytes
               * will be used.
               * @dev gas-optimized function name because called during mint sequence
               * @dev if a separate event is required when the token hash is set, e.g.
               * for indexing purposes, it must be emitted by the randomizer. This is to
               * minimize gas when minting.
               */
              function setTokenHash_8PT(uint256 _tokenId, bytes32 _hashSeed) external {
                  _onlyValidTokenId(_tokenId);
                  OwnerAndHashSeed storage ownerAndHashSeed = _ownersAndHashSeeds[
                      _tokenId
                  ];
                  require(
                      msg.sender == address(randomizerContract),
                      "Only randomizer may set"
                  );
                  require(
                      ownerAndHashSeed.hashSeed == bytes12(0),
                      "Token hash already set"
                  );
                  require(_hashSeed != bytes12(0), "No zero hash seed");
                  ownerAndHashSeed.hashSeed = bytes12(_hashSeed);
              }
              /**
               * @notice Allows owner (AdminACL) to revoke ownership of the contract.
               * Note that the contract is intended to continue to function after the
               * owner renounces ownership, but no new projects will be able to be added.
               * Renouncing ownership will leave the contract without an owner,
               * thereby removing any functionality that is only available to the
               * owner/AdminACL contract. The same is true for any dependent contracts
               * that also integrate with the owner/AdminACL contract (e.g. potentially
               * minter suite contracts, registry contracts, etc.).
               * After renouncing ownership, artists will be in control of updates to
               * their payment addresses and splits (see modifier
               * onlyAdminACLOrRenouncedArtist`).
               * While there is no currently intended reason to call this method based on
               * typical Engine partner business practices, this method exists to allow
               * artists to continue to maintain the limited set of contract
               * functionality that exists post-project-lock in an environment in which
               * there is no longer an admin maintaining this smart contract.
               * @dev This function is intended to be called directly by the AdminACL,
               * not by an address allowed by the AdminACL contract.
               */
              function renounceOwnership() public override onlyOwner {
                  // broadcast that new projects are no longer allowed (if not already)
                  _forbidNewProjects();
                  // renounce ownership viw Ownable
                  Ownable.renounceOwnership();
              }
              /**
               * @notice Updates reference to Art Blocks Dependency Registry contract.
               * @param _artblocksDependencyRegistryAddress Address of new Dependency
               * Registry.
               */
              function updateArtblocksDependencyRegistryAddress(
                  address _artblocksDependencyRegistryAddress
              ) external {
                  _onlyAdminACL(this.updateArtblocksDependencyRegistryAddress.selector);
                  _onlyNonZeroAddress(_artblocksDependencyRegistryAddress);
                  artblocksDependencyRegistryAddress = _artblocksDependencyRegistryAddress;
                  emit PlatformUpdated(FIELD_ARTBLOCKS_DEPENDENCY_REGISTRY_ADDRESS);
              }
              /**
               * @notice Updates sales addresses for the platform and render providers to
               * the input parameters.
               * @param _renderProviderPrimarySalesAddress Address of new primary sales
               * payment address.
               * @param _renderProviderSecondarySalesAddress Address of new secondary sales
               * payment address.
               * @param _platformProviderPrimarySalesAddress Address of new primary sales
               * payment address.
               * @param _platformProviderSecondarySalesAddress Address of new secondary sales
               * payment address.
               */
              function updateProviderSalesAddresses(
                  address payable _renderProviderPrimarySalesAddress,
                  address payable _renderProviderSecondarySalesAddress,
                  address payable _platformProviderPrimarySalesAddress,
                  address payable _platformProviderSecondarySalesAddress
              ) external {
                  _onlyAdminACL(this.updateProviderSalesAddresses.selector);
                  _onlyNonZeroAddress(_renderProviderPrimarySalesAddress);
                  _onlyNonZeroAddress(_renderProviderSecondarySalesAddress);
                  _onlyNonZeroAddress(_platformProviderPrimarySalesAddress);
                  _onlyNonZeroAddress(_platformProviderSecondarySalesAddress);
                  _updateProviderSalesAddresses(
                      _renderProviderPrimarySalesAddress,
                      _renderProviderSecondarySalesAddress,
                      _platformProviderPrimarySalesAddress,
                      _platformProviderSecondarySalesAddress
                  );
              }
              /**
               * @notice Updates the render and platform provider primary sales revenue percentage to
               * the provided inputs.
               * @param renderProviderPrimarySalesPercentage_ New primary sales revenue % for the render provider
               * @param platformProviderPrimarySalesPercentage_ New primary sales revenue % for the platform provider
               * percentage.
               */
              function updateProviderPrimarySalesPercentages(
                  uint256 renderProviderPrimarySalesPercentage_,
                  uint256 platformProviderPrimarySalesPercentage_
              ) external {
                  _onlyAdminACL(this.updateProviderPrimarySalesPercentages.selector);
                  // Validate that the sum of the proposed %s, does not exceed 100%.
                  require(
                      (renderProviderPrimarySalesPercentage_ +
                          platformProviderPrimarySalesPercentage_) <= ONE_HUNDRED,
                      "Max sum of ONE_HUNDRED %"
                  );
                  // Casting to `uint8` here is safe due check above, which does not allow
                  // overflow as of solidity version ^0.8.0.
                  _renderProviderPrimarySalesPercentage = uint8(
                      renderProviderPrimarySalesPercentage_
                  );
                  _platformProviderPrimarySalesPercentage = uint8(
                      platformProviderPrimarySalesPercentage_
                  );
                  emit PlatformUpdated(FIELD_PROVIDER_PRIMARY_SALES_PERCENTAGES);
              }
              /**
               * @notice Updates render and platform provider secondary sales royalty Basis Points to
               * the provided inputs.
               * @param _renderProviderSecondarySalesBPS New secondary sales royalty Basis
               * points.
               * @param _platformProviderSecondarySalesBPS New secondary sales royalty Basis
               * points.
               * @dev Due to secondary royalties being ultimately enforced via social
               * consensus, no hard upper limit is imposed on the BPS value, other than
               * <= 100% royalty, which would not make mathematical sense. Realistically,
               * changing this value is expected to either never occur, or be a rare
               * occurrence.
               */
              function updateProviderSecondarySalesBPS(
                  uint256 _renderProviderSecondarySalesBPS,
                  uint256 _platformProviderSecondarySalesBPS
              ) external {
                  _onlyAdminACL(this.updateProviderSecondarySalesBPS.selector);
                  // Validate that the sum of the proposed provider BPS, does not exceed 10_000 BPS.
                  require(
                      (_renderProviderSecondarySalesBPS +
                          _platformProviderSecondarySalesBPS) <=
                          MAX_PROVIDER_SECONDARY_SALES_BPS,
                      "Over max sum of BPS"
                  );
                  renderProviderSecondarySalesBPS = _renderProviderSecondarySalesBPS;
                  platformProviderSecondarySalesBPS = _platformProviderSecondarySalesBPS;
                  emit PlatformUpdated(FIELD_PROVIDER_SECONDARY_SALES_BPS);
              }
              /**
               * @notice Updates minter to `_address`.
               * @param _address Address of new minter.
               */
              function updateMinterContract(address _address) external {
                  _onlyAdminACL(this.updateMinterContract.selector);
                  _onlyNonZeroAddress(_address);
                  minterContract = _address;
                  emit MinterUpdated(_address);
              }
              /**
               * @notice Updates randomizer to `_randomizerAddress`.
               * @param _randomizerAddress Address of new randomizer.
               */
              function updateRandomizerAddress(address _randomizerAddress) external {
                  _onlyAdminACL(this.updateRandomizerAddress.selector);
                  _onlyNonZeroAddress(_randomizerAddress);
                  _updateRandomizerAddress(_randomizerAddress);
              }
              /**
               * @notice Toggles project `_projectId` as active/inactive.
               * @param _projectId Project ID to be toggled.
               */
              function toggleProjectIsActive(uint256 _projectId) external {
                  _onlyAdminACL(this.toggleProjectIsActive.selector);
                  _onlyValidProjectId(_projectId);
                  projects[_projectId].active = !projects[_projectId].active;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_ACTIVE);
              }
              /**
               * @notice Artist proposes updated set of artist address, additional payee
               * addresses, and percentage splits for project `_projectId`. Addresses and
               * percentages do not have to all be changed, but they must all be defined
               * as a complete set.
               * Note that if the artist is only proposing a change to the payee percentage
               * splits, without modifying the payee addresses, the proposal will be
               * automatically approved and the new splits will become active immediately.
               * Automatic approval will also be granted if the artist is only removing
               * additional payee addresses, without adding any new ones.
               * Also note that if `autoApproveArtistSplitProposals` is true, proposals
               * will always be auto-approved, regardless of what is being changed.
               * Also note that if the artist is proposing sending funds to the zero
               * address, this function will revert and the proposal will not be created.
               * @param _projectId Project ID.
               * @param _artistAddress Artist address that controls the project, and may
               * receive payments.
               * @param _additionalPayeePrimarySales Address that may receive a
               * percentage split of the artist's primary sales revenue.
               * @param _additionalPayeePrimarySalesPercentage Percent of artist's
               * portion of primary sale revenue that will be split to address
               * `_additionalPayeePrimarySales`.
               * @param _additionalPayeeSecondarySales Address that may receive a percentage
               * split of the secondary sales royalties.
               * @param _additionalPayeeSecondarySalesPercentage Percent of artist's portion
               * of secondary sale royalties that will be split to address
               * `_additionalPayeeSecondarySales`.
               * @dev `_artistAddress` must be a valid address (non-zero-address), but it
               * is intentionally allowable for `_additionalPayee{Primary,Secondaary}Sales`
               * and their associated percentages to be zero'd out by the controlling artist.
               */
              function proposeArtistPaymentAddressesAndSplits(
                  uint256 _projectId,
                  address payable _artistAddress,
                  address payable _additionalPayeePrimarySales,
                  uint256 _additionalPayeePrimarySalesPercentage,
                  address payable _additionalPayeeSecondarySales,
                  uint256 _additionalPayeeSecondarySalesPercentage
              ) external {
                  _onlyValidProjectId(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.proposeArtistPaymentAddressesAndSplits.selector
                  );
                  _onlyNonZeroAddress(_artistAddress);
                  ProjectFinance storage projectFinance = projectIdToFinancials[
                      _projectId
                  ];
                  // checks
                  require(
                      _additionalPayeePrimarySalesPercentage <= ONE_HUNDRED &&
                          _additionalPayeeSecondarySalesPercentage <= ONE_HUNDRED,
                      "Max of 100%"
                  );
                  require(
                      _additionalPayeePrimarySalesPercentage == 0 ||
                          _additionalPayeePrimarySales != address(0),
                      "Primary payee is zero address"
                  );
                  require(
                      _additionalPayeeSecondarySalesPercentage == 0 ||
                          _additionalPayeeSecondarySales != address(0),
                      "Secondary payee is zero address"
                  );
                  // effects
                  // emit event for off-chain indexing
                  // note: always emit a proposal event, even in the pathway of
                  // automatic approval, to simplify indexing expectations
                  emit ProposedArtistAddressesAndSplits(
                      _projectId,
                      _artistAddress,
                      _additionalPayeePrimarySales,
                      _additionalPayeePrimarySalesPercentage,
                      _additionalPayeeSecondarySales,
                      _additionalPayeeSecondarySalesPercentage
                  );
                  // automatically accept if no proposed addresses modifications, or if
                  // the proposal only removes payee addresses, or if contract is set to
                  // always auto-approve.
                  // store proposal hash on-chain, only if not automatic accept
                  bool automaticAccept = autoApproveArtistSplitProposals;
                  if (!automaticAccept) {
                      // block scope to avoid stack too deep error
                      bool artistUnchanged = _artistAddress ==
                          projectFinance.artistAddress;
                      bool additionalPrimaryUnchangedOrRemoved = (_additionalPayeePrimarySales ==
                              projectFinance.additionalPayeePrimarySales) ||
                              (_additionalPayeePrimarySales == address(0));
                      bool additionalSecondaryUnchangedOrRemoved = (_additionalPayeeSecondarySales ==
                              projectFinance.additionalPayeeSecondarySales) ||
                              (_additionalPayeeSecondarySales == address(0));
                      automaticAccept =
                          artistUnchanged &&
                          additionalPrimaryUnchangedOrRemoved &&
                          additionalSecondaryUnchangedOrRemoved;
                  }
                  if (automaticAccept) {
                      // clear any previously proposed values
                      proposedArtistAddressesAndSplitsHash[_projectId] = bytes32(0);
                      // update storage
                      // artist address can change during automatic accept if
                      // autoApproveArtistSplitProposals is true
                      projectFinance.artistAddress = _artistAddress;
                      projectFinance
                          .additionalPayeePrimarySales = _additionalPayeePrimarySales;
                      // safe to cast as uint8 as max is 100%, max uint8 is 255
                      projectFinance.additionalPayeePrimarySalesPercentage = uint8(
                          _additionalPayeePrimarySalesPercentage
                      );
                      projectFinance
                          .additionalPayeeSecondarySales = _additionalPayeeSecondarySales;
                      // safe to cast as uint8 as max is 100%, max uint8 is 255
                      projectFinance.additionalPayeeSecondarySalesPercentage = uint8(
                          _additionalPayeeSecondarySalesPercentage
                      );
                      // emit event for off-chain indexing
                      emit AcceptedArtistAddressesAndSplits(_projectId);
                  } else {
                      proposedArtistAddressesAndSplitsHash[_projectId] = keccak256(
                          abi.encode(
                              _artistAddress,
                              _additionalPayeePrimarySales,
                              _additionalPayeePrimarySalesPercentage,
                              _additionalPayeeSecondarySales,
                              _additionalPayeeSecondarySalesPercentage
                          )
                      );
                  }
              }
              /**
               * @notice Admin accepts a proposed set of updated artist address,
               * additional payee addresses, and percentage splits for project
               * `_projectId`. Addresses and percentages do not have to all be changed,
               * but they must all be defined as a complete set.
               * @param _projectId Project ID.
               * @param _artistAddress Artist address that controls the project, and may
               * receive payments.
               * @param _additionalPayeePrimarySales Address that may receive a
               * percentage split of the artist's primary sales revenue.
               * @param _additionalPayeePrimarySalesPercentage Percent of artist's
               * portion of primary sale revenue that will be split to address
               * `_additionalPayeePrimarySales`.
               * @param _additionalPayeeSecondarySales Address that may receive a percentage
               * split of the secondary sales royalties.
               * @param _additionalPayeeSecondarySalesPercentage Percent of artist's portion
               * of secondary sale royalties that will be split to address
               * `_additionalPayeeSecondarySales`.
               * @dev this must be called by the Admin ACL contract, and must only accept
               * the most recent proposed values for a given project (validated on-chain
               * by comparing the hash of the proposed and accepted values).
               * @dev `_artistAddress` must be a valid address (non-zero-address), but it
               * is intentionally allowable for `_additionalPayee{Primary,Secondaary}Sales`
               * and their associated percentages to be zero'd out by the controlling artist.
               */
              function adminAcceptArtistAddressesAndSplits(
                  uint256 _projectId,
                  address payable _artistAddress,
                  address payable _additionalPayeePrimarySales,
                  uint256 _additionalPayeePrimarySalesPercentage,
                  address payable _additionalPayeeSecondarySales,
                  uint256 _additionalPayeeSecondarySalesPercentage
              ) external {
                  _onlyValidProjectId(_projectId);
                  _onlyAdminACLOrRenouncedArtist(
                      _projectId,
                      this.adminAcceptArtistAddressesAndSplits.selector
                  );
                  _onlyNonZeroAddress(_artistAddress);
                  // checks
                  require(
                      proposedArtistAddressesAndSplitsHash[_projectId] ==
                          keccak256(
                              abi.encode(
                                  _artistAddress,
                                  _additionalPayeePrimarySales,
                                  _additionalPayeePrimarySalesPercentage,
                                  _additionalPayeeSecondarySales,
                                  _additionalPayeeSecondarySalesPercentage
                              )
                          ),
                      "Must match artist proposal"
                  );
                  // effects
                  ProjectFinance storage projectFinance = projectIdToFinancials[
                      _projectId
                  ];
                  projectFinance.artistAddress = _artistAddress;
                  projectFinance
                      .additionalPayeePrimarySales = _additionalPayeePrimarySales;
                  projectFinance.additionalPayeePrimarySalesPercentage = uint8(
                      _additionalPayeePrimarySalesPercentage
                  );
                  projectFinance
                      .additionalPayeeSecondarySales = _additionalPayeeSecondarySales;
                  projectFinance.additionalPayeeSecondarySalesPercentage = uint8(
                      _additionalPayeeSecondarySalesPercentage
                  );
                  // clear proposed values
                  proposedArtistAddressesAndSplitsHash[_projectId] = bytes32(0);
                  // emit event for off-chain indexing
                  emit AcceptedArtistAddressesAndSplits(_projectId);
              }
              /**
               * @notice Updates artist of project `_projectId` to `_artistAddress`.
               * This is to only be used in the event that the artist address is
               * compromised or sanctioned.
               * @param _projectId Project ID.
               * @param _artistAddress New artist address.
               */
              function updateProjectArtistAddress(
                  uint256 _projectId,
                  address payable _artistAddress
              ) external {
                  _onlyValidProjectId(_projectId);
                  _onlyAdminACLOrRenouncedArtist(
                      _projectId,
                      this.updateProjectArtistAddress.selector
                  );
                  _onlyNonZeroAddress(_artistAddress);
                  projectIdToFinancials[_projectId].artistAddress = _artistAddress;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_ARTIST_ADDRESS);
              }
              /**
               * @notice Toggles paused state of project `_projectId`.
               * @param _projectId Project ID to be toggled.
               */
              function toggleProjectIsPaused(uint256 _projectId) external {
                  _onlyValidProjectId(_projectId);
                  _onlyArtistOrAdminACL(_projectId, this.toggleProjectIsPaused.selector);
                  projects[_projectId].paused = !projects[_projectId].paused;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_PAUSED);
              }
              /**
               * @notice Adds new project `_projectName` by `_artistAddress`.
               * @param _projectName Project name.
               * @param _artistAddress Artist's address.
               * @dev token price now stored on minter
               */
              function addProject(
                  string memory _projectName,
                  address payable _artistAddress
              ) external {
                  _onlyAdminACL(this.addProject.selector);
                  _onlyNonEmptyString(_projectName);
                  _onlyNonZeroAddress(_artistAddress);
                  require(!newProjectsForbidden, "New projects forbidden");
                  uint256 projectId = _nextProjectId;
                  projectIdToFinancials[projectId].artistAddress = _artistAddress;
                  projects[projectId].name = _projectName;
                  projects[projectId].paused = true;
                  projects[projectId].maxInvocations = ONE_MILLION_UINT24;
                  projects[projectId].projectBaseURI = defaultBaseURI;
                  _nextProjectId = uint248(projectId) + 1;
                  emit ProjectUpdated(projectId, FIELD_PROJECT_CREATED);
              }
              /**
               * @notice Forever forbids new projects from being added to this contract.
               */
              function forbidNewProjects() external {
                  _onlyAdminACL(this.forbidNewProjects.selector);
                  require(!newProjectsForbidden, "Already forbidden");
                  _forbidNewProjects();
              }
              /**
               * @notice Updates name of project `_projectId` to be `_projectName`.
               * @param _projectId Project ID.
               * @param _projectName New project name.
               */
              function updateProjectName(
                  uint256 _projectId,
                  string memory _projectName
              ) external {
                  _onlyUnlocked(_projectId);
                  _onlyArtistOrAdminACL(_projectId, this.updateProjectName.selector);
                  _onlyNonEmptyString(_projectName);
                  projects[_projectId].name = _projectName;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_NAME);
              }
              /**
               * @notice Updates artist name for project `_projectId` to be
               * `_projectArtistName`.
               * @param _projectId Project ID.
               * @param _projectArtistName New artist name.
               */
              function updateProjectArtistName(
                  uint256 _projectId,
                  string memory _projectArtistName
              ) external {
                  _onlyUnlocked(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.updateProjectArtistName.selector
                  );
                  _onlyNonEmptyString(_projectArtistName);
                  projects[_projectId].artist = _projectArtistName;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_ARTIST_NAME);
              }
              /**
               * @notice Updates artist secondary market royalties for project
               * `_projectId` to be `_secondMarketRoyalty` percent.
               * This DOES NOT include the secondary market royalty percentages collected
               * by the issuing platform; it is only the total percentage of royalties
               * that will be split to artist and additionalSecondaryPayee.
               * @param _projectId Project ID.
               * @param _secondMarketRoyalty Percent of secondary sales revenue that will
               * be split to artist and additionalSecondaryPayee. This must be less than
               * or equal to ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE percent.
               */
              function updateProjectSecondaryMarketRoyaltyPercentage(
                  uint256 _projectId,
                  uint256 _secondMarketRoyalty
              ) external {
                  _onlyValidProjectId(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.updateProjectSecondaryMarketRoyaltyPercentage.selector
                  );
                  require(
                      _secondMarketRoyalty <= ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE,
                      "Over max percent"
                  );
                  projectIdToFinancials[_projectId]
                      .secondaryMarketRoyaltyPercentage = uint8(_secondMarketRoyalty);
                  emit ProjectUpdated(
                      _projectId,
                      FIELD_PROJECT_SECONDARY_MARKET_ROYALTY_PERCENTAGE
                  );
              }
              /**
               * @notice Updates description of project `_projectId`.
               * Only artist may call when unlocked, only admin may call when locked.
               * @param _projectId Project ID.
               * @param _projectDescription New project description.
               */
              function updateProjectDescription(
                  uint256 _projectId,
                  string memory _projectDescription
              ) external {
                  // checks
                  require(
                      _projectUnlocked(_projectId)
                          ? msg.sender == projectIdToFinancials[_projectId].artistAddress
                          : adminACLAllowed(
                              msg.sender,
                              address(this),
                              this.updateProjectDescription.selector
                          ),
                      "Only artist when unlocked, owner when locked"
                  );
                  // effects
                  projects[_projectId].description = _projectDescription;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_DESCRIPTION);
              }
              /**
               * @notice Updates website of project `_projectId` to be `_projectWebsite`.
               * @param _projectId Project ID.
               * @param _projectWebsite New project website.
               * @dev It is intentionally allowed for this to be set to the empty string.
               */
              function updateProjectWebsite(
                  uint256 _projectId,
                  string memory _projectWebsite
              ) external {
                  _onlyValidProjectId(_projectId);
                  _onlyArtistOrAdminACL(_projectId, this.updateProjectWebsite.selector);
                  projects[_projectId].website = _projectWebsite;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_WEBSITE);
              }
              /**
               * @notice Updates license for project `_projectId`.
               * @param _projectId Project ID.
               * @param _projectLicense New project license.
               */
              function updateProjectLicense(
                  uint256 _projectId,
                  string memory _projectLicense
              ) external {
                  _onlyUnlocked(_projectId);
                  _onlyArtistOrAdminACL(_projectId, this.updateProjectLicense.selector);
                  _onlyNonEmptyString(_projectLicense);
                  projects[_projectId].license = _projectLicense;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_LICENSE);
              }
              /**
               * @notice Updates maximum invocations for project `_projectId` to
               * `_maxInvocations`. Maximum invocations may only be decreased by the
               * artist, and must be greater than or equal to current invocations.
               * New projects are created with maximum invocations of 1 million by
               * default.
               * @param _projectId Project ID.
               * @param _maxInvocations New maximum invocations.
               */
              function updateProjectMaxInvocations(
                  uint256 _projectId,
                  uint24 _maxInvocations
              ) external {
                  _onlyValidProjectId(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.updateProjectMaxInvocations.selector
                  );
                  // CHECKS
                  Project storage project = projects[_projectId];
                  uint256 _invocations = project.invocations;
                  require(
                      (_maxInvocations < project.maxInvocations),
                      "Only maxInvocations decrease"
                  );
                  require(_maxInvocations >= _invocations, "Only gte invocations");
                  // EFFECTS
                  project.maxInvocations = _maxInvocations;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_MAX_INVOCATIONS);
                  // register completed timestamp if action completed the project
                  if (_maxInvocations == _invocations) {
                      _completeProject(_projectId);
                  }
              }
              /**
               * @notice Adds a script to project `_projectId`.
               * @param _projectId Project to be updated.
               * @param _script Script to be added. Required to be a non-empty string,
               * but no further validation is performed.
               */
              function addProjectScript(
                  uint256 _projectId,
                  string memory _script
              ) external {
                  _onlyUnlocked(_projectId);
                  _onlyArtistOrAdminACL(_projectId, this.addProjectScript.selector);
                  _onlyNonEmptyString(_script);
                  Project storage project = projects[_projectId];
                  // store script in contract bytecode
                  project.scriptBytecodeAddresses[project.scriptCount] = _script
                      .writeToBytecode();
                  project.scriptCount = project.scriptCount + 1;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT);
              }
              /**
               * @notice Updates script for project `_projectId` at script ID `_scriptId`.
               * @param _projectId Project to be updated.
               * @param _scriptId Script ID to be updated.
               * @param _script The updated script value. Required to be a non-empty
               *                string, but no further validation is performed.
               */
              function updateProjectScript(
                  uint256 _projectId,
                  uint256 _scriptId,
                  string memory _script
              ) external {
                  _onlyUnlocked(_projectId);
                  _onlyArtistOrAdminACL(_projectId, this.updateProjectScript.selector);
                  _onlyNonEmptyString(_script);
                  Project storage project = projects[_projectId];
                  require(_scriptId < project.scriptCount, "scriptId out of range");
                  // store script in contract bytecode, replacing reference address from
                  // the contract that no longer exists with the newly created one
                  project.scriptBytecodeAddresses[_scriptId] = _script.writeToBytecode();
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT);
              }
              /**
               * @notice Removes last script from project `_projectId`.
               * @param _projectId Project to be updated.
               */
              function removeProjectLastScript(uint256 _projectId) external {
                  _onlyUnlocked(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.removeProjectLastScript.selector
                  );
                  Project storage project = projects[_projectId];
                  require(project.scriptCount > 0, "No scripts to remove");
                  // delete reference to contract address that no longer exists
                  delete project.scriptBytecodeAddresses[project.scriptCount - 1];
                  unchecked {
                      project.scriptCount = project.scriptCount - 1;
                  }
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT);
              }
              /**
               * @notice Updates script type for project `_projectId`.
               * @param _projectId Project to be updated.
               * @param _scriptTypeAndVersion Script type and version e.g. "[email protected]",
               * as bytes32 encoded string.
               */
              function updateProjectScriptType(
                  uint256 _projectId,
                  bytes32 _scriptTypeAndVersion
              ) external {
                  _onlyUnlocked(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.updateProjectScriptType.selector
                  );
                  Project storage project = projects[_projectId];
                  // require exactly one @ symbol in _scriptTypeAndVersion
                  require(
                      _scriptTypeAndVersion.containsExactCharacterQty(
                          AT_CHARACTER_CODE,
                          uint8(1)
                      ),
                      "must contain exactly one @"
                  );
                  project.scriptTypeAndVersion = _scriptTypeAndVersion;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT_TYPE);
              }
              /**
               * @notice Updates project's aspect ratio.
               * @param _projectId Project to be updated.
               * @param _aspectRatio Aspect ratio to be set. Intended to be string in the
               * format of a decimal, e.g. "1" for square, "1.77777778" for 16:9, etc.,
               * allowing for a maximum of 10 digits and one (optional) decimal separator.
               */
              function updateProjectAspectRatio(
                  uint256 _projectId,
                  string memory _aspectRatio
              ) external {
                  _onlyUnlocked(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.updateProjectAspectRatio.selector
                  );
                  _onlyNonEmptyString(_aspectRatio);
                  // Perform more detailed input validation for aspect ratio.
                  bytes memory aspectRatioBytes = bytes(_aspectRatio);
                  uint256 bytesLength = aspectRatioBytes.length;
                  require(bytesLength <= 11, "Aspect ratio format too long");
                  bool hasSeenDecimalSeparator = false;
                  bool hasSeenNumber = false;
                  for (uint256 i; i < bytesLength; i++) {
                      bytes1 character = aspectRatioBytes[i];
                      // Allow as many #s as desired.
                      if (character >= 0x30 && character <= 0x39) {
                          // 9-0
                          // We need to ensure there is at least 1 `9-0` occurrence.
                          hasSeenNumber = true;
                          continue;
                      }
                      if (character == 0x2E) {
                          // .
                          // Allow no more than 1 `.` occurrence.
                          if (!hasSeenDecimalSeparator) {
                              hasSeenDecimalSeparator = true;
                              continue;
                          }
                      }
                      revert("Improperly formatted aspect ratio");
                  }
                  require(hasSeenNumber, "Aspect ratio has no numbers");
                  projects[_projectId].aspectRatio = _aspectRatio;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_ASPECT_RATIO);
              }
              /**
               * @notice Updates base URI for project `_projectId` to `_newBaseURI`.
               * This is the controlling base URI for all tokens in the project. The
               * contract-level defaultBaseURI is only used when initializing new
               * projects.
               * @param _projectId Project to be updated.
               * @param _newBaseURI New base URI.
               */
              function updateProjectBaseURI(
                  uint256 _projectId,
                  string memory _newBaseURI
              ) external {
                  _onlyValidProjectId(_projectId);
                  _onlyArtistOrAdminACL(_projectId, this.updateProjectBaseURI.selector);
                  _onlyNonEmptyString(_newBaseURI);
                  projects[_projectId].projectBaseURI = _newBaseURI;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_BASE_URI);
              }
              /**
               * @notice Updates default base URI to `_defaultBaseURI`. The
               * contract-level defaultBaseURI is only used when initializing new
               * projects. Token URIs are determined by their project's `projectBaseURI`.
               * @param _defaultBaseURI New default base URI.
               */
              function updateDefaultBaseURI(string memory _defaultBaseURI) external {
                  _onlyAdminACL(this.updateDefaultBaseURI.selector);
                  _onlyNonEmptyString(_defaultBaseURI);
                  _updateDefaultBaseURI(_defaultBaseURI);
              }
              /**
               * @notice Next project ID to be created on this contract.
               * @return uint256 Next project ID.
               */
              function nextProjectId() external view returns (uint256) {
                  return _nextProjectId;
              }
              /**
               * @notice Returns token hash for token ID `_tokenId`. Returns null if hash
               * has not been set.
               * @param _tokenId Token ID to be queried.
               * @return bytes32 Token hash.
               * @dev token hash is the keccak256 hash of the stored hash seed
               */
              function tokenIdToHash(uint256 _tokenId) external view returns (bytes32) {
                  bytes12 _hashSeed = _ownersAndHashSeeds[_tokenId].hashSeed;
                  if (_hashSeed == 0) {
                      return 0;
                  }
                  return keccak256(abi.encode(_hashSeed));
              }
              /**
               * @notice Returns token hash **seed** for token ID `_tokenId`. Returns
               * null if hash seed has not been set. The hash seed id the bytes12 value
               * which is hashed to produce the token hash.
               * @param _tokenId Token ID to be queried.
               * @return bytes12 Token hash seed.
               * @dev token hash seed is keccak256 hashed to give the token hash
               */
              function tokenIdToHashSeed(
                  uint256 _tokenId
              ) external view returns (bytes12) {
                  return _ownersAndHashSeeds[_tokenId].hashSeed;
              }
              /**
               * @notice View function returning the render provider portion of
               * primary sales, in percent.
               * @return uint256 The render provider portion of primary sales,
               * in percent.
               */
              function renderProviderPrimarySalesPercentage()
                  external
                  view
                  returns (uint256)
              {
                  return _renderProviderPrimarySalesPercentage;
              }
              /**
               * @notice View function returning the platform provider portion of
               * primary sales, in percent.
               * @return uint256 The platform provider portion of primary sales,
               * in percent.
               */
              function platformProviderPrimarySalesPercentage()
                  external
                  view
                  returns (uint256)
              {
                  return _platformProviderPrimarySalesPercentage;
              }
              /**
               * @notice View function returning Artist's address for project
               * `_projectId`.
               * @param _projectId Project ID to be queried.
               * @return address Artist's address.
               */
              function projectIdToArtistAddress(
                  uint256 _projectId
              ) external view returns (address payable) {
                  return projectIdToFinancials[_projectId].artistAddress;
              }
              /**
               * @notice View function returning Artist's secondary market royalty
               * percentage for project `_projectId`.
               * This does not include render/platform providers portions of secondary
               * market royalties.
               * @param _projectId Project ID to be queried.
               * @return uint256 Artist's secondary market royalty percentage.
               */
              function projectIdToSecondaryMarketRoyaltyPercentage(
                  uint256 _projectId
              ) external view returns (uint256) {
                  return
                      projectIdToFinancials[_projectId].secondaryMarketRoyaltyPercentage;
              }
              /**
               * @notice View function returning Artist's additional payee address for
               * primary sales, for project `_projectId`.
               * @param _projectId Project ID to be queried.
               * @return address Artist's additional payee address for primary sales.
               */
              function projectIdToAdditionalPayeePrimarySales(
                  uint256 _projectId
              ) external view returns (address payable) {
                  return projectIdToFinancials[_projectId].additionalPayeePrimarySales;
              }
              /**
               * @notice View function returning Artist's additional payee primary sales
               * percentage, for project `_projectId`.
               * @param _projectId Project ID to be queried.
               * @return uint256 Artist's additional payee primary sales percentage.
               */
              function projectIdToAdditionalPayeePrimarySalesPercentage(
                  uint256 _projectId
              ) external view returns (uint256) {
                  return
                      projectIdToFinancials[_projectId]
                          .additionalPayeePrimarySalesPercentage;
              }
              /**
               * @notice View function returning Artist's additional payee address for
               * secondary sales, for project `_projectId`.
               * @param _projectId Project ID to be queried.
               * @return address payable Artist's additional payee address for secondary
               * sales.
               */
              function projectIdToAdditionalPayeeSecondarySales(
                  uint256 _projectId
              ) external view returns (address payable) {
                  return projectIdToFinancials[_projectId].additionalPayeeSecondarySales;
              }
              /**
               * @notice View function returning Artist's additional payee secondary
               * sales percentage, for project `_projectId`.
               * @param _projectId Project ID to be queried.
               * @return uint256 Artist's additional payee secondary sales percentage.
               */
              function projectIdToAdditionalPayeeSecondarySalesPercentage(
                  uint256 _projectId
              ) external view returns (uint256) {
                  return
                      projectIdToFinancials[_projectId]
                          .additionalPayeeSecondarySalesPercentage;
              }
              /**
               * @notice Returns project details for project `_projectId`.
               * @param _projectId Project to be queried.
               * @return projectName Name of project
               * @return artist Artist of project
               * @return description Project description
               * @return website Project website
               * @return license Project license
               * @dev this function was named projectDetails prior to V3 core contract.
               */
              function projectDetails(
                  uint256 _projectId
              )
                  external
                  view
                  returns (
                      string memory projectName,
                      string memory artist,
                      string memory description,
                      string memory website,
                      string memory license
                  )
              {
                  Project storage project = projects[_projectId];
                  projectName = project.name;
                  artist = project.artist;
                  description = project.description;
                  website = project.website;
                  license = project.license;
              }
              /**
               * @notice Returns project state data for project `_projectId`.
               * @param _projectId Project to be queried
               * @return invocations Current number of invocations
               * @return maxInvocations Maximum allowed invocations
               * @return active Boolean representing if project is currently active
               * @return paused Boolean representing if project is paused
               * @return completedTimestamp zero if project not complete, otherwise
               * timestamp of project completion.
               * @return locked Boolean representing if project is locked
               * @dev price and currency info are located on minter contracts
               */
              function projectStateData(
                  uint256 _projectId
              )
                  external
                  view
                  returns (
                      uint256 invocations,
                      uint256 maxInvocations,
                      bool active,
                      bool paused,
                      uint256 completedTimestamp,
                      bool locked
                  )
              {
                  Project storage project = projects[_projectId];
                  invocations = project.invocations;
                  maxInvocations = project.maxInvocations;
                  active = project.active;
                  paused = project.paused;
                  completedTimestamp = project.completedTimestamp;
                  locked = !_projectUnlocked(_projectId);
              }
              /**
               * @notice Returns artist payment information for project `_projectId`.
               * @param _projectId Project to be queried
               * @return artistAddress Project Artist's address
               * @return additionalPayeePrimarySales Additional payee address for primary
               * sales
               * @return additionalPayeePrimarySalesPercentage Percentage of artist revenue
               * to be sent to the additional payee address for primary sales
               * @return additionalPayeeSecondarySales Additional payee address for secondary
               * sales royalties
               * @return additionalPayeeSecondarySalesPercentage Percentage of artist revenue
               * to be sent to the additional payee address for secondary sales royalties
               * @return secondaryMarketRoyaltyPercentage Royalty percentage to be sent to
               * combination of artist and additional payee. This does not include the
               * platform's percentage of secondary sales royalties, which is defined as
               * the sum of `renderProviderSecondarySalesBPS`
               * and `platformProviderSecondarySalesBPS`.
               */
              function projectArtistPaymentInfo(
                  uint256 _projectId
              )
                  external
                  view
                  returns (
                      address artistAddress,
                      address additionalPayeePrimarySales,
                      uint256 additionalPayeePrimarySalesPercentage,
                      address additionalPayeeSecondarySales,
                      uint256 additionalPayeeSecondarySalesPercentage,
                      uint256 secondaryMarketRoyaltyPercentage
                  )
              {
                  ProjectFinance storage projectFinance = projectIdToFinancials[
                      _projectId
                  ];
                  artistAddress = projectFinance.artistAddress;
                  additionalPayeePrimarySales = projectFinance
                      .additionalPayeePrimarySales;
                  additionalPayeePrimarySalesPercentage = projectFinance
                      .additionalPayeePrimarySalesPercentage;
                  additionalPayeeSecondarySales = projectFinance
                      .additionalPayeeSecondarySales;
                  additionalPayeeSecondarySalesPercentage = projectFinance
                      .additionalPayeeSecondarySalesPercentage;
                  secondaryMarketRoyaltyPercentage = projectFinance
                      .secondaryMarketRoyaltyPercentage;
              }
              /**
               * @notice Returns script information for project `_projectId`.
               * @param _projectId Project to be queried.
               * @return scriptTypeAndVersion Project's script type and version
               * (e.g. "p5js(atSymbol)1.0.0")
               * @return aspectRatio Aspect ratio of project (e.g. "1" for square,
               * "1.77777778" for 16:9, etc.)
               * @return scriptCount Count of scripts for project
               */
              function projectScriptDetails(
                  uint256 _projectId
              )
                  external
                  view
                  override(IGenArt721CoreContractV3_Base, IDependencyRegistryCompatibleV0)
                  returns (
                      string memory scriptTypeAndVersion,
                      string memory aspectRatio,
                      uint256 scriptCount
                  )
              {
                  Project storage project = projects[_projectId];
                  scriptTypeAndVersion = project.scriptTypeAndVersion.toString();
                  aspectRatio = project.aspectRatio;
                  scriptCount = project.scriptCount;
              }
              /**
               * @notice Returns address with bytecode containing project script for
               * project `_projectId` at script index `_index`.
               */
              function projectScriptBytecodeAddressByIndex(
                  uint256 _projectId,
                  uint256 _index
              ) external view returns (address) {
                  return projects[_projectId].scriptBytecodeAddresses[_index];
              }
              /**
               * @notice Returns script for project `_projectId` at script index `_index`.
               * @param _projectId Project to be queried.
               * @param _index Index of script to be queried.
               */
              function projectScriptByIndex(
                  uint256 _projectId,
                  uint256 _index
              ) external view returns (string memory) {
                  Project storage project = projects[_projectId];
                  // If trying to access an out-of-index script, return the empty string.
                  if (_index >= project.scriptCount) {
                      return "";
                  }
                  return project.scriptBytecodeAddresses[_index].readFromBytecode();
              }
              /**
               * @notice Returns base URI for project `_projectId`.
               * @param _projectId Project to be queried.
               * @return projectBaseURI Base URI for project
               */
              function projectURIInfo(
                  uint256 _projectId
              ) external view returns (string memory projectBaseURI) {
                  projectBaseURI = projects[_projectId].projectBaseURI;
              }
              /**
               * @notice Backwards-compatible (pre-V3) function returning if `_minter` is
               * minterContract.
               * @param _minter Address to be queried.
               * @return bool Boolean representing if `_minter` is minterContract.
               */
              function isMintWhitelisted(address _minter) external view returns (bool) {
                  return (minterContract == _minter);
              }
              /**
               * @notice Gets qty of randomizers in history of all randomizers used by
               * this core contract. If a randomizer is switched away from then back to,
               * it will show up in the history twice.
               * @return randomizerHistoryCount Count of randomizers in history
               */
              function numHistoricalRandomizers() external view returns (uint256) {
                  return _historicalRandomizerAddresses.length;
              }
              /**
               * @notice Gets address of randomizer at index `_index` in history of all
               * randomizers used by this core contract. Index is zero-based.
               * @param _index Historical index of randomizer to be queried.
               * @return randomizerAddress Address of randomizer at index `_index`.
               * @dev If a randomizer is switched away from and then switched back to, it
               * will show up in the history twice.
               */
              function getHistoricalRandomizerAt(
                  uint256 _index
              ) external view returns (address) {
                  require(
                      _index < _historicalRandomizerAddresses.length,
                      "Index out of bounds"
                  );
                  return _historicalRandomizerAddresses[_index];
              }
              /**
               * @notice Gets royalty Basis Points (BPS) for token ID `_tokenId`.
               * This conforms to the IManifold interface designated in the Royalty
               * Registry's RoyaltyEngineV1.sol contract.
               * ref: https://github.com/manifoldxyz/royalty-registry-solidity
               * @param _tokenId Token ID to be queried.
               * @return recipients Array of royalty payment recipients
               * @return bps Array of Basis Points (BPS) allocated to each recipient,
               * aligned by index.
               * @dev reverts if invalid _tokenId
               * @dev only returns recipients that have a non-zero BPS allocation
               */
              function getRoyalties(
                  uint256 _tokenId
              )
                  external
                  view
                  returns (address payable[] memory recipients, uint256[] memory bps)
              {
                  _onlyValidTokenId(_tokenId);
                  // initialize arrays with maximum potential length
                  recipients = new address payable[](4);
                  bps = new uint256[](4);
                  uint256 projectId = tokenIdToProjectId(_tokenId);
                  ProjectFinance storage projectFinance = projectIdToFinancials[
                      projectId
                  ];
                  // load values into memory
                  uint256 royaltyPercentageForArtistAndAdditional = projectFinance
                      .secondaryMarketRoyaltyPercentage;
                  uint256 additionalPayeePercentage = projectFinance
                      .additionalPayeeSecondarySalesPercentage;
                  // calculate BPS = percentage * 100
                  uint256 artistBPS = (ONE_HUNDRED - additionalPayeePercentage) *
                      royaltyPercentageForArtistAndAdditional;
                  uint256 additionalBPS = additionalPayeePercentage *
                      royaltyPercentageForArtistAndAdditional;
                  uint256 renderProviderBPS = renderProviderSecondarySalesBPS;
                  uint256 platformProviderBPS = platformProviderSecondarySalesBPS;
                  // populate arrays
                  uint256 payeeCount;
                  if (artistBPS > 0) {
                      recipients[payeeCount] = projectFinance.artistAddress;
                      bps[payeeCount++] = artistBPS;
                  }
                  if (additionalBPS > 0) {
                      recipients[payeeCount] = projectFinance
                          .additionalPayeeSecondarySales;
                      bps[payeeCount++] = additionalBPS;
                  }
                  if (renderProviderBPS > 0) {
                      recipients[payeeCount] = renderProviderSecondarySalesAddress;
                      bps[payeeCount++] = renderProviderBPS;
                  }
                  if (platformProviderBPS > 0) {
                      recipients[payeeCount] = platformProviderSecondarySalesAddress;
                      bps[payeeCount++] = platformProviderBPS;
                  }
                  // trim arrays if necessary
                  if (4 > payeeCount) {
                      assembly {
                          let decrease := sub(4, payeeCount)
                          mstore(recipients, sub(mload(recipients), decrease))
                          mstore(bps, sub(mload(bps), decrease))
                      }
                  }
                  return (recipients, bps);
              }
              /**
               * @notice View function that returns appropriate revenue splits between
               * different render provider, platform provider, Artist, and Artist's
               * additional primary sales payee given a sale price of `_price` on
               * project `_projectId`.
               * This always returns four revenue amounts and four addresses, but if a
               * revenue is zero for either Artist or additional payee, the corresponding
               * address returned will also be null (for gas optimization).
               * Does not account for refund if user overpays for a token (minter should
               * handle a refund of the difference, if appropriate).
               * Some minters may have alternative methods of splitting payments, in
               * which case they should implement their own payment splitting logic.
               * @param _projectId Project ID to be queried.
               * @param _price Sale price of token.
               * @return renderProviderRevenue_ amount of revenue to be sent to the
               * render provider
               * @return renderProviderAddress_ address to send render provider revenue to
               * @return platformProviderRevenue_ amount of revenue to be sent to the
               * platform provider
               * @return platformProviderAddress_ address to send platform provider revenue to
               * @return artistRevenue_ amount of revenue to be sent to Artist
               * @return artistAddress_ address to send Artist revenue to. Will be null
               * if no revenue is due to artist (gas optimization).
               * @return additionalPayeePrimaryRevenue_ amount of revenue to be sent to
               * additional payee for primary sales
               * @return additionalPayeePrimaryAddress_ address to send Artist's
               * additional payee for primary sales revenue to. Will be null if no
               * revenue is due to additional payee for primary sales (gas optimization).
               * @dev this always returns four addresses and four revenues, but if the
               * revenue is zero, the corresponding address will be address(0). It is up
               * to the contract performing the revenue split to handle this
               * appropriately.
               */
              function getPrimaryRevenueSplits(
                  uint256 _projectId,
                  uint256 _price
              )
                  external
                  view
                  returns (
                      uint256 renderProviderRevenue_,
                      address payable renderProviderAddress_,
                      uint256 platformProviderRevenue_,
                      address payable platformProviderAddress_,
                      uint256 artistRevenue_,
                      address payable artistAddress_,
                      uint256 additionalPayeePrimaryRevenue_,
                      address payable additionalPayeePrimaryAddress_
                  )
              {
                  ProjectFinance storage projectFinance = projectIdToFinancials[
                      _projectId
                  ];
                  // calculate revenues – this is a three-way split between the
                  // render provider, the platform provider, and the artist, and
                  // is safe to perform this given that in the case of loss of
                  // precision Solidity will round down.
                  uint256 projectFunds = _price;
                  renderProviderRevenue_ =
                      (_price * uint256(_renderProviderPrimarySalesPercentage)) /
                      ONE_HUNDRED;
                  // renderProviderRevenue_ percentage is always <=100, so guaranteed to never underflow
                  projectFunds -= renderProviderRevenue_;
                  platformProviderRevenue_ =
                      (_price * uint256(_platformProviderPrimarySalesPercentage)) /
                      ONE_HUNDRED;
                  // platformProviderRevenue_ percentage is always <=100, so guaranteed to never underflow
                  projectFunds -= platformProviderRevenue_;
                  additionalPayeePrimaryRevenue_ =
                      (projectFunds *
                          projectFinance.additionalPayeePrimarySalesPercentage) /
                      ONE_HUNDRED;
                  // projectIdToAdditionalPayeePrimarySalesPercentage is always
                  // <=100, so guaranteed to never underflow
                  artistRevenue_ = projectFunds - additionalPayeePrimaryRevenue_;
                  // set addresses from storage
                  renderProviderAddress_ = renderProviderPrimarySalesAddress;
                  platformProviderAddress_ = platformProviderPrimarySalesAddress;
                  if (artistRevenue_ > 0) {
                      artistAddress_ = projectFinance.artistAddress;
                  }
                  if (additionalPayeePrimaryRevenue_ > 0) {
                      additionalPayeePrimaryAddress_ = projectFinance
                          .additionalPayeePrimarySales;
                  }
              }
              /**
               * @notice Returns external asset dependency for project `_projectId` at index `_index`.
               * If the dependencyType is ONCHAIN, the `data` field will contain the extrated bytecode data and `cid`
               * will be an empty string. Conversly, for any other dependencyType, the `data` field will be an empty string
               * and the `bytecodeAddress` will point to the zero address.
               */
              function projectExternalAssetDependencyByIndex(
                  uint256 _projectId,
                  uint256 _index
              ) external view returns (ExternalAssetDependencyWithData memory) {
                  ExternalAssetDependency storage _dependency = projects[_projectId]
                      .externalAssetDependencies[_index];
                  address _bytecodeAddress = _dependency.bytecodeAddress;
                  return
                      ExternalAssetDependencyWithData({
                          dependencyType: _dependency.dependencyType,
                          cid: _dependency.cid,
                          bytecodeAddress: _bytecodeAddress,
                          data: (_dependency.dependencyType ==
                              ExternalAssetDependencyType.ONCHAIN)
                              ? _bytecodeAddress.readFromBytecode()
                              : ""
                      });
              }
              /**
               * @notice Returns external asset dependency count for project `_projectId` at index `_index`.
               */
              function projectExternalAssetDependencyCount(
                  uint256 _projectId
              ) external view returns (uint256) {
                  return uint256(projects[_projectId].externalAssetDependencyCount);
              }
              /**
               * @notice Backwards-compatible (pre-V3) getter returning contract admin
               * @return address Address of contract admin (same as owner)
               */
              function admin() external view returns (address) {
                  return owner();
              }
              /**
               * @notice Gets the project ID for a given `_tokenId`.
               * @param _tokenId Token ID to be queried.
               * @return _projectId Project ID for given `_tokenId`.
               */
              function tokenIdToProjectId(
                  uint256 _tokenId
              ) public pure returns (uint256 _projectId) {
                  return _tokenId / ONE_MILLION;
              }
              /**
               * @notice Convenience function that returns whether `_sender` is allowed
               * to call function with selector `_selector` on contract `_contract`, as
               * determined by this contract's current Admin ACL contract. Expected use
               * cases include minter contracts checking if caller is allowed to call
               * admin-gated functions on minter contracts.
               * @param _sender Address of the sender calling function with selector
               * `_selector` on contract `_contract`.
               * @param _contract Address of the contract being called by `_sender`.
               * @param _selector Function selector of the function being called by
               * `_sender`.
               * @return bool Whether `_sender` is allowed to call function with selector
               * `_selector` on contract `_contract`.
               * @dev assumes the Admin ACL contract is the owner of this contract, which
               * is expected to always be true.
               * @dev adminACLContract is expected to either be null address (if owner
               * has renounced ownership), or conform to IAdminACLV0 interface. Check for
               * null address first to avoid revert when admin has renounced ownership.
               */
              function adminACLAllowed(
                  address _sender,
                  address _contract,
                  bytes4 _selector
              ) public returns (bool) {
                  return
                      owner() != address(0) &&
                      adminACLContract.allowed(_sender, _contract, _selector);
              }
              /**
               * @notice Returns contract owner. Set to deployer's address by default on
               * contract deployment.
               * @return address Address of contract owner.
               * @dev ref: https://docs.openzeppelin.com/contracts/4.x/api/access#Ownable
               * @dev owner role was called `admin` prior to V3 core contract
               */
              function owner()
                  public
                  view
                  override(Ownable, IGenArt721CoreContractV3_Base)
                  returns (address)
              {
                  return Ownable.owner();
              }
              /**
               * @notice Gets token URI for token ID `_tokenId`.
               * @param _tokenId Token ID to be queried.
               * @return string URI of token ID `_tokenId`.
               * @dev token URIs are the concatenation of the project base URI and the
               * token ID.
               */
              function tokenURI(
                  uint256 _tokenId
              ) public view override returns (string memory) {
                  _onlyValidTokenId(_tokenId);
                  string memory _projectBaseURI = projects[tokenIdToProjectId(_tokenId)]
                      .projectBaseURI;
                  return string.concat(_projectBaseURI, toString(_tokenId));
              }
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(
                  bytes4 interfaceId
              ) public view virtual override returns (bool) {
                  return
                      interfaceId == type(IManifold).interfaceId ||
                      super.supportsInterface(interfaceId);
              }
              /**
               * @notice Forbids new projects from being created
               * @dev only performs operation and emits event if contract is not already
               * forbidding new projects.
               */
              function _forbidNewProjects() internal {
                  if (!newProjectsForbidden) {
                      newProjectsForbidden = true;
                      emit PlatformUpdated(FIELD_NEW_PROJECTS_FORBIDDEN);
                  }
              }
              /**
               * @notice Transfers ownership of the contract to a new account (`newOwner`).
               * Internal function without access restriction.
               * @param newOwner New owner.
               * @dev owner role was called `admin` prior to V3 core contract.
               * @dev Overrides and wraps OpenZeppelin's _transferOwnership function to
               * also update adminACLContract for improved introspection.
               */
              function _transferOwnership(address newOwner) internal override {
                  Ownable._transferOwnership(newOwner);
                  adminACLContract = IAdminACLV0(newOwner);
              }
              /**
               * @notice Updates sales addresses for the platform and render providers to
               * the input parameters.
               * @param _renderProviderPrimarySalesAddress Address of new primary sales
               * payment address.
               * @param _renderProviderSecondarySalesAddress Address of new secondary sales
               * payment address.
               * @param _platformProviderPrimarySalesAddress Address of new primary sales
               * payment address.
               * @param _platformProviderSecondarySalesAddress Address of new secondary sales
               * payment address.
               * @dev Note that this method does not check that the input address is
               * not `address(0)`, as it is expected that callers of this method should
               * perform input validation where applicable.
               */
              function _updateProviderSalesAddresses(
                  address _renderProviderPrimarySalesAddress,
                  address _renderProviderSecondarySalesAddress,
                  address _platformProviderPrimarySalesAddress,
                  address _platformProviderSecondarySalesAddress
              ) internal {
                  platformProviderPrimarySalesAddress = payable(
                      _platformProviderPrimarySalesAddress
                  );
                  platformProviderSecondarySalesAddress = payable(
                      _platformProviderSecondarySalesAddress
                  );
                  renderProviderPrimarySalesAddress = payable(
                      _renderProviderPrimarySalesAddress
                  );
                  renderProviderSecondarySalesAddress = payable(
                      _renderProviderSecondarySalesAddress
                  );
                  emit PlatformUpdated(FIELD_PROVIDER_SALES_ADDRESSES);
              }
              /**
               * @notice Updates randomizer address to `_randomizerAddress`.
               * @param _randomizerAddress New randomizer address.
               * @dev Note that this method does not check that the input address is
               * not `address(0)`, as it is expected that callers of this method should
               * perform input validation where applicable.
               */
              function _updateRandomizerAddress(address _randomizerAddress) internal {
                  randomizerContract = IRandomizerV2(_randomizerAddress);
                  // populate historical randomizer array
                  _historicalRandomizerAddresses.push(_randomizerAddress);
                  emit PlatformUpdated(FIELD_RANDOMIZER_ADDRESS);
              }
              /**
               * @notice Updates default base URI to `_defaultBaseURI`.
               * When new projects are added, their `projectBaseURI` is automatically
               * initialized to `_defaultBaseURI`.
               * @param _defaultBaseURI New default base URI.
               * @dev Note that this method does not check that the input string is not
               * the empty string, as it is expected that callers of this method should
               * perform input validation where applicable.
               */
              function _updateDefaultBaseURI(string memory _defaultBaseURI) internal {
                  defaultBaseURI = _defaultBaseURI;
                  emit PlatformUpdated(FIELD_DEFAULT_BASE_URI);
              }
              /**
               * @notice Internal function to complete a project.
               * @param _projectId Project ID to be completed.
               */
              function _completeProject(uint256 _projectId) internal {
                  projects[_projectId].completedTimestamp = uint64(block.timestamp);
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_COMPLETED);
              }
              /**
               * @notice Internal function that returns whether a project is unlocked.
               * Projects automatically lock four weeks after they are completed.
               * Projects are considered completed when they have been invoked the
               * maximum number of times.
               * @param _projectId Project ID to be queried.
               * @return bool true if project is unlocked, false otherwise.
               * @dev This also enforces that the `_projectId` passed in is valid.
               */
              function _projectUnlocked(uint256 _projectId) internal view returns (bool) {
                  _onlyValidProjectId(_projectId);
                  uint256 projectCompletedTimestamp = projects[_projectId]
                      .completedTimestamp;
                  bool projectOpen = projectCompletedTimestamp == 0;
                  return
                      projectOpen ||
                      (block.timestamp - projectCompletedTimestamp <
                          FOUR_WEEKS_IN_SECONDS);
              }
              // strings library from OpenZeppelin, modified for no constants
              bytes16 private _HEX_SYMBOLS = "0123456789abcdef";
              uint8 private _ADDRESS_LENGTH = 20;
              /**
               * @dev Converts a `uint256` to its ASCII `string` decimal representation.
               */
              function toString(uint256 value) internal pure returns (string memory) {
                  // Inspired by OraclizeAPI's implementation - MIT licence
                  // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
                  if (value == 0) {
                      return "0";
                  }
                  uint256 temp = value;
                  uint256 digits;
                  while (temp != 0) {
                      digits++;
                      temp /= 10;
                  }
                  bytes memory buffer = new bytes(digits);
                  while (value != 0) {
                      digits -= 1;
                      buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
                      value /= 10;
                  }
                  return string(buffer);
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
               */
              function toHexString(
                  uint256 value,
                  uint256 length
              ) internal view returns (string memory) {
                  bytes memory buffer = new bytes(2 * length + 2);
                  buffer[0] = "0";
                  buffer[1] = "x";
                  for (uint256 i = 2 * length + 1; i > 1; --i) {
                      buffer[i] = _HEX_SYMBOLS[value & 0xf];
                      value >>= 4;
                  }
                  require(value == 0, "hex length insufficient");
                  return string(buffer);
              }
              /**
               * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
               */
              function toHexString(address addr) internal view returns (string memory) {
                  return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
              }
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.0;
          interface IAdminACLV0 {
              /**
               * @notice Token ID `_tokenId` minted to `_to`.
               * @param previousSuperAdmin The previous superAdmin address.
               * @param newSuperAdmin The new superAdmin address.
               * @param genArt721CoreAddressesToUpdate Array of genArt721Core
               * addresses to update to the new superAdmin, for indexing purposes only.
               */
              event SuperAdminTransferred(
                  address indexed previousSuperAdmin,
                  address indexed newSuperAdmin,
                  address[] genArt721CoreAddressesToUpdate
              );
              /// Type of the Admin ACL contract, e.g. "AdminACLV0"
              function AdminACLType() external view returns (string memory);
              /// super admin address
              function superAdmin() external view returns (address);
              /**
               * @notice Calls transferOwnership on other contract from this contract.
               * This is useful for updating to a new AdminACL contract.
               * @dev this function should be gated to only superAdmin-like addresses.
               */
              function transferOwnershipOn(
                  address _contract,
                  address _newAdminACL
              ) external;
              /**
               * @notice Calls renounceOwnership on other contract from this contract.
               * @dev this function should be gated to only superAdmin-like addresses.
               */
              function renounceOwnershipOn(address _contract) external;
              /**
               * @notice Checks if sender `_sender` is allowed to call function with selector
               * `_selector` on contract `_contract`.
               */
              function allowed(
                  address _sender,
                  address _contract,
                  bytes4 _selector
              ) external returns (bool);
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.17;
          interface IDependencyRegistryCompatibleV0 {
              /// Dependency registry managed by Art Blocks
              function artblocksDependencyRegistryAddress()
                  external
                  view
                  returns (address);
              /**
               * @notice Returns script information for project `_projectId`.
               * @param _projectId Project to be queried.
               * @return scriptTypeAndVersion Project's script type and version
               * (e.g. "p5js(atSymbol)1.0.0")
               * @return aspectRatio Aspect ratio of project (e.g. "1" for square,
               * "1.77777778" for 16:9, etc.)
               * @return scriptCount Count of scripts for project
               */
              function projectScriptDetails(
                  uint256 _projectId
              )
                  external
                  view
                  returns (
                      string memory scriptTypeAndVersion,
                      string memory aspectRatio,
                      uint256 scriptCount
                  );
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.17;
          interface IEngineRegistryV0 {
              /// ADDRESS
              /**
               * @notice contract has been registered as a contract that is powered by the Art Blocks Engine.
               */
              event ContractRegistered(
                  address indexed _contractAddress,
                  bytes32 _coreVersion,
                  bytes32 _coreType
              );
              /// ADDRESS
              /**
               * @notice contract has been unregistered as a contract that is powered by the Art Blocks Engine.
               */
              event ContractUnregistered(address indexed _contractAddress);
              /**
               * @notice Emits a `ContractRegistered` event with the provided information.
               * @dev this function should be gated to only deployer addresses.
               */
              function registerContract(
                  address _contractAddress,
                  bytes32 _coreVersion,
                  bytes32 _coreType
              ) external;
              /**
               * @notice Emits a `ContractUnregistered` event with the provided information, validating that the provided
               *         address was indeed previously registered.
               * @dev this function should be gated to only deployer addresses.
               */
              function unregisterContract(address _contractAddress) external;
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.0;
          import "./IAdminACLV0.sol";
          /// use the Royalty Registry's IManifold interface for token royalties
          import "./IManifold.sol";
          /**
           * @title This interface is intended to house interface items that are common
           * across all GenArt721CoreContractV3 flagship and derivative implementations.
           * This interface extends the IManifold royalty interface in order to
           * add support the Royalty Registry by default.
           * @author Art Blocks Inc.
           */
          interface IGenArt721CoreContractV3_Base is IManifold {
              /**
               * @notice Token ID `_tokenId` minted to `_to`.
               */
              event Mint(address indexed _to, uint256 indexed _tokenId);
              /**
               * @notice currentMinter updated to `_currentMinter`.
               * @dev Implemented starting with V3 core
               */
              event MinterUpdated(address indexed _currentMinter);
              /**
               * @notice Platform updated on bytes32-encoded field `_field`.
               */
              event PlatformUpdated(bytes32 indexed _field);
              /**
               * @notice Project ID `_projectId` updated on bytes32-encoded field
               * `_update`.
               */
              event ProjectUpdated(uint256 indexed _projectId, bytes32 indexed _update);
              event ProposedArtistAddressesAndSplits(
                  uint256 indexed _projectId,
                  address _artistAddress,
                  address _additionalPayeePrimarySales,
                  uint256 _additionalPayeePrimarySalesPercentage,
                  address _additionalPayeeSecondarySales,
                  uint256 _additionalPayeeSecondarySalesPercentage
              );
              event AcceptedArtistAddressesAndSplits(uint256 indexed _projectId);
              // version and type of the core contract
              // coreVersion is a string of the form "0.x.y"
              function coreVersion() external view returns (string memory);
              // coreType is a string of the form "GenArt721CoreV3"
              function coreType() external view returns (string memory);
              // owner (pre-V3 was named admin) of contract
              // this is expected to be an Admin ACL contract for V3
              function owner() external view returns (address);
              // Admin ACL contract for V3, will be at the address owner()
              function adminACLContract() external returns (IAdminACLV0);
              // backwards-compatible (pre-V3) admin - equal to owner()
              function admin() external view returns (address);
              /**
               * Function determining if _sender is allowed to call function with
               * selector _selector on contract `_contract`. Intended to be used with
               * peripheral contracts such as minters, as well as internally by the
               * core contract itself.
               */
              function adminACLAllowed(
                  address _sender,
                  address _contract,
                  bytes4 _selector
              ) external returns (bool);
              // getter function of public variable
              function nextProjectId() external view returns (uint256);
              // getter function of public mapping
              function tokenIdToProjectId(
                  uint256 tokenId
              ) external view returns (uint256 projectId);
              // @dev this is not available in V0
              function isMintWhitelisted(address minter) external view returns (bool);
              function projectIdToArtistAddress(
                  uint256 _projectId
              ) external view returns (address payable);
              function projectIdToAdditionalPayeePrimarySales(
                  uint256 _projectId
              ) external view returns (address payable);
              function projectIdToAdditionalPayeePrimarySalesPercentage(
                  uint256 _projectId
              ) external view returns (uint256);
              function projectIdToSecondaryMarketRoyaltyPercentage(
                  uint256 _projectId
              ) external view returns (uint256);
              function projectURIInfo(
                  uint256 _projectId
              ) external view returns (string memory projectBaseURI);
              // @dev new function in V3
              function projectStateData(
                  uint256 _projectId
              )
                  external
                  view
                  returns (
                      uint256 invocations,
                      uint256 maxInvocations,
                      bool active,
                      bool paused,
                      uint256 completedTimestamp,
                      bool locked
                  );
              function projectDetails(
                  uint256 _projectId
              )
                  external
                  view
                  returns (
                      string memory projectName,
                      string memory artist,
                      string memory description,
                      string memory website,
                      string memory license
                  );
              function projectScriptDetails(
                  uint256 _projectId
              )
                  external
                  view
                  returns (
                      string memory scriptTypeAndVersion,
                      string memory aspectRatio,
                      uint256 scriptCount
                  );
              function projectScriptByIndex(
                  uint256 _projectId,
                  uint256 _index
              ) external view returns (string memory);
              function tokenIdToHash(uint256 _tokenId) external view returns (bytes32);
              // function to set a token's hash (must be guarded)
              function setTokenHash_8PT(uint256 _tokenId, bytes32 _hash) external;
              // @dev gas-optimized signature in V3 for `mint`
              function mint_Ecf(
                  address _to,
                  uint256 _projectId,
                  address _by
              ) external returns (uint256 tokenId);
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.0;
          import "./IAdminACLV0.sol";
          import "./IGenArt721CoreContractV3_Engine.sol";
          /**
           * @title This interface is intended to house interface items that are common
           * across all GenArt721CoreContractV3 Engine Flex and derivative implementations.
           * @author Art Blocks Inc.
           */
          interface IGenArt721CoreContractV3_Engine_Flex is
              IGenArt721CoreContractV3_Engine
          {
              /**
               * @notice When an external asset dependency is updated or added, this event is emitted.
               * @param _projectId The project ID of the project that was updated.
               * @param _index The index of the external asset dependency that was updated.
               * @param _cid The content ID of the external asset dependency. This is an empty string
               * if the dependency type is ONCHAIN.
               * @param _dependencyType The type of the external asset dependency.
               * @param _externalAssetDependencyCount The number of external asset dependencies.
               */
              event ExternalAssetDependencyUpdated(
                  uint256 indexed _projectId,
                  uint256 indexed _index,
                  string _cid,
                  ExternalAssetDependencyType _dependencyType,
                  uint24 _externalAssetDependencyCount
              );
              /**
               * @notice The project id `_projectId` has had an external asset dependency removed at index `_index`.
               */
              event ExternalAssetDependencyRemoved(
                  uint256 indexed _projectId,
                  uint256 indexed _index
              );
              /**
               * @notice The preferred gateway for dependency type `_dependencyType` has been updated to `_gatewayAddress`.
               */
              event GatewayUpdated(
                  ExternalAssetDependencyType indexed _dependencyType,
                  string _gatewayAddress
              );
              /**
               * @notice The project id `_projectId` has had all external asset dependencies locked.
               * @dev This is a one-way operation. Once locked, the external asset dependencies cannot be updated.
               */
              event ProjectExternalAssetDependenciesLocked(uint256 indexed _projectId);
              /**
               * @notice An external asset dependency type. Can be one of IPFS, ARWEAVE, or ONCHAIN.
               */
              enum ExternalAssetDependencyType {
                  IPFS,
                  ARWEAVE,
                  ONCHAIN
              }
              /**
               * @notice An external asset dependency. This is a struct that contains the CID of the dependency,
               * the type of the dependency, and the address of the bytecode for this dependency.
               */
              struct ExternalAssetDependency {
                  string cid;
                  ExternalAssetDependencyType dependencyType;
                  address bytecodeAddress;
              }
              /**
               * @notice An external asset dependency with data. This is a convenience struct that contains the CID of the dependency,
               * the type of the dependency, the address of the bytecode for this dependency, and the data retrieved from this bytecode address.
               */
              struct ExternalAssetDependencyWithData {
                  string cid;
                  ExternalAssetDependencyType dependencyType;
                  address bytecodeAddress;
                  string data;
              }
              // preferredIPFSGateway is a url string
              function preferredIPFSGateway() external view returns (string memory);
              // preferredArweaveGateway is a url string
              function preferredArweaveGateway() external view returns (string memory);
              // updates the preferred IPFS gateway
              function updateIPFSGateway(string calldata _gateway) external;
              // updates the preferred Arweave gateway
              function updateArweaveGateway(string calldata _gateway) external;
              // locks the external asset dependencies for a project
              function lockProjectExternalAssetDependencies(uint256 _projectId) external;
              // updates the external asset dependency for a project at a given index
              function updateProjectExternalAssetDependency(
                  uint256 _projectId,
                  uint256 _index,
                  string memory _cidOrData,
                  ExternalAssetDependencyType _dependencyType
              ) external;
              // adds an external asset dependency for a project
              function addProjectExternalAssetDependency(
                  uint256 _projectId,
                  string memory _cidOrData,
                  ExternalAssetDependencyType _dependencyType
              ) external;
              // removes an external asset dependency for a project at a given index
              function removeProjectExternalAssetDependency(
                  uint256 _projectId,
                  uint256 _index
              ) external;
              // getter function for project external asset dependencies
              function projectExternalAssetDependencyByIndex(
                  uint256 _projectId,
                  uint256 _index
              ) external view returns (ExternalAssetDependencyWithData memory);
              // getter function project external asset dependency count
              function projectExternalAssetDependencyCount(
                  uint256 _projectId
              ) external view returns (uint256);
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.0;
          import "./IAdminACLV0.sol";
          import "./IGenArt721CoreContractV3_Base.sol";
          interface IGenArt721CoreContractV3_Engine is IGenArt721CoreContractV3_Base {
              // @dev new function in V3
              function getPrimaryRevenueSplits(
                  uint256 _projectId,
                  uint256 _price
              )
                  external
                  view
                  returns (
                      uint256 renderProviderRevenue_,
                      address payable renderProviderAddress_,
                      uint256 platformProviderRevenue_,
                      address payable platformProviderAddress_,
                      uint256 artistRevenue_,
                      address payable artistAddress_,
                      uint256 additionalPayeePrimaryRevenue_,
                      address payable additionalPayeePrimaryAddress_
                  );
              // @dev The render provider primary sales payment address
              function renderProviderPrimarySalesAddress()
                  external
                  view
                  returns (address payable);
              // @dev The platform provider primary sales payment address
              function platformProviderPrimarySalesAddress()
                  external
                  view
                  returns (address payable);
              // @dev Percentage of primary sales allocated to the render provider
              function renderProviderPrimarySalesPercentage()
                  external
                  view
                  returns (uint256);
              // @dev Percentage of primary sales allocated to the platform provider
              function platformProviderPrimarySalesPercentage()
                  external
                  view
                  returns (uint256);
              // @dev The render provider secondary sales royalties payment address
              function renderProviderSecondarySalesAddress()
                  external
                  view
                  returns (address payable);
              // @dev The platform provider secondary sales royalties payment address
              function platformProviderSecondarySalesAddress()
                  external
                  view
                  returns (address payable);
              // @dev Basis points of secondary sales allocated to the render provider
              function renderProviderSecondarySalesBPS() external view returns (uint256);
              // @dev Basis points of secondary sales allocated to the platform provider
              function platformProviderSecondarySalesBPS()
                  external
                  view
                  returns (uint256);
              // function to read the hash-seed for a given tokenId
              function tokenIdToHashSeed(
                  uint256 _tokenId
              ) external view returns (bytes12);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          /// @dev Royalty Registry interface, used to support the Royalty Registry.
          /// @dev Source: https://github.com/manifoldxyz/royalty-registry-solidity/blob/main/contracts/specs/IManifold.sol
          /// @author: manifold.xyz
          /**
           * @dev Royalty interface for creator core classes
           */
          interface IManifold {
              /**
               * @dev Get royalites of a token.  Returns list of receivers and basisPoints
               *
               *  bytes4(keccak256('getRoyalties(uint256)')) == 0xbb3bafd6
               *
               *  => 0xbb3bafd6 = 0xbb3bafd6
               */
              function getRoyalties(
                  uint256 tokenId
              ) external view returns (address payable[] memory, uint256[] memory);
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Creatd By: Art Blocks Inc.
          pragma solidity ^0.8.0;
          import "./IGenArt721CoreContractV3_Base.sol";
          interface IRandomizerV2 {
              // The core contract that may interact with this randomizer contract.
              function genArt721Core()
                  external
                  view
                  returns (IGenArt721CoreContractV3_Base);
              // When a core contract calls this, it can be assured that the randomizer
              // will set a bytes32 hash for tokenId `_tokenId` on the core contract.
              function assignTokenHash(uint256 _tokenId) external;
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.0;
          /**
           * @title Art Blocks Script Storage Library
           * @notice Utilize contract bytecode as persistant storage for large chunks of script string data.
           *
           * @author Art Blocks Inc.
           * @author Modified from 0xSequence (https://github.com/0xsequence/sstore2/blob/master/contracts/SSTORE2.sol)
           * @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SSTORE2.sol)
           *
           * @dev Compared to the above two rerferenced libraries, this contracts-as-storage implementation makes a few
           *      notably different design decisions:
           *      - uses the `string` data type for input/output on reads, rather than speaking in bytes directly
           *      - exposes "delete" functionality, allowing no-longer-used storage to be purged from chain state
           *      - stores the "writer" address (library user) in the deployed contract bytes, which is useful for both:
           *         a) providing necessary information for safe deletion; and
           *         b) allowing this to be introspected on-chain
           *      Also, given that much of this library is written in assembly, this library makes use of a slightly
           *      different convention (when compared to the rest of the Art Blocks smart contract repo) around
           *      pre-defining return values in some cases in order to simplify need to directly memory manage these
           *      return values.
           */
          library BytecodeStorage {
              //---------------------------------------------------------------------------------------------------------------//
              // Starting Index | Size | Ending Index | Description                                                            //
              //---------------------------------------------------------------------------------------------------------------//
              // 0              | N/A  | 0            |                                                                        //
              // 0              | 72   | 72           | the bytes of the gated-cleanup-logic allowing for `selfdestruct`ion    //
              // 72             | 32   | 104          | the 32 bytes for storing the deploying contract's (0-padded) address   //
              //---------------------------------------------------------------------------------------------------------------//
              // Define the offset for where the "logic bytes" end, and the "data bytes" begin. Note that this is a manually
              // calculated value, and must be updated if the above table is changed. It is expected that tests will fail
              // loudly if these values are not updated in-step with eachother.
              uint256 internal constant DATA_OFFSET = 104;
              uint256 internal constant ADDRESS_OFFSET = 72;
              /*//////////////////////////////////////////////////////////////
                                     WRITE LOGIC
              //////////////////////////////////////////////////////////////*/
              /**
               * @notice Write a string to contract bytecode
               * @param _data string to be written to contract. No input validation is performed on this parameter.
               * @return address_ address of deployed contract with bytecode containing concat(gated-cleanup-logic, address, data)
               */
              function writeToBytecode(
                  string memory _data
              ) internal returns (address address_) {
                  // prefix bytecode with
                  bytes memory creationCode = abi.encodePacked(
                      //---------------------------------------------------------------------------------------------------------------//
                      // Opcode  | Opcode + Arguments  | Description  | Stack View                                                     //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (0) creation code returns all code in the contract except for the first 11 (0B in hex) bytes, as these 11
                      //     bytes are the creation code itself which we do not want to store in the deployed storage contract result
                      //---------------------------------------------------------------------------------------------------------------//
                      // 0x60    |  0x60_0B            | PUSH1 11     | codeOffset                                                     //
                      // 0x59    |  0x59               | MSIZE        | 0 codeOffset                                                   //
                      // 0x81    |  0x81               | DUP2         | codeOffset 0 codeOffset                                        //
                      // 0x38    |  0x38               | CODESIZE     | codeSize codeOffset 0 codeOffset                               //
                      // 0x03    |  0x03               | SUB          | (codeSize - codeOffset) 0 codeOffset                           //
                      // 0x80    |  0x80               | DUP          | (codeSize - codeOffset) (codeSize - codeOffset) 0 codeOffset   //
                      // 0x92    |  0x92               | SWAP3        | codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset)   //
                      // 0x59    |  0x59               | MSIZE        | 0 codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset) //
                      // 0x39    |  0x39               | CODECOPY     | 0 (codeSize - codeOffset)                                      //
                      // 0xf3    |  0xf3               | RETURN       |                                                                //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (11 bytes)
                      hex"60_0B_59_81_38_03_80_92_59_39_F3",
                      //---------------------------------------------------------------------------------------------------------------//
                      // Opcode  | Opcode + Arguments  | Description  | Stack View                                                     //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (1a) conditional logic for determing purge-gate (only the bytecode contract deployer can `selfdestruct`)
                      //---------------------------------------------------------------------------------------------------------------//
                      // 0x60    |  0x60_20            | PUSH1 32           | 32                                                       //
                      // 0x60    |  0x60_48            | PUSH1 72 (*)       | contractOffset 32                                        //
                      // 0x60    |  0x60_00            | PUSH1 0            | 0 contractOffset 32                                      //
                      // 0x39    |  0x39               | CODECOPY           |                                                          //
                      // 0x60    |  0x60_00            | PUSH1 0            | 0                                                        //
                      // 0x51    |  0x51               | MLOAD              | byteDeployerAddress                                      //
                      // 0x33    |  0x33               | CALLER             | msg.sender byteDeployerAddress                           //
                      // 0x14    |  0x14               | EQ                 | (msg.sender == byteDeployerAddress)                      //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (12 bytes: 0-11 in deployed contract)
                      hex"60_20_60_48_60_00_39_60_00_51_33_14",
                      //---------------------------------------------------------------------------------------------------------------//
                      // (1b) load up the destination jump address for `(2a) calldata length check` logic, jump or raise `invalid` op-code
                      //---------------------------------------------------------------------------------------------------------------//
                      // 0x60    |  0x60_10            | PUSH1 16 (^)       | jumpDestination (msg.sender == byteDeployerAddress)      //
                      // 0x57    |  0x57               | JUMPI              |                                                          //
                      // 0xFE    |  0xFE               | INVALID            |                                                          //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (4 bytes: 12-15 in deployed contract)
                      hex"60_10_57_FE",
                      //---------------------------------------------------------------------------------------------------------------//
                      // (2a) conditional logic for determing purge-gate (only if calldata length is 1 byte)
                      //---------------------------------------------------------------------------------------------------------------//
                      // 0x5B    |  0x5B               | JUMPDEST (16)      |                                                          //
                      // 0x60    |  0x60_01            | PUSH1 1            | 1                                                        //
                      // 0x36    |  0x36               | CALLDATASIZE       | calldataSize 1                                           //
                      // 0x14    |  0x14               | EQ                 | (calldataSize == 1)                                      //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (5 bytes: 16-20 in deployed contract)
                      hex"5B_60_01_36_14",
                      //---------------------------------------------------------------------------------------------------------------//
                      // (2b) load up the destination jump address for `(3a) calldata value check` logic, jump or raise `invalid` op-code
                      //---------------------------------------------------------------------------------------------------------------//
                      // 0x60    |  0x60_19            | PUSH1 25 (^)       | jumpDestination (calldataSize == 1)                      //
                      // 0x57    |  0x57               | JUMPI              |                                                          //
                      // 0xFE    |  0xFE               | INVALID            |                                                          //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (4 bytes: 21-24 in deployed contract)
                      hex"60_19_57_FE",
                      //---------------------------------------------------------------------------------------------------------------//
                      // (3a) conditional logic for determing purge-gate (only if calldata is `0xFF`)
                      //---------------------------------------------------------------------------------------------------------------//
                      // 0x5B    |  0x5B               | JUMPDEST (25)      |                                                          //
                      // 0x60    |  0x60_00            | PUSH1 0            | 0                                                        //
                      // 0x35    |  0x35               | CALLDATALOAD       | calldata                                                 //
                      // 0x7F    |  0x7F_FF_00_..._00  | PUSH32 0xFF00...00 | 0xFF0...00 calldata                                      //
                      // 0x14    |  0x14               | EQ                 | (0xFF00...00 == calldata)                                //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (4 bytes: 25-28 in deployed contract)
                      hex"5B_60_00_35",
                      // (33 bytes: 29-61 in deployed contract)
                      hex"7F_FF_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00",
                      // (1 byte: 62 in deployed contract)
                      hex"14",
                      //---------------------------------------------------------------------------------------------------------------//
                      // (3b) load up the destination jump address for actual purging (4), jump or raise `invalid` op-code
                      //---------------------------------------------------------------------------------------------------------------//
                      // 0x60    |  0x60_43            | PUSH1 67 (^)       | jumpDestination (0xFF00...00 == calldata)                //
                      // 0x57    |  0x57               | JUMPI              |                                                          //
                      // 0xFE    |  0xFE               | INVALID            |                                                          //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (4 bytes: 63-66 in deployed contract)
                      hex"60_43_57_FE",
                      //---------------------------------------------------------------------------------------------------------------//
                      // (4) perform actual purging
                      //---------------------------------------------------------------------------------------------------------------//
                      // 0x5B    |  0x5B               | JUMPDEST (67)      |                                                          //
                      // 0x60    |  0x60_00            | PUSH1 0            | 0                                                        //
                      // 0x51    |  0x51               | MLOAD              | byteDeployerAddress                                      //
                      // 0xFF    |  0xFF               | SELFDESTRUCT       |                                                          //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (5 bytes: 67-71 in deployed contract)
                      hex"5B_60_00_51_FF",
                      //---------------------------------------------------------------------------------------------------------------//
                      // (*) Note: this value must be adjusted if selfdestruct purge logic is adjusted, to refer to the correct start  //
                      //           offset for where the `msg.sender` address was stored in deployed bytecode.                          //
                      //                                                                                                               //
                      // (^) Note: this value must be adjusted if portions of the selfdestruct purge logic are adjusted.               //
                      //---------------------------------------------------------------------------------------------------------------//
                      //
                      // store the deploying-contract's address (to be used to gate and call `selfdestruct`),
                      // with expected 0-padding to fit a 20-byte address into a 30-byte slot.
                      //
                      // note: it is important that this address is the executing contract's address
                      //      (the address that represents the client-application smart contract of this library)
                      //      which means that it is the responsibility of the client-application smart contract
                      //      to determine how deletes are gated (or if they are exposed at all) as it is only
                      //      this contract that will be able to call `purgeBytecode` as the `CALLER` that is
                      //      checked above (op-code 0x33).
                      hex"00_00_00_00_00_00_00_00_00_00_00_00", // left-pad 20-byte address with 12 0x00 bytes
                      address(this),
                      // uploaded data (stored as bytecode) comes last
                      _data
                  );
                  assembly {
                      // deploy a new contract with the generated creation code.
                      // start 32 bytes into creationCode to avoid copying the byte length.
                      address_ := create(0, add(creationCode, 0x20), mload(creationCode))
                  }
                  // address must be non-zero if contract was deployed successfully
                  require(address_ != address(0), "ContractAsStorage: Write Error");
              }
              /*//////////////////////////////////////////////////////////////
                                         READ LOGIC
              //////////////////////////////////////////////////////////////*/
              /**
               * @notice Read a string from contract bytecode
               * @param _address address of deployed contract with bytecode containing concat(gated-cleanup-logic, address, data)
               * @return data string read from contract bytecode
               */
              function readFromBytecode(
                  address _address
              ) internal view returns (string memory data) {
                  // get the size of the bytecode
                  uint256 bytecodeSize = _bytecodeSizeAt(_address);
                  // handle case where address contains code < DATA_OFFSET
                  // note: the first check here also captures the case where
                  //       (bytecodeSize == 0) implicitly, but we add the second check of
                  //       (bytecodeSize == 0) as a fall-through that will never execute
                  //       unless `DATA_OFFSET` is set to 0 at some point.
                  if ((bytecodeSize < DATA_OFFSET) || (bytecodeSize == 0)) {
                      revert("ContractAsStorage: Read Error");
                  }
                  // handle case where address contains code >= DATA_OFFSET
                  // decrement by DATA_OFFSET to account for purge logic
                  uint256 size;
                  unchecked {
                      size = bytecodeSize - DATA_OFFSET;
                  }
                  assembly {
                      // allocate free memory
                      data := mload(0x40)
                      // update free memory pointer
                      // use and(x, not(0x1f) as cheaper equivalent to sub(x, mod(x, 0x20)).
                      // adding 0x1f to size + logic above ensures the free memory pointer
                      // remains word-aligned, following the Solidity convention.
                      mstore(0x40, add(data, and(add(add(size, 0x20), 0x1f), not(0x1f))))
                      // store length of data in first 32 bytes
                      mstore(data, size)
                      // copy code to memory, excluding the gated-cleanup-logic and address
                      extcodecopy(_address, add(data, 0x20), DATA_OFFSET, size)
                  }
              }
              /**
               * @notice Get address for deployer for given contract bytecode
               * @param _address address of deployed contract with bytecode containing concat(gated-cleanup-logic, address, data)
               * @return writerAddress address read from contract bytecode
               */
              function getWriterAddressForBytecode(
                  address _address
              ) internal view returns (address) {
                  // get the size of the data
                  uint256 bytecodeSize = _bytecodeSizeAt(_address);
                  // handle case where address contains code < DATA_OFFSET
                  // note: the first check here also captures the case where
                  //       (bytecodeSize == 0) implicitly, but we add the second check of
                  //       (bytecodeSize == 0) as a fall-through that will never execute
                  //       unless `DATA_OFFSET` is set to 0 at some point.
                  if ((bytecodeSize < DATA_OFFSET) || (bytecodeSize == 0)) {
                      revert("ContractAsStorage: Read Error");
                  }
                  assembly {
                      // allocate free memory
                      let writerAddress := mload(0x40)
                      // shift free memory pointer by one slot
                      mstore(0x40, add(mload(0x40), 0x20))
                      // copy the 32-byte address of the data contract writer to memory
                      // note: this relies on the assumption noted at the top-level of
                      //       this file that the storage layout for the deployed
                      //       contracts-as-storage contract looks like:
                      //       | gated-cleanup-logic | deployer-address (padded) | data |
                      extcodecopy(
                          _address,
                          writerAddress,
                          ADDRESS_OFFSET,
                          0x20 // full 32-bytes, as address is expected to be zero-padded
                      )
                      return(
                          writerAddress,
                          0x20 // return size is entire slot, as it is zero-padded
                      )
                  }
              }
              /*//////////////////////////////////////////////////////////////
                                        DELETE LOGIC
              //////////////////////////////////////////////////////////////*/
              /**
               * @notice Purge contract bytecode for cleanup purposes
               * note: Although this does reduce usage of Ethereum state, it does not reduce the gas costs of removal
               * transactions. We believe this is the best behavior at the time of writing, and do not expect this to
               * result in any breaking changes in the future. All current proposals to change the self-destruct opcode
               * are backwards compatible, but may result in not removing the bytecode from the blockchain state. This
               * implementation is compatible with that architecture, as it does not rely on the bytecode being removed
               * from the blockchain state (as opposed to using a CREATE2 style opcode when creating bytecode contracts,
               * which could be used in a way that may rely on the bytecode being removed from the blockchain state,
               * e.g. replacing a contract at a given deployed address).
               * @param _address address of deployed contract with bytecode containing concat(gated-cleanup-logic, address, data)
               * @dev This contract is only callable by the address of the contract that originally deployed the bytecode
               *      being purged. If this method is called by any other address, it will revert with the `INVALID` op-code.
               *      Additionally, for security purposes, the contract must be called with calldata `0xFF` to ensure that
               *      the `selfdestruct` op-code is intentionally being invoked, otherwise the `INVALID` op-code will be raised.
               */
              function purgeBytecode(address _address) internal {
                  // deployed bytecode (above) handles all logic for purging state, so no
                  // call data is expected to be passed along to perform data purge
                  (bool success /* `data` not needed */, ) = _address.call(hex"FF");
                  if (!success) {
                      revert("ContractAsStorage: Delete Error");
                  }
              }
              /*//////////////////////////////////////////////////////////////
                                    INTERNAL HELPER LOGIC
              //////////////////////////////////////////////////////////////*/
              /**
                  @notice Returns the size of the bytecode at address `_address`
                  @param _address address that may or may not contain bytecode
                  @return size size of the bytecode code at `_address`
              */
              function _bytecodeSizeAt(
                  address _address
              ) private view returns (uint256 size) {
                  assembly {
                      size := extcodesize(_address)
                  }
              }
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          // Inspired by: https://ethereum.stackexchange.com/a/123950/103422
          pragma solidity ^0.8.0;
          /**
           * @dev Operations on bytes32 data type, dealing with conversion to string.
           */
          library Bytes32Strings {
              /**
               * @dev Intended to convert a `bytes32`-encoded string literal to `string`.
               * Trims zero padding to arrive at original string literal.
               */
              function toString(
                  bytes32 source
              ) internal pure returns (string memory result) {
                  uint8 length = 0;
                  while (source[length] != 0 && length < 32) {
                      length++;
                  }
                  assembly {
                      // free memory pointer
                      result := mload(0x40)
                      // update free memory pointer to new "memory end"
                      // (offset is 64-bytes: 32 for length, 32 for data)
                      mstore(0x40, add(result, 0x40))
                      // store length in first 32-byte memory slot
                      mstore(result, length)
                      // write actual data in second 32-byte memory slot
                      mstore(add(result, 0x20), source)
                  }
              }
              /**
               * @dev Intended to check if a `bytes32`-encoded string contains a given
               * character with UTF-8 character code `utf8CharCode exactly `targetQty`
               * times. Does not support searching for multi-byte characters, only
               * characters with UTF-8 character codes < 0x80.
               */
              function containsExactCharacterQty(
                  bytes32 source,
                  uint8 utf8CharCode,
                  uint8 targetQty
              ) internal pure returns (bool) {
                  uint8 _occurrences = 0;
                  uint8 i;
                  for (i = 0; i < 32; ) {
                      uint8 _charCode = uint8(source[i]);
                      // if not a null byte, or a multi-byte UTF-8 character, check match
                      if (_charCode != 0 && _charCode < 0x80) {
                          if (_charCode == utf8CharCode) {
                              unchecked {
                                  // no risk of overflow since max 32 iterations < max uin8=255
                                  ++_occurrences;
                              }
                          }
                      }
                      unchecked {
                          // no risk of overflow since max 32 iterations < max uin8=255
                          ++i;
                      }
                  }
                  return _occurrences == targetQty;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/ERC721.sol)
          pragma solidity ^0.8.0;
          import "@openzeppelin-4.7/contracts/token/ERC721/IERC721.sol";
          import "@openzeppelin-4.7/contracts/token/ERC721/IERC721Receiver.sol";
          import "@openzeppelin-4.7/contracts/token/ERC721/extensions/IERC721Metadata.sol";
          import "@openzeppelin-4.7/contracts/utils/Address.sol";
          import "@openzeppelin-4.7/contracts/utils/Context.sol";
          import "@openzeppelin-4.7/contracts/utils/Strings.sol";
          import "@openzeppelin-4.7/contracts/utils/introspection/ERC165.sol";
          /**
           * @dev Forked version of the OpenZeppelin v4.7.1 ERC721 contract. Utilizes a
           * struct to pack owner and hash seed into a single storage slot.
           * ---------------------
           * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
           * the Metadata extension, but not including the Enumerable extension, which is available separately as
           * {ERC721Enumerable}.
           */
          contract ERC721_PackedHashSeed is Context, ERC165, IERC721, IERC721Metadata {
              using Address for address;
              using Strings for uint256;
              // Token name
              string private _name;
              // Token symbol
              string private _symbol;
              /// struct to pack a token owner and hash seed into same storage slot
              struct OwnerAndHashSeed {
                  // 20 bytes for address of token's owner
                  address owner;
                  // remaining 12 bytes allocated to token hash seed
                  bytes12 hashSeed;
              }
              /// mapping of token ID to OwnerAndHashSeed
              /// @dev visibility internal so inheriting contracts can access
              mapping(uint256 => OwnerAndHashSeed) internal _ownersAndHashSeeds;
              // Mapping owner address to token count
              mapping(address => uint256) private _balances;
              // Mapping from token ID to approved address
              mapping(uint256 => address) private _tokenApprovals;
              // Mapping from owner to operator approvals
              mapping(address => mapping(address => bool)) private _operatorApprovals;
              /**
               * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
               */
              constructor(string memory name_, string memory symbol_) {
                  _name = name_;
                  _symbol = symbol_;
              }
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(
                  bytes4 interfaceId
              ) public view virtual override(ERC165, IERC165) returns (bool) {
                  return
                      interfaceId == type(IERC721).interfaceId ||
                      interfaceId == type(IERC721Metadata).interfaceId ||
                      super.supportsInterface(interfaceId);
              }
              /**
               * @dev See {IERC721-balanceOf}.
               */
              function balanceOf(
                  address owner
              ) public view virtual override returns (uint256) {
                  require(
                      owner != address(0),
                      "ERC721: address zero is not a valid owner"
                  );
                  return _balances[owner];
              }
              /**
               * @dev See {IERC721-ownerOf}.
               */
              function ownerOf(
                  uint256 tokenId
              ) public view virtual override returns (address) {
                  address owner = _ownersAndHashSeeds[tokenId].owner;
                  require(owner != address(0), "ERC721: invalid token ID");
                  return owner;
              }
              /**
               * @dev See {IERC721Metadata-name}.
               */
              function name() public view virtual override returns (string memory) {
                  return _name;
              }
              /**
               * @dev See {IERC721Metadata-symbol}.
               */
              function symbol() public view virtual override returns (string memory) {
                  return _symbol;
              }
              /**
               * @dev See {IERC721Metadata-tokenURI}.
               */
              function tokenURI(
                  uint256 tokenId
              ) public view virtual override returns (string memory) {
                  _requireMinted(tokenId);
                  string memory baseURI = _baseURI();
                  return
                      bytes(baseURI).length > 0
                          ? string(abi.encodePacked(baseURI, tokenId.toString()))
                          : "";
              }
              /**
               * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
               * token will be the concatenation of the `baseURI` and the `tokenId`. Empty
               * by default, can be overridden in child contracts.
               */
              function _baseURI() internal view virtual returns (string memory) {
                  return "";
              }
              /**
               * @dev See {IERC721-approve}.
               */
              function approve(address to, uint256 tokenId) public virtual override {
                  address owner = ERC721_PackedHashSeed.ownerOf(tokenId);
                  require(to != owner, "ERC721: approval to current owner");
                  require(
                      _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
                      "ERC721: approve caller is not token owner nor approved for all"
                  );
                  _approve(to, tokenId);
              }
              /**
               * @dev See {IERC721-getApproved}.
               */
              function getApproved(
                  uint256 tokenId
              ) public view virtual override returns (address) {
                  _requireMinted(tokenId);
                  return _tokenApprovals[tokenId];
              }
              /**
               * @dev See {IERC721-setApprovalForAll}.
               */
              function setApprovalForAll(
                  address operator,
                  bool approved
              ) public virtual override {
                  _setApprovalForAll(_msgSender(), operator, approved);
              }
              /**
               * @dev See {IERC721-isApprovedForAll}.
               */
              function isApprovedForAll(
                  address owner,
                  address operator
              ) public view virtual override returns (bool) {
                  return _operatorApprovals[owner][operator];
              }
              /**
               * @dev See {IERC721-transferFrom}.
               */
              function transferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) public virtual override {
                  //solhint-disable-next-line max-line-length
                  require(
                      _isApprovedOrOwner(_msgSender(), tokenId),
                      "ERC721: caller is not token owner nor approved"
                  );
                  _transfer(from, to, tokenId);
              }
              /**
               * @dev See {IERC721-safeTransferFrom}.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) public virtual override {
                  safeTransferFrom(from, to, tokenId, "");
              }
              /**
               * @dev See {IERC721-safeTransferFrom}.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory data
              ) public virtual override {
                  require(
                      _isApprovedOrOwner(_msgSender(), tokenId),
                      "ERC721: caller is not token owner nor approved"
                  );
                  _safeTransfer(from, to, tokenId, data);
              }
              /**
               * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
               * are aware of the ERC721 protocol to prevent tokens from being forever locked.
               *
               * `data` is additional data, it has no specified format and it is sent in call to `to`.
               *
               * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
               * implement alternative mechanisms to perform token transfer, such as signature-based.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must exist and be owned by `from`.
               * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function _safeTransfer(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory data
              ) internal virtual {
                  _transfer(from, to, tokenId);
                  require(
                      _checkOnERC721Received(from, to, tokenId, data),
                      "ERC721: transfer to non ERC721Receiver implementer"
                  );
              }
              /**
               * @dev Returns whether `tokenId` exists.
               *
               * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
               *
               * Tokens start existing when they are minted (`_mint`),
               * and stop existing when they are burned (`_burn`).
               */
              function _exists(uint256 tokenId) internal view virtual returns (bool) {
                  return _ownersAndHashSeeds[tokenId].owner != address(0);
              }
              /**
               * @dev Returns whether `spender` is allowed to manage `tokenId`.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               */
              function _isApprovedOrOwner(
                  address spender,
                  uint256 tokenId
              ) internal view virtual returns (bool) {
                  address owner = ERC721_PackedHashSeed.ownerOf(tokenId);
                  return (spender == owner ||
                      isApprovedForAll(owner, spender) ||
                      getApproved(tokenId) == spender);
              }
              /**
               * @dev Safely mints `tokenId` and transfers it to `to`.
               *
               * Requirements:
               *
               * - `tokenId` must not exist.
               * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function _safeMint(address to, uint256 tokenId) internal virtual {
                  _safeMint(to, tokenId, "");
              }
              /**
               * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
               * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
               */
              function _safeMint(
                  address to,
                  uint256 tokenId,
                  bytes memory data
              ) internal virtual {
                  _mint(to, tokenId);
                  require(
                      _checkOnERC721Received(address(0), to, tokenId, data),
                      "ERC721: transfer to non ERC721Receiver implementer"
                  );
              }
              /**
               * @dev Mints `tokenId` and transfers it to `to`.
               *
               * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
               *
               * Requirements:
               *
               * - `tokenId` must not exist.
               * - `to` cannot be the zero address.
               *
               * Emits a {Transfer} event.
               */
              function _mint(address to, uint256 tokenId) internal virtual {
                  require(to != address(0), "ERC721: mint to the zero address");
                  require(!_exists(tokenId), "ERC721: token already minted");
                  _beforeTokenTransfer(address(0), to, tokenId);
                  _balances[to] += 1;
                  _ownersAndHashSeeds[tokenId].owner = to;
                  emit Transfer(address(0), to, tokenId);
                  _afterTokenTransfer(address(0), to, tokenId);
              }
              /**
               * @dev Destroys `tokenId`.
               * The approval is cleared when the token is burned.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               *
               * Emits a {Transfer} event.
               */
              function _burn(uint256 tokenId) internal virtual {
                  address owner = ERC721_PackedHashSeed.ownerOf(tokenId);
                  _beforeTokenTransfer(owner, address(0), tokenId);
                  // Clear approvals
                  _approve(address(0), tokenId);
                  _balances[owner] -= 1;
                  delete _ownersAndHashSeeds[tokenId].owner;
                  emit Transfer(owner, address(0), tokenId);
                  _afterTokenTransfer(owner, address(0), tokenId);
              }
              /**
               * @dev Transfers `tokenId` from `from` to `to`.
               *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
               *
               * Requirements:
               *
               * - `to` cannot be the zero address.
               * - `tokenId` token must be owned by `from`.
               *
               * Emits a {Transfer} event.
               */
              function _transfer(
                  address from,
                  address to,
                  uint256 tokenId
              ) internal virtual {
                  require(
                      ERC721_PackedHashSeed.ownerOf(tokenId) == from,
                      "ERC721: transfer from incorrect owner"
                  );
                  require(to != address(0), "ERC721: transfer to the zero address");
                  _beforeTokenTransfer(from, to, tokenId);
                  // Clear approvals from the previous owner
                  _approve(address(0), tokenId);
                  _balances[from] -= 1;
                  _balances[to] += 1;
                  _ownersAndHashSeeds[tokenId].owner = to;
                  emit Transfer(from, to, tokenId);
                  _afterTokenTransfer(from, to, tokenId);
              }
              /**
               * @dev Approve `to` to operate on `tokenId`
               *
               * Emits an {Approval} event.
               */
              function _approve(address to, uint256 tokenId) internal virtual {
                  _tokenApprovals[tokenId] = to;
                  emit Approval(ERC721_PackedHashSeed.ownerOf(tokenId), to, tokenId);
              }
              /**
               * @dev Approve `operator` to operate on all of `owner` tokens
               *
               * Emits an {ApprovalForAll} event.
               */
              function _setApprovalForAll(
                  address owner,
                  address operator,
                  bool approved
              ) internal virtual {
                  require(owner != operator, "ERC721: approve to caller");
                  _operatorApprovals[owner][operator] = approved;
                  emit ApprovalForAll(owner, operator, approved);
              }
              /**
               * @dev Reverts if the `tokenId` has not been minted yet.
               */
              function _requireMinted(uint256 tokenId) internal view virtual {
                  require(_exists(tokenId), "ERC721: invalid token ID");
              }
              /**
               * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
               * The call is not executed if the target address is not a contract.
               *
               * @param from address representing the previous owner of the given token ID
               * @param to target address that will receive the tokens
               * @param tokenId uint256 ID of the token to be transferred
               * @param data bytes optional data to send along with the call
               * @return bool whether the call correctly returned the expected magic value
               */
              function _checkOnERC721Received(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory data
              ) private returns (bool) {
                  if (to.isContract()) {
                      try
                          IERC721Receiver(to).onERC721Received(
                              _msgSender(),
                              from,
                              tokenId,
                              data
                          )
                      returns (bytes4 retval) {
                          return retval == IERC721Receiver.onERC721Received.selector;
                      } catch (bytes memory reason) {
                          if (reason.length == 0) {
                              revert(
                                  "ERC721: transfer to non ERC721Receiver implementer"
                              );
                          } else {
                              /// @solidity memory-safe-assembly
                              assembly {
                                  revert(add(32, reason), mload(reason))
                              }
                          }
                      }
                  } else {
                      return true;
                  }
              }
              /**
               * @dev Hook that is called before any token transfer. This includes minting
               * and burning.
               *
               * 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, ``from``'s `tokenId` will be burned.
               * - `from` and `to` are never both zero.
               *
               * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
               */
              function _beforeTokenTransfer(
                  address from,
                  address to,
                  uint256 tokenId
              ) internal virtual {}
              /**
               * @dev Hook that is called after any transfer of tokens. This includes
               * minting and burning.
               *
               * Calling conditions:
               *
               * - when `from` and `to` are both non-zero.
               * - `from` and `to` are never both zero.
               *
               * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
               */
              function _afterTokenTransfer(
                  address from,
                  address to,
                  uint256 tokenId
              ) internal virtual {}
          }
          

          File 4 of 4: DiamondExhibition
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity ^0.8.15;
          import {Strings} from "openzeppelin-contracts/utils/Strings.sol";
          import {GenArt721CoreV3_Engine_Flex_PROOF} from "artblocks-contracts/GenArt721CoreV3_Engine_Flex_PROOF.sol";
          import {ERC721A, ERC721ACommon, BaseTokenURI, ERC721ACommonBaseTokenURI} from "ethier/erc721/BaseTokenURI.sol";
          import {OperatorFilterOS} from "ethier/erc721/OperatorFilterOS.sol";
          import {artblocksTokenID} from "proof/artblocks/TokenIDMapping.sol";
          import {IGenArt721CoreContractV3_Mintable} from "proof/artblocks/IGenArt721CoreContractV3_Mintable.sol";
          import {SellableERC721ACommon} from "proof/sellers/sellable/SellableERC721ACommon.sol";
          import {ProjectsConfig} from "./ProjectsConfig.sol";
          import {TokenInfoManager} from "./TokenInfoManager.sol";
          /**
           * @notice Library for encoding and decoding purchase data for the Diamond Exhibition sellers.
           */
          library DiamondExhibitionLib {
              function encodePurchaseData(uint8[] memory projectIds) internal pure returns (bytes memory) {
                  return abi.encode(projectIds);
              }
              function dencodePurchaseData(bytes memory data) internal pure returns (uint8[] memory) {
                  return abi.decode(data, (uint8[]));
              }
          }
          /**
           * @notice Diamond Exhibition
           * @author David Huber (@cxkoda)
           * @custom:reviewer Arran Schlosberg (@divergencearran)
           */
          contract DiamondExhibition is
              ERC721ACommonBaseTokenURI,
              OperatorFilterOS,
              SellableERC721ACommon,
              ProjectsConfig,
              TokenInfoManager
          {
              // =================================================================================================================
              //                          Errors
              // =================================================================================================================
              /**
               * @notice Thrown if the number of requested purchases exceeds the number of remaining tokens.
               */
              error ExceedingMaxTotalSupply(uint256 num, uint256 numLeft);
              /**
               * @notice Thrown if a user attempts to purchase tokens from an exhausted project.
               */
              error ProjectExhausted(uint8 projectId);
              /**
               * @notice Thrown if a user attempts to purchase tokens from an invalid project.
               */
              error InvalidProject(uint8 projectId);
              // =================================================================================================================
              //                          Constants
              // =================================================================================================================
              /**
               * @notice The ArtBlocks engine flex contract.
               */
              GenArt721CoreV3_Engine_Flex_PROOF public immutable flex;
              /**
               * @notice The ArtBlocks engine flex contract or a minter multiplexer.
               */
              IGenArt721CoreContractV3_Mintable public immutable flexMintGateway;
              /**
               * @notice The maximum total number of tokens that can be minted.
               * @dev This is intentionally not a compile-time constant for the sake of testing.
               */
              uint256 public immutable maxTotalSupply;
              // =========================================================================
              //                          Storage
              // =================================================================================================================
              /**
               * @notice The number of tokens minted per project.
               */
              uint16[NUM_PROJECTS] internal _numPurchasedPerProject;
              // =================================================================================================================
              //                          Storage
              // =================================================================================================================
              struct ConstructorParams {
                  address admin;
                  address steerer;
                  address payable secondaryReceiver;
                  GenArt721CoreV3_Engine_Flex_PROOF flex;
                  IGenArt721CoreContractV3_Mintable flexMintGateway;
              }
              constructor(ConstructorParams memory params)
                  ERC721ACommon(params.admin, params.steerer, "Diamond Exhibition", "DIAMOND", params.secondaryReceiver, 500)
                  BaseTokenURI("https://metadata.proof.xyz/diamond-exhibition/")
              {
                  uint256 total;
                  uint256[NUM_PROJECTS] memory maxNumPerProject_ = _maxNumPerProject();
                  for (uint256 i = 0; i < NUM_PROJECTS; i++) {
                      total += maxNumPerProject_[i];
                  }
                  maxTotalSupply = total;
                  flex = params.flex;
                  flexMintGateway = params.flexMintGateway;
              }
              // =================================================================================================================
              //                          Selling
              // =================================================================================================================
              /**
               * @notice Assigns a project to a token.
               * @dev Mints from the associated ArtBlocks project if the project is a longform project.
               */
              function _assignProject(uint256 tokenId, uint8 projectId, uint256[NUM_PROJECTS] memory maxNumPerProject_)
                  internal
              {
                  if (projectId >= NUM_PROJECTS) {
                      revert InvalidProject(projectId);
                  }
                  uint16 numPurchased = _numPurchasedPerProject[projectId];
                  if (numPurchased >= maxNumPerProject_[projectId]) {
                      revert ProjectExhausted(projectId);
                  }
                  _numPurchasedPerProject[projectId] = numPurchased + 1;
                  if (_isLongformProject(projectId)) {
                      flexMintGateway.mint_Ecf(address(this), _artblocksProjectId(projectId), address(this));
                  }
                  _setTokenInfo(tokenId, projectId, numPurchased /* edition */ );
              }
              /**
               * @inheritdoc SellableERC721ACommon
               */
              function _handleSale(address to, uint64 num, bytes calldata data) internal virtual override {
                  if (num + _totalMinted() > maxTotalSupply) {
                      revert ExceedingMaxTotalSupply(num, maxTotalSupply - _totalMinted());
                  }
                  uint8[] memory projectIds = DiamondExhibitionLib.dencodePurchaseData(data);
                  assert(projectIds.length == num);
                  uint256 tokenId = _nextTokenId();
                  uint256[NUM_PROJECTS] memory maxNumPerProject_ = _maxNumPerProject();
                  for (uint256 i = 0; i < num; ++i) {
                      _assignProject(tokenId++, projectIds[i], maxNumPerProject_);
                  }
                  SellableERC721ACommon._handleSale(to, num, data);
              }
              /**
               * @inheritdoc ERC721A
               */
              function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
                  TokenInfo memory info = _tokenInfo(tokenId);
                  if (projectType(info.projectId) == ProjectType.Curated) {
                      return string.concat(_baseURI(), Strings.toString(tokenId));
                  }
                  return flex.tokenURI(artblocksTokenID(_artblocksProjectId(info.projectId), info.edition));
              }
              /**
               * @notice Returns all tokenIds for a given project.
               * @dev Intended for front-end consumption and not optimised for gas.
               */
              function tokenIdsByProjectId(uint8 projectId) external view returns (uint256[] memory) {
                  uint256[] memory tokenIds = new uint256[](_numPurchasedPerProject[projectId]);
                  uint256 cursor;
                  uint256 supply = totalSupply();
                  for (uint256 tokenId = 0; tokenId < supply; ++tokenId) {
                      if (_tokenInfo(tokenId).projectId == projectId) {
                          tokenIds[cursor++] = tokenId;
                      }
                  }
                  return tokenIds;
              }
              /**
               * @notice Returns the number of tokens purchased for each project.
               * @dev Intended for front-end consumption and not optimised for gas.
               */
              function numPurchasedPerProject() external view returns (uint16[NUM_PROJECTS] memory) {
                  return _numPurchasedPerProject;
              }
              // =================================================================================================================
              //                          Inheritance resolution
              // =================================================================================================================
              /**
               * @notice Helper function that returns true if the token belongs to a longform project.
               */
              function _isLongformToken(uint256 tokenId) internal view virtual returns (bool) {
                  return _isLongformProject(_tokenInfo(tokenId).projectId);
              }
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(ERC721ACommon, ERC721ACommonBaseTokenURI, SellableERC721ACommon)
                  returns (bool)
              {
                  return ERC721ACommonBaseTokenURI.supportsInterface(interfaceId);
              }
              function _baseURI() internal view virtual override(ERC721A, ERC721ACommonBaseTokenURI) returns (string memory) {
                  return ERC721ACommonBaseTokenURI._baseURI();
              }
              function setApprovalForAll(address operator, bool approved) public virtual override(ERC721A, OperatorFilterOS) {
                  ERC721A.setApprovalForAll(operator, approved);
              }
              function approve(address operator, uint256 tokenId) public payable virtual override(ERC721A, OperatorFilterOS) {
                  if (_isLongformToken(tokenId)) {
                      ERC721A.approve(operator, tokenId);
                  } else {
                      OperatorFilterOS.approve(operator, tokenId);
                  }
              }
              function transferFrom(address from, address to, uint256 tokenId)
                  public
                  payable
                  virtual
                  override(ERC721A, OperatorFilterOS)
              {
                  if (_isLongformToken(tokenId)) {
                      ERC721A.transferFrom(from, to, tokenId);
                  } else {
                      OperatorFilterOS.transferFrom(from, to, tokenId);
                  }
              }
              function safeTransferFrom(address from, address to, uint256 tokenId)
                  public
                  payable
                  virtual
                  override(ERC721A, OperatorFilterOS)
              {
                  if (_isLongformToken(tokenId)) {
                      ERC721A.safeTransferFrom(from, to, tokenId);
                  } else {
                      OperatorFilterOS.safeTransferFrom(from, to, tokenId);
                  }
              }
              function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data)
                  public
                  payable
                  virtual
                  override(ERC721A, OperatorFilterOS)
              {
                  if (_isLongformToken(tokenId)) {
                      ERC721A.safeTransferFrom(from, to, tokenId, data);
                  } else {
                      OperatorFilterOS.safeTransferFrom(from, to, tokenId, data);
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)
          pragma solidity ^0.8.0;
          import "./math/Math.sol";
          /**
           * @dev String operations.
           */
          library Strings {
              bytes16 private constant _SYMBOLS = "0123456789abcdef";
              uint8 private constant _ADDRESS_LENGTH = 20;
              /**
               * @dev Converts a `uint256` to its ASCII `string` decimal representation.
               */
              function toString(uint256 value) internal pure returns (string memory) {
                  unchecked {
                      uint256 length = Math.log10(value) + 1;
                      string memory buffer = new string(length);
                      uint256 ptr;
                      /// @solidity memory-safe-assembly
                      assembly {
                          ptr := add(buffer, add(32, length))
                      }
                      while (true) {
                          ptr--;
                          /// @solidity memory-safe-assembly
                          assembly {
                              mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                          }
                          value /= 10;
                          if (value == 0) break;
                      }
                      return buffer;
                  }
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
               */
              function toHexString(uint256 value) internal pure returns (string memory) {
                  unchecked {
                      return toHexString(value, Math.log256(value) + 1);
                  }
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
               */
              function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                  bytes memory buffer = new bytes(2 * length + 2);
                  buffer[0] = "0";
                  buffer[1] = "x";
                  for (uint256 i = 2 * length + 1; i > 1; --i) {
                      buffer[i] = _SYMBOLS[value & 0xf];
                      value >>= 4;
                  }
                  require(value == 0, "Strings: hex length insufficient");
                  return string(buffer);
              }
              /**
               * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
               */
              function toHexString(address addr) internal pure returns (string memory) {
                  return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
              }
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          pragma solidity 0.8.17;
          // Created By: Art Blocks Inc.
          import "./interfaces/0.8.x/IRandomizerV2.sol";
          import "./interfaces/0.8.x/IAdminACLV0.sol";
          import "./interfaces/0.8.x/IEngineRegistryV0.sol";
          import "./interfaces/0.8.x/IGenArt721CoreContractV3_Engine_Flex.sol";
          import "./interfaces/0.8.x/IDependencyRegistryCompatibleV0.sol";
          import "./interfaces/0.8.x/IManifold.sol";
          import "@openzeppelin-4.7/contracts/access/Ownable.sol";
          import "./libs/0.8.x/ERC721_PackedHashSeed.sol";
          import "./libs/0.8.x/BytecodeStorage.sol";
          import "./libs/0.8.x/Bytes32Strings.sol";
          /**
           * @title Art Blocks Engine Flex ERC-721 core contract, V3.
           * @author Art Blocks Inc.
           * @notice Privileged Roles and Ownership:
           * This contract is designed to be managed, with progressively limited powers
           * as a project progresses from active to locked.
           * Privileged roles and abilities are controlled by the admin ACL contract and
           * artists. Both of these roles hold extensive power and can arbitrarily
           * control and modify portions of projects, dependent upon project state. After
           * a project is locked, important project metadata fields are locked including
           * the project name, artist name, and script and display details. Edition size
           * can never be increased.
           * Care must be taken to ensure that the admin ACL contract and artist
           * addresses are secure behind a multi-sig or other access control mechanism.
           * ----------------------------------------------------------------------------
           * The following functions are restricted to the Admin ACL contract:
           * - updateArtblocksDependencyRegistryAddress
           * - updateProviderSalesAddresses
           * - updateProviderPrimarySalesPercentages (up to 100%)
           * - updateProviderSecondarySalesBPS (up to 100%)
           * - updateMinterContract
           * - updateRandomizerAddress
           * - toggleProjectIsActive
           * - addProject
           * - forbidNewProjects (forever forbidding new projects)
           * - updateDefaultBaseURI (used to initialize new project base URIs)
           * - updateIPFSGateway
           * - updateArweaveGateway
           * ----------------------------------------------------------------------------
           * The following functions are restricted to either the the Artist address or
           * the Admin ACL contract, only when the project is not locked:
           * - updateProjectName
           * - updateProjectArtistName
           * - updateProjectLicense
           * - Change project script via addProjectScript, updateProjectScript,
           *   and removeProjectLastScript
           * - updateProjectScriptType
           * - updateProjectAspectRatio
           * ----------------------------------------------------------------------------
           * The following functions are restricted to only the Artist or Admin ACL
           * contract of a valid project ID:
           * - proposeArtistPaymentAddressesAndSplits (Note that this has to be accepted
           *   by adminAcceptArtistAddressesAndSplits to take effect, which is restricted
           *   to the Admin ACL contract, or the artist if the core contract owner has
           *   renounced ownership. Also note that a proposal will be automatically
           *   accepted if the artist only proposes changed payee percentages without
           *   modifying any payee addresses, or is only removing payee addresses, or
           *   if the global config `autoApproveArtistSplitProposals` is set to `true`.)
           * - toggleProjectIsPaused (note the artist can still mint while paused)
           * - updateProjectSecondaryMarketRoyaltyPercentage (up to
           *   ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE percent)
           * - updateProjectWebsite
           * - updateProjectMaxInvocations (to a number greater than or equal to the
           *   current number of invocations, and less than current project maximum
           *   invocations)
           * - updateProjectBaseURI (controlling the base URI for tokens in the project)
           * ----------------------------------------------------------------------------
           * The following function is restricted to either the Admin ACL contract, or
           * the Artist address if the core contract owner has renounced ownership:
           * - adminAcceptArtistAddressesAndSplits
           * - updateProjectArtistAddress (owner ultimately controlling the project and
           *   its and-on revenue, unless owner has renounced ownership)
           * ----------------------------------------------------------------------------
           * The following function is restricted to the artist when a project is
           * unlocked, and only callable by Admin ACL contract when a project is locked:
           * - updateProjectDescription
           * ----------------------------------------------------------------------------
           * The following functions for managing external asset dependencies are restricted
           * to projects with external asset dependencies that are unlocked:
           * - lockProjectExternalAssetDependencies
           * - updateProjectExternalAssetDependency
           * - removeProjectExternalAssetDependency
           * - addProjectExternalAssetDependency
           * ----------------------------------------------------------------------------
           * The following function is restricted to owner calling directly:
           * - transferOwnership
           * - renounceOwnership
           * ----------------------------------------------------------------------------
           * The following configuration variables are set at time of contract deployment,
           * and not modifiable thereafter (immutable after the point of deployment):
           * - (bool) autoApproveArtistSplitProposals
           * ----------------------------------------------------------------------------
           * Additional admin and artist privileged roles may be described on minters,
           * registries, and other contracts that may interact with this core contract.
           */
          contract GenArt721CoreV3_Engine_Flex_PROOF is
              ERC721_PackedHashSeed,
              Ownable,
              IDependencyRegistryCompatibleV0,
              IManifold,
              IGenArt721CoreContractV3_Engine_Flex
          {
              using BytecodeStorage for string;
              using BytecodeStorage for address;
              using Bytes32Strings for bytes32;
              uint256 constant ONE_HUNDRED = 100;
              uint256 constant ONE_MILLION = 1_000_000;
              uint24 constant ONE_MILLION_UINT24 = 1_000_000;
              uint256 constant FOUR_WEEKS_IN_SECONDS = 2_419_200;
              uint8 constant AT_CHARACTER_CODE = uint8(bytes1("@")); // 0x40
              // numeric constants
              uint256 constant MAX_PROVIDER_SECONDARY_SALES_BPS = 10000; // 10_000 BPS = 100%
              uint256 constant ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE = 95; // 95%
              // This contract emits generic events that contain fields that indicate
              // which parameter has been updated. This is sufficient for application
              // state management, while also simplifying the contract and indexing code.
              // This was done as an alternative to having custom events that emit what
              // field-values have changed for each event, given that changed values can
              // be introspected by indexers due to the design of this smart contract
              // exposing these state changes via publicly viewable fields.
              //
              // The following fields are used to indicate which contract-level parameter
              // has been updated in the `PlatformUpdated` event:
              bytes32 constant FIELD_NEXT_PROJECT_ID = "nextProjectId";
              bytes32 constant FIELD_NEW_PROJECTS_FORBIDDEN = "newProjectsForbidden";
              bytes32 constant FIELD_DEFAULT_BASE_URI = "defaultBaseURI";
              bytes32 constant FIELD_RANDOMIZER_ADDRESS = "randomizerAddress";
              bytes32 constant FIELD_ARTBLOCKS_DEPENDENCY_REGISTRY_ADDRESS =
                  "dependencyRegistryAddress";
              bytes32 constant FIELD_PROVIDER_SALES_ADDRESSES = "providerSalesAddresses";
              bytes32 constant FIELD_PROVIDER_PRIMARY_SALES_PERCENTAGES =
                  "providerPrimaryPercentages";
              bytes32 constant FIELD_PROVIDER_SECONDARY_SALES_BPS =
                  "providerSecondaryBPS";
              // The following fields are used to indicate which project-level parameter
              // has been updated in the `ProjectUpdated` event:
              bytes32 constant FIELD_PROJECT_COMPLETED = "completed";
              bytes32 constant FIELD_PROJECT_ACTIVE = "active";
              bytes32 constant FIELD_PROJECT_ARTIST_ADDRESS = "artistAddress";
              bytes32 constant FIELD_PROJECT_PAUSED = "paused";
              bytes32 constant FIELD_PROJECT_CREATED = "created";
              bytes32 constant FIELD_PROJECT_NAME = "name";
              bytes32 constant FIELD_PROJECT_ARTIST_NAME = "artistName";
              bytes32 constant FIELD_PROJECT_SECONDARY_MARKET_ROYALTY_PERCENTAGE =
                  "royaltyPercentage";
              bytes32 constant FIELD_PROJECT_DESCRIPTION = "description";
              bytes32 constant FIELD_PROJECT_WEBSITE = "website";
              bytes32 constant FIELD_PROJECT_LICENSE = "license";
              bytes32 constant FIELD_PROJECT_MAX_INVOCATIONS = "maxInvocations";
              bytes32 constant FIELD_PROJECT_SCRIPT = "script";
              bytes32 constant FIELD_PROJECT_SCRIPT_TYPE = "scriptType";
              bytes32 constant FIELD_PROJECT_ASPECT_RATIO = "aspectRatio";
              bytes32 constant FIELD_PROJECT_BASE_URI = "baseURI";
              /// Dependency registry managed by Art Blocks
              address public artblocksDependencyRegistryAddress;
              /// current randomizer contract
              IRandomizerV2 public randomizerContract;
              /// append-only array of all randomizer contract addresses ever used by
              /// this contract
              address[] private _historicalRandomizerAddresses;
              /// admin ACL contract
              IAdminACLV0 public adminACLContract;
              struct Project {
                  uint24 invocations;
                  uint24 maxInvocations;
                  uint24 scriptCount;
                  // max uint64 ~= 1.8e19 sec ~= 570 billion years
                  uint64 completedTimestamp;
                  bool active;
                  bool paused;
                  string name;
                  string artist;
                  string description;
                  string website;
                  string license;
                  string projectBaseURI;
                  bytes32 scriptTypeAndVersion;
                  string aspectRatio;
                  // mapping from script index to address storing script in bytecode
                  mapping(uint256 => address) scriptBytecodeAddresses;
                  bool externalAssetDependenciesLocked;
                  uint24 externalAssetDependencyCount;
                  mapping(uint256 => ExternalAssetDependency) externalAssetDependencies;
              }
              mapping(uint256 => Project) projects;
              string public preferredIPFSGateway;
              string public preferredArweaveGateway;
              /// packed struct containing project financial information
              struct ProjectFinance {
                  address payable additionalPayeePrimarySales;
                  // packed uint: max of 95, max uint8 = 255
                  uint8 secondaryMarketRoyaltyPercentage;
                  address payable additionalPayeeSecondarySales;
                  // packed uint: max of 100, max uint8 = 255
                  uint8 additionalPayeeSecondarySalesPercentage;
                  address payable artistAddress;
                  // packed uint: max of 100, max uint8 = 255
                  uint8 additionalPayeePrimarySalesPercentage;
              }
              // Project financials mapping
              mapping(uint256 => ProjectFinance) projectIdToFinancials;
              /// hash of artist's proposed payment updates to be approved by admin
              mapping(uint256 => bytes32) public proposedArtistAddressesAndSplitsHash;
              /// The render provider payment address for all primary sales revenues
              /// (packed)
              address payable public renderProviderPrimarySalesAddress;
              /// Percentage of primary sales revenue allocated to the render provider
              /// (packed)
              // packed uint: max of 100, max uint8 = 255
              uint8 private _renderProviderPrimarySalesPercentage = 10;
              /// The platform provider payment address for all primary sales revenues
              /// (packed)
              address payable public platformProviderPrimarySalesAddress;
              /// Percentage of primary sales revenue allocated to the platform provider
              /// (packed)
              // packed uint: max of 100, max uint8 = 255
              uint8 private _platformProviderPrimarySalesPercentage = 10;
              /// The render provider payment address for all secondary sales royalty
              /// revenues
              address payable public renderProviderSecondarySalesAddress;
              /// Basis Points of secondary sales royalties allocated to the
              /// render provider
              uint256 public renderProviderSecondarySalesBPS = 250;
              /// The platform provider payment address for all secondary sales royalty
              /// revenues
              address payable public platformProviderSecondarySalesAddress;
              /// Basis Points of secondary sales royalties allocated to the
              /// platform provider
              uint256 public platformProviderSecondarySalesBPS = 250;
              /// single minter allowed for this core contract
              address public minterContract;
              /// starting (initial) project ID on this contract
              uint256 public immutable startingProjectId;
              /// next project ID to be created
              uint248 private _nextProjectId;
              /// bool indicating if adding new projects is forbidden;
              /// default behavior is to allow new projects
              bool public newProjectsForbidden;
              /// configuration variable (determined at time of deployment)
              /// that determines whether or not admin approval^ should be required
              /// to accept artist address change proposals, or if these proposals
              /// should always auto-approve, as determined by the business process
              /// requirements of the Engine partner using this contract.
              ///
              /// ^does not apply in the case where contract-ownership itself is revoked
              bool public immutable autoApproveArtistSplitProposals;
              /// version & type of this core contract
              bytes32 constant CORE_VERSION = "v3.1.2";
              function coreVersion() external pure returns (string memory) {
                  return CORE_VERSION.toString();
              }
              bytes32 constant CORE_TYPE = "GenArt721CoreV3_Engine_Flex";
              function coreType() external pure returns (string memory) {
                  return CORE_TYPE.toString();
              }
              /// default base URI to initialize all new project projectBaseURI values to
              string public defaultBaseURI;
              function _onlyUnlockedProjectExternalAssetDependencies(
                  uint256 _projectId
              ) internal view {
                  require(
                      !projects[_projectId].externalAssetDependenciesLocked,
                      "External dependencies locked"
                  );
              }
              function _onlyNonZeroAddress(address _address) internal pure {
                  require(_address != address(0), "Must input non-zero address");
              }
              function _onlyNonEmptyString(string memory _string) internal pure {
                  require(bytes(_string).length != 0, "Must input non-empty string");
              }
              function _onlyValidTokenId(uint256 _tokenId) internal view {
                  require(_exists(_tokenId), "Token ID does not exist");
              }
              function _onlyValidProjectId(uint256 _projectId) internal view {
                  require(
                      (_projectId >= startingProjectId) && (_projectId < _nextProjectId),
                      "Project ID does not exist"
                  );
              }
              function _onlyUnlocked(uint256 _projectId) internal view {
                  // Note: calling `_projectUnlocked` enforces that the `_projectId`
                  //       passed in is valid.`
                  require(_projectUnlocked(_projectId), "Only if unlocked");
              }
              function _onlyAdminACL(bytes4 _selector) internal {
                  require(
                      adminACLAllowed(msg.sender, address(this), _selector),
                      "Only Admin ACL allowed"
                  );
              }
              function _onlyArtistOrAdminACL(
                  uint256 _projectId,
                  bytes4 _selector
              ) internal {
                  require(
                      msg.sender == projectIdToFinancials[_projectId].artistAddress ||
                          adminACLAllowed(msg.sender, address(this), _selector),
                      "Only artist or Admin ACL allowed"
                  );
              }
              /**
               * This modifier allows the artist of a project to call a function if the
               * owner of the contract has renounced ownership. This is to allow the
               * contract to continue to function if the owner decides to renounce
               * ownership.
               */
              function _onlyAdminACLOrRenouncedArtist(
                  uint256 _projectId,
                  bytes4 _selector
              ) internal {
                  require(
                      adminACLAllowed(msg.sender, address(this), _selector) ||
                          (owner() == address(0) &&
                              msg.sender ==
                              projectIdToFinancials[_projectId].artistAddress),
                      "Only Admin ACL allowed, or artist if owner has renounced"
                  );
              }
              /**
               * @notice Initializes contract.
               * @param _tokenName Name of token.
               * @param _tokenSymbol Token symbol.
               * @param _randomizerContract Randomizer contract.
               * @param _adminACLContract Address of admin access control contract, to be
               * set as contract owner.
               * @param _startingProjectId The initial next project ID.
               * @param _autoApproveArtistSplitProposals Whether or not to always
               * auto-approve proposed artist split updates.
               * @dev _startingProjectId should be set to a value much, much less than
               * max(uint248), but an explicit input type of `uint248` is used as it is
               * safer to cast up to `uint256` than it is to cast down for the purposes
               * of setting `_nextProjectId`.
               */
              constructor(
                  string memory _tokenName,
                  string memory _tokenSymbol,
                  address _renderProviderAddress,
                  address _platformProviderAddress,
                  address _randomizerContract,
                  address _adminACLContract,
                  uint248 _startingProjectId,
                  bool _autoApproveArtistSplitProposals,
                  address _engineRegistryContract
              ) ERC721_PackedHashSeed(_tokenName, _tokenSymbol) {
                  _onlyNonZeroAddress(_renderProviderAddress);
                  _onlyNonZeroAddress(_platformProviderAddress);
                  _onlyNonZeroAddress(_randomizerContract);
                  _onlyNonZeroAddress(_adminACLContract);
                  // setup immutable `autoApproveArtistSplitProposals` config
                  autoApproveArtistSplitProposals = _autoApproveArtistSplitProposals;
                  // record contracts starting project ID
                  // casting-up is safe
                  startingProjectId = uint256(_startingProjectId);
                  _updateProviderSalesAddresses(
                      _renderProviderAddress,
                      _renderProviderAddress,
                      _platformProviderAddress,
                      _platformProviderAddress
                  );
                  _updateRandomizerAddress(_randomizerContract);
                  // set AdminACL management contract as owner
                  _transferOwnership(_adminACLContract);
                  // initialize default base URI
                  _updateDefaultBaseURI(
                      string.concat(
                          "https://token.artblocks.io/",
                          toHexString(address(this)),
                          "/"
                      )
                  );
                  // initialize next project ID
                  _nextProjectId = _startingProjectId;
                  emit PlatformUpdated(FIELD_NEXT_PROJECT_ID);
                  // register contract as an Engine contract
                  IEngineRegistryV0(_engineRegistryContract).registerContract(
                      address(this),
                      CORE_VERSION,
                      CORE_TYPE
                  );
              }
              /**
               * @notice Updates preferredIPFSGateway to `_gateway`.
               */
              function updateIPFSGateway(string calldata _gateway) public {
                  _onlyAdminACL(this.updateIPFSGateway.selector);
                  preferredIPFSGateway = _gateway;
                  emit GatewayUpdated(ExternalAssetDependencyType.IPFS, _gateway);
              }
              /**
               * @notice Updates preferredArweaveGateway to `_gateway`.
               */
              function updateArweaveGateway(string calldata _gateway) public {
                  _onlyAdminACL(this.updateArweaveGateway.selector);
                  preferredArweaveGateway = _gateway;
                  emit GatewayUpdated(ExternalAssetDependencyType.ARWEAVE, _gateway);
              }
              /**
               * @notice Locks external asset dependencies for project `_projectId`.
               */
              function lockProjectExternalAssetDependencies(uint256 _projectId) external {
                  _onlyUnlockedProjectExternalAssetDependencies(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.lockProjectExternalAssetDependencies.selector
                  );
                  projects[_projectId].externalAssetDependenciesLocked = true;
                  emit ProjectExternalAssetDependenciesLocked(_projectId);
              }
              /**
               * @notice Updates external asset dependency for project `_projectId`.
               * @param _projectId Project to be updated.
               * @param _index Asset index.
               * @param _cidOrData Asset cid (Content identifier) or data string to be translated into bytecode.
               * @param _dependencyType Asset dependency type.
               *  0 - IPFS
               *  1 - ARWEAVE
               *  2 - ONCHAIN
               */
              function updateProjectExternalAssetDependency(
                  uint256 _projectId,
                  uint256 _index,
                  string memory _cidOrData,
                  ExternalAssetDependencyType _dependencyType
              ) external {
                  _onlyUnlockedProjectExternalAssetDependencies(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.updateProjectExternalAssetDependency.selector
                  );
                  uint24 assetCount = projects[_projectId].externalAssetDependencyCount;
                  require(_index < assetCount, "Asset index out of range");
                  ExternalAssetDependency storage _oldDependency = projects[_projectId]
                      .externalAssetDependencies[_index];
                  ExternalAssetDependencyType _oldDependencyType = _oldDependency
                      .dependencyType;
                  projects[_projectId]
                      .externalAssetDependencies[_index]
                      .dependencyType = _dependencyType;
                  // if the incoming dependency type is onchain, we need to write the data to bytecode
                  if (_dependencyType == ExternalAssetDependencyType.ONCHAIN) {
                      if (_oldDependencyType != ExternalAssetDependencyType.ONCHAIN) {
                          // we only need to set the cid to an empty string if we are replacing an offchain asset
                          // an onchain asset will already have an empty cid
                          projects[_projectId].externalAssetDependencies[_index].cid = "";
                      }
                      projects[_projectId]
                          .externalAssetDependencies[_index]
                          .bytecodeAddress = _cidOrData.writeToBytecode();
                      // we don't want to emit data, so we emit the cid as an empty string
                      _cidOrData = "";
                  } else {
                      projects[_projectId]
                          .externalAssetDependencies[_index]
                          .cid = _cidOrData;
                  }
                  emit ExternalAssetDependencyUpdated(
                      _projectId,
                      _index,
                      _cidOrData,
                      _dependencyType,
                      assetCount
                  );
              }
              /**
               * @notice Removes external asset dependency for project `_projectId` at index `_index`.
               * Removal is done by swapping the element to be removed with the last element in the array, then deleting this last element.
               * Assets with indices higher than `_index` can have their indices adjusted as a result of this operation.
               * @param _projectId Project to be updated.
               * @param _index Asset index
               */
              function removeProjectExternalAssetDependency(
                  uint256 _projectId,
                  uint256 _index
              ) external {
                  _onlyUnlockedProjectExternalAssetDependencies(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.removeProjectExternalAssetDependency.selector
                  );
                  uint24 assetCount = projects[_projectId].externalAssetDependencyCount;
                  require(_index < assetCount, "Asset index out of range");
                  uint24 lastElementIndex = assetCount - 1;
                  // copy last element to index of element to be removed
                  projects[_projectId].externalAssetDependencies[_index] = projects[
                      _projectId
                  ].externalAssetDependencies[lastElementIndex];
                  delete projects[_projectId].externalAssetDependencies[lastElementIndex];
                  projects[_projectId].externalAssetDependencyCount = lastElementIndex;
                  emit ExternalAssetDependencyRemoved(_projectId, _index);
              }
              /**
               * @notice Adds external asset dependency for project `_projectId`.
               * @param _projectId Project to be updated.
               * @param _cidOrData Asset cid (Content identifier) or data string to be translated into bytecode.
               * @param _dependencyType Asset dependency type.
               *  0 - IPFS
               *  1 - ARWEAVE
               *  2 - ONCHAIN
               */
              function addProjectExternalAssetDependency(
                  uint256 _projectId,
                  string memory _cidOrData,
                  ExternalAssetDependencyType _dependencyType
              ) external {
                  _onlyUnlockedProjectExternalAssetDependencies(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.addProjectExternalAssetDependency.selector
                  );
                  uint24 assetCount = projects[_projectId].externalAssetDependencyCount;
                  address _bytecodeAddress = address(0);
                  // if the incoming dependency type is onchain, we need to write the data to bytecode
                  if (_dependencyType == ExternalAssetDependencyType.ONCHAIN) {
                      _bytecodeAddress = _cidOrData.writeToBytecode();
                      // we don't want to emit data, so we emit the cid as an empty string
                      _cidOrData = "";
                  }
                  ExternalAssetDependency memory asset = ExternalAssetDependency({
                      cid: _cidOrData,
                      dependencyType: _dependencyType,
                      bytecodeAddress: _bytecodeAddress
                  });
                  projects[_projectId].externalAssetDependencies[assetCount] = asset;
                  projects[_projectId].externalAssetDependencyCount = assetCount + 1;
                  emit ExternalAssetDependencyUpdated(
                      _projectId,
                      assetCount,
                      _cidOrData,
                      _dependencyType,
                      assetCount + 1
                  );
              }
              /**
               * @notice Mints a token from project `_projectId` and sets the
               * token's owner to `_to`. Hash may or may not be assigned to the token
               * during the mint transaction, depending on the randomizer contract.
               * @param _to Address to be the minted token's owner.
               * @param _projectId Project ID to mint a token on.
               * @param _by Purchaser of minted token.
               * @return _tokenId The ID of the minted token.
               * @dev sender must be the allowed minterContract
               * @dev name of function is optimized for gas usage
               */
              function mint_Ecf(
                  address _to,
                  uint256 _projectId,
                  address _by
              ) external returns (uint256 _tokenId) {
                  // CHECKS
                  require(msg.sender == minterContract, "Must mint from minter contract");
                  Project storage project = projects[_projectId];
                  // load invocations into memory
                  uint24 invocationsBefore = project.invocations;
                  uint24 invocationsAfter;
                  unchecked {
                      // invocationsBefore guaranteed <= maxInvocations <= 1_000_000,
                      // 1_000_000 << max uint24, so no possible overflow
                      invocationsAfter = invocationsBefore + 1;
                  }
                  uint24 maxInvocations = project.maxInvocations;
                  require(
                      invocationsBefore < maxInvocations,
                      "Must not exceed max invocations"
                  );
                  require(
                      project.active ||
                          _by == projectIdToFinancials[_projectId].artistAddress,
                      "Project must exist and be active"
                  );
                  require(
                      !project.paused ||
                          _by == projectIdToFinancials[_projectId].artistAddress,
                      "Purchases are paused."
                  );
                  // EFFECTS
                  // increment project's invocations
                  project.invocations = invocationsAfter;
                  uint256 thisTokenId;
                  unchecked {
                      // invocationsBefore is uint24 << max uint256. In production use,
                      // _projectId * ONE_MILLION must be << max uint256, otherwise
                      // tokenIdToProjectId function become invalid.
                      // Therefore, no risk of overflow
                      thisTokenId = (_projectId * ONE_MILLION) + invocationsBefore;
                  }
                  // mark project as completed if hit max invocations
                  if (invocationsAfter == maxInvocations) {
                      _completeProject(_projectId);
                  }
                  // INTERACTIONS
                  _mint(_to, thisTokenId);
                  // token hash is updated by the randomizer contract on V3
                  randomizerContract.assignTokenHash(thisTokenId);
                  // Do not need to also log `projectId` in event, as the `projectId` for
                  // a given token can be derived from the `tokenId` with:
                  //   projectId = tokenId / 1_000_000
                  emit Mint(_to, thisTokenId);
                  return thisTokenId;
              }
              /**
               * @notice Sets the hash seed for a given token ID `_tokenId`.
               * May only be called by the current randomizer contract.
               * May only be called for tokens that have not already been assigned a
               * non-zero hash.
               * @param _tokenId Token ID to set the hash for.
               * @param _hashSeed Hash seed to set for the token ID. Only last 12 bytes
               * will be used.
               * @dev gas-optimized function name because called during mint sequence
               * @dev if a separate event is required when the token hash is set, e.g.
               * for indexing purposes, it must be emitted by the randomizer. This is to
               * minimize gas when minting.
               */
              function setTokenHash_8PT(uint256 _tokenId, bytes32 _hashSeed) external {
                  _onlyValidTokenId(_tokenId);
                  OwnerAndHashSeed storage ownerAndHashSeed = _ownersAndHashSeeds[
                      _tokenId
                  ];
                  require(
                      msg.sender == address(randomizerContract),
                      "Only randomizer may set"
                  );
                  require(
                      ownerAndHashSeed.hashSeed == bytes12(0),
                      "Token hash already set"
                  );
                  require(_hashSeed != bytes12(0), "No zero hash seed");
                  ownerAndHashSeed.hashSeed = bytes12(_hashSeed);
              }
              /**
               * @notice Allows owner (AdminACL) to revoke ownership of the contract.
               * Note that the contract is intended to continue to function after the
               * owner renounces ownership, but no new projects will be able to be added.
               * Renouncing ownership will leave the contract without an owner,
               * thereby removing any functionality that is only available to the
               * owner/AdminACL contract. The same is true for any dependent contracts
               * that also integrate with the owner/AdminACL contract (e.g. potentially
               * minter suite contracts, registry contracts, etc.).
               * After renouncing ownership, artists will be in control of updates to
               * their payment addresses and splits (see modifier
               * onlyAdminACLOrRenouncedArtist`).
               * While there is no currently intended reason to call this method based on
               * typical Engine partner business practices, this method exists to allow
               * artists to continue to maintain the limited set of contract
               * functionality that exists post-project-lock in an environment in which
               * there is no longer an admin maintaining this smart contract.
               * @dev This function is intended to be called directly by the AdminACL,
               * not by an address allowed by the AdminACL contract.
               */
              function renounceOwnership() public override onlyOwner {
                  // broadcast that new projects are no longer allowed (if not already)
                  _forbidNewProjects();
                  // renounce ownership viw Ownable
                  Ownable.renounceOwnership();
              }
              /**
               * @notice Updates reference to Art Blocks Dependency Registry contract.
               * @param _artblocksDependencyRegistryAddress Address of new Dependency
               * Registry.
               */
              function updateArtblocksDependencyRegistryAddress(
                  address _artblocksDependencyRegistryAddress
              ) external {
                  _onlyAdminACL(this.updateArtblocksDependencyRegistryAddress.selector);
                  _onlyNonZeroAddress(_artblocksDependencyRegistryAddress);
                  artblocksDependencyRegistryAddress = _artblocksDependencyRegistryAddress;
                  emit PlatformUpdated(FIELD_ARTBLOCKS_DEPENDENCY_REGISTRY_ADDRESS);
              }
              /**
               * @notice Updates sales addresses for the platform and render providers to
               * the input parameters.
               * @param _renderProviderPrimarySalesAddress Address of new primary sales
               * payment address.
               * @param _renderProviderSecondarySalesAddress Address of new secondary sales
               * payment address.
               * @param _platformProviderPrimarySalesAddress Address of new primary sales
               * payment address.
               * @param _platformProviderSecondarySalesAddress Address of new secondary sales
               * payment address.
               */
              function updateProviderSalesAddresses(
                  address payable _renderProviderPrimarySalesAddress,
                  address payable _renderProviderSecondarySalesAddress,
                  address payable _platformProviderPrimarySalesAddress,
                  address payable _platformProviderSecondarySalesAddress
              ) external {
                  _onlyAdminACL(this.updateProviderSalesAddresses.selector);
                  _onlyNonZeroAddress(_renderProviderPrimarySalesAddress);
                  _onlyNonZeroAddress(_renderProviderSecondarySalesAddress);
                  _onlyNonZeroAddress(_platformProviderPrimarySalesAddress);
                  _onlyNonZeroAddress(_platformProviderSecondarySalesAddress);
                  _updateProviderSalesAddresses(
                      _renderProviderPrimarySalesAddress,
                      _renderProviderSecondarySalesAddress,
                      _platformProviderPrimarySalesAddress,
                      _platformProviderSecondarySalesAddress
                  );
              }
              /**
               * @notice Updates the render and platform provider primary sales revenue percentage to
               * the provided inputs.
               * @param renderProviderPrimarySalesPercentage_ New primary sales revenue % for the render provider
               * @param platformProviderPrimarySalesPercentage_ New primary sales revenue % for the platform provider
               * percentage.
               */
              function updateProviderPrimarySalesPercentages(
                  uint256 renderProviderPrimarySalesPercentage_,
                  uint256 platformProviderPrimarySalesPercentage_
              ) external {
                  _onlyAdminACL(this.updateProviderPrimarySalesPercentages.selector);
                  // Validate that the sum of the proposed %s, does not exceed 100%.
                  require(
                      (renderProviderPrimarySalesPercentage_ +
                          platformProviderPrimarySalesPercentage_) <= ONE_HUNDRED,
                      "Max sum of ONE_HUNDRED %"
                  );
                  // Casting to `uint8` here is safe due check above, which does not allow
                  // overflow as of solidity version ^0.8.0.
                  _renderProviderPrimarySalesPercentage = uint8(
                      renderProviderPrimarySalesPercentage_
                  );
                  _platformProviderPrimarySalesPercentage = uint8(
                      platformProviderPrimarySalesPercentage_
                  );
                  emit PlatformUpdated(FIELD_PROVIDER_PRIMARY_SALES_PERCENTAGES);
              }
              /**
               * @notice Updates render and platform provider secondary sales royalty Basis Points to
               * the provided inputs.
               * @param _renderProviderSecondarySalesBPS New secondary sales royalty Basis
               * points.
               * @param _platformProviderSecondarySalesBPS New secondary sales royalty Basis
               * points.
               * @dev Due to secondary royalties being ultimately enforced via social
               * consensus, no hard upper limit is imposed on the BPS value, other than
               * <= 100% royalty, which would not make mathematical sense. Realistically,
               * changing this value is expected to either never occur, or be a rare
               * occurrence.
               */
              function updateProviderSecondarySalesBPS(
                  uint256 _renderProviderSecondarySalesBPS,
                  uint256 _platformProviderSecondarySalesBPS
              ) external {
                  _onlyAdminACL(this.updateProviderSecondarySalesBPS.selector);
                  // Validate that the sum of the proposed provider BPS, does not exceed 10_000 BPS.
                  require(
                      (_renderProviderSecondarySalesBPS +
                          _platformProviderSecondarySalesBPS) <=
                          MAX_PROVIDER_SECONDARY_SALES_BPS,
                      "Over max sum of BPS"
                  );
                  renderProviderSecondarySalesBPS = _renderProviderSecondarySalesBPS;
                  platformProviderSecondarySalesBPS = _platformProviderSecondarySalesBPS;
                  emit PlatformUpdated(FIELD_PROVIDER_SECONDARY_SALES_BPS);
              }
              /**
               * @notice Updates minter to `_address`.
               * @param _address Address of new minter.
               */
              function updateMinterContract(address _address) external {
                  _onlyAdminACL(this.updateMinterContract.selector);
                  _onlyNonZeroAddress(_address);
                  minterContract = _address;
                  emit MinterUpdated(_address);
              }
              /**
               * @notice Updates randomizer to `_randomizerAddress`.
               * @param _randomizerAddress Address of new randomizer.
               */
              function updateRandomizerAddress(address _randomizerAddress) external {
                  _onlyAdminACL(this.updateRandomizerAddress.selector);
                  _onlyNonZeroAddress(_randomizerAddress);
                  _updateRandomizerAddress(_randomizerAddress);
              }
              /**
               * @notice Toggles project `_projectId` as active/inactive.
               * @param _projectId Project ID to be toggled.
               */
              function toggleProjectIsActive(uint256 _projectId) external {
                  _onlyAdminACL(this.toggleProjectIsActive.selector);
                  _onlyValidProjectId(_projectId);
                  projects[_projectId].active = !projects[_projectId].active;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_ACTIVE);
              }
              /**
               * @notice Artist proposes updated set of artist address, additional payee
               * addresses, and percentage splits for project `_projectId`. Addresses and
               * percentages do not have to all be changed, but they must all be defined
               * as a complete set.
               * Note that if the artist is only proposing a change to the payee percentage
               * splits, without modifying the payee addresses, the proposal will be
               * automatically approved and the new splits will become active immediately.
               * Automatic approval will also be granted if the artist is only removing
               * additional payee addresses, without adding any new ones.
               * Also note that if `autoApproveArtistSplitProposals` is true, proposals
               * will always be auto-approved, regardless of what is being changed.
               * Also note that if the artist is proposing sending funds to the zero
               * address, this function will revert and the proposal will not be created.
               * @param _projectId Project ID.
               * @param _artistAddress Artist address that controls the project, and may
               * receive payments.
               * @param _additionalPayeePrimarySales Address that may receive a
               * percentage split of the artist's primary sales revenue.
               * @param _additionalPayeePrimarySalesPercentage Percent of artist's
               * portion of primary sale revenue that will be split to address
               * `_additionalPayeePrimarySales`.
               * @param _additionalPayeeSecondarySales Address that may receive a percentage
               * split of the secondary sales royalties.
               * @param _additionalPayeeSecondarySalesPercentage Percent of artist's portion
               * of secondary sale royalties that will be split to address
               * `_additionalPayeeSecondarySales`.
               * @dev `_artistAddress` must be a valid address (non-zero-address), but it
               * is intentionally allowable for `_additionalPayee{Primary,Secondaary}Sales`
               * and their associated percentages to be zero'd out by the controlling artist.
               */
              function proposeArtistPaymentAddressesAndSplits(
                  uint256 _projectId,
                  address payable _artistAddress,
                  address payable _additionalPayeePrimarySales,
                  uint256 _additionalPayeePrimarySalesPercentage,
                  address payable _additionalPayeeSecondarySales,
                  uint256 _additionalPayeeSecondarySalesPercentage
              ) external {
                  _onlyValidProjectId(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.proposeArtistPaymentAddressesAndSplits.selector
                  );
                  _onlyNonZeroAddress(_artistAddress);
                  ProjectFinance storage projectFinance = projectIdToFinancials[
                      _projectId
                  ];
                  // checks
                  require(
                      _additionalPayeePrimarySalesPercentage <= ONE_HUNDRED &&
                          _additionalPayeeSecondarySalesPercentage <= ONE_HUNDRED,
                      "Max of 100%"
                  );
                  require(
                      _additionalPayeePrimarySalesPercentage == 0 ||
                          _additionalPayeePrimarySales != address(0),
                      "Primary payee is zero address"
                  );
                  require(
                      _additionalPayeeSecondarySalesPercentage == 0 ||
                          _additionalPayeeSecondarySales != address(0),
                      "Secondary payee is zero address"
                  );
                  // effects
                  // emit event for off-chain indexing
                  // note: always emit a proposal event, even in the pathway of
                  // automatic approval, to simplify indexing expectations
                  emit ProposedArtistAddressesAndSplits(
                      _projectId,
                      _artistAddress,
                      _additionalPayeePrimarySales,
                      _additionalPayeePrimarySalesPercentage,
                      _additionalPayeeSecondarySales,
                      _additionalPayeeSecondarySalesPercentage
                  );
                  // automatically accept if no proposed addresses modifications, or if
                  // the proposal only removes payee addresses, or if contract is set to
                  // always auto-approve.
                  // store proposal hash on-chain, only if not automatic accept
                  bool automaticAccept = autoApproveArtistSplitProposals;
                  if (!automaticAccept) {
                      // block scope to avoid stack too deep error
                      bool artistUnchanged = _artistAddress ==
                          projectFinance.artistAddress;
                      bool additionalPrimaryUnchangedOrRemoved = (_additionalPayeePrimarySales ==
                              projectFinance.additionalPayeePrimarySales) ||
                              (_additionalPayeePrimarySales == address(0));
                      bool additionalSecondaryUnchangedOrRemoved = (_additionalPayeeSecondarySales ==
                              projectFinance.additionalPayeeSecondarySales) ||
                              (_additionalPayeeSecondarySales == address(0));
                      automaticAccept =
                          artistUnchanged &&
                          additionalPrimaryUnchangedOrRemoved &&
                          additionalSecondaryUnchangedOrRemoved;
                  }
                  if (automaticAccept) {
                      // clear any previously proposed values
                      proposedArtistAddressesAndSplitsHash[_projectId] = bytes32(0);
                      // update storage
                      // artist address can change during automatic accept if
                      // autoApproveArtistSplitProposals is true
                      projectFinance.artistAddress = _artistAddress;
                      projectFinance
                          .additionalPayeePrimarySales = _additionalPayeePrimarySales;
                      // safe to cast as uint8 as max is 100%, max uint8 is 255
                      projectFinance.additionalPayeePrimarySalesPercentage = uint8(
                          _additionalPayeePrimarySalesPercentage
                      );
                      projectFinance
                          .additionalPayeeSecondarySales = _additionalPayeeSecondarySales;
                      // safe to cast as uint8 as max is 100%, max uint8 is 255
                      projectFinance.additionalPayeeSecondarySalesPercentage = uint8(
                          _additionalPayeeSecondarySalesPercentage
                      );
                      // emit event for off-chain indexing
                      emit AcceptedArtistAddressesAndSplits(_projectId);
                  } else {
                      proposedArtistAddressesAndSplitsHash[_projectId] = keccak256(
                          abi.encode(
                              _artistAddress,
                              _additionalPayeePrimarySales,
                              _additionalPayeePrimarySalesPercentage,
                              _additionalPayeeSecondarySales,
                              _additionalPayeeSecondarySalesPercentage
                          )
                      );
                  }
              }
              /**
               * @notice Admin accepts a proposed set of updated artist address,
               * additional payee addresses, and percentage splits for project
               * `_projectId`. Addresses and percentages do not have to all be changed,
               * but they must all be defined as a complete set.
               * @param _projectId Project ID.
               * @param _artistAddress Artist address that controls the project, and may
               * receive payments.
               * @param _additionalPayeePrimarySales Address that may receive a
               * percentage split of the artist's primary sales revenue.
               * @param _additionalPayeePrimarySalesPercentage Percent of artist's
               * portion of primary sale revenue that will be split to address
               * `_additionalPayeePrimarySales`.
               * @param _additionalPayeeSecondarySales Address that may receive a percentage
               * split of the secondary sales royalties.
               * @param _additionalPayeeSecondarySalesPercentage Percent of artist's portion
               * of secondary sale royalties that will be split to address
               * `_additionalPayeeSecondarySales`.
               * @dev this must be called by the Admin ACL contract, and must only accept
               * the most recent proposed values for a given project (validated on-chain
               * by comparing the hash of the proposed and accepted values).
               * @dev `_artistAddress` must be a valid address (non-zero-address), but it
               * is intentionally allowable for `_additionalPayee{Primary,Secondaary}Sales`
               * and their associated percentages to be zero'd out by the controlling artist.
               */
              function adminAcceptArtistAddressesAndSplits(
                  uint256 _projectId,
                  address payable _artistAddress,
                  address payable _additionalPayeePrimarySales,
                  uint256 _additionalPayeePrimarySalesPercentage,
                  address payable _additionalPayeeSecondarySales,
                  uint256 _additionalPayeeSecondarySalesPercentage
              ) external {
                  _onlyValidProjectId(_projectId);
                  _onlyAdminACLOrRenouncedArtist(
                      _projectId,
                      this.adminAcceptArtistAddressesAndSplits.selector
                  );
                  _onlyNonZeroAddress(_artistAddress);
                  // checks
                  require(
                      proposedArtistAddressesAndSplitsHash[_projectId] ==
                          keccak256(
                              abi.encode(
                                  _artistAddress,
                                  _additionalPayeePrimarySales,
                                  _additionalPayeePrimarySalesPercentage,
                                  _additionalPayeeSecondarySales,
                                  _additionalPayeeSecondarySalesPercentage
                              )
                          ),
                      "Must match artist proposal"
                  );
                  // effects
                  ProjectFinance storage projectFinance = projectIdToFinancials[
                      _projectId
                  ];
                  projectFinance.artistAddress = _artistAddress;
                  projectFinance
                      .additionalPayeePrimarySales = _additionalPayeePrimarySales;
                  projectFinance.additionalPayeePrimarySalesPercentage = uint8(
                      _additionalPayeePrimarySalesPercentage
                  );
                  projectFinance
                      .additionalPayeeSecondarySales = _additionalPayeeSecondarySales;
                  projectFinance.additionalPayeeSecondarySalesPercentage = uint8(
                      _additionalPayeeSecondarySalesPercentage
                  );
                  // clear proposed values
                  proposedArtistAddressesAndSplitsHash[_projectId] = bytes32(0);
                  // emit event for off-chain indexing
                  emit AcceptedArtistAddressesAndSplits(_projectId);
              }
              /**
               * @notice Updates artist of project `_projectId` to `_artistAddress`.
               * This is to only be used in the event that the artist address is
               * compromised or sanctioned.
               * @param _projectId Project ID.
               * @param _artistAddress New artist address.
               */
              function updateProjectArtistAddress(
                  uint256 _projectId,
                  address payable _artistAddress
              ) external {
                  _onlyValidProjectId(_projectId);
                  _onlyAdminACLOrRenouncedArtist(
                      _projectId,
                      this.updateProjectArtistAddress.selector
                  );
                  _onlyNonZeroAddress(_artistAddress);
                  projectIdToFinancials[_projectId].artistAddress = _artistAddress;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_ARTIST_ADDRESS);
              }
              /**
               * @notice Toggles paused state of project `_projectId`.
               * @param _projectId Project ID to be toggled.
               */
              function toggleProjectIsPaused(uint256 _projectId) external {
                  _onlyValidProjectId(_projectId);
                  _onlyArtistOrAdminACL(_projectId, this.toggleProjectIsPaused.selector);
                  projects[_projectId].paused = !projects[_projectId].paused;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_PAUSED);
              }
              /**
               * @notice Adds new project `_projectName` by `_artistAddress`.
               * @param _projectName Project name.
               * @param _artistAddress Artist's address.
               * @dev token price now stored on minter
               */
              function addProject(
                  string memory _projectName,
                  address payable _artistAddress
              ) external {
                  _onlyAdminACL(this.addProject.selector);
                  _onlyNonEmptyString(_projectName);
                  _onlyNonZeroAddress(_artistAddress);
                  require(!newProjectsForbidden, "New projects forbidden");
                  uint256 projectId = _nextProjectId;
                  projectIdToFinancials[projectId].artistAddress = _artistAddress;
                  projects[projectId].name = _projectName;
                  projects[projectId].paused = true;
                  projects[projectId].maxInvocations = ONE_MILLION_UINT24;
                  projects[projectId].projectBaseURI = defaultBaseURI;
                  _nextProjectId = uint248(projectId) + 1;
                  emit ProjectUpdated(projectId, FIELD_PROJECT_CREATED);
              }
              /**
               * @notice Forever forbids new projects from being added to this contract.
               */
              function forbidNewProjects() external {
                  _onlyAdminACL(this.forbidNewProjects.selector);
                  require(!newProjectsForbidden, "Already forbidden");
                  _forbidNewProjects();
              }
              /**
               * @notice Updates name of project `_projectId` to be `_projectName`.
               * @param _projectId Project ID.
               * @param _projectName New project name.
               */
              function updateProjectName(
                  uint256 _projectId,
                  string memory _projectName
              ) external {
                  _onlyUnlocked(_projectId);
                  _onlyArtistOrAdminACL(_projectId, this.updateProjectName.selector);
                  _onlyNonEmptyString(_projectName);
                  projects[_projectId].name = _projectName;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_NAME);
              }
              /**
               * @notice Updates artist name for project `_projectId` to be
               * `_projectArtistName`.
               * @param _projectId Project ID.
               * @param _projectArtistName New artist name.
               */
              function updateProjectArtistName(
                  uint256 _projectId,
                  string memory _projectArtistName
              ) external {
                  _onlyUnlocked(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.updateProjectArtistName.selector
                  );
                  _onlyNonEmptyString(_projectArtistName);
                  projects[_projectId].artist = _projectArtistName;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_ARTIST_NAME);
              }
              /**
               * @notice Updates artist secondary market royalties for project
               * `_projectId` to be `_secondMarketRoyalty` percent.
               * This DOES NOT include the secondary market royalty percentages collected
               * by the issuing platform; it is only the total percentage of royalties
               * that will be split to artist and additionalSecondaryPayee.
               * @param _projectId Project ID.
               * @param _secondMarketRoyalty Percent of secondary sales revenue that will
               * be split to artist and additionalSecondaryPayee. This must be less than
               * or equal to ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE percent.
               */
              function updateProjectSecondaryMarketRoyaltyPercentage(
                  uint256 _projectId,
                  uint256 _secondMarketRoyalty
              ) external {
                  _onlyValidProjectId(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.updateProjectSecondaryMarketRoyaltyPercentage.selector
                  );
                  require(
                      _secondMarketRoyalty <= ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE,
                      "Over max percent"
                  );
                  projectIdToFinancials[_projectId]
                      .secondaryMarketRoyaltyPercentage = uint8(_secondMarketRoyalty);
                  emit ProjectUpdated(
                      _projectId,
                      FIELD_PROJECT_SECONDARY_MARKET_ROYALTY_PERCENTAGE
                  );
              }
              /**
               * @notice Updates description of project `_projectId`.
               * Only artist may call when unlocked, only admin may call when locked.
               * @param _projectId Project ID.
               * @param _projectDescription New project description.
               */
              function updateProjectDescription(
                  uint256 _projectId,
                  string memory _projectDescription
              ) external {
                  // checks
                  require(
                      _projectUnlocked(_projectId)
                          ? msg.sender == projectIdToFinancials[_projectId].artistAddress
                          : adminACLAllowed(
                              msg.sender,
                              address(this),
                              this.updateProjectDescription.selector
                          ),
                      "Only artist when unlocked, owner when locked"
                  );
                  // effects
                  projects[_projectId].description = _projectDescription;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_DESCRIPTION);
              }
              /**
               * @notice Updates website of project `_projectId` to be `_projectWebsite`.
               * @param _projectId Project ID.
               * @param _projectWebsite New project website.
               * @dev It is intentionally allowed for this to be set to the empty string.
               */
              function updateProjectWebsite(
                  uint256 _projectId,
                  string memory _projectWebsite
              ) external {
                  _onlyValidProjectId(_projectId);
                  _onlyArtistOrAdminACL(_projectId, this.updateProjectWebsite.selector);
                  projects[_projectId].website = _projectWebsite;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_WEBSITE);
              }
              /**
               * @notice Updates license for project `_projectId`.
               * @param _projectId Project ID.
               * @param _projectLicense New project license.
               */
              function updateProjectLicense(
                  uint256 _projectId,
                  string memory _projectLicense
              ) external {
                  _onlyUnlocked(_projectId);
                  _onlyArtistOrAdminACL(_projectId, this.updateProjectLicense.selector);
                  _onlyNonEmptyString(_projectLicense);
                  projects[_projectId].license = _projectLicense;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_LICENSE);
              }
              /**
               * @notice Updates maximum invocations for project `_projectId` to
               * `_maxInvocations`. Maximum invocations may only be decreased by the
               * artist, and must be greater than or equal to current invocations.
               * New projects are created with maximum invocations of 1 million by
               * default.
               * @param _projectId Project ID.
               * @param _maxInvocations New maximum invocations.
               */
              function updateProjectMaxInvocations(
                  uint256 _projectId,
                  uint24 _maxInvocations
              ) external {
                  _onlyValidProjectId(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.updateProjectMaxInvocations.selector
                  );
                  // CHECKS
                  Project storage project = projects[_projectId];
                  uint256 _invocations = project.invocations;
                  require(
                      (_maxInvocations < project.maxInvocations),
                      "Only maxInvocations decrease"
                  );
                  require(_maxInvocations >= _invocations, "Only gte invocations");
                  // EFFECTS
                  project.maxInvocations = _maxInvocations;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_MAX_INVOCATIONS);
                  // register completed timestamp if action completed the project
                  if (_maxInvocations == _invocations) {
                      _completeProject(_projectId);
                  }
              }
              /**
               * @notice Adds a script to project `_projectId`.
               * @param _projectId Project to be updated.
               * @param _script Script to be added. Required to be a non-empty string,
               * but no further validation is performed.
               */
              function addProjectScript(
                  uint256 _projectId,
                  string memory _script
              ) external {
                  _onlyUnlocked(_projectId);
                  _onlyArtistOrAdminACL(_projectId, this.addProjectScript.selector);
                  _onlyNonEmptyString(_script);
                  Project storage project = projects[_projectId];
                  // store script in contract bytecode
                  project.scriptBytecodeAddresses[project.scriptCount] = _script
                      .writeToBytecode();
                  project.scriptCount = project.scriptCount + 1;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT);
              }
              /**
               * @notice Updates script for project `_projectId` at script ID `_scriptId`.
               * @param _projectId Project to be updated.
               * @param _scriptId Script ID to be updated.
               * @param _script The updated script value. Required to be a non-empty
               *                string, but no further validation is performed.
               */
              function updateProjectScript(
                  uint256 _projectId,
                  uint256 _scriptId,
                  string memory _script
              ) external {
                  _onlyUnlocked(_projectId);
                  _onlyArtistOrAdminACL(_projectId, this.updateProjectScript.selector);
                  _onlyNonEmptyString(_script);
                  Project storage project = projects[_projectId];
                  require(_scriptId < project.scriptCount, "scriptId out of range");
                  // store script in contract bytecode, replacing reference address from
                  // the contract that no longer exists with the newly created one
                  project.scriptBytecodeAddresses[_scriptId] = _script.writeToBytecode();
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT);
              }
              /**
               * @notice Removes last script from project `_projectId`.
               * @param _projectId Project to be updated.
               */
              function removeProjectLastScript(uint256 _projectId) external {
                  _onlyUnlocked(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.removeProjectLastScript.selector
                  );
                  Project storage project = projects[_projectId];
                  require(project.scriptCount > 0, "No scripts to remove");
                  // delete reference to contract address that no longer exists
                  delete project.scriptBytecodeAddresses[project.scriptCount - 1];
                  unchecked {
                      project.scriptCount = project.scriptCount - 1;
                  }
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT);
              }
              /**
               * @notice Updates script type for project `_projectId`.
               * @param _projectId Project to be updated.
               * @param _scriptTypeAndVersion Script type and version e.g. "[email protected]",
               * as bytes32 encoded string.
               */
              function updateProjectScriptType(
                  uint256 _projectId,
                  bytes32 _scriptTypeAndVersion
              ) external {
                  _onlyUnlocked(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.updateProjectScriptType.selector
                  );
                  Project storage project = projects[_projectId];
                  // require exactly one @ symbol in _scriptTypeAndVersion
                  require(
                      _scriptTypeAndVersion.containsExactCharacterQty(
                          AT_CHARACTER_CODE,
                          uint8(1)
                      ),
                      "must contain exactly one @"
                  );
                  project.scriptTypeAndVersion = _scriptTypeAndVersion;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT_TYPE);
              }
              /**
               * @notice Updates project's aspect ratio.
               * @param _projectId Project to be updated.
               * @param _aspectRatio Aspect ratio to be set. Intended to be string in the
               * format of a decimal, e.g. "1" for square, "1.77777778" for 16:9, etc.,
               * allowing for a maximum of 10 digits and one (optional) decimal separator.
               */
              function updateProjectAspectRatio(
                  uint256 _projectId,
                  string memory _aspectRatio
              ) external {
                  _onlyUnlocked(_projectId);
                  _onlyArtistOrAdminACL(
                      _projectId,
                      this.updateProjectAspectRatio.selector
                  );
                  _onlyNonEmptyString(_aspectRatio);
                  // Perform more detailed input validation for aspect ratio.
                  bytes memory aspectRatioBytes = bytes(_aspectRatio);
                  uint256 bytesLength = aspectRatioBytes.length;
                  require(bytesLength <= 11, "Aspect ratio format too long");
                  bool hasSeenDecimalSeparator = false;
                  bool hasSeenNumber = false;
                  for (uint256 i; i < bytesLength; i++) {
                      bytes1 character = aspectRatioBytes[i];
                      // Allow as many #s as desired.
                      if (character >= 0x30 && character <= 0x39) {
                          // 9-0
                          // We need to ensure there is at least 1 `9-0` occurrence.
                          hasSeenNumber = true;
                          continue;
                      }
                      if (character == 0x2E) {
                          // .
                          // Allow no more than 1 `.` occurrence.
                          if (!hasSeenDecimalSeparator) {
                              hasSeenDecimalSeparator = true;
                              continue;
                          }
                      }
                      revert("Improperly formatted aspect ratio");
                  }
                  require(hasSeenNumber, "Aspect ratio has no numbers");
                  projects[_projectId].aspectRatio = _aspectRatio;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_ASPECT_RATIO);
              }
              /**
               * @notice Updates base URI for project `_projectId` to `_newBaseURI`.
               * This is the controlling base URI for all tokens in the project. The
               * contract-level defaultBaseURI is only used when initializing new
               * projects.
               * @param _projectId Project to be updated.
               * @param _newBaseURI New base URI.
               */
              function updateProjectBaseURI(
                  uint256 _projectId,
                  string memory _newBaseURI
              ) external {
                  _onlyValidProjectId(_projectId);
                  _onlyArtistOrAdminACL(_projectId, this.updateProjectBaseURI.selector);
                  _onlyNonEmptyString(_newBaseURI);
                  projects[_projectId].projectBaseURI = _newBaseURI;
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_BASE_URI);
              }
              /**
               * @notice Updates default base URI to `_defaultBaseURI`. The
               * contract-level defaultBaseURI is only used when initializing new
               * projects. Token URIs are determined by their project's `projectBaseURI`.
               * @param _defaultBaseURI New default base URI.
               */
              function updateDefaultBaseURI(string memory _defaultBaseURI) external {
                  _onlyAdminACL(this.updateDefaultBaseURI.selector);
                  _onlyNonEmptyString(_defaultBaseURI);
                  _updateDefaultBaseURI(_defaultBaseURI);
              }
              /**
               * @notice Next project ID to be created on this contract.
               * @return uint256 Next project ID.
               */
              function nextProjectId() external view returns (uint256) {
                  return _nextProjectId;
              }
              /**
               * @notice Returns token hash for token ID `_tokenId`. Returns null if hash
               * has not been set.
               * @param _tokenId Token ID to be queried.
               * @return bytes32 Token hash.
               * @dev token hash is the keccak256 hash of the stored hash seed
               */
              function tokenIdToHash(uint256 _tokenId) external view returns (bytes32) {
                  bytes12 _hashSeed = _ownersAndHashSeeds[_tokenId].hashSeed;
                  if (_hashSeed == 0) {
                      return 0;
                  }
                  return keccak256(abi.encode(_hashSeed));
              }
              /**
               * @notice Returns token hash **seed** for token ID `_tokenId`. Returns
               * null if hash seed has not been set. The hash seed id the bytes12 value
               * which is hashed to produce the token hash.
               * @param _tokenId Token ID to be queried.
               * @return bytes12 Token hash seed.
               * @dev token hash seed is keccak256 hashed to give the token hash
               */
              function tokenIdToHashSeed(
                  uint256 _tokenId
              ) external view returns (bytes12) {
                  return _ownersAndHashSeeds[_tokenId].hashSeed;
              }
              /**
               * @notice View function returning the render provider portion of
               * primary sales, in percent.
               * @return uint256 The render provider portion of primary sales,
               * in percent.
               */
              function renderProviderPrimarySalesPercentage()
                  external
                  view
                  returns (uint256)
              {
                  return _renderProviderPrimarySalesPercentage;
              }
              /**
               * @notice View function returning the platform provider portion of
               * primary sales, in percent.
               * @return uint256 The platform provider portion of primary sales,
               * in percent.
               */
              function platformProviderPrimarySalesPercentage()
                  external
                  view
                  returns (uint256)
              {
                  return _platformProviderPrimarySalesPercentage;
              }
              /**
               * @notice View function returning Artist's address for project
               * `_projectId`.
               * @param _projectId Project ID to be queried.
               * @return address Artist's address.
               */
              function projectIdToArtistAddress(
                  uint256 _projectId
              ) external view returns (address payable) {
                  return projectIdToFinancials[_projectId].artistAddress;
              }
              /**
               * @notice View function returning Artist's secondary market royalty
               * percentage for project `_projectId`.
               * This does not include render/platform providers portions of secondary
               * market royalties.
               * @param _projectId Project ID to be queried.
               * @return uint256 Artist's secondary market royalty percentage.
               */
              function projectIdToSecondaryMarketRoyaltyPercentage(
                  uint256 _projectId
              ) external view returns (uint256) {
                  return
                      projectIdToFinancials[_projectId].secondaryMarketRoyaltyPercentage;
              }
              /**
               * @notice View function returning Artist's additional payee address for
               * primary sales, for project `_projectId`.
               * @param _projectId Project ID to be queried.
               * @return address Artist's additional payee address for primary sales.
               */
              function projectIdToAdditionalPayeePrimarySales(
                  uint256 _projectId
              ) external view returns (address payable) {
                  return projectIdToFinancials[_projectId].additionalPayeePrimarySales;
              }
              /**
               * @notice View function returning Artist's additional payee primary sales
               * percentage, for project `_projectId`.
               * @param _projectId Project ID to be queried.
               * @return uint256 Artist's additional payee primary sales percentage.
               */
              function projectIdToAdditionalPayeePrimarySalesPercentage(
                  uint256 _projectId
              ) external view returns (uint256) {
                  return
                      projectIdToFinancials[_projectId]
                          .additionalPayeePrimarySalesPercentage;
              }
              /**
               * @notice View function returning Artist's additional payee address for
               * secondary sales, for project `_projectId`.
               * @param _projectId Project ID to be queried.
               * @return address payable Artist's additional payee address for secondary
               * sales.
               */
              function projectIdToAdditionalPayeeSecondarySales(
                  uint256 _projectId
              ) external view returns (address payable) {
                  return projectIdToFinancials[_projectId].additionalPayeeSecondarySales;
              }
              /**
               * @notice View function returning Artist's additional payee secondary
               * sales percentage, for project `_projectId`.
               * @param _projectId Project ID to be queried.
               * @return uint256 Artist's additional payee secondary sales percentage.
               */
              function projectIdToAdditionalPayeeSecondarySalesPercentage(
                  uint256 _projectId
              ) external view returns (uint256) {
                  return
                      projectIdToFinancials[_projectId]
                          .additionalPayeeSecondarySalesPercentage;
              }
              /**
               * @notice Returns project details for project `_projectId`.
               * @param _projectId Project to be queried.
               * @return projectName Name of project
               * @return artist Artist of project
               * @return description Project description
               * @return website Project website
               * @return license Project license
               * @dev this function was named projectDetails prior to V3 core contract.
               */
              function projectDetails(
                  uint256 _projectId
              )
                  external
                  view
                  returns (
                      string memory projectName,
                      string memory artist,
                      string memory description,
                      string memory website,
                      string memory license
                  )
              {
                  Project storage project = projects[_projectId];
                  projectName = project.name;
                  artist = project.artist;
                  description = project.description;
                  website = project.website;
                  license = project.license;
              }
              /**
               * @notice Returns project state data for project `_projectId`.
               * @param _projectId Project to be queried
               * @return invocations Current number of invocations
               * @return maxInvocations Maximum allowed invocations
               * @return active Boolean representing if project is currently active
               * @return paused Boolean representing if project is paused
               * @return completedTimestamp zero if project not complete, otherwise
               * timestamp of project completion.
               * @return locked Boolean representing if project is locked
               * @dev price and currency info are located on minter contracts
               */
              function projectStateData(
                  uint256 _projectId
              )
                  external
                  view
                  returns (
                      uint256 invocations,
                      uint256 maxInvocations,
                      bool active,
                      bool paused,
                      uint256 completedTimestamp,
                      bool locked
                  )
              {
                  Project storage project = projects[_projectId];
                  invocations = project.invocations;
                  maxInvocations = project.maxInvocations;
                  active = project.active;
                  paused = project.paused;
                  completedTimestamp = project.completedTimestamp;
                  locked = !_projectUnlocked(_projectId);
              }
              /**
               * @notice Returns artist payment information for project `_projectId`.
               * @param _projectId Project to be queried
               * @return artistAddress Project Artist's address
               * @return additionalPayeePrimarySales Additional payee address for primary
               * sales
               * @return additionalPayeePrimarySalesPercentage Percentage of artist revenue
               * to be sent to the additional payee address for primary sales
               * @return additionalPayeeSecondarySales Additional payee address for secondary
               * sales royalties
               * @return additionalPayeeSecondarySalesPercentage Percentage of artist revenue
               * to be sent to the additional payee address for secondary sales royalties
               * @return secondaryMarketRoyaltyPercentage Royalty percentage to be sent to
               * combination of artist and additional payee. This does not include the
               * platform's percentage of secondary sales royalties, which is defined as
               * the sum of `renderProviderSecondarySalesBPS`
               * and `platformProviderSecondarySalesBPS`.
               */
              function projectArtistPaymentInfo(
                  uint256 _projectId
              )
                  external
                  view
                  returns (
                      address artistAddress,
                      address additionalPayeePrimarySales,
                      uint256 additionalPayeePrimarySalesPercentage,
                      address additionalPayeeSecondarySales,
                      uint256 additionalPayeeSecondarySalesPercentage,
                      uint256 secondaryMarketRoyaltyPercentage
                  )
              {
                  ProjectFinance storage projectFinance = projectIdToFinancials[
                      _projectId
                  ];
                  artistAddress = projectFinance.artistAddress;
                  additionalPayeePrimarySales = projectFinance
                      .additionalPayeePrimarySales;
                  additionalPayeePrimarySalesPercentage = projectFinance
                      .additionalPayeePrimarySalesPercentage;
                  additionalPayeeSecondarySales = projectFinance
                      .additionalPayeeSecondarySales;
                  additionalPayeeSecondarySalesPercentage = projectFinance
                      .additionalPayeeSecondarySalesPercentage;
                  secondaryMarketRoyaltyPercentage = projectFinance
                      .secondaryMarketRoyaltyPercentage;
              }
              /**
               * @notice Returns script information for project `_projectId`.
               * @param _projectId Project to be queried.
               * @return scriptTypeAndVersion Project's script type and version
               * (e.g. "p5js(atSymbol)1.0.0")
               * @return aspectRatio Aspect ratio of project (e.g. "1" for square,
               * "1.77777778" for 16:9, etc.)
               * @return scriptCount Count of scripts for project
               */
              function projectScriptDetails(
                  uint256 _projectId
              )
                  external
                  view
                  override(IGenArt721CoreContractV3_Base, IDependencyRegistryCompatibleV0)
                  returns (
                      string memory scriptTypeAndVersion,
                      string memory aspectRatio,
                      uint256 scriptCount
                  )
              {
                  Project storage project = projects[_projectId];
                  scriptTypeAndVersion = project.scriptTypeAndVersion.toString();
                  aspectRatio = project.aspectRatio;
                  scriptCount = project.scriptCount;
              }
              /**
               * @notice Returns address with bytecode containing project script for
               * project `_projectId` at script index `_index`.
               */
              function projectScriptBytecodeAddressByIndex(
                  uint256 _projectId,
                  uint256 _index
              ) external view returns (address) {
                  return projects[_projectId].scriptBytecodeAddresses[_index];
              }
              /**
               * @notice Returns script for project `_projectId` at script index `_index`.
               * @param _projectId Project to be queried.
               * @param _index Index of script to be queried.
               */
              function projectScriptByIndex(
                  uint256 _projectId,
                  uint256 _index
              ) external view returns (string memory) {
                  Project storage project = projects[_projectId];
                  // If trying to access an out-of-index script, return the empty string.
                  if (_index >= project.scriptCount) {
                      return "";
                  }
                  return project.scriptBytecodeAddresses[_index].readFromBytecode();
              }
              /**
               * @notice Returns base URI for project `_projectId`.
               * @param _projectId Project to be queried.
               * @return projectBaseURI Base URI for project
               */
              function projectURIInfo(
                  uint256 _projectId
              ) external view returns (string memory projectBaseURI) {
                  projectBaseURI = projects[_projectId].projectBaseURI;
              }
              /**
               * @notice Backwards-compatible (pre-V3) function returning if `_minter` is
               * minterContract.
               * @param _minter Address to be queried.
               * @return bool Boolean representing if `_minter` is minterContract.
               */
              function isMintWhitelisted(address _minter) external view returns (bool) {
                  return (minterContract == _minter);
              }
              /**
               * @notice Gets qty of randomizers in history of all randomizers used by
               * this core contract. If a randomizer is switched away from then back to,
               * it will show up in the history twice.
               * @return randomizerHistoryCount Count of randomizers in history
               */
              function numHistoricalRandomizers() external view returns (uint256) {
                  return _historicalRandomizerAddresses.length;
              }
              /**
               * @notice Gets address of randomizer at index `_index` in history of all
               * randomizers used by this core contract. Index is zero-based.
               * @param _index Historical index of randomizer to be queried.
               * @return randomizerAddress Address of randomizer at index `_index`.
               * @dev If a randomizer is switched away from and then switched back to, it
               * will show up in the history twice.
               */
              function getHistoricalRandomizerAt(
                  uint256 _index
              ) external view returns (address) {
                  require(
                      _index < _historicalRandomizerAddresses.length,
                      "Index out of bounds"
                  );
                  return _historicalRandomizerAddresses[_index];
              }
              /**
               * @notice Gets royalty Basis Points (BPS) for token ID `_tokenId`.
               * This conforms to the IManifold interface designated in the Royalty
               * Registry's RoyaltyEngineV1.sol contract.
               * ref: https://github.com/manifoldxyz/royalty-registry-solidity
               * @param _tokenId Token ID to be queried.
               * @return recipients Array of royalty payment recipients
               * @return bps Array of Basis Points (BPS) allocated to each recipient,
               * aligned by index.
               * @dev reverts if invalid _tokenId
               * @dev only returns recipients that have a non-zero BPS allocation
               */
              function getRoyalties(
                  uint256 _tokenId
              )
                  external
                  view
                  returns (address payable[] memory recipients, uint256[] memory bps)
              {
                  _onlyValidTokenId(_tokenId);
                  // initialize arrays with maximum potential length
                  recipients = new address payable[](4);
                  bps = new uint256[](4);
                  uint256 projectId = tokenIdToProjectId(_tokenId);
                  ProjectFinance storage projectFinance = projectIdToFinancials[
                      projectId
                  ];
                  // load values into memory
                  uint256 royaltyPercentageForArtistAndAdditional = projectFinance
                      .secondaryMarketRoyaltyPercentage;
                  uint256 additionalPayeePercentage = projectFinance
                      .additionalPayeeSecondarySalesPercentage;
                  // calculate BPS = percentage * 100
                  uint256 artistBPS = (ONE_HUNDRED - additionalPayeePercentage) *
                      royaltyPercentageForArtistAndAdditional;
                  uint256 additionalBPS = additionalPayeePercentage *
                      royaltyPercentageForArtistAndAdditional;
                  uint256 renderProviderBPS = renderProviderSecondarySalesBPS;
                  uint256 platformProviderBPS = platformProviderSecondarySalesBPS;
                  // populate arrays
                  uint256 payeeCount;
                  if (artistBPS > 0) {
                      recipients[payeeCount] = projectFinance.artistAddress;
                      bps[payeeCount++] = artistBPS;
                  }
                  if (additionalBPS > 0) {
                      recipients[payeeCount] = projectFinance
                          .additionalPayeeSecondarySales;
                      bps[payeeCount++] = additionalBPS;
                  }
                  if (renderProviderBPS > 0) {
                      recipients[payeeCount] = renderProviderSecondarySalesAddress;
                      bps[payeeCount++] = renderProviderBPS;
                  }
                  if (platformProviderBPS > 0) {
                      recipients[payeeCount] = platformProviderSecondarySalesAddress;
                      bps[payeeCount++] = platformProviderBPS;
                  }
                  // trim arrays if necessary
                  if (4 > payeeCount) {
                      assembly {
                          let decrease := sub(4, payeeCount)
                          mstore(recipients, sub(mload(recipients), decrease))
                          mstore(bps, sub(mload(bps), decrease))
                      }
                  }
                  return (recipients, bps);
              }
              /**
               * @notice View function that returns appropriate revenue splits between
               * different render provider, platform provider, Artist, and Artist's
               * additional primary sales payee given a sale price of `_price` on
               * project `_projectId`.
               * This always returns four revenue amounts and four addresses, but if a
               * revenue is zero for either Artist or additional payee, the corresponding
               * address returned will also be null (for gas optimization).
               * Does not account for refund if user overpays for a token (minter should
               * handle a refund of the difference, if appropriate).
               * Some minters may have alternative methods of splitting payments, in
               * which case they should implement their own payment splitting logic.
               * @param _projectId Project ID to be queried.
               * @param _price Sale price of token.
               * @return renderProviderRevenue_ amount of revenue to be sent to the
               * render provider
               * @return renderProviderAddress_ address to send render provider revenue to
               * @return platformProviderRevenue_ amount of revenue to be sent to the
               * platform provider
               * @return platformProviderAddress_ address to send platform provider revenue to
               * @return artistRevenue_ amount of revenue to be sent to Artist
               * @return artistAddress_ address to send Artist revenue to. Will be null
               * if no revenue is due to artist (gas optimization).
               * @return additionalPayeePrimaryRevenue_ amount of revenue to be sent to
               * additional payee for primary sales
               * @return additionalPayeePrimaryAddress_ address to send Artist's
               * additional payee for primary sales revenue to. Will be null if no
               * revenue is due to additional payee for primary sales (gas optimization).
               * @dev this always returns four addresses and four revenues, but if the
               * revenue is zero, the corresponding address will be address(0). It is up
               * to the contract performing the revenue split to handle this
               * appropriately.
               */
              function getPrimaryRevenueSplits(
                  uint256 _projectId,
                  uint256 _price
              )
                  external
                  view
                  returns (
                      uint256 renderProviderRevenue_,
                      address payable renderProviderAddress_,
                      uint256 platformProviderRevenue_,
                      address payable platformProviderAddress_,
                      uint256 artistRevenue_,
                      address payable artistAddress_,
                      uint256 additionalPayeePrimaryRevenue_,
                      address payable additionalPayeePrimaryAddress_
                  )
              {
                  ProjectFinance storage projectFinance = projectIdToFinancials[
                      _projectId
                  ];
                  // calculate revenues – this is a three-way split between the
                  // render provider, the platform provider, and the artist, and
                  // is safe to perform this given that in the case of loss of
                  // precision Solidity will round down.
                  uint256 projectFunds = _price;
                  renderProviderRevenue_ =
                      (_price * uint256(_renderProviderPrimarySalesPercentage)) /
                      ONE_HUNDRED;
                  // renderProviderRevenue_ percentage is always <=100, so guaranteed to never underflow
                  projectFunds -= renderProviderRevenue_;
                  platformProviderRevenue_ =
                      (_price * uint256(_platformProviderPrimarySalesPercentage)) /
                      ONE_HUNDRED;
                  // platformProviderRevenue_ percentage is always <=100, so guaranteed to never underflow
                  projectFunds -= platformProviderRevenue_;
                  additionalPayeePrimaryRevenue_ =
                      (projectFunds *
                          projectFinance.additionalPayeePrimarySalesPercentage) /
                      ONE_HUNDRED;
                  // projectIdToAdditionalPayeePrimarySalesPercentage is always
                  // <=100, so guaranteed to never underflow
                  artistRevenue_ = projectFunds - additionalPayeePrimaryRevenue_;
                  // set addresses from storage
                  renderProviderAddress_ = renderProviderPrimarySalesAddress;
                  platformProviderAddress_ = platformProviderPrimarySalesAddress;
                  if (artistRevenue_ > 0) {
                      artistAddress_ = projectFinance.artistAddress;
                  }
                  if (additionalPayeePrimaryRevenue_ > 0) {
                      additionalPayeePrimaryAddress_ = projectFinance
                          .additionalPayeePrimarySales;
                  }
              }
              /**
               * @notice Returns external asset dependency for project `_projectId` at index `_index`.
               * If the dependencyType is ONCHAIN, the `data` field will contain the extrated bytecode data and `cid`
               * will be an empty string. Conversly, for any other dependencyType, the `data` field will be an empty string
               * and the `bytecodeAddress` will point to the zero address.
               */
              function projectExternalAssetDependencyByIndex(
                  uint256 _projectId,
                  uint256 _index
              ) external view returns (ExternalAssetDependencyWithData memory) {
                  ExternalAssetDependency storage _dependency = projects[_projectId]
                      .externalAssetDependencies[_index];
                  address _bytecodeAddress = _dependency.bytecodeAddress;
                  return
                      ExternalAssetDependencyWithData({
                          dependencyType: _dependency.dependencyType,
                          cid: _dependency.cid,
                          bytecodeAddress: _bytecodeAddress,
                          data: (_dependency.dependencyType ==
                              ExternalAssetDependencyType.ONCHAIN)
                              ? _bytecodeAddress.readFromBytecode()
                              : ""
                      });
              }
              /**
               * @notice Returns external asset dependency count for project `_projectId` at index `_index`.
               */
              function projectExternalAssetDependencyCount(
                  uint256 _projectId
              ) external view returns (uint256) {
                  return uint256(projects[_projectId].externalAssetDependencyCount);
              }
              /**
               * @notice Backwards-compatible (pre-V3) getter returning contract admin
               * @return address Address of contract admin (same as owner)
               */
              function admin() external view returns (address) {
                  return owner();
              }
              /**
               * @notice Gets the project ID for a given `_tokenId`.
               * @param _tokenId Token ID to be queried.
               * @return _projectId Project ID for given `_tokenId`.
               */
              function tokenIdToProjectId(
                  uint256 _tokenId
              ) public pure returns (uint256 _projectId) {
                  return _tokenId / ONE_MILLION;
              }
              /**
               * @notice Convenience function that returns whether `_sender` is allowed
               * to call function with selector `_selector` on contract `_contract`, as
               * determined by this contract's current Admin ACL contract. Expected use
               * cases include minter contracts checking if caller is allowed to call
               * admin-gated functions on minter contracts.
               * @param _sender Address of the sender calling function with selector
               * `_selector` on contract `_contract`.
               * @param _contract Address of the contract being called by `_sender`.
               * @param _selector Function selector of the function being called by
               * `_sender`.
               * @return bool Whether `_sender` is allowed to call function with selector
               * `_selector` on contract `_contract`.
               * @dev assumes the Admin ACL contract is the owner of this contract, which
               * is expected to always be true.
               * @dev adminACLContract is expected to either be null address (if owner
               * has renounced ownership), or conform to IAdminACLV0 interface. Check for
               * null address first to avoid revert when admin has renounced ownership.
               */
              function adminACLAllowed(
                  address _sender,
                  address _contract,
                  bytes4 _selector
              ) public returns (bool) {
                  return
                      owner() != address(0) &&
                      adminACLContract.allowed(_sender, _contract, _selector);
              }
              /**
               * @notice Returns contract owner. Set to deployer's address by default on
               * contract deployment.
               * @return address Address of contract owner.
               * @dev ref: https://docs.openzeppelin.com/contracts/4.x/api/access#Ownable
               * @dev owner role was called `admin` prior to V3 core contract
               */
              function owner()
                  public
                  view
                  override(Ownable, IGenArt721CoreContractV3_Base)
                  returns (address)
              {
                  return Ownable.owner();
              }
              /**
               * @notice Gets token URI for token ID `_tokenId`.
               * @param _tokenId Token ID to be queried.
               * @return string URI of token ID `_tokenId`.
               * @dev token URIs are the concatenation of the project base URI and the
               * token ID.
               */
              function tokenURI(
                  uint256 _tokenId
              ) public view override returns (string memory) {
                  _onlyValidTokenId(_tokenId);
                  string memory _projectBaseURI = projects[tokenIdToProjectId(_tokenId)]
                      .projectBaseURI;
                  return string.concat(_projectBaseURI, toString(_tokenId));
              }
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(
                  bytes4 interfaceId
              ) public view virtual override returns (bool) {
                  return
                      interfaceId == type(IManifold).interfaceId ||
                      super.supportsInterface(interfaceId);
              }
              /**
               * @notice Forbids new projects from being created
               * @dev only performs operation and emits event if contract is not already
               * forbidding new projects.
               */
              function _forbidNewProjects() internal {
                  if (!newProjectsForbidden) {
                      newProjectsForbidden = true;
                      emit PlatformUpdated(FIELD_NEW_PROJECTS_FORBIDDEN);
                  }
              }
              /**
               * @notice Transfers ownership of the contract to a new account (`newOwner`).
               * Internal function without access restriction.
               * @param newOwner New owner.
               * @dev owner role was called `admin` prior to V3 core contract.
               * @dev Overrides and wraps OpenZeppelin's _transferOwnership function to
               * also update adminACLContract for improved introspection.
               */
              function _transferOwnership(address newOwner) internal override {
                  Ownable._transferOwnership(newOwner);
                  adminACLContract = IAdminACLV0(newOwner);
              }
              /**
               * @notice Updates sales addresses for the platform and render providers to
               * the input parameters.
               * @param _renderProviderPrimarySalesAddress Address of new primary sales
               * payment address.
               * @param _renderProviderSecondarySalesAddress Address of new secondary sales
               * payment address.
               * @param _platformProviderPrimarySalesAddress Address of new primary sales
               * payment address.
               * @param _platformProviderSecondarySalesAddress Address of new secondary sales
               * payment address.
               * @dev Note that this method does not check that the input address is
               * not `address(0)`, as it is expected that callers of this method should
               * perform input validation where applicable.
               */
              function _updateProviderSalesAddresses(
                  address _renderProviderPrimarySalesAddress,
                  address _renderProviderSecondarySalesAddress,
                  address _platformProviderPrimarySalesAddress,
                  address _platformProviderSecondarySalesAddress
              ) internal {
                  platformProviderPrimarySalesAddress = payable(
                      _platformProviderPrimarySalesAddress
                  );
                  platformProviderSecondarySalesAddress = payable(
                      _platformProviderSecondarySalesAddress
                  );
                  renderProviderPrimarySalesAddress = payable(
                      _renderProviderPrimarySalesAddress
                  );
                  renderProviderSecondarySalesAddress = payable(
                      _renderProviderSecondarySalesAddress
                  );
                  emit PlatformUpdated(FIELD_PROVIDER_SALES_ADDRESSES);
              }
              /**
               * @notice Updates randomizer address to `_randomizerAddress`.
               * @param _randomizerAddress New randomizer address.
               * @dev Note that this method does not check that the input address is
               * not `address(0)`, as it is expected that callers of this method should
               * perform input validation where applicable.
               */
              function _updateRandomizerAddress(address _randomizerAddress) internal {
                  randomizerContract = IRandomizerV2(_randomizerAddress);
                  // populate historical randomizer array
                  _historicalRandomizerAddresses.push(_randomizerAddress);
                  emit PlatformUpdated(FIELD_RANDOMIZER_ADDRESS);
              }
              /**
               * @notice Updates default base URI to `_defaultBaseURI`.
               * When new projects are added, their `projectBaseURI` is automatically
               * initialized to `_defaultBaseURI`.
               * @param _defaultBaseURI New default base URI.
               * @dev Note that this method does not check that the input string is not
               * the empty string, as it is expected that callers of this method should
               * perform input validation where applicable.
               */
              function _updateDefaultBaseURI(string memory _defaultBaseURI) internal {
                  defaultBaseURI = _defaultBaseURI;
                  emit PlatformUpdated(FIELD_DEFAULT_BASE_URI);
              }
              /**
               * @notice Internal function to complete a project.
               * @param _projectId Project ID to be completed.
               */
              function _completeProject(uint256 _projectId) internal {
                  projects[_projectId].completedTimestamp = uint64(block.timestamp);
                  emit ProjectUpdated(_projectId, FIELD_PROJECT_COMPLETED);
              }
              /**
               * @notice Internal function that returns whether a project is unlocked.
               * Projects automatically lock four weeks after they are completed.
               * Projects are considered completed when they have been invoked the
               * maximum number of times.
               * @param _projectId Project ID to be queried.
               * @return bool true if project is unlocked, false otherwise.
               * @dev This also enforces that the `_projectId` passed in is valid.
               */
              function _projectUnlocked(uint256 _projectId) internal view returns (bool) {
                  _onlyValidProjectId(_projectId);
                  uint256 projectCompletedTimestamp = projects[_projectId]
                      .completedTimestamp;
                  bool projectOpen = projectCompletedTimestamp == 0;
                  return
                      projectOpen ||
                      (block.timestamp - projectCompletedTimestamp <
                          FOUR_WEEKS_IN_SECONDS);
              }
              // strings library from OpenZeppelin, modified for no constants
              bytes16 private _HEX_SYMBOLS = "0123456789abcdef";
              uint8 private _ADDRESS_LENGTH = 20;
              /**
               * @dev Converts a `uint256` to its ASCII `string` decimal representation.
               */
              function toString(uint256 value) internal pure returns (string memory) {
                  // Inspired by OraclizeAPI's implementation - MIT licence
                  // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
                  if (value == 0) {
                      return "0";
                  }
                  uint256 temp = value;
                  uint256 digits;
                  while (temp != 0) {
                      digits++;
                      temp /= 10;
                  }
                  bytes memory buffer = new bytes(digits);
                  while (value != 0) {
                      digits -= 1;
                      buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
                      value /= 10;
                  }
                  return string(buffer);
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
               */
              function toHexString(
                  uint256 value,
                  uint256 length
              ) internal view returns (string memory) {
                  bytes memory buffer = new bytes(2 * length + 2);
                  buffer[0] = "0";
                  buffer[1] = "x";
                  for (uint256 i = 2 * length + 1; i > 1; --i) {
                      buffer[i] = _HEX_SYMBOLS[value & 0xf];
                      value >>= 4;
                  }
                  require(value == 0, "hex length insufficient");
                  return string(buffer);
              }
              /**
               * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
               */
              function toHexString(address addr) internal view returns (string memory) {
                  return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright (c) 2023 the ethier authors (github.com/divergencetech/ethier)
          pragma solidity >=0.8.0 <0.9.0;
          import {AccessControlEnumerable} from "../utils/AccessControlEnumerable.sol";
          import {ERC721A, ERC721ACommon} from "./ERC721ACommon.sol";
          /**
           * @notice ERC721 extension that implements a commonly used _baseURI() function
           * to return an URL prefix that can be set by the contract steerer.
           */
          contract BaseTokenURI is AccessControlEnumerable {
              /**
               * @notice Base token URI used as a prefix by tokenURI().
               */
              string private _baseTokenURI;
              constructor(string memory baseTokenURI_) {
                  _setBaseTokenURI(baseTokenURI_);
              }
              /**
               * @notice Sets the base token URI prefix.
               * @dev Only callable by the contract steerer.
               */
              function setBaseTokenURI(string memory baseTokenURI_)
                  public
                  onlyRole(DEFAULT_STEERING_ROLE)
              {
                  _setBaseTokenURI(baseTokenURI_);
              }
              /**
               * @notice Sets the base token URI prefix.
               */
              function _setBaseTokenURI(string memory baseTokenURI_) internal virtual {
                  _baseTokenURI = baseTokenURI_;
              }
              /**
               * @notice Returns the `baseTokenURI`.
               */
              function baseTokenURI() public view virtual returns (string memory) {
                  return _baseTokenURI;
              }
              /**
               * @notice Returns the base token URI * without any additional characters (e.g. a slash).
               */
              function _baseURI() internal view virtual returns (string memory) {
                  return _baseTokenURI;
              }
          }
          /**
           * @notice ERC721ACommon extension that adds BaseTokenURI.
           */
          abstract contract ERC721ACommonBaseTokenURI is ERC721ACommon, BaseTokenURI {
              /**
               * @notice Overrides supportsInterface as required by inheritance.
               */
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(ERC721ACommon, AccessControlEnumerable)
                  returns (bool)
              {
                  return
                      ERC721ACommon.supportsInterface(interfaceId) ||
                      AccessControlEnumerable.supportsInterface(interfaceId);
              }
              /**
               * @dev Inheritance resolution.
               */
              function _baseURI()
                  internal
                  view
                  virtual
                  override(ERC721A, BaseTokenURI)
                  returns (string memory)
              {
                  return BaseTokenURI._baseURI();
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright (c) 2023 the ethier authors (github.com/divergencetech/ethier)
          pragma solidity >=0.8.0 <0.9.0;
          import {Address} from "@openzeppelin/contracts/utils/Address.sol";
          import {DefaultOperatorFilterer} from "operator-filter-registry/src/DefaultOperatorFilterer.sol";
          import {ERC721A, ERC721ACommon} from "./ERC721ACommon.sol";
          /**
           * @notice ERC721ACommon extension that adds Opensea's operator filtering.
           */
          abstract contract OperatorFilterOS is ERC721ACommon, DefaultOperatorFilterer {
              using Address for address;
              /**
               * @notice Calling the operator filter registry with given calldata.
               * @dev The registry contract did not foresee role-based contract access
               * control -- only the contract itself, or its (EIP-173) owner is allowed to
               * change subscription settings. To work around this, we enforce
               * authorisation here and forward arbitrary calldata to the registry.
               * Use with care!
               */
              function callOperatorFilterRegistry(bytes calldata cdata)
                  external
                  onlyRole(DEFAULT_STEERING_ROLE)
                  returns (bytes memory)
              {
                  return address(OPERATOR_FILTER_REGISTRY).functionCall(cdata);
              }
              // =========================================================================
              //                           Operator filtering
              // =========================================================================
              function setApprovalForAll(address operator, bool approved)
                  public
                  virtual
                  override
                  onlyAllowedOperatorApproval(operator)
              {
                  super.setApprovalForAll(operator, approved);
              }
              function approve(address operator, uint256 tokenId)
                  public
                  payable
                  virtual
                  override
                  onlyAllowedOperatorApproval(operator)
              {
                  super.approve(operator, tokenId);
              }
              function transferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) public payable virtual override onlyAllowedOperator(from) {
                  super.transferFrom(from, to, tokenId);
              }
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) public payable virtual override onlyAllowedOperator(from) {
                  super.safeTransferFrom(from, to, tokenId);
              }
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory data
              ) public payable virtual override onlyAllowedOperator(from) {
                  super.safeTransferFrom(from, to, tokenId, data);
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 Proof Holdings Inc.
          pragma solidity ^0.8.0;
          uint256 constant AB_ENGINE_PROJECT_MULTIPLIER = 1_000_000;
          function artblocksTokenID(uint256 projectId, uint256 edition) pure returns (uint256) {
              return (projectId * AB_ENGINE_PROJECT_MULTIPLIER) + edition;
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 Proof Holdings Inc.
          pragma solidity >=0.8.0;
          /**
           * @notice IGenArt721CoreContractV3_Base minting interface
           */
          interface IGenArt721CoreContractV3_Mintable {
              function mint_Ecf(address to, uint256 projectId, address sender) external returns (uint256 _tokenId);
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity >=0.8.0 <0.9.0;
          import {ERC721ACommon} from "ethier/erc721/ERC721ACommon.sol";
          import {AccessControlEnumerable, BaseSellable} from "./BaseSellable.sol";
          /**
           * @notice Base contract for sellable ERC721ACommon tokens.
           */
          abstract contract SellableERC721ACommon is BaseSellable, ERC721ACommon {
              /**
               * @inheritdoc BaseSellable
               */
              function _handleSale(address to, uint64 num, bytes calldata) internal virtual override {
                  _mint(to, num);
              }
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(ERC721ACommon, AccessControlEnumerable)
                  returns (bool)
              {
                  return ERC721ACommon.supportsInterface(interfaceId) || AccessControlEnumerable.supportsInterface(interfaceId);
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 Proof Holdings Inc.
          pragma solidity >=0.8.17;
          /**
           * @notice Diamond Exhibition - Projects configuration.
           * @author David Huber (@cxkoda)
           * @custom:reviewer Arran Schlosberg (@divergencearran)
           */
          contract ProjectsConfig {
              /**
               * @notice The number of longform projects.
               */
              uint8 internal constant _NUM_LONGFORM_PROJECTS = 11;
              /**
               * @notice The number of pre-curated projects.
               */
              uint8 internal constant _NUM_CURATED_PROJECTS = 10;
              /**
               * @notice The total number of projects.
               */
              uint8 public constant NUM_PROJECTS = _NUM_LONGFORM_PROJECTS + _NUM_CURATED_PROJECTS;
              /**
               * @notice Returns the number of projects than can be minted per project.
               */
              function _maxNumPerProject() internal pure virtual returns (uint256[NUM_PROJECTS] memory sizes) {
                  return [
                      // Longform
                      uint256(600), // Impossible Distance
                      600, // cathedral study
                      600, // Deja Vu
                      800, // WaveShapes
                      1000, // Ephemeral Tides
                      600, // StackSlash
                      450, // Viridaria
                      1000, // Windwoven
                      256, // Memory Loss
                      1000, // The Collector's Room
                      1000, // Extrañezas
                      // Pre-curated
                      100, // Everydays: Group Effort
                      100, // Kid Heart
                      100, // BEHEADED (SELF PORTRAIT)
                      1127, // End Transmissions
                      77, // DES CHOSES™
                      100, // A Wintry Night in Chinatown
                      100, // Penthouse
                      200, // Hands of Umbra
                      100, // Solitaire
                      100 // Remnants of a Distant Dream
                  ];
              }
              /**
               * @notice Returns the number of projects than can be minted per project.
               */
              function maxNumPerProject() external pure returns (uint256[NUM_PROJECTS] memory) {
                  return _maxNumPerProject();
              }
              // =========================================================================
              //                          Project Types
              // =========================================================================
              /**
               * @notice The different types of projects.
               */
              enum ProjectType {
                  Longform,
                  Curated
              }
              /**
               * @notice Returns the project type for a given project ID.
               */
              function projectType(uint8 projectId) public pure returns (ProjectType) {
                  return projectId < _NUM_LONGFORM_PROJECTS ? ProjectType.Longform : ProjectType.Curated;
              }
              /**
               * @notice Returns true iff the project is a longform project.
               */
              function _isLongformProject(uint8 projectId) internal pure virtual returns (bool) {
                  return projectType(projectId) == ProjectType.Longform;
              }
              // =========================================================================
              //                          Artblocks
              // =========================================================================
              /**
               * @notice Returns the ArtBlocks engine project IDs for the longform projects.
               */
              function _artblocksProjectIds() internal pure virtual returns (uint8[_NUM_LONGFORM_PROJECTS] memory) {
                  return [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
              }
              /**
               * @notice Returns the ArtBlocks engine project IDs for the longform projects.
               */
              function artblocksProjectIds() external pure returns (uint8[_NUM_LONGFORM_PROJECTS] memory) {
                  return _artblocksProjectIds();
              }
              /**
               * @notice Returns the ArtBlocks engine project ID for a given project ID.
               * @dev Reverts if the project is not long-form.
               */
              function _artblocksProjectId(uint8 projectId) internal pure returns (uint256) {
                  assert(_isLongformProject(projectId));
                  return _artblocksProjectIds()[projectId];
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 Proof Holdings Inc.
          pragma solidity >=0.8.17;
          /**
           * @notice Token information module for Diamond Exhibition.
           * @author David Huber (@cxkoda)
           * @custom:reviewer Arran Schlosberg (@divergencearran)
           */
          contract TokenInfoManager {
              /**
               * @notice Encodes token information.
               * @param projectId the ID of the project associated with the token.
               * @param edition the edition of the token within the given project.
               */
              struct TokenInfo {
                  uint8 projectId;
                  uint16 edition;
              }
              /**
               * @notice Max numbers of tokens that this contract can store.
               * @dev This constant is intentionally very large so we never have to worry about it.
               */
              uint256 internal constant _NUM_MAX_TOKEN_INFO = (1 << 32);
              /**
               * @notice Stores token information.
               */
              TokenInfo[_NUM_MAX_TOKEN_INFO] private _infos;
              /**
               * @notice Returns the token information for the given token IDs.
               * @dev Intended for off-chain use only.
               */
              function tokenInfos(uint256[] calldata tokenIds) external view returns (TokenInfo[] memory) {
                  TokenInfo[] memory infos = new TokenInfo[](tokenIds.length);
                  for (uint256 i = 0; i < tokenIds.length; ++i) {
                      infos[i] = _tokenInfo(tokenIds[i]);
                  }
                  return infos;
              }
              /**
               * @notice Returns the token information for the given token ID.
               */
              function _tokenInfo(uint256 tokenId) internal view returns (TokenInfo memory) {
                  return _infos[tokenId];
              }
              /**
               * @notice Sets the token information for the given token ID.
               */
              function _setTokenInfo(uint256 tokenId, uint8 projectId, uint16 edition) internal {
                  _infos[tokenId] = TokenInfo({projectId: projectId, edition: edition});
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Standard math utilities missing in the Solidity language.
           */
          library Math {
              enum Rounding {
                  Down, // Toward negative infinity
                  Up, // Toward infinity
                  Zero // Toward zero
              }
              /**
               * @dev Returns the largest of two numbers.
               */
              function max(uint256 a, uint256 b) internal pure returns (uint256) {
                  return a > b ? a : b;
              }
              /**
               * @dev Returns the smallest of two numbers.
               */
              function min(uint256 a, uint256 b) internal pure returns (uint256) {
                  return a < b ? a : b;
              }
              /**
               * @dev Returns the average of two numbers. The result is rounded towards
               * zero.
               */
              function average(uint256 a, uint256 b) internal pure returns (uint256) {
                  // (a + b) / 2 can overflow.
                  return (a & b) + (a ^ b) / 2;
              }
              /**
               * @dev Returns the ceiling of the division of two numbers.
               *
               * This differs from standard division with `/` in that it rounds up instead
               * of rounding down.
               */
              function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
                  // (a + b - 1) / b can overflow on addition, so we distribute.
                  return a == 0 ? 0 : (a - 1) / b + 1;
              }
              /**
               * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
               * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
               * with further edits by Uniswap Labs also under MIT license.
               */
              function mulDiv(
                  uint256 x,
                  uint256 y,
                  uint256 denominator
              ) internal pure returns (uint256 result) {
                  unchecked {
                      // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                      // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                      // variables such that product = prod1 * 2^256 + prod0.
                      uint256 prod0; // Least significant 256 bits of the product
                      uint256 prod1; // Most significant 256 bits of the product
                      assembly {
                          let mm := mulmod(x, y, not(0))
                          prod0 := mul(x, y)
                          prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                      }
                      // Handle non-overflow cases, 256 by 256 division.
                      if (prod1 == 0) {
                          return prod0 / denominator;
                      }
                      // Make sure the result is less than 2^256. Also prevents denominator == 0.
                      require(denominator > prod1);
                      ///////////////////////////////////////////////
                      // 512 by 256 division.
                      ///////////////////////////////////////////////
                      // Make division exact by subtracting the remainder from [prod1 prod0].
                      uint256 remainder;
                      assembly {
                          // Compute remainder using mulmod.
                          remainder := mulmod(x, y, denominator)
                          // Subtract 256 bit number from 512 bit number.
                          prod1 := sub(prod1, gt(remainder, prod0))
                          prod0 := sub(prod0, remainder)
                      }
                      // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
                      // See https://cs.stackexchange.com/q/138556/92363.
                      // Does not overflow because the denominator cannot be zero at this stage in the function.
                      uint256 twos = denominator & (~denominator + 1);
                      assembly {
                          // Divide denominator by twos.
                          denominator := div(denominator, twos)
                          // Divide [prod1 prod0] by twos.
                          prod0 := div(prod0, twos)
                          // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                          twos := add(div(sub(0, twos), twos), 1)
                      }
                      // Shift in bits from prod1 into prod0.
                      prod0 |= prod1 * twos;
                      // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                      // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                      // four bits. That is, denominator * inv = 1 mod 2^4.
                      uint256 inverse = (3 * denominator) ^ 2;
                      // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
                      // in modular arithmetic, doubling the correct bits in each step.
                      inverse *= 2 - denominator * inverse; // inverse mod 2^8
                      inverse *= 2 - denominator * inverse; // inverse mod 2^16
                      inverse *= 2 - denominator * inverse; // inverse mod 2^32
                      inverse *= 2 - denominator * inverse; // inverse mod 2^64
                      inverse *= 2 - denominator * inverse; // inverse mod 2^128
                      inverse *= 2 - denominator * inverse; // inverse mod 2^256
                      // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                      // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                      // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                      // is no longer required.
                      result = prod0 * inverse;
                      return result;
                  }
              }
              /**
               * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
               */
              function mulDiv(
                  uint256 x,
                  uint256 y,
                  uint256 denominator,
                  Rounding rounding
              ) internal pure returns (uint256) {
                  uint256 result = mulDiv(x, y, denominator);
                  if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
                      result += 1;
                  }
                  return result;
              }
              /**
               * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
               *
               * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
               */
              function sqrt(uint256 a) internal pure returns (uint256) {
                  if (a == 0) {
                      return 0;
                  }
                  // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
                  //
                  // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
                  // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
                  //
                  // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
                  // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
                  // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
                  //
                  // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
                  uint256 result = 1 << (log2(a) >> 1);
                  // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
                  // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
                  // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
                  // into the expected uint128 result.
                  unchecked {
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      return min(result, a / result);
                  }
              }
              /**
               * @notice Calculates sqrt(a), following the selected rounding direction.
               */
              function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = sqrt(a);
                      return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
                  }
              }
              /**
               * @dev Return the log in base 2, rounded down, of a positive value.
               * Returns 0 if given 0.
               */
              function log2(uint256 value) internal pure returns (uint256) {
                  uint256 result = 0;
                  unchecked {
                      if (value >> 128 > 0) {
                          value >>= 128;
                          result += 128;
                      }
                      if (value >> 64 > 0) {
                          value >>= 64;
                          result += 64;
                      }
                      if (value >> 32 > 0) {
                          value >>= 32;
                          result += 32;
                      }
                      if (value >> 16 > 0) {
                          value >>= 16;
                          result += 16;
                      }
                      if (value >> 8 > 0) {
                          value >>= 8;
                          result += 8;
                      }
                      if (value >> 4 > 0) {
                          value >>= 4;
                          result += 4;
                      }
                      if (value >> 2 > 0) {
                          value >>= 2;
                          result += 2;
                      }
                      if (value >> 1 > 0) {
                          result += 1;
                      }
                  }
                  return result;
              }
              /**
               * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
               * Returns 0 if given 0.
               */
              function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = log2(value);
                      return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
                  }
              }
              /**
               * @dev Return the log in base 10, rounded down, of a positive value.
               * Returns 0 if given 0.
               */
              function log10(uint256 value) internal pure returns (uint256) {
                  uint256 result = 0;
                  unchecked {
                      if (value >= 10**64) {
                          value /= 10**64;
                          result += 64;
                      }
                      if (value >= 10**32) {
                          value /= 10**32;
                          result += 32;
                      }
                      if (value >= 10**16) {
                          value /= 10**16;
                          result += 16;
                      }
                      if (value >= 10**8) {
                          value /= 10**8;
                          result += 8;
                      }
                      if (value >= 10**4) {
                          value /= 10**4;
                          result += 4;
                      }
                      if (value >= 10**2) {
                          value /= 10**2;
                          result += 2;
                      }
                      if (value >= 10**1) {
                          result += 1;
                      }
                  }
                  return result;
              }
              /**
               * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
               * Returns 0 if given 0.
               */
              function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = log10(value);
                      return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
                  }
              }
              /**
               * @dev Return the log in base 256, rounded down, of a positive value.
               * Returns 0 if given 0.
               *
               * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
               */
              function log256(uint256 value) internal pure returns (uint256) {
                  uint256 result = 0;
                  unchecked {
                      if (value >> 128 > 0) {
                          value >>= 128;
                          result += 16;
                      }
                      if (value >> 64 > 0) {
                          value >>= 64;
                          result += 8;
                      }
                      if (value >> 32 > 0) {
                          value >>= 32;
                          result += 4;
                      }
                      if (value >> 16 > 0) {
                          value >>= 16;
                          result += 2;
                      }
                      if (value >> 8 > 0) {
                          result += 1;
                      }
                  }
                  return result;
              }
              /**
               * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
               * Returns 0 if given 0.
               */
              function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = log256(value);
                      return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
                  }
              }
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Creatd By: Art Blocks Inc.
          pragma solidity ^0.8.0;
          import "./IGenArt721CoreContractV3_Base.sol";
          interface IRandomizerV2 {
              // The core contract that may interact with this randomizer contract.
              function genArt721Core()
                  external
                  view
                  returns (IGenArt721CoreContractV3_Base);
              // When a core contract calls this, it can be assured that the randomizer
              // will set a bytes32 hash for tokenId `_tokenId` on the core contract.
              function assignTokenHash(uint256 _tokenId) external;
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.0;
          interface IAdminACLV0 {
              /**
               * @notice Token ID `_tokenId` minted to `_to`.
               * @param previousSuperAdmin The previous superAdmin address.
               * @param newSuperAdmin The new superAdmin address.
               * @param genArt721CoreAddressesToUpdate Array of genArt721Core
               * addresses to update to the new superAdmin, for indexing purposes only.
               */
              event SuperAdminTransferred(
                  address indexed previousSuperAdmin,
                  address indexed newSuperAdmin,
                  address[] genArt721CoreAddressesToUpdate
              );
              /// Type of the Admin ACL contract, e.g. "AdminACLV0"
              function AdminACLType() external view returns (string memory);
              /// super admin address
              function superAdmin() external view returns (address);
              /**
               * @notice Calls transferOwnership on other contract from this contract.
               * This is useful for updating to a new AdminACL contract.
               * @dev this function should be gated to only superAdmin-like addresses.
               */
              function transferOwnershipOn(
                  address _contract,
                  address _newAdminACL
              ) external;
              /**
               * @notice Calls renounceOwnership on other contract from this contract.
               * @dev this function should be gated to only superAdmin-like addresses.
               */
              function renounceOwnershipOn(address _contract) external;
              /**
               * @notice Checks if sender `_sender` is allowed to call function with selector
               * `_selector` on contract `_contract`.
               */
              function allowed(
                  address _sender,
                  address _contract,
                  bytes4 _selector
              ) external returns (bool);
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.17;
          interface IEngineRegistryV0 {
              /// ADDRESS
              /**
               * @notice contract has been registered as a contract that is powered by the Art Blocks Engine.
               */
              event ContractRegistered(
                  address indexed _contractAddress,
                  bytes32 _coreVersion,
                  bytes32 _coreType
              );
              /// ADDRESS
              /**
               * @notice contract has been unregistered as a contract that is powered by the Art Blocks Engine.
               */
              event ContractUnregistered(address indexed _contractAddress);
              /**
               * @notice Emits a `ContractRegistered` event with the provided information.
               * @dev this function should be gated to only deployer addresses.
               */
              function registerContract(
                  address _contractAddress,
                  bytes32 _coreVersion,
                  bytes32 _coreType
              ) external;
              /**
               * @notice Emits a `ContractUnregistered` event with the provided information, validating that the provided
               *         address was indeed previously registered.
               * @dev this function should be gated to only deployer addresses.
               */
              function unregisterContract(address _contractAddress) external;
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.0;
          import "./IAdminACLV0.sol";
          import "./IGenArt721CoreContractV3_Engine.sol";
          /**
           * @title This interface is intended to house interface items that are common
           * across all GenArt721CoreContractV3 Engine Flex and derivative implementations.
           * @author Art Blocks Inc.
           */
          interface IGenArt721CoreContractV3_Engine_Flex is
              IGenArt721CoreContractV3_Engine
          {
              /**
               * @notice When an external asset dependency is updated or added, this event is emitted.
               * @param _projectId The project ID of the project that was updated.
               * @param _index The index of the external asset dependency that was updated.
               * @param _cid The content ID of the external asset dependency. This is an empty string
               * if the dependency type is ONCHAIN.
               * @param _dependencyType The type of the external asset dependency.
               * @param _externalAssetDependencyCount The number of external asset dependencies.
               */
              event ExternalAssetDependencyUpdated(
                  uint256 indexed _projectId,
                  uint256 indexed _index,
                  string _cid,
                  ExternalAssetDependencyType _dependencyType,
                  uint24 _externalAssetDependencyCount
              );
              /**
               * @notice The project id `_projectId` has had an external asset dependency removed at index `_index`.
               */
              event ExternalAssetDependencyRemoved(
                  uint256 indexed _projectId,
                  uint256 indexed _index
              );
              /**
               * @notice The preferred gateway for dependency type `_dependencyType` has been updated to `_gatewayAddress`.
               */
              event GatewayUpdated(
                  ExternalAssetDependencyType indexed _dependencyType,
                  string _gatewayAddress
              );
              /**
               * @notice The project id `_projectId` has had all external asset dependencies locked.
               * @dev This is a one-way operation. Once locked, the external asset dependencies cannot be updated.
               */
              event ProjectExternalAssetDependenciesLocked(uint256 indexed _projectId);
              /**
               * @notice An external asset dependency type. Can be one of IPFS, ARWEAVE, or ONCHAIN.
               */
              enum ExternalAssetDependencyType {
                  IPFS,
                  ARWEAVE,
                  ONCHAIN
              }
              /**
               * @notice An external asset dependency. This is a struct that contains the CID of the dependency,
               * the type of the dependency, and the address of the bytecode for this dependency.
               */
              struct ExternalAssetDependency {
                  string cid;
                  ExternalAssetDependencyType dependencyType;
                  address bytecodeAddress;
              }
              /**
               * @notice An external asset dependency with data. This is a convenience struct that contains the CID of the dependency,
               * the type of the dependency, the address of the bytecode for this dependency, and the data retrieved from this bytecode address.
               */
              struct ExternalAssetDependencyWithData {
                  string cid;
                  ExternalAssetDependencyType dependencyType;
                  address bytecodeAddress;
                  string data;
              }
              // preferredIPFSGateway is a url string
              function preferredIPFSGateway() external view returns (string memory);
              // preferredArweaveGateway is a url string
              function preferredArweaveGateway() external view returns (string memory);
              // updates the preferred IPFS gateway
              function updateIPFSGateway(string calldata _gateway) external;
              // updates the preferred Arweave gateway
              function updateArweaveGateway(string calldata _gateway) external;
              // locks the external asset dependencies for a project
              function lockProjectExternalAssetDependencies(uint256 _projectId) external;
              // updates the external asset dependency for a project at a given index
              function updateProjectExternalAssetDependency(
                  uint256 _projectId,
                  uint256 _index,
                  string memory _cidOrData,
                  ExternalAssetDependencyType _dependencyType
              ) external;
              // adds an external asset dependency for a project
              function addProjectExternalAssetDependency(
                  uint256 _projectId,
                  string memory _cidOrData,
                  ExternalAssetDependencyType _dependencyType
              ) external;
              // removes an external asset dependency for a project at a given index
              function removeProjectExternalAssetDependency(
                  uint256 _projectId,
                  uint256 _index
              ) external;
              // getter function for project external asset dependencies
              function projectExternalAssetDependencyByIndex(
                  uint256 _projectId,
                  uint256 _index
              ) external view returns (ExternalAssetDependencyWithData memory);
              // getter function project external asset dependency count
              function projectExternalAssetDependencyCount(
                  uint256 _projectId
              ) external view returns (uint256);
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.17;
          interface IDependencyRegistryCompatibleV0 {
              /// Dependency registry managed by Art Blocks
              function artblocksDependencyRegistryAddress()
                  external
                  view
                  returns (address);
              /**
               * @notice Returns script information for project `_projectId`.
               * @param _projectId Project to be queried.
               * @return scriptTypeAndVersion Project's script type and version
               * (e.g. "p5js(atSymbol)1.0.0")
               * @return aspectRatio Aspect ratio of project (e.g. "1" for square,
               * "1.77777778" for 16:9, etc.)
               * @return scriptCount Count of scripts for project
               */
              function projectScriptDetails(
                  uint256 _projectId
              )
                  external
                  view
                  returns (
                      string memory scriptTypeAndVersion,
                      string memory aspectRatio,
                      uint256 scriptCount
                  );
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          /// @dev Royalty Registry interface, used to support the Royalty Registry.
          /// @dev Source: https://github.com/manifoldxyz/royalty-registry-solidity/blob/main/contracts/specs/IManifold.sol
          /// @author: manifold.xyz
          /**
           * @dev Royalty interface for creator core classes
           */
          interface IManifold {
              /**
               * @dev Get royalites of a token.  Returns list of receivers and basisPoints
               *
               *  bytes4(keccak256('getRoyalties(uint256)')) == 0xbb3bafd6
               *
               *  => 0xbb3bafd6 = 0xbb3bafd6
               */
              function getRoyalties(
                  uint256 tokenId
              ) external view returns (address payable[] memory, uint256[] memory);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
          pragma solidity ^0.8.0;
          import "../utils/Context.sol";
          /**
           * @dev Contract module which provides a basic access control mechanism, where
           * there is an account (an owner) that can be granted exclusive access to
           * specific functions.
           *
           * By default, the owner account will be the one that deploys the contract. This
           * can later be changed with {transferOwnership}.
           *
           * This module is used through inheritance. It will make available the modifier
           * `onlyOwner`, which can be applied to your functions to restrict their use to
           * the owner.
           */
          abstract contract Ownable is Context {
              address private _owner;
              event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
              /**
               * @dev Initializes the contract setting the deployer as the initial owner.
               */
              constructor() {
                  _transferOwnership(_msgSender());
              }
              /**
               * @dev Throws if called by any account other than the owner.
               */
              modifier onlyOwner() {
                  _checkOwner();
                  _;
              }
              /**
               * @dev Returns the address of the current owner.
               */
              function owner() public view virtual returns (address) {
                  return _owner;
              }
              /**
               * @dev Throws if the sender is not the owner.
               */
              function _checkOwner() internal view virtual {
                  require(owner() == _msgSender(), "Ownable: caller is not the owner");
              }
              /**
               * @dev Leaves the contract without owner. It will not be possible to call
               * `onlyOwner` functions anymore. Can only be called by the current owner.
               *
               * NOTE: Renouncing ownership will leave the contract without an owner,
               * thereby removing any functionality that is only available to the owner.
               */
              function renounceOwnership() public virtual onlyOwner {
                  _transferOwnership(address(0));
              }
              /**
               * @dev Transfers ownership of the contract to a new account (`newOwner`).
               * Can only be called by the current owner.
               */
              function transferOwnership(address newOwner) public virtual onlyOwner {
                  require(newOwner != address(0), "Ownable: new owner is the zero address");
                  _transferOwnership(newOwner);
              }
              /**
               * @dev Transfers ownership of the contract to a new account (`newOwner`).
               * Internal function without access restriction.
               */
              function _transferOwnership(address newOwner) internal virtual {
                  address oldOwner = _owner;
                  _owner = newOwner;
                  emit OwnershipTransferred(oldOwner, newOwner);
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/ERC721.sol)
          pragma solidity ^0.8.0;
          import "@openzeppelin-4.7/contracts/token/ERC721/IERC721.sol";
          import "@openzeppelin-4.7/contracts/token/ERC721/IERC721Receiver.sol";
          import "@openzeppelin-4.7/contracts/token/ERC721/extensions/IERC721Metadata.sol";
          import "@openzeppelin-4.7/contracts/utils/Address.sol";
          import "@openzeppelin-4.7/contracts/utils/Context.sol";
          import "@openzeppelin-4.7/contracts/utils/Strings.sol";
          import "@openzeppelin-4.7/contracts/utils/introspection/ERC165.sol";
          /**
           * @dev Forked version of the OpenZeppelin v4.7.1 ERC721 contract. Utilizes a
           * struct to pack owner and hash seed into a single storage slot.
           * ---------------------
           * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
           * the Metadata extension, but not including the Enumerable extension, which is available separately as
           * {ERC721Enumerable}.
           */
          contract ERC721_PackedHashSeed is Context, ERC165, IERC721, IERC721Metadata {
              using Address for address;
              using Strings for uint256;
              // Token name
              string private _name;
              // Token symbol
              string private _symbol;
              /// struct to pack a token owner and hash seed into same storage slot
              struct OwnerAndHashSeed {
                  // 20 bytes for address of token's owner
                  address owner;
                  // remaining 12 bytes allocated to token hash seed
                  bytes12 hashSeed;
              }
              /// mapping of token ID to OwnerAndHashSeed
              /// @dev visibility internal so inheriting contracts can access
              mapping(uint256 => OwnerAndHashSeed) internal _ownersAndHashSeeds;
              // Mapping owner address to token count
              mapping(address => uint256) private _balances;
              // Mapping from token ID to approved address
              mapping(uint256 => address) private _tokenApprovals;
              // Mapping from owner to operator approvals
              mapping(address => mapping(address => bool)) private _operatorApprovals;
              /**
               * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
               */
              constructor(string memory name_, string memory symbol_) {
                  _name = name_;
                  _symbol = symbol_;
              }
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(
                  bytes4 interfaceId
              ) public view virtual override(ERC165, IERC165) returns (bool) {
                  return
                      interfaceId == type(IERC721).interfaceId ||
                      interfaceId == type(IERC721Metadata).interfaceId ||
                      super.supportsInterface(interfaceId);
              }
              /**
               * @dev See {IERC721-balanceOf}.
               */
              function balanceOf(
                  address owner
              ) public view virtual override returns (uint256) {
                  require(
                      owner != address(0),
                      "ERC721: address zero is not a valid owner"
                  );
                  return _balances[owner];
              }
              /**
               * @dev See {IERC721-ownerOf}.
               */
              function ownerOf(
                  uint256 tokenId
              ) public view virtual override returns (address) {
                  address owner = _ownersAndHashSeeds[tokenId].owner;
                  require(owner != address(0), "ERC721: invalid token ID");
                  return owner;
              }
              /**
               * @dev See {IERC721Metadata-name}.
               */
              function name() public view virtual override returns (string memory) {
                  return _name;
              }
              /**
               * @dev See {IERC721Metadata-symbol}.
               */
              function symbol() public view virtual override returns (string memory) {
                  return _symbol;
              }
              /**
               * @dev See {IERC721Metadata-tokenURI}.
               */
              function tokenURI(
                  uint256 tokenId
              ) public view virtual override returns (string memory) {
                  _requireMinted(tokenId);
                  string memory baseURI = _baseURI();
                  return
                      bytes(baseURI).length > 0
                          ? string(abi.encodePacked(baseURI, tokenId.toString()))
                          : "";
              }
              /**
               * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
               * token will be the concatenation of the `baseURI` and the `tokenId`. Empty
               * by default, can be overridden in child contracts.
               */
              function _baseURI() internal view virtual returns (string memory) {
                  return "";
              }
              /**
               * @dev See {IERC721-approve}.
               */
              function approve(address to, uint256 tokenId) public virtual override {
                  address owner = ERC721_PackedHashSeed.ownerOf(tokenId);
                  require(to != owner, "ERC721: approval to current owner");
                  require(
                      _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
                      "ERC721: approve caller is not token owner nor approved for all"
                  );
                  _approve(to, tokenId);
              }
              /**
               * @dev See {IERC721-getApproved}.
               */
              function getApproved(
                  uint256 tokenId
              ) public view virtual override returns (address) {
                  _requireMinted(tokenId);
                  return _tokenApprovals[tokenId];
              }
              /**
               * @dev See {IERC721-setApprovalForAll}.
               */
              function setApprovalForAll(
                  address operator,
                  bool approved
              ) public virtual override {
                  _setApprovalForAll(_msgSender(), operator, approved);
              }
              /**
               * @dev See {IERC721-isApprovedForAll}.
               */
              function isApprovedForAll(
                  address owner,
                  address operator
              ) public view virtual override returns (bool) {
                  return _operatorApprovals[owner][operator];
              }
              /**
               * @dev See {IERC721-transferFrom}.
               */
              function transferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) public virtual override {
                  //solhint-disable-next-line max-line-length
                  require(
                      _isApprovedOrOwner(_msgSender(), tokenId),
                      "ERC721: caller is not token owner nor approved"
                  );
                  _transfer(from, to, tokenId);
              }
              /**
               * @dev See {IERC721-safeTransferFrom}.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) public virtual override {
                  safeTransferFrom(from, to, tokenId, "");
              }
              /**
               * @dev See {IERC721-safeTransferFrom}.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory data
              ) public virtual override {
                  require(
                      _isApprovedOrOwner(_msgSender(), tokenId),
                      "ERC721: caller is not token owner nor approved"
                  );
                  _safeTransfer(from, to, tokenId, data);
              }
              /**
               * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
               * are aware of the ERC721 protocol to prevent tokens from being forever locked.
               *
               * `data` is additional data, it has no specified format and it is sent in call to `to`.
               *
               * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
               * implement alternative mechanisms to perform token transfer, such as signature-based.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must exist and be owned by `from`.
               * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function _safeTransfer(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory data
              ) internal virtual {
                  _transfer(from, to, tokenId);
                  require(
                      _checkOnERC721Received(from, to, tokenId, data),
                      "ERC721: transfer to non ERC721Receiver implementer"
                  );
              }
              /**
               * @dev Returns whether `tokenId` exists.
               *
               * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
               *
               * Tokens start existing when they are minted (`_mint`),
               * and stop existing when they are burned (`_burn`).
               */
              function _exists(uint256 tokenId) internal view virtual returns (bool) {
                  return _ownersAndHashSeeds[tokenId].owner != address(0);
              }
              /**
               * @dev Returns whether `spender` is allowed to manage `tokenId`.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               */
              function _isApprovedOrOwner(
                  address spender,
                  uint256 tokenId
              ) internal view virtual returns (bool) {
                  address owner = ERC721_PackedHashSeed.ownerOf(tokenId);
                  return (spender == owner ||
                      isApprovedForAll(owner, spender) ||
                      getApproved(tokenId) == spender);
              }
              /**
               * @dev Safely mints `tokenId` and transfers it to `to`.
               *
               * Requirements:
               *
               * - `tokenId` must not exist.
               * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function _safeMint(address to, uint256 tokenId) internal virtual {
                  _safeMint(to, tokenId, "");
              }
              /**
               * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
               * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
               */
              function _safeMint(
                  address to,
                  uint256 tokenId,
                  bytes memory data
              ) internal virtual {
                  _mint(to, tokenId);
                  require(
                      _checkOnERC721Received(address(0), to, tokenId, data),
                      "ERC721: transfer to non ERC721Receiver implementer"
                  );
              }
              /**
               * @dev Mints `tokenId` and transfers it to `to`.
               *
               * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
               *
               * Requirements:
               *
               * - `tokenId` must not exist.
               * - `to` cannot be the zero address.
               *
               * Emits a {Transfer} event.
               */
              function _mint(address to, uint256 tokenId) internal virtual {
                  require(to != address(0), "ERC721: mint to the zero address");
                  require(!_exists(tokenId), "ERC721: token already minted");
                  _beforeTokenTransfer(address(0), to, tokenId);
                  _balances[to] += 1;
                  _ownersAndHashSeeds[tokenId].owner = to;
                  emit Transfer(address(0), to, tokenId);
                  _afterTokenTransfer(address(0), to, tokenId);
              }
              /**
               * @dev Destroys `tokenId`.
               * The approval is cleared when the token is burned.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               *
               * Emits a {Transfer} event.
               */
              function _burn(uint256 tokenId) internal virtual {
                  address owner = ERC721_PackedHashSeed.ownerOf(tokenId);
                  _beforeTokenTransfer(owner, address(0), tokenId);
                  // Clear approvals
                  _approve(address(0), tokenId);
                  _balances[owner] -= 1;
                  delete _ownersAndHashSeeds[tokenId].owner;
                  emit Transfer(owner, address(0), tokenId);
                  _afterTokenTransfer(owner, address(0), tokenId);
              }
              /**
               * @dev Transfers `tokenId` from `from` to `to`.
               *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
               *
               * Requirements:
               *
               * - `to` cannot be the zero address.
               * - `tokenId` token must be owned by `from`.
               *
               * Emits a {Transfer} event.
               */
              function _transfer(
                  address from,
                  address to,
                  uint256 tokenId
              ) internal virtual {
                  require(
                      ERC721_PackedHashSeed.ownerOf(tokenId) == from,
                      "ERC721: transfer from incorrect owner"
                  );
                  require(to != address(0), "ERC721: transfer to the zero address");
                  _beforeTokenTransfer(from, to, tokenId);
                  // Clear approvals from the previous owner
                  _approve(address(0), tokenId);
                  _balances[from] -= 1;
                  _balances[to] += 1;
                  _ownersAndHashSeeds[tokenId].owner = to;
                  emit Transfer(from, to, tokenId);
                  _afterTokenTransfer(from, to, tokenId);
              }
              /**
               * @dev Approve `to` to operate on `tokenId`
               *
               * Emits an {Approval} event.
               */
              function _approve(address to, uint256 tokenId) internal virtual {
                  _tokenApprovals[tokenId] = to;
                  emit Approval(ERC721_PackedHashSeed.ownerOf(tokenId), to, tokenId);
              }
              /**
               * @dev Approve `operator` to operate on all of `owner` tokens
               *
               * Emits an {ApprovalForAll} event.
               */
              function _setApprovalForAll(
                  address owner,
                  address operator,
                  bool approved
              ) internal virtual {
                  require(owner != operator, "ERC721: approve to caller");
                  _operatorApprovals[owner][operator] = approved;
                  emit ApprovalForAll(owner, operator, approved);
              }
              /**
               * @dev Reverts if the `tokenId` has not been minted yet.
               */
              function _requireMinted(uint256 tokenId) internal view virtual {
                  require(_exists(tokenId), "ERC721: invalid token ID");
              }
              /**
               * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
               * The call is not executed if the target address is not a contract.
               *
               * @param from address representing the previous owner of the given token ID
               * @param to target address that will receive the tokens
               * @param tokenId uint256 ID of the token to be transferred
               * @param data bytes optional data to send along with the call
               * @return bool whether the call correctly returned the expected magic value
               */
              function _checkOnERC721Received(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory data
              ) private returns (bool) {
                  if (to.isContract()) {
                      try
                          IERC721Receiver(to).onERC721Received(
                              _msgSender(),
                              from,
                              tokenId,
                              data
                          )
                      returns (bytes4 retval) {
                          return retval == IERC721Receiver.onERC721Received.selector;
                      } catch (bytes memory reason) {
                          if (reason.length == 0) {
                              revert(
                                  "ERC721: transfer to non ERC721Receiver implementer"
                              );
                          } else {
                              /// @solidity memory-safe-assembly
                              assembly {
                                  revert(add(32, reason), mload(reason))
                              }
                          }
                      }
                  } else {
                      return true;
                  }
              }
              /**
               * @dev Hook that is called before any token transfer. This includes minting
               * and burning.
               *
               * 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, ``from``'s `tokenId` will be burned.
               * - `from` and `to` are never both zero.
               *
               * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
               */
              function _beforeTokenTransfer(
                  address from,
                  address to,
                  uint256 tokenId
              ) internal virtual {}
              /**
               * @dev Hook that is called after any transfer of tokens. This includes
               * minting and burning.
               *
               * Calling conditions:
               *
               * - when `from` and `to` are both non-zero.
               * - `from` and `to` are never both zero.
               *
               * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
               */
              function _afterTokenTransfer(
                  address from,
                  address to,
                  uint256 tokenId
              ) internal virtual {}
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.0;
          /**
           * @title Art Blocks Script Storage Library
           * @notice Utilize contract bytecode as persistant storage for large chunks of script string data.
           *
           * @author Art Blocks Inc.
           * @author Modified from 0xSequence (https://github.com/0xsequence/sstore2/blob/master/contracts/SSTORE2.sol)
           * @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SSTORE2.sol)
           *
           * @dev Compared to the above two rerferenced libraries, this contracts-as-storage implementation makes a few
           *      notably different design decisions:
           *      - uses the `string` data type for input/output on reads, rather than speaking in bytes directly
           *      - exposes "delete" functionality, allowing no-longer-used storage to be purged from chain state
           *      - stores the "writer" address (library user) in the deployed contract bytes, which is useful for both:
           *         a) providing necessary information for safe deletion; and
           *         b) allowing this to be introspected on-chain
           *      Also, given that much of this library is written in assembly, this library makes use of a slightly
           *      different convention (when compared to the rest of the Art Blocks smart contract repo) around
           *      pre-defining return values in some cases in order to simplify need to directly memory manage these
           *      return values.
           */
          library BytecodeStorage {
              //---------------------------------------------------------------------------------------------------------------//
              // Starting Index | Size | Ending Index | Description                                                            //
              //---------------------------------------------------------------------------------------------------------------//
              // 0              | N/A  | 0            |                                                                        //
              // 0              | 72   | 72           | the bytes of the gated-cleanup-logic allowing for `selfdestruct`ion    //
              // 72             | 32   | 104          | the 32 bytes for storing the deploying contract's (0-padded) address   //
              //---------------------------------------------------------------------------------------------------------------//
              // Define the offset for where the "logic bytes" end, and the "data bytes" begin. Note that this is a manually
              // calculated value, and must be updated if the above table is changed. It is expected that tests will fail
              // loudly if these values are not updated in-step with eachother.
              uint256 internal constant DATA_OFFSET = 104;
              uint256 internal constant ADDRESS_OFFSET = 72;
              /*//////////////////////////////////////////////////////////////
                                     WRITE LOGIC
              //////////////////////////////////////////////////////////////*/
              /**
               * @notice Write a string to contract bytecode
               * @param _data string to be written to contract. No input validation is performed on this parameter.
               * @return address_ address of deployed contract with bytecode containing concat(gated-cleanup-logic, address, data)
               */
              function writeToBytecode(
                  string memory _data
              ) internal returns (address address_) {
                  // prefix bytecode with
                  bytes memory creationCode = abi.encodePacked(
                      //---------------------------------------------------------------------------------------------------------------//
                      // Opcode  | Opcode + Arguments  | Description  | Stack View                                                     //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (0) creation code returns all code in the contract except for the first 11 (0B in hex) bytes, as these 11
                      //     bytes are the creation code itself which we do not want to store in the deployed storage contract result
                      //---------------------------------------------------------------------------------------------------------------//
                      // 0x60    |  0x60_0B            | PUSH1 11     | codeOffset                                                     //
                      // 0x59    |  0x59               | MSIZE        | 0 codeOffset                                                   //
                      // 0x81    |  0x81               | DUP2         | codeOffset 0 codeOffset                                        //
                      // 0x38    |  0x38               | CODESIZE     | codeSize codeOffset 0 codeOffset                               //
                      // 0x03    |  0x03               | SUB          | (codeSize - codeOffset) 0 codeOffset                           //
                      // 0x80    |  0x80               | DUP          | (codeSize - codeOffset) (codeSize - codeOffset) 0 codeOffset   //
                      // 0x92    |  0x92               | SWAP3        | codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset)   //
                      // 0x59    |  0x59               | MSIZE        | 0 codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset) //
                      // 0x39    |  0x39               | CODECOPY     | 0 (codeSize - codeOffset)                                      //
                      // 0xf3    |  0xf3               | RETURN       |                                                                //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (11 bytes)
                      hex"60_0B_59_81_38_03_80_92_59_39_F3",
                      //---------------------------------------------------------------------------------------------------------------//
                      // Opcode  | Opcode + Arguments  | Description  | Stack View                                                     //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (1a) conditional logic for determing purge-gate (only the bytecode contract deployer can `selfdestruct`)
                      //---------------------------------------------------------------------------------------------------------------//
                      // 0x60    |  0x60_20            | PUSH1 32           | 32                                                       //
                      // 0x60    |  0x60_48            | PUSH1 72 (*)       | contractOffset 32                                        //
                      // 0x60    |  0x60_00            | PUSH1 0            | 0 contractOffset 32                                      //
                      // 0x39    |  0x39               | CODECOPY           |                                                          //
                      // 0x60    |  0x60_00            | PUSH1 0            | 0                                                        //
                      // 0x51    |  0x51               | MLOAD              | byteDeployerAddress                                      //
                      // 0x33    |  0x33               | CALLER             | msg.sender byteDeployerAddress                           //
                      // 0x14    |  0x14               | EQ                 | (msg.sender == byteDeployerAddress)                      //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (12 bytes: 0-11 in deployed contract)
                      hex"60_20_60_48_60_00_39_60_00_51_33_14",
                      //---------------------------------------------------------------------------------------------------------------//
                      // (1b) load up the destination jump address for `(2a) calldata length check` logic, jump or raise `invalid` op-code
                      //---------------------------------------------------------------------------------------------------------------//
                      // 0x60    |  0x60_10            | PUSH1 16 (^)       | jumpDestination (msg.sender == byteDeployerAddress)      //
                      // 0x57    |  0x57               | JUMPI              |                                                          //
                      // 0xFE    |  0xFE               | INVALID            |                                                          //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (4 bytes: 12-15 in deployed contract)
                      hex"60_10_57_FE",
                      //---------------------------------------------------------------------------------------------------------------//
                      // (2a) conditional logic for determing purge-gate (only if calldata length is 1 byte)
                      //---------------------------------------------------------------------------------------------------------------//
                      // 0x5B    |  0x5B               | JUMPDEST (16)      |                                                          //
                      // 0x60    |  0x60_01            | PUSH1 1            | 1                                                        //
                      // 0x36    |  0x36               | CALLDATASIZE       | calldataSize 1                                           //
                      // 0x14    |  0x14               | EQ                 | (calldataSize == 1)                                      //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (5 bytes: 16-20 in deployed contract)
                      hex"5B_60_01_36_14",
                      //---------------------------------------------------------------------------------------------------------------//
                      // (2b) load up the destination jump address for `(3a) calldata value check` logic, jump or raise `invalid` op-code
                      //---------------------------------------------------------------------------------------------------------------//
                      // 0x60    |  0x60_19            | PUSH1 25 (^)       | jumpDestination (calldataSize == 1)                      //
                      // 0x57    |  0x57               | JUMPI              |                                                          //
                      // 0xFE    |  0xFE               | INVALID            |                                                          //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (4 bytes: 21-24 in deployed contract)
                      hex"60_19_57_FE",
                      //---------------------------------------------------------------------------------------------------------------//
                      // (3a) conditional logic for determing purge-gate (only if calldata is `0xFF`)
                      //---------------------------------------------------------------------------------------------------------------//
                      // 0x5B    |  0x5B               | JUMPDEST (25)      |                                                          //
                      // 0x60    |  0x60_00            | PUSH1 0            | 0                                                        //
                      // 0x35    |  0x35               | CALLDATALOAD       | calldata                                                 //
                      // 0x7F    |  0x7F_FF_00_..._00  | PUSH32 0xFF00...00 | 0xFF0...00 calldata                                      //
                      // 0x14    |  0x14               | EQ                 | (0xFF00...00 == calldata)                                //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (4 bytes: 25-28 in deployed contract)
                      hex"5B_60_00_35",
                      // (33 bytes: 29-61 in deployed contract)
                      hex"7F_FF_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00",
                      // (1 byte: 62 in deployed contract)
                      hex"14",
                      //---------------------------------------------------------------------------------------------------------------//
                      // (3b) load up the destination jump address for actual purging (4), jump or raise `invalid` op-code
                      //---------------------------------------------------------------------------------------------------------------//
                      // 0x60    |  0x60_43            | PUSH1 67 (^)       | jumpDestination (0xFF00...00 == calldata)                //
                      // 0x57    |  0x57               | JUMPI              |                                                          //
                      // 0xFE    |  0xFE               | INVALID            |                                                          //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (4 bytes: 63-66 in deployed contract)
                      hex"60_43_57_FE",
                      //---------------------------------------------------------------------------------------------------------------//
                      // (4) perform actual purging
                      //---------------------------------------------------------------------------------------------------------------//
                      // 0x5B    |  0x5B               | JUMPDEST (67)      |                                                          //
                      // 0x60    |  0x60_00            | PUSH1 0            | 0                                                        //
                      // 0x51    |  0x51               | MLOAD              | byteDeployerAddress                                      //
                      // 0xFF    |  0xFF               | SELFDESTRUCT       |                                                          //
                      //---------------------------------------------------------------------------------------------------------------//
                      // (5 bytes: 67-71 in deployed contract)
                      hex"5B_60_00_51_FF",
                      //---------------------------------------------------------------------------------------------------------------//
                      // (*) Note: this value must be adjusted if selfdestruct purge logic is adjusted, to refer to the correct start  //
                      //           offset for where the `msg.sender` address was stored in deployed bytecode.                          //
                      //                                                                                                               //
                      // (^) Note: this value must be adjusted if portions of the selfdestruct purge logic are adjusted.               //
                      //---------------------------------------------------------------------------------------------------------------//
                      //
                      // store the deploying-contract's address (to be used to gate and call `selfdestruct`),
                      // with expected 0-padding to fit a 20-byte address into a 30-byte slot.
                      //
                      // note: it is important that this address is the executing contract's address
                      //      (the address that represents the client-application smart contract of this library)
                      //      which means that it is the responsibility of the client-application smart contract
                      //      to determine how deletes are gated (or if they are exposed at all) as it is only
                      //      this contract that will be able to call `purgeBytecode` as the `CALLER` that is
                      //      checked above (op-code 0x33).
                      hex"00_00_00_00_00_00_00_00_00_00_00_00", // left-pad 20-byte address with 12 0x00 bytes
                      address(this),
                      // uploaded data (stored as bytecode) comes last
                      _data
                  );
                  assembly {
                      // deploy a new contract with the generated creation code.
                      // start 32 bytes into creationCode to avoid copying the byte length.
                      address_ := create(0, add(creationCode, 0x20), mload(creationCode))
                  }
                  // address must be non-zero if contract was deployed successfully
                  require(address_ != address(0), "ContractAsStorage: Write Error");
              }
              /*//////////////////////////////////////////////////////////////
                                         READ LOGIC
              //////////////////////////////////////////////////////////////*/
              /**
               * @notice Read a string from contract bytecode
               * @param _address address of deployed contract with bytecode containing concat(gated-cleanup-logic, address, data)
               * @return data string read from contract bytecode
               */
              function readFromBytecode(
                  address _address
              ) internal view returns (string memory data) {
                  // get the size of the bytecode
                  uint256 bytecodeSize = _bytecodeSizeAt(_address);
                  // handle case where address contains code < DATA_OFFSET
                  // note: the first check here also captures the case where
                  //       (bytecodeSize == 0) implicitly, but we add the second check of
                  //       (bytecodeSize == 0) as a fall-through that will never execute
                  //       unless `DATA_OFFSET` is set to 0 at some point.
                  if ((bytecodeSize < DATA_OFFSET) || (bytecodeSize == 0)) {
                      revert("ContractAsStorage: Read Error");
                  }
                  // handle case where address contains code >= DATA_OFFSET
                  // decrement by DATA_OFFSET to account for purge logic
                  uint256 size;
                  unchecked {
                      size = bytecodeSize - DATA_OFFSET;
                  }
                  assembly {
                      // allocate free memory
                      data := mload(0x40)
                      // update free memory pointer
                      // use and(x, not(0x1f) as cheaper equivalent to sub(x, mod(x, 0x20)).
                      // adding 0x1f to size + logic above ensures the free memory pointer
                      // remains word-aligned, following the Solidity convention.
                      mstore(0x40, add(data, and(add(add(size, 0x20), 0x1f), not(0x1f))))
                      // store length of data in first 32 bytes
                      mstore(data, size)
                      // copy code to memory, excluding the gated-cleanup-logic and address
                      extcodecopy(_address, add(data, 0x20), DATA_OFFSET, size)
                  }
              }
              /**
               * @notice Get address for deployer for given contract bytecode
               * @param _address address of deployed contract with bytecode containing concat(gated-cleanup-logic, address, data)
               * @return writerAddress address read from contract bytecode
               */
              function getWriterAddressForBytecode(
                  address _address
              ) internal view returns (address) {
                  // get the size of the data
                  uint256 bytecodeSize = _bytecodeSizeAt(_address);
                  // handle case where address contains code < DATA_OFFSET
                  // note: the first check here also captures the case where
                  //       (bytecodeSize == 0) implicitly, but we add the second check of
                  //       (bytecodeSize == 0) as a fall-through that will never execute
                  //       unless `DATA_OFFSET` is set to 0 at some point.
                  if ((bytecodeSize < DATA_OFFSET) || (bytecodeSize == 0)) {
                      revert("ContractAsStorage: Read Error");
                  }
                  assembly {
                      // allocate free memory
                      let writerAddress := mload(0x40)
                      // shift free memory pointer by one slot
                      mstore(0x40, add(mload(0x40), 0x20))
                      // copy the 32-byte address of the data contract writer to memory
                      // note: this relies on the assumption noted at the top-level of
                      //       this file that the storage layout for the deployed
                      //       contracts-as-storage contract looks like:
                      //       | gated-cleanup-logic | deployer-address (padded) | data |
                      extcodecopy(
                          _address,
                          writerAddress,
                          ADDRESS_OFFSET,
                          0x20 // full 32-bytes, as address is expected to be zero-padded
                      )
                      return(
                          writerAddress,
                          0x20 // return size is entire slot, as it is zero-padded
                      )
                  }
              }
              /*//////////////////////////////////////////////////////////////
                                        DELETE LOGIC
              //////////////////////////////////////////////////////////////*/
              /**
               * @notice Purge contract bytecode for cleanup purposes
               * note: Although this does reduce usage of Ethereum state, it does not reduce the gas costs of removal
               * transactions. We believe this is the best behavior at the time of writing, and do not expect this to
               * result in any breaking changes in the future. All current proposals to change the self-destruct opcode
               * are backwards compatible, but may result in not removing the bytecode from the blockchain state. This
               * implementation is compatible with that architecture, as it does not rely on the bytecode being removed
               * from the blockchain state (as opposed to using a CREATE2 style opcode when creating bytecode contracts,
               * which could be used in a way that may rely on the bytecode being removed from the blockchain state,
               * e.g. replacing a contract at a given deployed address).
               * @param _address address of deployed contract with bytecode containing concat(gated-cleanup-logic, address, data)
               * @dev This contract is only callable by the address of the contract that originally deployed the bytecode
               *      being purged. If this method is called by any other address, it will revert with the `INVALID` op-code.
               *      Additionally, for security purposes, the contract must be called with calldata `0xFF` to ensure that
               *      the `selfdestruct` op-code is intentionally being invoked, otherwise the `INVALID` op-code will be raised.
               */
              function purgeBytecode(address _address) internal {
                  // deployed bytecode (above) handles all logic for purging state, so no
                  // call data is expected to be passed along to perform data purge
                  (bool success /* `data` not needed */, ) = _address.call(hex"FF");
                  if (!success) {
                      revert("ContractAsStorage: Delete Error");
                  }
              }
              /*//////////////////////////////////////////////////////////////
                                    INTERNAL HELPER LOGIC
              //////////////////////////////////////////////////////////////*/
              /**
                  @notice Returns the size of the bytecode at address `_address`
                  @param _address address that may or may not contain bytecode
                  @return size size of the bytecode code at `_address`
              */
              function _bytecodeSizeAt(
                  address _address
              ) private view returns (uint256 size) {
                  assembly {
                      size := extcodesize(_address)
                  }
              }
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          // Inspired by: https://ethereum.stackexchange.com/a/123950/103422
          pragma solidity ^0.8.0;
          /**
           * @dev Operations on bytes32 data type, dealing with conversion to string.
           */
          library Bytes32Strings {
              /**
               * @dev Intended to convert a `bytes32`-encoded string literal to `string`.
               * Trims zero padding to arrive at original string literal.
               */
              function toString(
                  bytes32 source
              ) internal pure returns (string memory result) {
                  uint8 length = 0;
                  while (source[length] != 0 && length < 32) {
                      length++;
                  }
                  assembly {
                      // free memory pointer
                      result := mload(0x40)
                      // update free memory pointer to new "memory end"
                      // (offset is 64-bytes: 32 for length, 32 for data)
                      mstore(0x40, add(result, 0x40))
                      // store length in first 32-byte memory slot
                      mstore(result, length)
                      // write actual data in second 32-byte memory slot
                      mstore(add(result, 0x20), source)
                  }
              }
              /**
               * @dev Intended to check if a `bytes32`-encoded string contains a given
               * character with UTF-8 character code `utf8CharCode exactly `targetQty`
               * times. Does not support searching for multi-byte characters, only
               * characters with UTF-8 character codes < 0x80.
               */
              function containsExactCharacterQty(
                  bytes32 source,
                  uint8 utf8CharCode,
                  uint8 targetQty
              ) internal pure returns (bool) {
                  uint8 _occurrences = 0;
                  uint8 i;
                  for (i = 0; i < 32; ) {
                      uint8 _charCode = uint8(source[i]);
                      // if not a null byte, or a multi-byte UTF-8 character, check match
                      if (_charCode != 0 && _charCode < 0x80) {
                          if (_charCode == utf8CharCode) {
                              unchecked {
                                  // no risk of overflow since max 32 iterations < max uin8=255
                                  ++_occurrences;
                              }
                          }
                      }
                      unchecked {
                          // no risk of overflow since max 32 iterations < max uin8=255
                          ++i;
                      }
                  }
                  return _occurrences == targetQty;
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright (c) 2023 the ethier authors (github.com/divergencetech/ethier)
          pragma solidity >=0.8.0 <0.9.0;
          import {AccessControlEnumerable as ACE} from "@openzeppelin/contracts/access/AccessControlEnumerable.sol";
          contract AccessControlEnumerable is ACE {
              /// @notice The default role intended to perform access-restricted actions.
              /// @dev We are using this instead of DEFAULT_ADMIN_ROLE because the latter
              /// is intended to grant/revoke roles and will be secured differently.
              bytes32 public constant DEFAULT_STEERING_ROLE =
                  keccak256("DEFAULT_STEERING_ROLE");
              /// @dev Overrides supportsInterface so that inheriting contracts can
              /// reference this contract instead of OZ's version for further overrides.
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(ACE)
                  returns (bool)
              {
                  return ACE.supportsInterface(interfaceId);
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright (c) 2022 the ethier authors (github.com/divergencetech/ethier)
          pragma solidity >=0.8.0 <0.9.0;
          import {ERC721A} from "erc721a/contracts/ERC721A.sol";
          import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol";
          import {AccessControlEnumerable} from "../utils/AccessControlEnumerable.sol";
          import {AccessControlPausable} from "../utils/AccessControlPausable.sol";
          import {ERC4906} from "./ERC4906.sol";
          /**
          @notice An ERC721A contract with common functionality:
           - Pausable with toggling functions exposed to Owner only
           - ERC2981 royalties
           */
          contract ERC721ACommon is ERC721A, AccessControlPausable, ERC2981, ERC4906 {
              constructor(
                  address admin,
                  address steerer,
                  string memory name,
                  string memory symbol,
                  address payable royaltyReciever,
                  uint96 royaltyBasisPoints
              ) ERC721A(name, symbol) {
                  _setDefaultRoyalty(royaltyReciever, royaltyBasisPoints);
                  _grantRole(DEFAULT_ADMIN_ROLE, admin);
                  _grantRole(DEFAULT_STEERING_ROLE, steerer);
              }
              /// @notice Requires that the token exists.
              modifier tokenExists(uint256 tokenId) {
                  require(ERC721A._exists(tokenId), "ERC721ACommon: Token doesn't exist");
                  _;
              }
              /// @notice Requires that msg.sender owns or is approved for the token.
              modifier onlyApprovedOrOwner(uint256 tokenId) {
                  require(
                      _ownershipOf(tokenId).addr == _msgSender() ||
                          getApproved(tokenId) == _msgSender(),
                      "ERC721ACommon: Not approved nor owner"
                  );
                  _;
              }
              function _beforeTokenTransfers(
                  address from,
                  address to,
                  uint256 startTokenId,
                  uint256 quantity
              ) internal virtual override {
                  require(!paused(), "ERC721ACommon: paused");
                  super._beforeTokenTransfers(from, to, startTokenId, quantity);
              }
              /// @notice Overrides supportsInterface as required by inheritance.
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(ERC721A, AccessControlEnumerable, ERC2981, ERC4906)
                  returns (bool)
              {
                  return
                      ERC721A.supportsInterface(interfaceId) ||
                      ERC2981.supportsInterface(interfaceId) ||
                      AccessControlEnumerable.supportsInterface(interfaceId) ||
                      ERC4906.supportsInterface(interfaceId);
              }
              /// @notice Sets the royalty receiver and percentage (in units of basis
              /// points = 0.01%).
              function setDefaultRoyalty(address receiver, uint96 basisPoints)
                  public
                  virtual
                  onlyRole(DEFAULT_STEERING_ROLE)
              {
                  _setDefaultRoyalty(receiver, basisPoints);
              }
              function emitMetadataUpdateForAll()
                  external
                  onlyRole(DEFAULT_STEERING_ROLE)
              {
                  // EIP4906 is unfortunately quite vague on whether the `toTokenId` in
                  // the following event is included or not. We hence use `totalSupply()`
                  // to ensure that the last actual `tokenId` is included in any case.
                  _refreshMetadata(0, totalSupply());
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)
          pragma solidity ^0.8.1;
          /**
           * @dev Collection of functions related to the address type
           */
          library Address {
              /**
               * @dev Returns true if `account` is a contract.
               *
               * [IMPORTANT]
               * ====
               * It is unsafe to assume that an address for which this function returns
               * false is an externally-owned account (EOA) and not a contract.
               *
               * Among others, `isContract` will return false for the following
               * types of addresses:
               *
               *  - an externally-owned account
               *  - a contract in construction
               *  - an address where a contract will be created
               *  - an address where a contract lived, but was destroyed
               * ====
               *
               * [IMPORTANT]
               * ====
               * You shouldn't rely on `isContract` to protect against flash loan attacks!
               *
               * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
               * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
               * constructor.
               * ====
               */
              function isContract(address account) internal view returns (bool) {
                  // This method relies on extcodesize/address.code.length, which returns 0
                  // for contracts in construction, since the code is only stored at the end
                  // of the constructor execution.
                  return account.code.length > 0;
              }
              /**
               * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
               * `recipient`, forwarding all available gas and reverting on errors.
               *
               * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
               * of certain opcodes, possibly making contracts go over the 2300 gas limit
               * imposed by `transfer`, making them unable to receive funds via
               * `transfer`. {sendValue} removes this limitation.
               *
               * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
               *
               * IMPORTANT: because control is transferred to `recipient`, care must be
               * taken to not create reentrancy vulnerabilities. Consider using
               * {ReentrancyGuard} or the
               * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
               */
              function sendValue(address payable recipient, uint256 amount) internal {
                  require(address(this).balance >= amount, "Address: insufficient balance");
                  (bool success, ) = recipient.call{value: amount}("");
                  require(success, "Address: unable to send value, recipient may have reverted");
              }
              /**
               * @dev Performs a Solidity function call using a low level `call`. A
               * plain `call` is an unsafe replacement for a function call: use this
               * function instead.
               *
               * If `target` reverts with a revert reason, it is bubbled up by this
               * function (like regular Solidity function calls).
               *
               * Returns the raw returned data. To convert to the expected return value,
               * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
               *
               * Requirements:
               *
               * - `target` must be a contract.
               * - calling `target` with `data` must not revert.
               *
               * _Available since v3.1._
               */
              function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, 0, "Address: low-level call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
               * `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, 0, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but also transferring `value` wei to `target`.
               *
               * Requirements:
               *
               * - the calling contract must have an ETH balance of at least `value`.
               * - the called Solidity function must be `payable`.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
              }
              /**
               * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
               * with `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  require(address(this).balance >= value, "Address: insufficient balance for call");
                  (bool success, bytes memory returndata) = target.call{value: value}(data);
                  return verifyCallResultFromTarget(target, success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                  return functionStaticCall(target, data, "Address: low-level static call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal view returns (bytes memory) {
                  (bool success, bytes memory returndata) = target.staticcall(data);
                  return verifyCallResultFromTarget(target, success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a delegate call.
               *
               * _Available since v3.4._
               */
              function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionDelegateCall(target, data, "Address: low-level delegate call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a delegate call.
               *
               * _Available since v3.4._
               */
              function functionDelegateCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  (bool success, bytes memory returndata) = target.delegatecall(data);
                  return verifyCallResultFromTarget(target, success, returndata, errorMessage);
              }
              /**
               * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
               * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
               *
               * _Available since v4.8._
               */
              function verifyCallResultFromTarget(
                  address target,
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal view returns (bytes memory) {
                  if (success) {
                      if (returndata.length == 0) {
                          // only check isContract if the call was successful and the return data is empty
                          // otherwise we already know that it was a contract
                          require(isContract(target), "Address: call to non-contract");
                      }
                      return returndata;
                  } else {
                      _revert(returndata, errorMessage);
                  }
              }
              /**
               * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
               * revert reason or using the provided one.
               *
               * _Available since v4.3._
               */
              function verifyCallResult(
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal pure returns (bytes memory) {
                  if (success) {
                      return returndata;
                  } else {
                      _revert(returndata, errorMessage);
                  }
              }
              function _revert(bytes memory returndata, string memory errorMessage) private pure {
                  // Look for revert reason and bubble it up if present
                  if (returndata.length > 0) {
                      // The easiest way to bubble the revert reason is using memory via assembly
                      /// @solidity memory-safe-assembly
                      assembly {
                          let returndata_size := mload(returndata)
                          revert(add(32, returndata), returndata_size)
                      }
                  } else {
                      revert(errorMessage);
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.13;
          import {OperatorFilterer} from "./OperatorFilterer.sol";
          import {CANONICAL_CORI_SUBSCRIPTION} from "./lib/Constants.sol";
          /**
           * @title  DefaultOperatorFilterer
           * @notice Inherits from OperatorFilterer and automatically subscribes to the default OpenSea subscription.
           * @dev    Please note that if your token contract does not provide an owner with EIP-173, it must provide
           *         administration methods on the contract itself to interact with the registry otherwise the subscription
           *         will be locked to the options set during construction.
           */
          abstract contract DefaultOperatorFilterer is OperatorFilterer {
              /// @dev The constructor that is called when the contract is being deployed.
              constructor() OperatorFilterer(CANONICAL_CORI_SUBSCRIPTION, true) {}
          }
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity >=0.8.0 <0.9.0;
          import {AccessControlEnumerable} from "ethier/utils/AccessControlEnumerable.sol";
          import {ISellable} from "../interfaces/ISellable.sol";
          /**
           * @notice A base contract for selling content via authorised sellers.
           */
          abstract contract BaseSellable is ISellable, AccessControlEnumerable {
              /**
               * @notice Authorised sellers.
               */
              bytes32 public constant AUTHORISED_SELLER_ROLE = keccak256("AUTHORISED_SELLER_ROLE");
              /**
               * @notice A role that cannot be granted or revoked.
               * @dev Used to lock in members of the `AUTHORISED_SELLER_ROLE` role.
               */
              bytes32 private constant _NOOP_ROLE = keccak256("NOOP_ROLE");
              constructor() {
                  _setRoleAdmin(AUTHORISED_SELLER_ROLE, DEFAULT_STEERING_ROLE);
                  _setRoleAdmin(_NOOP_ROLE, _NOOP_ROLE);
              }
              /**
               * @notice Handles the sale of sellable content via an authorised seller.
               * @dev Delegates the implementation to `_handleSale`.
               */
              function handleSale(address to, uint64 num, bytes calldata data)
                  external
                  payable
                  onlyRole(AUTHORISED_SELLER_ROLE)
              {
                  _handleSale(to, num, data);
              }
              /**
               * @notice Handles the sale of sellable content.
               */
              function _handleSale(address to, uint64 num, bytes calldata data) internal virtual;
              /**
               * @notice Locks the `AUTHORISED_SELLER_ROLE` role.
               */
              function lockSellers() external onlyRole(DEFAULT_STEERING_ROLE) {
                  _lockSellers();
              }
              /**
               * @notice Locks the `AUTHORISED_SELLER_ROLE` role.
               */
              function _lockSellers() internal {
                  _setRoleAdmin(AUTHORISED_SELLER_ROLE, _NOOP_ROLE);
              }
              /**
               * @notice Revokes approval for all sellers.
               */
              function _revokeAllSellers() internal {
                  uint256 num = getRoleMemberCount(AUTHORISED_SELLER_ROLE);
                  for (uint256 i = 0; i < num; i++) {
                      // Akin to a popFront
                      address seller = getRoleMember(AUTHORISED_SELLER_ROLE, 0);
                      _revokeRole(AUTHORISED_SELLER_ROLE, seller);
                  }
              }
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.0;
          import "./IAdminACLV0.sol";
          /// use the Royalty Registry's IManifold interface for token royalties
          import "./IManifold.sol";
          /**
           * @title This interface is intended to house interface items that are common
           * across all GenArt721CoreContractV3 flagship and derivative implementations.
           * This interface extends the IManifold royalty interface in order to
           * add support the Royalty Registry by default.
           * @author Art Blocks Inc.
           */
          interface IGenArt721CoreContractV3_Base is IManifold {
              /**
               * @notice Token ID `_tokenId` minted to `_to`.
               */
              event Mint(address indexed _to, uint256 indexed _tokenId);
              /**
               * @notice currentMinter updated to `_currentMinter`.
               * @dev Implemented starting with V3 core
               */
              event MinterUpdated(address indexed _currentMinter);
              /**
               * @notice Platform updated on bytes32-encoded field `_field`.
               */
              event PlatformUpdated(bytes32 indexed _field);
              /**
               * @notice Project ID `_projectId` updated on bytes32-encoded field
               * `_update`.
               */
              event ProjectUpdated(uint256 indexed _projectId, bytes32 indexed _update);
              event ProposedArtistAddressesAndSplits(
                  uint256 indexed _projectId,
                  address _artistAddress,
                  address _additionalPayeePrimarySales,
                  uint256 _additionalPayeePrimarySalesPercentage,
                  address _additionalPayeeSecondarySales,
                  uint256 _additionalPayeeSecondarySalesPercentage
              );
              event AcceptedArtistAddressesAndSplits(uint256 indexed _projectId);
              // version and type of the core contract
              // coreVersion is a string of the form "0.x.y"
              function coreVersion() external view returns (string memory);
              // coreType is a string of the form "GenArt721CoreV3"
              function coreType() external view returns (string memory);
              // owner (pre-V3 was named admin) of contract
              // this is expected to be an Admin ACL contract for V3
              function owner() external view returns (address);
              // Admin ACL contract for V3, will be at the address owner()
              function adminACLContract() external returns (IAdminACLV0);
              // backwards-compatible (pre-V3) admin - equal to owner()
              function admin() external view returns (address);
              /**
               * Function determining if _sender is allowed to call function with
               * selector _selector on contract `_contract`. Intended to be used with
               * peripheral contracts such as minters, as well as internally by the
               * core contract itself.
               */
              function adminACLAllowed(
                  address _sender,
                  address _contract,
                  bytes4 _selector
              ) external returns (bool);
              // getter function of public variable
              function nextProjectId() external view returns (uint256);
              // getter function of public mapping
              function tokenIdToProjectId(
                  uint256 tokenId
              ) external view returns (uint256 projectId);
              // @dev this is not available in V0
              function isMintWhitelisted(address minter) external view returns (bool);
              function projectIdToArtistAddress(
                  uint256 _projectId
              ) external view returns (address payable);
              function projectIdToAdditionalPayeePrimarySales(
                  uint256 _projectId
              ) external view returns (address payable);
              function projectIdToAdditionalPayeePrimarySalesPercentage(
                  uint256 _projectId
              ) external view returns (uint256);
              function projectIdToSecondaryMarketRoyaltyPercentage(
                  uint256 _projectId
              ) external view returns (uint256);
              function projectURIInfo(
                  uint256 _projectId
              ) external view returns (string memory projectBaseURI);
              // @dev new function in V3
              function projectStateData(
                  uint256 _projectId
              )
                  external
                  view
                  returns (
                      uint256 invocations,
                      uint256 maxInvocations,
                      bool active,
                      bool paused,
                      uint256 completedTimestamp,
                      bool locked
                  );
              function projectDetails(
                  uint256 _projectId
              )
                  external
                  view
                  returns (
                      string memory projectName,
                      string memory artist,
                      string memory description,
                      string memory website,
                      string memory license
                  );
              function projectScriptDetails(
                  uint256 _projectId
              )
                  external
                  view
                  returns (
                      string memory scriptTypeAndVersion,
                      string memory aspectRatio,
                      uint256 scriptCount
                  );
              function projectScriptByIndex(
                  uint256 _projectId,
                  uint256 _index
              ) external view returns (string memory);
              function tokenIdToHash(uint256 _tokenId) external view returns (bytes32);
              // function to set a token's hash (must be guarded)
              function setTokenHash_8PT(uint256 _tokenId, bytes32 _hash) external;
              // @dev gas-optimized signature in V3 for `mint`
              function mint_Ecf(
                  address _to,
                  uint256 _projectId,
                  address _by
              ) external returns (uint256 tokenId);
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.0;
          import "./IAdminACLV0.sol";
          import "./IGenArt721CoreContractV3_Base.sol";
          interface IGenArt721CoreContractV3_Engine is IGenArt721CoreContractV3_Base {
              // @dev new function in V3
              function getPrimaryRevenueSplits(
                  uint256 _projectId,
                  uint256 _price
              )
                  external
                  view
                  returns (
                      uint256 renderProviderRevenue_,
                      address payable renderProviderAddress_,
                      uint256 platformProviderRevenue_,
                      address payable platformProviderAddress_,
                      uint256 artistRevenue_,
                      address payable artistAddress_,
                      uint256 additionalPayeePrimaryRevenue_,
                      address payable additionalPayeePrimaryAddress_
                  );
              // @dev The render provider primary sales payment address
              function renderProviderPrimarySalesAddress()
                  external
                  view
                  returns (address payable);
              // @dev The platform provider primary sales payment address
              function platformProviderPrimarySalesAddress()
                  external
                  view
                  returns (address payable);
              // @dev Percentage of primary sales allocated to the render provider
              function renderProviderPrimarySalesPercentage()
                  external
                  view
                  returns (uint256);
              // @dev Percentage of primary sales allocated to the platform provider
              function platformProviderPrimarySalesPercentage()
                  external
                  view
                  returns (uint256);
              // @dev The render provider secondary sales royalties payment address
              function renderProviderSecondarySalesAddress()
                  external
                  view
                  returns (address payable);
              // @dev The platform provider secondary sales royalties payment address
              function platformProviderSecondarySalesAddress()
                  external
                  view
                  returns (address payable);
              // @dev Basis points of secondary sales allocated to the render provider
              function renderProviderSecondarySalesBPS() external view returns (uint256);
              // @dev Basis points of secondary sales allocated to the platform provider
              function platformProviderSecondarySalesBPS()
                  external
                  view
                  returns (uint256);
              // function to read the hash-seed for a given tokenId
              function tokenIdToHashSeed(
                  uint256 _tokenId
              ) external view returns (bytes12);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Provides information about the current execution context, including the
           * sender of the transaction and its data. While these are generally available
           * via msg.sender and msg.data, they should not be accessed in such a direct
           * manner, since when dealing with meta-transactions the account sending and
           * paying for execution may not be the actual sender (as far as an application
           * is concerned).
           *
           * This contract is only required for intermediate, library-like contracts.
           */
          abstract contract Context {
              function _msgSender() internal view virtual returns (address) {
                  return msg.sender;
              }
              function _msgData() internal view virtual returns (bytes calldata) {
                  return msg.data;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/IERC721.sol)
          pragma solidity ^0.8.0;
          import "../../utils/introspection/IERC165.sol";
          /**
           * @dev Required interface of an ERC721 compliant contract.
           */
          interface IERC721 is IERC165 {
              /**
               * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
               */
              event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
              /**
               * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
               */
              event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
              /**
               * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
               */
              event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
              /**
               * @dev Returns the number of tokens in ``owner``'s account.
               */
              function balanceOf(address owner) external view returns (uint256 balance);
              /**
               * @dev Returns the owner of the `tokenId` token.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               */
              function ownerOf(uint256 tokenId) external view returns (address owner);
              /**
               * @dev Safely transfers `tokenId` token from `from` to `to`.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must exist and be owned by `from`.
               * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
               * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes calldata data
              ) external;
              /**
               * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
               * are aware of the ERC721 protocol to prevent tokens from being forever locked.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must exist and be owned by `from`.
               * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
               * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) external;
              /**
               * @dev Transfers `tokenId` token from `from` to `to`.
               *
               * WARNING: 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);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)
          pragma solidity ^0.8.0;
          /**
           * @title ERC721 token receiver interface
           * @dev Interface for any contract that wants to support safeTransfers
           * from ERC721 asset contracts.
           */
          interface IERC721Receiver {
              /**
               * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
               * by `operator` from `from`, this function is called.
               *
               * It must return its Solidity selector to confirm the token transfer.
               * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
               *
               * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
               */
              function onERC721Received(
                  address operator,
                  address from,
                  uint256 tokenId,
                  bytes calldata data
              ) external returns (bytes4);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)
          pragma solidity ^0.8.0;
          import "../IERC721.sol";
          /**
           * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
           * @dev See https://eips.ethereum.org/EIPS/eip-721
           */
          interface IERC721Metadata is IERC721 {
              /**
               * @dev Returns the token collection name.
               */
              function name() external view returns (string memory);
              /**
               * @dev Returns the token collection symbol.
               */
              function symbol() external view returns (string memory);
              /**
               * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
               */
              function tokenURI(uint256 tokenId) external view returns (string memory);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol)
          pragma solidity ^0.8.1;
          /**
           * @dev Collection of functions related to the address type
           */
          library Address {
              /**
               * @dev Returns true if `account` is a contract.
               *
               * [IMPORTANT]
               * ====
               * It is unsafe to assume that an address for which this function returns
               * false is an externally-owned account (EOA) and not a contract.
               *
               * Among others, `isContract` will return false for the following
               * types of addresses:
               *
               *  - an externally-owned account
               *  - a contract in construction
               *  - an address where a contract will be created
               *  - an address where a contract lived, but was destroyed
               * ====
               *
               * [IMPORTANT]
               * ====
               * You shouldn't rely on `isContract` to protect against flash loan attacks!
               *
               * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
               * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
               * constructor.
               * ====
               */
              function isContract(address account) internal view returns (bool) {
                  // This method relies on extcodesize/address.code.length, which returns 0
                  // for contracts in construction, since the code is only stored at the end
                  // of the constructor execution.
                  return account.code.length > 0;
              }
              /**
               * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
               * `recipient`, forwarding all available gas and reverting on errors.
               *
               * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
               * of certain opcodes, possibly making contracts go over the 2300 gas limit
               * imposed by `transfer`, making them unable to receive funds via
               * `transfer`. {sendValue} removes this limitation.
               *
               * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
               *
               * IMPORTANT: because control is transferred to `recipient`, care must be
               * taken to not create reentrancy vulnerabilities. Consider using
               * {ReentrancyGuard} or the
               * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
               */
              function sendValue(address payable recipient, uint256 amount) internal {
                  require(address(this).balance >= amount, "Address: insufficient balance");
                  (bool success, ) = recipient.call{value: amount}("");
                  require(success, "Address: unable to send value, recipient may have reverted");
              }
              /**
               * @dev Performs a Solidity function call using a low level `call`. A
               * plain `call` is an unsafe replacement for a function call: use this
               * function instead.
               *
               * If `target` reverts with a revert reason, it is bubbled up by this
               * function (like regular Solidity function calls).
               *
               * Returns the raw returned data. To convert to the expected return value,
               * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
               *
               * Requirements:
               *
               * - `target` must be a contract.
               * - calling `target` with `data` must not revert.
               *
               * _Available since v3.1._
               */
              function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionCall(target, data, "Address: low-level call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
               * `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, 0, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but also transferring `value` wei to `target`.
               *
               * Requirements:
               *
               * - the calling contract must have an ETH balance of at least `value`.
               * - the called Solidity function must be `payable`.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
              }
              /**
               * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
               * with `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  require(address(this).balance >= value, "Address: insufficient balance for call");
                  require(isContract(target), "Address: call to non-contract");
                  (bool success, bytes memory returndata) = target.call{value: value}(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                  return functionStaticCall(target, data, "Address: low-level static call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal view returns (bytes memory) {
                  require(isContract(target), "Address: static call to non-contract");
                  (bool success, bytes memory returndata) = target.staticcall(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a delegate call.
               *
               * _Available since v3.4._
               */
              function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionDelegateCall(target, data, "Address: low-level delegate call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a delegate call.
               *
               * _Available since v3.4._
               */
              function functionDelegateCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  require(isContract(target), "Address: delegate call to non-contract");
                  (bool success, bytes memory returndata) = target.delegatecall(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
               * revert reason 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 {
                      // Look for revert reason and bubble it up if present
                      if (returndata.length > 0) {
                          // The easiest way to bubble the revert reason is using memory via assembly
                          /// @solidity memory-safe-assembly
                          assembly {
                              let returndata_size := mload(returndata)
                              revert(add(32, returndata), returndata_size)
                          }
                      } else {
                          revert(errorMessage);
                      }
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev String operations.
           */
          library Strings {
              bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
              uint8 private constant _ADDRESS_LENGTH = 20;
              /**
               * @dev Converts a `uint256` to its ASCII `string` decimal representation.
               */
              function toString(uint256 value) internal pure returns (string memory) {
                  // Inspired by OraclizeAPI's implementation - MIT licence
                  // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
                  if (value == 0) {
                      return "0";
                  }
                  uint256 temp = value;
                  uint256 digits;
                  while (temp != 0) {
                      digits++;
                      temp /= 10;
                  }
                  bytes memory buffer = new bytes(digits);
                  while (value != 0) {
                      digits -= 1;
                      buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
                      value /= 10;
                  }
                  return string(buffer);
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
               */
              function toHexString(uint256 value) internal pure returns (string memory) {
                  if (value == 0) {
                      return "0x00";
                  }
                  uint256 temp = value;
                  uint256 length = 0;
                  while (temp != 0) {
                      length++;
                      temp >>= 8;
                  }
                  return toHexString(value, length);
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
               */
              function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                  bytes memory buffer = new bytes(2 * length + 2);
                  buffer[0] = "0";
                  buffer[1] = "x";
                  for (uint256 i = 2 * length + 1; i > 1; --i) {
                      buffer[i] = _HEX_SYMBOLS[value & 0xf];
                      value >>= 4;
                  }
                  require(value == 0, "Strings: hex length insufficient");
                  return string(buffer);
              }
              /**
               * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
               */
              function toHexString(address addr) internal pure returns (string memory) {
                  return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
          pragma solidity ^0.8.0;
          import "./IERC165.sol";
          /**
           * @dev Implementation of the {IERC165} interface.
           *
           * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
           * for the additional interface id that will be supported. For example:
           *
           * ```solidity
           * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
           *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
           * }
           * ```
           *
           * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
           */
          abstract contract ERC165 is IERC165 {
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                  return interfaceId == type(IERC165).interfaceId;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControlEnumerable.sol)
          pragma solidity ^0.8.0;
          import "./IAccessControlEnumerable.sol";
          import "./AccessControl.sol";
          import "../utils/structs/EnumerableSet.sol";
          /**
           * @dev Extension of {AccessControl} that allows enumerating the members of each role.
           */
          abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {
              using EnumerableSet for EnumerableSet.AddressSet;
              mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers;
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                  return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);
              }
              /**
               * @dev Returns one of the accounts that have `role`. `index` must be a
               * value between 0 and {getRoleMemberCount}, non-inclusive.
               *
               * Role bearers are not sorted in any particular way, and their ordering may
               * change at any point.
               *
               * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
               * you perform all queries on the same block. See the following
               * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
               * for more information.
               */
              function getRoleMember(bytes32 role, uint256 index) public view virtual override returns (address) {
                  return _roleMembers[role].at(index);
              }
              /**
               * @dev Returns the number of accounts that have `role`. Can be used
               * together with {getRoleMember} to enumerate all bearers of a role.
               */
              function getRoleMemberCount(bytes32 role) public view virtual override returns (uint256) {
                  return _roleMembers[role].length();
              }
              /**
               * @dev Overload {_grantRole} to track enumerable memberships
               */
              function _grantRole(bytes32 role, address account) internal virtual override {
                  super._grantRole(role, account);
                  _roleMembers[role].add(account);
              }
              /**
               * @dev Overload {_revokeRole} to track enumerable memberships
               */
              function _revokeRole(bytes32 role, address account) internal virtual override {
                  super._revokeRole(role, account);
                  _roleMembers[role].remove(account);
              }
          }
          // SPDX-License-Identifier: MIT
          // ERC721A Contracts v4.2.3
          // Creator: Chiru Labs
          pragma solidity ^0.8.4;
          import './IERC721A.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 ERC721A is IERC721A {
              // 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
              // =============================================================
              constructor(string memory name_, string memory symbol_) {
                  _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 payable 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 payable virtual override {
                  uint256 prevOwnershipPacked = _packedOwnershipOf(tokenId);
                  if (address(uint160(prevOwnershipPacked)) != from) revert TransferFromIncorrectOwner();
                  (uint256 approvedAddressSlot, address approvedAddress) = _getApprovedSlotAndAddress(tokenId);
                  // The nested ifs save around 20+ gas over a compound boolean condition.
                  if (!_isSenderApprovedOrOwner(approvedAddress, from, _msgSenderERC721A()))
                      if (!isApprovedForAll(from, _msgSenderERC721A())) revert TransferCallerNotOwnerNorApproved();
                  if (to == address(0)) revert TransferToZeroAddress();
                  _beforeTokenTransfers(from, to, tokenId, 1);
                  // Clear approvals from the previous owner.
                  assembly {
                      if approvedAddress {
                          // This is equivalent to `delete _tokenApprovals[tokenId]`.
                          sstore(approvedAddressSlot, 0)
                      }
                  }
                  // Underflow of the sender's balance is impossible because we check for
                  // ownership above and the recipient's balance can't realistically overflow.
                  // Counter overflow is incredibly unrealistic as `tokenId` would have to be 2**256.
                  unchecked {
                      // We can directly increment and decrement the balances.
                      --_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 payable virtual override {
                  safeTransferFrom(from, to, tokenId, '');
              }
              /**
               * @dev Safely transfers `tokenId` token from `from` to `to`.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must exist and be owned by `from`.
               * - If the caller is not `from`, it must be approved to move this token
               * by either {approve} or {setApprovalForAll}.
               * - If `to` refers to a smart contract, it must implement
               * {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory _data
              ) public payable virtual override {
                  transferFrom(from, to, tokenId);
                  if (to.code.length != 0)
                      if (!_checkContractOnERC721Received(from, to, tokenId, _data)) {
                          revert TransferToNonERC721ReceiverImplementer();
                      }
              }
              /**
               * @dev Hook that is called before a set of serially-ordered token IDs
               * are about to be transferred. This includes minting.
               * And also called before burning one token.
               *
               * `startTokenId` - the first token ID to be transferred.
               * `quantity` - the amount to be transferred.
               *
               * Calling conditions:
               *
               * - When `from` and `to` are both non-zero, `from`'s `tokenId` will be
               * transferred to `to`.
               * - When `from` is zero, `tokenId` will be minted for `to`.
               * - When `to` is zero, `tokenId` will be burned by `from`.
               * - `from` and `to` are never both zero.
               */
              function _beforeTokenTransfers(
                  address from,
                  address to,
                  uint256 startTokenId,
                  uint256 quantity
              ) internal virtual {}
              /**
               * @dev Hook that is called after a set of serially-ordered token IDs
               * have been transferred. This includes minting.
               * And also called after one token has been burned.
               *
               * `startTokenId` - the first token ID to be transferred.
               * `quantity` - the amount to be transferred.
               *
               * Calling conditions:
               *
               * - When `from` and `to` are both non-zero, `from`'s `tokenId` has been
               * transferred to `to`.
               * - When `from` is zero, `tokenId` has been minted for `to`.
               * - When `to` is zero, `tokenId` has been burned by `from`.
               * - `from` and `to` are never both zero.
               */
              function _afterTokenTransfers(
                  address from,
                  address to,
                  uint256 startTokenId,
                  uint256 quantity
              ) internal virtual {}
              /**
               * @dev Private function to invoke {IERC721Receiver-onERC721Received} on a target contract.
               *
               * `from` - Previous owner of the given token ID.
               * `to` - Target address that will receive the token.
               * `tokenId` - Token ID to be transferred.
               * `_data` - Optional data to send along with the call.
               *
               * Returns whether the call correctly returned the expected magic value.
               */
              function _checkContractOnERC721Received(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory _data
              ) private returns (bool) {
                  try ERC721A__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 (last updated v4.7.0) (token/common/ERC2981.sol)
          pragma solidity ^0.8.0;
          import "../../interfaces/IERC2981.sol";
          import "../../utils/introspection/ERC165.sol";
          /**
           * @dev Implementation of the NFT Royalty Standard, a standardized way to retrieve royalty payment information.
           *
           * Royalty information can be specified globally for all token ids via {_setDefaultRoyalty}, and/or individually for
           * specific token ids via {_setTokenRoyalty}. The latter takes precedence over the first.
           *
           * Royalty is specified as a fraction of sale price. {_feeDenominator} is overridable but defaults to 10000, meaning the
           * fee is specified in basis points by default.
           *
           * IMPORTANT: ERC-2981 only specifies a way to signal royalty information and does not enforce its payment. See
           * https://eips.ethereum.org/EIPS/eip-2981#optional-royalty-payments[Rationale] in the EIP. Marketplaces are expected to
           * voluntarily pay royalties together with sales, but note that this standard is not yet widely supported.
           *
           * _Available since v4.5._
           */
          abstract contract ERC2981 is IERC2981, ERC165 {
              struct RoyaltyInfo {
                  address receiver;
                  uint96 royaltyFraction;
              }
              RoyaltyInfo private _defaultRoyaltyInfo;
              mapping(uint256 => RoyaltyInfo) private _tokenRoyaltyInfo;
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) {
                  return interfaceId == type(IERC2981).interfaceId || super.supportsInterface(interfaceId);
              }
              /**
               * @inheritdoc IERC2981
               */
              function royaltyInfo(uint256 _tokenId, uint256 _salePrice) public view virtual override returns (address, uint256) {
                  RoyaltyInfo memory royalty = _tokenRoyaltyInfo[_tokenId];
                  if (royalty.receiver == address(0)) {
                      royalty = _defaultRoyaltyInfo;
                  }
                  uint256 royaltyAmount = (_salePrice * royalty.royaltyFraction) / _feeDenominator();
                  return (royalty.receiver, royaltyAmount);
              }
              /**
               * @dev The denominator with which to interpret the fee set in {_setTokenRoyalty} and {_setDefaultRoyalty} as a
               * fraction of the sale price. Defaults to 10000 so fees are expressed in basis points, but may be customized by an
               * override.
               */
              function _feeDenominator() internal pure virtual returns (uint96) {
                  return 10000;
              }
              /**
               * @dev Sets the royalty information that all ids in this contract will default to.
               *
               * Requirements:
               *
               * - `receiver` cannot be the zero address.
               * - `feeNumerator` cannot be greater than the fee denominator.
               */
              function _setDefaultRoyalty(address receiver, uint96 feeNumerator) internal virtual {
                  require(feeNumerator <= _feeDenominator(), "ERC2981: royalty fee will exceed salePrice");
                  require(receiver != address(0), "ERC2981: invalid receiver");
                  _defaultRoyaltyInfo = RoyaltyInfo(receiver, feeNumerator);
              }
              /**
               * @dev Removes default royalty information.
               */
              function _deleteDefaultRoyalty() internal virtual {
                  delete _defaultRoyaltyInfo;
              }
              /**
               * @dev Sets the royalty information for a specific token id, overriding the global default.
               *
               * Requirements:
               *
               * - `receiver` cannot be the zero address.
               * - `feeNumerator` cannot be greater than the fee denominator.
               */
              function _setTokenRoyalty(
                  uint256 tokenId,
                  address receiver,
                  uint96 feeNumerator
              ) internal virtual {
                  require(feeNumerator <= _feeDenominator(), "ERC2981: royalty fee will exceed salePrice");
                  require(receiver != address(0), "ERC2981: Invalid parameters");
                  _tokenRoyaltyInfo[tokenId] = RoyaltyInfo(receiver, feeNumerator);
              }
              /**
               * @dev Resets royalty information for the token id back to the global default.
               */
              function _resetTokenRoyalty(uint256 tokenId) internal virtual {
                  delete _tokenRoyaltyInfo[tokenId];
              }
          }
          // SPDX-License-Identifier: MIT
          // Copyright (c) 2021 the ethier authors (github.com/divergencetech/ethier)
          pragma solidity >=0.8.0 <0.9.0;
          import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol";
          import {AccessControlEnumerable} from "./AccessControlEnumerable.sol";
          /// @notice A Pausable contract that can only be toggled by a member of the
          /// STEERING role.
          contract AccessControlPausable is AccessControlEnumerable, Pausable {
              /// @notice Pauses the contract.
              function pause() public onlyRole(DEFAULT_STEERING_ROLE) {
                  Pausable._pause();
              }
              /// @notice Unpauses the contract.
              function unpause() public onlyRole(DEFAULT_STEERING_ROLE) {
                  Pausable._unpause();
              }
          }
          // SPDX-License-Identifier: CC0-1.0
          pragma solidity ^0.8.0;
          import {IERC165, ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
          interface IERC4906Events {
              /// @dev This event emits when the metadata of a token is changed.
              /// So that the third-party platforms such as NFT market could
              /// timely update the images and related attributes of the NFT.
              event MetadataUpdate(uint256 _tokenId);
              /// @dev This event emits when the metadata of a range of tokens is changed.
              /// So that the third-party platforms such as NFT market could
              /// timely update the images and related attributes of the NFTs.
              event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId);
          }
          /// @title EIP-721 Metadata Update Extension
          // solhint-disable-next-line no-empty-blocks
          interface IERC4906 is IERC165, IERC4906Events {
          }
          contract ERC4906 is IERC4906, ERC165 {
              function _refreshMetadata(uint256 tokenId) internal {
                  emit MetadataUpdate(tokenId);
              }
              function _refreshMetadata(uint256 fromTokenId, uint256 toTokenId) internal {
                  emit BatchMetadataUpdate(fromTokenId, toTokenId);
              }
              /// @dev See {IERC165-supportsInterface}.
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(ERC165, IERC165)
                  returns (bool)
              {
                  return
                      interfaceId == bytes4(0x49064906) ||
                      ERC165.supportsInterface(interfaceId);
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.13;
          import {IOperatorFilterRegistry} from "./IOperatorFilterRegistry.sol";
          import {CANONICAL_OPERATOR_FILTER_REGISTRY_ADDRESS} from "./lib/Constants.sol";
          /**
           * @title  OperatorFilterer
           * @notice Abstract contract whose constructor automatically registers and optionally subscribes to or copies another
           *         registrant's entries in the OperatorFilterRegistry.
           * @dev    This smart contract is meant to be inherited by token contracts so they can use the following:
           *         - `onlyAllowedOperator` modifier for `transferFrom` and `safeTransferFrom` methods.
           *         - `onlyAllowedOperatorApproval` modifier for `approve` and `setApprovalForAll` methods.
           *         Please note that if your token contract does not provide an owner with EIP-173, it must provide
           *         administration methods on the contract itself to interact with the registry otherwise the subscription
           *         will be locked to the options set during construction.
           */
          abstract contract OperatorFilterer {
              /// @dev Emitted when an operator is not allowed.
              error OperatorNotAllowed(address operator);
              IOperatorFilterRegistry public constant OPERATOR_FILTER_REGISTRY =
                  IOperatorFilterRegistry(CANONICAL_OPERATOR_FILTER_REGISTRY_ADDRESS);
              /// @dev The constructor that is called when the contract is being deployed.
              constructor(address subscriptionOrRegistrantToCopy, bool subscribe) {
                  // If an inheriting token contract is deployed to a network without the registry deployed, the modifier
                  // will not revert, but the contract will need to be registered with the registry once it is deployed in
                  // order for the modifier to filter addresses.
                  if (address(OPERATOR_FILTER_REGISTRY).code.length > 0) {
                      if (subscribe) {
                          OPERATOR_FILTER_REGISTRY.registerAndSubscribe(address(this), subscriptionOrRegistrantToCopy);
                      } else {
                          if (subscriptionOrRegistrantToCopy != address(0)) {
                              OPERATOR_FILTER_REGISTRY.registerAndCopyEntries(address(this), subscriptionOrRegistrantToCopy);
                          } else {
                              OPERATOR_FILTER_REGISTRY.register(address(this));
                          }
                      }
                  }
              }
              /**
               * @dev A helper function to check if an operator is allowed.
               */
              modifier onlyAllowedOperator(address from) virtual {
                  // Allow spending tokens from addresses with balance
                  // Note that this still allows listings and marketplaces with escrow to transfer tokens if transferred
                  // from an EOA.
                  if (from != msg.sender) {
                      _checkFilterOperator(msg.sender);
                  }
                  _;
              }
              /**
               * @dev A helper function to check if an operator approval is allowed.
               */
              modifier onlyAllowedOperatorApproval(address operator) virtual {
                  _checkFilterOperator(operator);
                  _;
              }
              /**
               * @dev A helper function to check if an operator is allowed.
               */
              function _checkFilterOperator(address operator) internal view virtual {
                  // Check registry code length to facilitate testing in environments without a deployed registry.
                  if (address(OPERATOR_FILTER_REGISTRY).code.length > 0) {
                      // under normal circumstances, this function will revert rather than return false, but inheriting contracts
                      // may specify their own OperatorFilterRegistry implementations, which may behave differently
                      if (!OPERATOR_FILTER_REGISTRY.isOperatorAllowed(address(this), operator)) {
                          revert OperatorNotAllowed(operator);
                      }
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.13;
          address constant CANONICAL_OPERATOR_FILTER_REGISTRY_ADDRESS = 0x000000000000AAeB6D7670E522A718067333cd4E;
          address constant CANONICAL_CORI_SUBSCRIPTION = 0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6;
          // SPDX-License-Identifier: MIT
          // Copyright 2023 PROOF Holdings Inc
          pragma solidity >=0.8.0 <0.9.0;
          /**
           * @notice Basic interface for a contract providing sellable content.
           */
          interface ISellable {
              /**
               * @notice Handles the sale of sellable content.
               * @dev This is usually only callable by Sellers.
               */
              function handleSale(address to, uint64 num, bytes calldata data) external payable;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Interface of the ERC165 standard, as defined in the
           * https://eips.ethereum.org/EIPS/eip-165[EIP].
           *
           * Implementers can declare support of contract interfaces, which can then be
           * queried by others ({ERC165Checker}).
           *
           * For an implementation, see {ERC165}.
           */
          interface IERC165 {
              /**
               * @dev Returns true if this contract implements the interface defined by
               * `interfaceId`. See the corresponding
               * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
               * to learn more about how these ids are created.
               *
               * This function call must use less than 30 000 gas.
               */
              function supportsInterface(bytes4 interfaceId) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol)
          pragma solidity ^0.8.0;
          import "./IAccessControl.sol";
          /**
           * @dev External interface of AccessControlEnumerable declared to support ERC165 detection.
           */
          interface IAccessControlEnumerable is IAccessControl {
              /**
               * @dev Returns one of the accounts that have `role`. `index` must be a
               * value between 0 and {getRoleMemberCount}, non-inclusive.
               *
               * Role bearers are not sorted in any particular way, and their ordering may
               * change at any point.
               *
               * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
               * you perform all queries on the same block. See the following
               * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
               * for more information.
               */
              function getRoleMember(bytes32 role, uint256 index) external view returns (address);
              /**
               * @dev Returns the number of accounts that have `role`. Can be used
               * together with {getRoleMember} to enumerate all bearers of a role.
               */
              function getRoleMemberCount(bytes32 role) external view returns (uint256);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (access/AccessControl.sol)
          pragma solidity ^0.8.0;
          import "./IAccessControl.sol";
          import "../utils/Context.sol";
          import "../utils/Strings.sol";
          import "../utils/introspection/ERC165.sol";
          /**
           * @dev Contract module that allows children to implement role-based access
           * control mechanisms. This is a lightweight version that doesn't allow enumerating role
           * members except through off-chain means by accessing the contract event logs. Some
           * applications may benefit from on-chain enumerability, for those cases see
           * {AccessControlEnumerable}.
           *
           * Roles are referred to by their `bytes32` identifier. These should be exposed
           * in the external API and be unique. The best way to achieve this is by
           * using `public constant` hash digests:
           *
           * ```
           * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
           * ```
           *
           * Roles can be used to represent a set of permissions. To restrict access to a
           * function call, use {hasRole}:
           *
           * ```
           * function foo() public {
           *     require(hasRole(MY_ROLE, msg.sender));
           *     ...
           * }
           * ```
           *
           * Roles can be granted and revoked dynamically via the {grantRole} and
           * {revokeRole} functions. Each role has an associated admin role, and only
           * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
           *
           * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
           * that only accounts with this role will be able to grant or revoke other
           * roles. More complex role relationships can be created by using
           * {_setRoleAdmin}.
           *
           * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
           * grant and revoke this role. Extra precautions should be taken to secure
           * accounts that have been granted it.
           */
          abstract contract AccessControl is Context, IAccessControl, ERC165 {
              struct RoleData {
                  mapping(address => bool) members;
                  bytes32 adminRole;
              }
              mapping(bytes32 => RoleData) private _roles;
              bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
              /**
               * @dev Modifier that checks that an account has a specific role. Reverts
               * with a standardized message including the required role.
               *
               * The format of the revert reason is given by the following regular expression:
               *
               *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
               *
               * _Available since v4.1._
               */
              modifier onlyRole(bytes32 role) {
                  _checkRole(role);
                  _;
              }
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                  return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
              }
              /**
               * @dev Returns `true` if `account` has been granted `role`.
               */
              function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
                  return _roles[role].members[account];
              }
              /**
               * @dev Revert with a standard message if `_msgSender()` is missing `role`.
               * Overriding this function changes the behavior of the {onlyRole} modifier.
               *
               * Format of the revert message is described in {_checkRole}.
               *
               * _Available since v4.6._
               */
              function _checkRole(bytes32 role) internal view virtual {
                  _checkRole(role, _msgSender());
              }
              /**
               * @dev Revert with a standard message if `account` is missing `role`.
               *
               * The format of the revert reason is given by the following regular expression:
               *
               *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
               */
              function _checkRole(bytes32 role, address account) internal view virtual {
                  if (!hasRole(role, account)) {
                      revert(
                          string(
                              abi.encodePacked(
                                  "AccessControl: account ",
                                  Strings.toHexString(account),
                                  " is missing role ",
                                  Strings.toHexString(uint256(role), 32)
                              )
                          )
                      );
                  }
              }
              /**
               * @dev Returns the admin role that controls `role`. See {grantRole} and
               * {revokeRole}.
               *
               * To change a role's admin, use {_setRoleAdmin}.
               */
              function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
                  return _roles[role].adminRole;
              }
              /**
               * @dev Grants `role` to `account`.
               *
               * If `account` had not been already granted `role`, emits a {RoleGranted}
               * event.
               *
               * Requirements:
               *
               * - the caller must have ``role``'s admin role.
               *
               * May emit a {RoleGranted} event.
               */
              function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
                  _grantRole(role, account);
              }
              /**
               * @dev Revokes `role` from `account`.
               *
               * If `account` had been granted `role`, emits a {RoleRevoked} event.
               *
               * Requirements:
               *
               * - the caller must have ``role``'s admin role.
               *
               * May emit a {RoleRevoked} event.
               */
              function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
                  _revokeRole(role, account);
              }
              /**
               * @dev Revokes `role` from the calling account.
               *
               * Roles are often managed via {grantRole} and {revokeRole}: this function's
               * purpose is to provide a mechanism for accounts to lose their privileges
               * if they are compromised (such as when a trusted device is misplaced).
               *
               * If the calling account had been revoked `role`, emits a {RoleRevoked}
               * event.
               *
               * Requirements:
               *
               * - the caller must be `account`.
               *
               * May emit a {RoleRevoked} event.
               */
              function renounceRole(bytes32 role, address account) public virtual override {
                  require(account == _msgSender(), "AccessControl: can only renounce roles for self");
                  _revokeRole(role, account);
              }
              /**
               * @dev Grants `role` to `account`.
               *
               * If `account` had not been already granted `role`, emits a {RoleGranted}
               * event. Note that unlike {grantRole}, this function doesn't perform any
               * checks on the calling account.
               *
               * May emit a {RoleGranted} event.
               *
               * [WARNING]
               * ====
               * This function should only be called from the constructor when setting
               * up the initial roles for the system.
               *
               * Using this function in any other way is effectively circumventing the admin
               * system imposed by {AccessControl}.
               * ====
               *
               * NOTE: This function is deprecated in favor of {_grantRole}.
               */
              function _setupRole(bytes32 role, address account) internal virtual {
                  _grantRole(role, account);
              }
              /**
               * @dev Sets `adminRole` as ``role``'s admin role.
               *
               * Emits a {RoleAdminChanged} event.
               */
              function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
                  bytes32 previousAdminRole = getRoleAdmin(role);
                  _roles[role].adminRole = adminRole;
                  emit RoleAdminChanged(role, previousAdminRole, adminRole);
              }
              /**
               * @dev Grants `role` to `account`.
               *
               * Internal function without access restriction.
               *
               * May emit a {RoleGranted} event.
               */
              function _grantRole(bytes32 role, address account) internal virtual {
                  if (!hasRole(role, account)) {
                      _roles[role].members[account] = true;
                      emit RoleGranted(role, account, _msgSender());
                  }
              }
              /**
               * @dev Revokes `role` from `account`.
               *
               * Internal function without access restriction.
               *
               * May emit a {RoleRevoked} event.
               */
              function _revokeRole(bytes32 role, address account) internal virtual {
                  if (hasRole(role, account)) {
                      _roles[role].members[account] = false;
                      emit RoleRevoked(role, account, _msgSender());
                  }
              }
          }
          // 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
          // ERC721A Contracts v4.2.3
          // 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 payable;
              /**
               * @dev Equivalent to `safeTransferFrom(from, to, tokenId, '')`.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) external payable;
              /**
               * @dev Transfers `tokenId` from `from` to `to`.
               *
               * WARNING: Usage of this method is discouraged, use {safeTransferFrom}
               * whenever possible.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must be owned by `from`.
               * - If the caller is not `from`, it must be approved to move this token
               * by either {approve} or {setApprovalForAll}.
               *
               * Emits a {Transfer} event.
               */
              function transferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) external payable;
              /**
               * @dev Gives permission to `to` to transfer `tokenId` token to another account.
               * The approval is cleared when the token is transferred.
               *
               * Only a single account can be approved at a time, so approving the
               * zero address clears previous approvals.
               *
               * Requirements:
               *
               * - The caller must own the token or be an approved operator.
               * - `tokenId` must exist.
               *
               * Emits an {Approval} event.
               */
              function approve(address to, uint256 tokenId) external payable;
              /**
               * @dev Approve or remove `operator` as an operator for the caller.
               * Operators can call {transferFrom} or {safeTransferFrom}
               * for any token owned by the caller.
               *
               * Requirements:
               *
               * - The `operator` cannot be the caller.
               *
               * Emits an {ApprovalForAll} event.
               */
              function setApprovalForAll(address operator, bool _approved) external;
              /**
               * @dev Returns the account approved for `tokenId` token.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               */
              function getApproved(uint256 tokenId) external view returns (address operator);
              /**
               * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
               *
               * See {setApprovalForAll}.
               */
              function isApprovedForAll(address owner, address operator) external view returns (bool);
              // =============================================================
              //                        IERC721Metadata
              // =============================================================
              /**
               * @dev Returns the token collection name.
               */
              function name() external view returns (string memory);
              /**
               * @dev Returns the token collection symbol.
               */
              function symbol() external view returns (string memory);
              /**
               * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
               */
              function tokenURI(uint256 tokenId) external view returns (string memory);
              // =============================================================
              //                           IERC2309
              // =============================================================
              /**
               * @dev Emitted when tokens in `fromTokenId` to `toTokenId`
               * (inclusive) is transferred from `from` to `to`, as defined in the
               * [ERC2309](https://eips.ethereum.org/EIPS/eip-2309) standard.
               *
               * See {_mintERC2309} for more details.
               */
              event ConsecutiveTransfer(uint256 indexed fromTokenId, uint256 toTokenId, address indexed from, address indexed to);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.6.0) (interfaces/IERC2981.sol)
          pragma solidity ^0.8.0;
          import "../utils/introspection/IERC165.sol";
          /**
           * @dev Interface for the NFT Royalty Standard.
           *
           * A standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal
           * support for royalty payments across all NFT marketplaces and ecosystem participants.
           *
           * _Available since v4.5._
           */
          interface IERC2981 is IERC165 {
              /**
               * @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of
               * exchange. The royalty amount is denominated and should be paid in that same unit of exchange.
               */
              function royaltyInfo(uint256 tokenId, uint256 salePrice)
                  external
                  view
                  returns (address receiver, uint256 royaltyAmount);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
          pragma solidity ^0.8.0;
          import "./IERC165.sol";
          /**
           * @dev Implementation of the {IERC165} interface.
           *
           * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
           * for the additional interface id that will be supported. For example:
           *
           * ```solidity
           * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
           *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
           * }
           * ```
           *
           * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
           */
          abstract contract ERC165 is IERC165 {
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                  return interfaceId == type(IERC165).interfaceId;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
          pragma solidity ^0.8.0;
          import "../utils/Context.sol";
          /**
           * @dev Contract module which allows children to implement an emergency stop
           * mechanism that can be triggered by an authorized account.
           *
           * This module is used through inheritance. It will make available the
           * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
           * the functions of your contract. Note that they will not be pausable by
           * simply including this module, only once the modifiers are put in place.
           */
          abstract contract Pausable is Context {
              /**
               * @dev Emitted when the pause is triggered by `account`.
               */
              event Paused(address account);
              /**
               * @dev Emitted when the pause is lifted by `account`.
               */
              event Unpaused(address account);
              bool private _paused;
              /**
               * @dev Initializes the contract in unpaused state.
               */
              constructor() {
                  _paused = false;
              }
              /**
               * @dev Modifier to make a function callable only when the contract is not paused.
               *
               * Requirements:
               *
               * - The contract must not be paused.
               */
              modifier whenNotPaused() {
                  _requireNotPaused();
                  _;
              }
              /**
               * @dev Modifier to make a function callable only when the contract is paused.
               *
               * Requirements:
               *
               * - The contract must be paused.
               */
              modifier whenPaused() {
                  _requirePaused();
                  _;
              }
              /**
               * @dev Returns true if the contract is paused, and false otherwise.
               */
              function paused() public view virtual returns (bool) {
                  return _paused;
              }
              /**
               * @dev Throws if the contract is paused.
               */
              function _requireNotPaused() internal view virtual {
                  require(!paused(), "Pausable: paused");
              }
              /**
               * @dev Throws if the contract is not paused.
               */
              function _requirePaused() internal view virtual {
                  require(paused(), "Pausable: not paused");
              }
              /**
               * @dev Triggers stopped state.
               *
               * Requirements:
               *
               * - The contract must not be paused.
               */
              function _pause() internal virtual whenNotPaused {
                  _paused = true;
                  emit Paused(_msgSender());
              }
              /**
               * @dev Returns to normal state.
               *
               * Requirements:
               *
               * - The contract must be paused.
               */
              function _unpause() internal virtual whenPaused {
                  _paused = false;
                  emit Unpaused(_msgSender());
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.13;
          interface IOperatorFilterRegistry {
              /**
               * @notice Returns true if operator is not filtered for a given token, either by address or codeHash. Also returns
               *         true if supplied registrant address is not registered.
               */
              function isOperatorAllowed(address registrant, address operator) external view returns (bool);
              /**
               * @notice Registers an address with the registry. May be called by address itself or by EIP-173 owner.
               */
              function register(address registrant) external;
              /**
               * @notice Registers an address with the registry and "subscribes" to another address's filtered operators and codeHashes.
               */
              function registerAndSubscribe(address registrant, address subscription) external;
              /**
               * @notice Registers an address with the registry and copies the filtered operators and codeHashes from another
               *         address without subscribing.
               */
              function registerAndCopyEntries(address registrant, address registrantToCopy) external;
              /**
               * @notice Unregisters an address with the registry and removes its subscription. May be called by address itself or by EIP-173 owner.
               *         Note that this does not remove any filtered addresses or codeHashes.
               *         Also note that any subscriptions to this registrant will still be active and follow the existing filtered addresses and codehashes.
               */
              function unregister(address addr) external;
              /**
               * @notice Update an operator address for a registered address - when filtered is true, the operator is filtered.
               */
              function updateOperator(address registrant, address operator, bool filtered) external;
              /**
               * @notice Update multiple operators for a registered address - when filtered is true, the operators will be filtered. Reverts on duplicates.
               */
              function updateOperators(address registrant, address[] calldata operators, bool filtered) external;
              /**
               * @notice Update a codeHash for a registered address - when filtered is true, the codeHash is filtered.
               */
              function updateCodeHash(address registrant, bytes32 codehash, bool filtered) external;
              /**
               * @notice Update multiple codeHashes for a registered address - when filtered is true, the codeHashes will be filtered. Reverts on duplicates.
               */
              function updateCodeHashes(address registrant, bytes32[] calldata codeHashes, bool filtered) external;
              /**
               * @notice Subscribe an address to another registrant's filtered operators and codeHashes. Will remove previous
               *         subscription if present.
               *         Note that accounts with subscriptions may go on to subscribe to other accounts - in this case,
               *         subscriptions will not be forwarded. Instead the former subscription's existing entries will still be
               *         used.
               */
              function subscribe(address registrant, address registrantToSubscribe) external;
              /**
               * @notice Unsubscribe an address from its current subscribed registrant, and optionally copy its filtered operators and codeHashes.
               */
              function unsubscribe(address registrant, bool copyExistingEntries) external;
              /**
               * @notice Get the subscription address of a given registrant, if any.
               */
              function subscriptionOf(address addr) external returns (address registrant);
              /**
               * @notice Get the set of addresses subscribed to a given registrant.
               *         Note that order is not guaranteed as updates are made.
               */
              function subscribers(address registrant) external returns (address[] memory);
              /**
               * @notice Get the subscriber at a given index in the set of addresses subscribed to a given registrant.
               *         Note that order is not guaranteed as updates are made.
               */
              function subscriberAt(address registrant, uint256 index) external returns (address);
              /**
               * @notice Copy filtered operators and codeHashes from a different registrantToCopy to addr.
               */
              function copyEntriesOf(address registrant, address registrantToCopy) external;
              /**
               * @notice Returns true if operator is filtered by a given address or its subscription.
               */
              function isOperatorFiltered(address registrant, address operator) external returns (bool);
              /**
               * @notice Returns true if the hash of an address's code is filtered by a given address or its subscription.
               */
              function isCodeHashOfFiltered(address registrant, address operatorWithCode) external returns (bool);
              /**
               * @notice Returns true if a codeHash is filtered by a given address or its subscription.
               */
              function isCodeHashFiltered(address registrant, bytes32 codeHash) external returns (bool);
              /**
               * @notice Returns a list of filtered operators for a given address or its subscription.
               */
              function filteredOperators(address addr) external returns (address[] memory);
              /**
               * @notice Returns the set of filtered codeHashes for a given address or its subscription.
               *         Note that order is not guaranteed as updates are made.
               */
              function filteredCodeHashes(address addr) external returns (bytes32[] memory);
              /**
               * @notice Returns the filtered operator at the given index of the set of filtered operators for a given address or
               *         its subscription.
               *         Note that order is not guaranteed as updates are made.
               */
              function filteredOperatorAt(address registrant, uint256 index) external returns (address);
              /**
               * @notice Returns the filtered codeHash at the given index of the list of filtered codeHashes for a given address or
               *         its subscription.
               *         Note that order is not guaranteed as updates are made.
               */
              function filteredCodeHashAt(address registrant, uint256 index) external returns (bytes32);
              /**
               * @notice Returns true if an address has registered
               */
              function isRegistered(address addr) external returns (bool);
              /**
               * @dev Convenience method to compute the code hash of an arbitrary contract
               */
              function codeHashOf(address addr) external returns (bytes32);
          }
          // SPDX-License-Identifier: MIT
          // 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 v4.4.1 (utils/Context.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Provides information about the current execution context, including the
           * sender of the transaction and its data. While these are generally available
           * via msg.sender and msg.data, they should not be accessed in such a direct
           * manner, since when dealing with meta-transactions the account sending and
           * paying for execution may not be the actual sender (as far as an application
           * is concerned).
           *
           * This contract is only required for intermediate, library-like contracts.
           */
          abstract contract Context {
              function _msgSender() internal view virtual returns (address) {
                  return msg.sender;
              }
              function _msgData() internal view virtual returns (bytes calldata) {
                  return msg.data;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/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);
          }