ETH Price: $2,518.61 (+0.30%)
Gas: 0.41 Gwei

Transaction Decoder

Block:
17417486 at Jun-05-2023 11:13:23 PM +UTC
Transaction Fee:
0.003615503610348737 ETH $9.11
Gas Used:
167,183 Gas / 21.626024239 Gwei

Emitted Events:

208 MinterDAExpSettlementV1.ReceiptUpdated( _purchaser=[Sender] 0xc444abef2558712680e3d4abaf201e34f716289f, _projectId=449, _numPurchased=1, _netPosted=80000000000000000 )
209 GenArt721CoreV3.Transfer( from=0x00000000...000000000, to=[Sender] 0xc444abef2558712680e3d4abaf201e34f716289f, tokenId=449000247 )
210 GenArt721CoreV3.Mint( _to=[Sender] 0xc444abef2558712680e3d4abaf201e34f716289f, _tokenId=449000247 )

Account State Difference:

  Address   Before After State Difference Code
0.594025992174889593 Eth0.594042710474889593 Eth0.0000167183
0x8a146BC9...4a270fa8A 12.11530508781364461 Eth12.18730508781364461 Eth0.072
0x99a9B7c1...cE509B069
0xc444AbEF...4f716289f
0.244246027335934543 Eth
Nonce: 372
0.160630523725585806 Eth
Nonce: 373
0.083615503610348737
0xf7323Ec4...663e91Cb4
0xf7A55108...E9D930153 38.350976797306933084 Eth38.358976797306933084 Eth0.008
0xfdE58c82...114EDe7E7

Execution Trace

ETH 0.08 MinterDAExpSettlementV1.purchase( _projectId=449 ) => ( tokenId=449000247 )
  • MinterFilterV1.mint( _to=0xc444AbEF2558712680E3d4abaF201e34f716289f, _projectId=449, sender=0xc444AbEF2558712680E3d4abaF201e34f716289f ) => ( _tokenId=449000247 )
    • GenArt721CoreV3.mint_Ecf( _to=0xc444AbEF2558712680E3d4abaF201e34f716289f, _projectId=449, _by=0xc444AbEF2558712680E3d4abaF201e34f716289f ) => ( _tokenId=449000247 )
      • 0xf7323ec4a3ca3cf4c36843902a2f746663e91cb4.b628171a( )
        • GenArt721CoreV3.setTokenHash_8PT( _tokenId=449000247, _hashSeed=05B4DACC21604456C9617BF8EC9CEA503B3B028414ABEC2905834514BB901B74 )
        • GenArt721CoreV3.projectStateData( _projectId=449 ) => ( invocations=248, maxInvocations=290, active=True, paused=False, completedTimestamp=0, locked=False )
        • GenArt721CoreV3.getPrimaryRevenueSplits( _projectId=449, _price=80000000000000000 ) => ( artblocksRevenue_=8000000000000000, artblocksAddress_=0xf7A55108A6E830a809e88e74cbf5f5DE9D930153, artistRevenue_=72000000000000000, artistAddress_=0x8a146BC9308b58D5ad1aC5a03e8F5574a270fa8A, additionalPayeePrimaryRevenue_=0, additionalPayeePrimaryAddress_=0x0000000000000000000000000000000000000000 )
        • ETH 0.008 0xf7a55108a6e830a809e88e74cbf5f5de9d930153.CALL( )
        • ETH 0.072 0x8a146bc9308b58d5ad1ac5a03e8f5574a270fa8a.CALL( )
          File 1 of 3: MinterDAExpSettlementV1
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (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() {
                  // On the first call to nonReentrant, _notEntered will be true
                  require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
                  // Any calls to nonReentrant after this point will fail
                  _status = _ENTERED;
                  _;
                  // By storing the original value once again, a refund is triggered (see
                  // https://eips.ethereum.org/EIPS/eip-2200)
                  _status = _NOT_ENTERED;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Interface of the ERC20 standard as defined in the EIP.
           */
          interface IERC20 {
              /**
               * @dev Emitted when `value` tokens are moved from one account (`from`) to
               * another (`to`).
               *
               * Note that `value` may be zero.
               */
              event Transfer(address indexed from, address indexed to, uint256 value);
              /**
               * @dev Emitted when the allowance of a `spender` for an `owner` is set by
               * a call to {approve}. `value` is the new allowance.
               */
              event Approval(address indexed owner, address indexed spender, uint256 value);
              /**
               * @dev Returns the amount of tokens in existence.
               */
              function totalSupply() external view returns (uint256);
              /**
               * @dev Returns the amount of tokens owned by `account`.
               */
              function balanceOf(address account) external view returns (uint256);
              /**
               * @dev Moves `amount` tokens from the caller's account to `to`.
               *
               * Returns a boolean value indicating whether the operation succeeded.
               *
               * Emits a {Transfer} event.
               */
              function transfer(address to, uint256 amount) external returns (bool);
              /**
               * @dev Returns the remaining number of tokens that `spender` will be
               * allowed to spend on behalf of `owner` through {transferFrom}. This is
               * zero by default.
               *
               * This value changes when {approve} or {transferFrom} are called.
               */
              function allowance(address owner, address spender) external view returns (uint256);
              /**
               * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
               *
               * Returns a boolean value indicating whether the operation succeeded.
               *
               * IMPORTANT: Beware that changing an allowance with this method brings the risk
               * that someone may use both the old and the new allowance by unfortunate
               * transaction ordering. One possible solution to mitigate this race
               * condition is to first reduce the spender's allowance to 0 and set the
               * desired value afterwards:
               * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
               *
               * Emits an {Approval} event.
               */
              function approve(address spender, uint256 amount) external returns (bool);
              /**
               * @dev Moves `amount` tokens from `from` to `to` using the
               * allowance mechanism. `amount` is then deducted from the caller's
               * allowance.
               *
               * Returns a boolean value indicating whether the operation succeeded.
               *
               * Emits a {Transfer} event.
               */
              function transferFrom(
                  address from,
                  address to,
                  uint256 amount
              ) external returns (bool);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (utils/math/SafeCast.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
           * checks.
           *
           * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
           * easily result in undesired exploitation or bugs, since developers usually
           * assume that overflows raise errors. `SafeCast` restores this intuition by
           * reverting the transaction when such an operation overflows.
           *
           * Using this library instead of the unchecked operations eliminates an entire
           * class of bugs, so it's recommended to use it always.
           *
           * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
           * all math on `uint256` and `int256` and then downcasting.
           */
          library SafeCast {
              /**
               * @dev Returns the downcasted uint248 from uint256, reverting on
               * overflow (when the input is greater than largest uint248).
               *
               * Counterpart to Solidity's `uint248` operator.
               *
               * Requirements:
               *
               * - input must fit into 248 bits
               *
               * _Available since v4.7._
               */
              function toUint248(uint256 value) internal pure returns (uint248) {
                  require(value <= type(uint248).max, "SafeCast: value doesn't fit in 248 bits");
                  return uint248(value);
              }
              /**
               * @dev Returns the downcasted uint240 from uint256, reverting on
               * overflow (when the input is greater than largest uint240).
               *
               * Counterpart to Solidity's `uint240` operator.
               *
               * Requirements:
               *
               * - input must fit into 240 bits
               *
               * _Available since v4.7._
               */
              function toUint240(uint256 value) internal pure returns (uint240) {
                  require(value <= type(uint240).max, "SafeCast: value doesn't fit in 240 bits");
                  return uint240(value);
              }
              /**
               * @dev Returns the downcasted uint232 from uint256, reverting on
               * overflow (when the input is greater than largest uint232).
               *
               * Counterpart to Solidity's `uint232` operator.
               *
               * Requirements:
               *
               * - input must fit into 232 bits
               *
               * _Available since v4.7._
               */
              function toUint232(uint256 value) internal pure returns (uint232) {
                  require(value <= type(uint232).max, "SafeCast: value doesn't fit in 232 bits");
                  return uint232(value);
              }
              /**
               * @dev Returns the downcasted uint224 from uint256, reverting on
               * overflow (when the input is greater than largest uint224).
               *
               * Counterpart to Solidity's `uint224` operator.
               *
               * Requirements:
               *
               * - input must fit into 224 bits
               *
               * _Available since v4.2._
               */
              function toUint224(uint256 value) internal pure returns (uint224) {
                  require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits");
                  return uint224(value);
              }
              /**
               * @dev Returns the downcasted uint216 from uint256, reverting on
               * overflow (when the input is greater than largest uint216).
               *
               * Counterpart to Solidity's `uint216` operator.
               *
               * Requirements:
               *
               * - input must fit into 216 bits
               *
               * _Available since v4.7._
               */
              function toUint216(uint256 value) internal pure returns (uint216) {
                  require(value <= type(uint216).max, "SafeCast: value doesn't fit in 216 bits");
                  return uint216(value);
              }
              /**
               * @dev Returns the downcasted uint208 from uint256, reverting on
               * overflow (when the input is greater than largest uint208).
               *
               * Counterpart to Solidity's `uint208` operator.
               *
               * Requirements:
               *
               * - input must fit into 208 bits
               *
               * _Available since v4.7._
               */
              function toUint208(uint256 value) internal pure returns (uint208) {
                  require(value <= type(uint208).max, "SafeCast: value doesn't fit in 208 bits");
                  return uint208(value);
              }
              /**
               * @dev Returns the downcasted uint200 from uint256, reverting on
               * overflow (when the input is greater than largest uint200).
               *
               * Counterpart to Solidity's `uint200` operator.
               *
               * Requirements:
               *
               * - input must fit into 200 bits
               *
               * _Available since v4.7._
               */
              function toUint200(uint256 value) internal pure returns (uint200) {
                  require(value <= type(uint200).max, "SafeCast: value doesn't fit in 200 bits");
                  return uint200(value);
              }
              /**
               * @dev Returns the downcasted uint192 from uint256, reverting on
               * overflow (when the input is greater than largest uint192).
               *
               * Counterpart to Solidity's `uint192` operator.
               *
               * Requirements:
               *
               * - input must fit into 192 bits
               *
               * _Available since v4.7._
               */
              function toUint192(uint256 value) internal pure returns (uint192) {
                  require(value <= type(uint192).max, "SafeCast: value doesn't fit in 192 bits");
                  return uint192(value);
              }
              /**
               * @dev Returns the downcasted uint184 from uint256, reverting on
               * overflow (when the input is greater than largest uint184).
               *
               * Counterpart to Solidity's `uint184` operator.
               *
               * Requirements:
               *
               * - input must fit into 184 bits
               *
               * _Available since v4.7._
               */
              function toUint184(uint256 value) internal pure returns (uint184) {
                  require(value <= type(uint184).max, "SafeCast: value doesn't fit in 184 bits");
                  return uint184(value);
              }
              /**
               * @dev Returns the downcasted uint176 from uint256, reverting on
               * overflow (when the input is greater than largest uint176).
               *
               * Counterpart to Solidity's `uint176` operator.
               *
               * Requirements:
               *
               * - input must fit into 176 bits
               *
               * _Available since v4.7._
               */
              function toUint176(uint256 value) internal pure returns (uint176) {
                  require(value <= type(uint176).max, "SafeCast: value doesn't fit in 176 bits");
                  return uint176(value);
              }
              /**
               * @dev Returns the downcasted uint168 from uint256, reverting on
               * overflow (when the input is greater than largest uint168).
               *
               * Counterpart to Solidity's `uint168` operator.
               *
               * Requirements:
               *
               * - input must fit into 168 bits
               *
               * _Available since v4.7._
               */
              function toUint168(uint256 value) internal pure returns (uint168) {
                  require(value <= type(uint168).max, "SafeCast: value doesn't fit in 168 bits");
                  return uint168(value);
              }
              /**
               * @dev Returns the downcasted uint160 from uint256, reverting on
               * overflow (when the input is greater than largest uint160).
               *
               * Counterpart to Solidity's `uint160` operator.
               *
               * Requirements:
               *
               * - input must fit into 160 bits
               *
               * _Available since v4.7._
               */
              function toUint160(uint256 value) internal pure returns (uint160) {
                  require(value <= type(uint160).max, "SafeCast: value doesn't fit in 160 bits");
                  return uint160(value);
              }
              /**
               * @dev Returns the downcasted uint152 from uint256, reverting on
               * overflow (when the input is greater than largest uint152).
               *
               * Counterpart to Solidity's `uint152` operator.
               *
               * Requirements:
               *
               * - input must fit into 152 bits
               *
               * _Available since v4.7._
               */
              function toUint152(uint256 value) internal pure returns (uint152) {
                  require(value <= type(uint152).max, "SafeCast: value doesn't fit in 152 bits");
                  return uint152(value);
              }
              /**
               * @dev Returns the downcasted uint144 from uint256, reverting on
               * overflow (when the input is greater than largest uint144).
               *
               * Counterpart to Solidity's `uint144` operator.
               *
               * Requirements:
               *
               * - input must fit into 144 bits
               *
               * _Available since v4.7._
               */
              function toUint144(uint256 value) internal pure returns (uint144) {
                  require(value <= type(uint144).max, "SafeCast: value doesn't fit in 144 bits");
                  return uint144(value);
              }
              /**
               * @dev Returns the downcasted uint136 from uint256, reverting on
               * overflow (when the input is greater than largest uint136).
               *
               * Counterpart to Solidity's `uint136` operator.
               *
               * Requirements:
               *
               * - input must fit into 136 bits
               *
               * _Available since v4.7._
               */
              function toUint136(uint256 value) internal pure returns (uint136) {
                  require(value <= type(uint136).max, "SafeCast: value doesn't fit in 136 bits");
                  return uint136(value);
              }
              /**
               * @dev Returns the downcasted uint128 from uint256, reverting on
               * overflow (when the input is greater than largest uint128).
               *
               * Counterpart to Solidity's `uint128` operator.
               *
               * Requirements:
               *
               * - input must fit into 128 bits
               *
               * _Available since v2.5._
               */
              function toUint128(uint256 value) internal pure returns (uint128) {
                  require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits");
                  return uint128(value);
              }
              /**
               * @dev Returns the downcasted uint120 from uint256, reverting on
               * overflow (when the input is greater than largest uint120).
               *
               * Counterpart to Solidity's `uint120` operator.
               *
               * Requirements:
               *
               * - input must fit into 120 bits
               *
               * _Available since v4.7._
               */
              function toUint120(uint256 value) internal pure returns (uint120) {
                  require(value <= type(uint120).max, "SafeCast: value doesn't fit in 120 bits");
                  return uint120(value);
              }
              /**
               * @dev Returns the downcasted uint112 from uint256, reverting on
               * overflow (when the input is greater than largest uint112).
               *
               * Counterpart to Solidity's `uint112` operator.
               *
               * Requirements:
               *
               * - input must fit into 112 bits
               *
               * _Available since v4.7._
               */
              function toUint112(uint256 value) internal pure returns (uint112) {
                  require(value <= type(uint112).max, "SafeCast: value doesn't fit in 112 bits");
                  return uint112(value);
              }
              /**
               * @dev Returns the downcasted uint104 from uint256, reverting on
               * overflow (when the input is greater than largest uint104).
               *
               * Counterpart to Solidity's `uint104` operator.
               *
               * Requirements:
               *
               * - input must fit into 104 bits
               *
               * _Available since v4.7._
               */
              function toUint104(uint256 value) internal pure returns (uint104) {
                  require(value <= type(uint104).max, "SafeCast: value doesn't fit in 104 bits");
                  return uint104(value);
              }
              /**
               * @dev Returns the downcasted uint96 from uint256, reverting on
               * overflow (when the input is greater than largest uint96).
               *
               * Counterpart to Solidity's `uint96` operator.
               *
               * Requirements:
               *
               * - input must fit into 96 bits
               *
               * _Available since v4.2._
               */
              function toUint96(uint256 value) internal pure returns (uint96) {
                  require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits");
                  return uint96(value);
              }
              /**
               * @dev Returns the downcasted uint88 from uint256, reverting on
               * overflow (when the input is greater than largest uint88).
               *
               * Counterpart to Solidity's `uint88` operator.
               *
               * Requirements:
               *
               * - input must fit into 88 bits
               *
               * _Available since v4.7._
               */
              function toUint88(uint256 value) internal pure returns (uint88) {
                  require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits");
                  return uint88(value);
              }
              /**
               * @dev Returns the downcasted uint80 from uint256, reverting on
               * overflow (when the input is greater than largest uint80).
               *
               * Counterpart to Solidity's `uint80` operator.
               *
               * Requirements:
               *
               * - input must fit into 80 bits
               *
               * _Available since v4.7._
               */
              function toUint80(uint256 value) internal pure returns (uint80) {
                  require(value <= type(uint80).max, "SafeCast: value doesn't fit in 80 bits");
                  return uint80(value);
              }
              /**
               * @dev Returns the downcasted uint72 from uint256, reverting on
               * overflow (when the input is greater than largest uint72).
               *
               * Counterpart to Solidity's `uint72` operator.
               *
               * Requirements:
               *
               * - input must fit into 72 bits
               *
               * _Available since v4.7._
               */
              function toUint72(uint256 value) internal pure returns (uint72) {
                  require(value <= type(uint72).max, "SafeCast: value doesn't fit in 72 bits");
                  return uint72(value);
              }
              /**
               * @dev Returns the downcasted uint64 from uint256, reverting on
               * overflow (when the input is greater than largest uint64).
               *
               * Counterpart to Solidity's `uint64` operator.
               *
               * Requirements:
               *
               * - input must fit into 64 bits
               *
               * _Available since v2.5._
               */
              function toUint64(uint256 value) internal pure returns (uint64) {
                  require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits");
                  return uint64(value);
              }
              /**
               * @dev Returns the downcasted uint56 from uint256, reverting on
               * overflow (when the input is greater than largest uint56).
               *
               * Counterpart to Solidity's `uint56` operator.
               *
               * Requirements:
               *
               * - input must fit into 56 bits
               *
               * _Available since v4.7._
               */
              function toUint56(uint256 value) internal pure returns (uint56) {
                  require(value <= type(uint56).max, "SafeCast: value doesn't fit in 56 bits");
                  return uint56(value);
              }
              /**
               * @dev Returns the downcasted uint48 from uint256, reverting on
               * overflow (when the input is greater than largest uint48).
               *
               * Counterpart to Solidity's `uint48` operator.
               *
               * Requirements:
               *
               * - input must fit into 48 bits
               *
               * _Available since v4.7._
               */
              function toUint48(uint256 value) internal pure returns (uint48) {
                  require(value <= type(uint48).max, "SafeCast: value doesn't fit in 48 bits");
                  return uint48(value);
              }
              /**
               * @dev Returns the downcasted uint40 from uint256, reverting on
               * overflow (when the input is greater than largest uint40).
               *
               * Counterpart to Solidity's `uint40` operator.
               *
               * Requirements:
               *
               * - input must fit into 40 bits
               *
               * _Available since v4.7._
               */
              function toUint40(uint256 value) internal pure returns (uint40) {
                  require(value <= type(uint40).max, "SafeCast: value doesn't fit in 40 bits");
                  return uint40(value);
              }
              /**
               * @dev Returns the downcasted uint32 from uint256, reverting on
               * overflow (when the input is greater than largest uint32).
               *
               * Counterpart to Solidity's `uint32` operator.
               *
               * Requirements:
               *
               * - input must fit into 32 bits
               *
               * _Available since v2.5._
               */
              function toUint32(uint256 value) internal pure returns (uint32) {
                  require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits");
                  return uint32(value);
              }
              /**
               * @dev Returns the downcasted uint24 from uint256, reverting on
               * overflow (when the input is greater than largest uint24).
               *
               * Counterpart to Solidity's `uint24` operator.
               *
               * Requirements:
               *
               * - input must fit into 24 bits
               *
               * _Available since v4.7._
               */
              function toUint24(uint256 value) internal pure returns (uint24) {
                  require(value <= type(uint24).max, "SafeCast: value doesn't fit in 24 bits");
                  return uint24(value);
              }
              /**
               * @dev Returns the downcasted uint16 from uint256, reverting on
               * overflow (when the input is greater than largest uint16).
               *
               * Counterpart to Solidity's `uint16` operator.
               *
               * Requirements:
               *
               * - input must fit into 16 bits
               *
               * _Available since v2.5._
               */
              function toUint16(uint256 value) internal pure returns (uint16) {
                  require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits");
                  return uint16(value);
              }
              /**
               * @dev Returns the downcasted uint8 from uint256, reverting on
               * overflow (when the input is greater than largest uint8).
               *
               * Counterpart to Solidity's `uint8` operator.
               *
               * Requirements:
               *
               * - input must fit into 8 bits
               *
               * _Available since v2.5._
               */
              function toUint8(uint256 value) internal pure returns (uint8) {
                  require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits");
                  return uint8(value);
              }
              /**
               * @dev Converts a signed int256 into an unsigned uint256.
               *
               * Requirements:
               *
               * - input must be greater than or equal to 0.
               *
               * _Available since v3.0._
               */
              function toUint256(int256 value) internal pure returns (uint256) {
                  require(value >= 0, "SafeCast: value must be positive");
                  return uint256(value);
              }
              /**
               * @dev Returns the downcasted int248 from int256, reverting on
               * overflow (when the input is less than smallest int248 or
               * greater than largest int248).
               *
               * Counterpart to Solidity's `int248` operator.
               *
               * Requirements:
               *
               * - input must fit into 248 bits
               *
               * _Available since v4.7._
               */
              function toInt248(int256 value) internal pure returns (int248) {
                  require(value >= type(int248).min && value <= type(int248).max, "SafeCast: value doesn't fit in 248 bits");
                  return int248(value);
              }
              /**
               * @dev Returns the downcasted int240 from int256, reverting on
               * overflow (when the input is less than smallest int240 or
               * greater than largest int240).
               *
               * Counterpart to Solidity's `int240` operator.
               *
               * Requirements:
               *
               * - input must fit into 240 bits
               *
               * _Available since v4.7._
               */
              function toInt240(int256 value) internal pure returns (int240) {
                  require(value >= type(int240).min && value <= type(int240).max, "SafeCast: value doesn't fit in 240 bits");
                  return int240(value);
              }
              /**
               * @dev Returns the downcasted int232 from int256, reverting on
               * overflow (when the input is less than smallest int232 or
               * greater than largest int232).
               *
               * Counterpart to Solidity's `int232` operator.
               *
               * Requirements:
               *
               * - input must fit into 232 bits
               *
               * _Available since v4.7._
               */
              function toInt232(int256 value) internal pure returns (int232) {
                  require(value >= type(int232).min && value <= type(int232).max, "SafeCast: value doesn't fit in 232 bits");
                  return int232(value);
              }
              /**
               * @dev Returns the downcasted int224 from int256, reverting on
               * overflow (when the input is less than smallest int224 or
               * greater than largest int224).
               *
               * Counterpart to Solidity's `int224` operator.
               *
               * Requirements:
               *
               * - input must fit into 224 bits
               *
               * _Available since v4.7._
               */
              function toInt224(int256 value) internal pure returns (int224) {
                  require(value >= type(int224).min && value <= type(int224).max, "SafeCast: value doesn't fit in 224 bits");
                  return int224(value);
              }
              /**
               * @dev Returns the downcasted int216 from int256, reverting on
               * overflow (when the input is less than smallest int216 or
               * greater than largest int216).
               *
               * Counterpart to Solidity's `int216` operator.
               *
               * Requirements:
               *
               * - input must fit into 216 bits
               *
               * _Available since v4.7._
               */
              function toInt216(int256 value) internal pure returns (int216) {
                  require(value >= type(int216).min && value <= type(int216).max, "SafeCast: value doesn't fit in 216 bits");
                  return int216(value);
              }
              /**
               * @dev Returns the downcasted int208 from int256, reverting on
               * overflow (when the input is less than smallest int208 or
               * greater than largest int208).
               *
               * Counterpart to Solidity's `int208` operator.
               *
               * Requirements:
               *
               * - input must fit into 208 bits
               *
               * _Available since v4.7._
               */
              function toInt208(int256 value) internal pure returns (int208) {
                  require(value >= type(int208).min && value <= type(int208).max, "SafeCast: value doesn't fit in 208 bits");
                  return int208(value);
              }
              /**
               * @dev Returns the downcasted int200 from int256, reverting on
               * overflow (when the input is less than smallest int200 or
               * greater than largest int200).
               *
               * Counterpart to Solidity's `int200` operator.
               *
               * Requirements:
               *
               * - input must fit into 200 bits
               *
               * _Available since v4.7._
               */
              function toInt200(int256 value) internal pure returns (int200) {
                  require(value >= type(int200).min && value <= type(int200).max, "SafeCast: value doesn't fit in 200 bits");
                  return int200(value);
              }
              /**
               * @dev Returns the downcasted int192 from int256, reverting on
               * overflow (when the input is less than smallest int192 or
               * greater than largest int192).
               *
               * Counterpart to Solidity's `int192` operator.
               *
               * Requirements:
               *
               * - input must fit into 192 bits
               *
               * _Available since v4.7._
               */
              function toInt192(int256 value) internal pure returns (int192) {
                  require(value >= type(int192).min && value <= type(int192).max, "SafeCast: value doesn't fit in 192 bits");
                  return int192(value);
              }
              /**
               * @dev Returns the downcasted int184 from int256, reverting on
               * overflow (when the input is less than smallest int184 or
               * greater than largest int184).
               *
               * Counterpart to Solidity's `int184` operator.
               *
               * Requirements:
               *
               * - input must fit into 184 bits
               *
               * _Available since v4.7._
               */
              function toInt184(int256 value) internal pure returns (int184) {
                  require(value >= type(int184).min && value <= type(int184).max, "SafeCast: value doesn't fit in 184 bits");
                  return int184(value);
              }
              /**
               * @dev Returns the downcasted int176 from int256, reverting on
               * overflow (when the input is less than smallest int176 or
               * greater than largest int176).
               *
               * Counterpart to Solidity's `int176` operator.
               *
               * Requirements:
               *
               * - input must fit into 176 bits
               *
               * _Available since v4.7._
               */
              function toInt176(int256 value) internal pure returns (int176) {
                  require(value >= type(int176).min && value <= type(int176).max, "SafeCast: value doesn't fit in 176 bits");
                  return int176(value);
              }
              /**
               * @dev Returns the downcasted int168 from int256, reverting on
               * overflow (when the input is less than smallest int168 or
               * greater than largest int168).
               *
               * Counterpart to Solidity's `int168` operator.
               *
               * Requirements:
               *
               * - input must fit into 168 bits
               *
               * _Available since v4.7._
               */
              function toInt168(int256 value) internal pure returns (int168) {
                  require(value >= type(int168).min && value <= type(int168).max, "SafeCast: value doesn't fit in 168 bits");
                  return int168(value);
              }
              /**
               * @dev Returns the downcasted int160 from int256, reverting on
               * overflow (when the input is less than smallest int160 or
               * greater than largest int160).
               *
               * Counterpart to Solidity's `int160` operator.
               *
               * Requirements:
               *
               * - input must fit into 160 bits
               *
               * _Available since v4.7._
               */
              function toInt160(int256 value) internal pure returns (int160) {
                  require(value >= type(int160).min && value <= type(int160).max, "SafeCast: value doesn't fit in 160 bits");
                  return int160(value);
              }
              /**
               * @dev Returns the downcasted int152 from int256, reverting on
               * overflow (when the input is less than smallest int152 or
               * greater than largest int152).
               *
               * Counterpart to Solidity's `int152` operator.
               *
               * Requirements:
               *
               * - input must fit into 152 bits
               *
               * _Available since v4.7._
               */
              function toInt152(int256 value) internal pure returns (int152) {
                  require(value >= type(int152).min && value <= type(int152).max, "SafeCast: value doesn't fit in 152 bits");
                  return int152(value);
              }
              /**
               * @dev Returns the downcasted int144 from int256, reverting on
               * overflow (when the input is less than smallest int144 or
               * greater than largest int144).
               *
               * Counterpart to Solidity's `int144` operator.
               *
               * Requirements:
               *
               * - input must fit into 144 bits
               *
               * _Available since v4.7._
               */
              function toInt144(int256 value) internal pure returns (int144) {
                  require(value >= type(int144).min && value <= type(int144).max, "SafeCast: value doesn't fit in 144 bits");
                  return int144(value);
              }
              /**
               * @dev Returns the downcasted int136 from int256, reverting on
               * overflow (when the input is less than smallest int136 or
               * greater than largest int136).
               *
               * Counterpart to Solidity's `int136` operator.
               *
               * Requirements:
               *
               * - input must fit into 136 bits
               *
               * _Available since v4.7._
               */
              function toInt136(int256 value) internal pure returns (int136) {
                  require(value >= type(int136).min && value <= type(int136).max, "SafeCast: value doesn't fit in 136 bits");
                  return int136(value);
              }
              /**
               * @dev Returns the downcasted int128 from int256, reverting on
               * overflow (when the input is less than smallest int128 or
               * greater than largest int128).
               *
               * Counterpart to Solidity's `int128` operator.
               *
               * Requirements:
               *
               * - input must fit into 128 bits
               *
               * _Available since v3.1._
               */
              function toInt128(int256 value) internal pure returns (int128) {
                  require(value >= type(int128).min && value <= type(int128).max, "SafeCast: value doesn't fit in 128 bits");
                  return int128(value);
              }
              /**
               * @dev Returns the downcasted int120 from int256, reverting on
               * overflow (when the input is less than smallest int120 or
               * greater than largest int120).
               *
               * Counterpart to Solidity's `int120` operator.
               *
               * Requirements:
               *
               * - input must fit into 120 bits
               *
               * _Available since v4.7._
               */
              function toInt120(int256 value) internal pure returns (int120) {
                  require(value >= type(int120).min && value <= type(int120).max, "SafeCast: value doesn't fit in 120 bits");
                  return int120(value);
              }
              /**
               * @dev Returns the downcasted int112 from int256, reverting on
               * overflow (when the input is less than smallest int112 or
               * greater than largest int112).
               *
               * Counterpart to Solidity's `int112` operator.
               *
               * Requirements:
               *
               * - input must fit into 112 bits
               *
               * _Available since v4.7._
               */
              function toInt112(int256 value) internal pure returns (int112) {
                  require(value >= type(int112).min && value <= type(int112).max, "SafeCast: value doesn't fit in 112 bits");
                  return int112(value);
              }
              /**
               * @dev Returns the downcasted int104 from int256, reverting on
               * overflow (when the input is less than smallest int104 or
               * greater than largest int104).
               *
               * Counterpart to Solidity's `int104` operator.
               *
               * Requirements:
               *
               * - input must fit into 104 bits
               *
               * _Available since v4.7._
               */
              function toInt104(int256 value) internal pure returns (int104) {
                  require(value >= type(int104).min && value <= type(int104).max, "SafeCast: value doesn't fit in 104 bits");
                  return int104(value);
              }
              /**
               * @dev Returns the downcasted int96 from int256, reverting on
               * overflow (when the input is less than smallest int96 or
               * greater than largest int96).
               *
               * Counterpart to Solidity's `int96` operator.
               *
               * Requirements:
               *
               * - input must fit into 96 bits
               *
               * _Available since v4.7._
               */
              function toInt96(int256 value) internal pure returns (int96) {
                  require(value >= type(int96).min && value <= type(int96).max, "SafeCast: value doesn't fit in 96 bits");
                  return int96(value);
              }
              /**
               * @dev Returns the downcasted int88 from int256, reverting on
               * overflow (when the input is less than smallest int88 or
               * greater than largest int88).
               *
               * Counterpart to Solidity's `int88` operator.
               *
               * Requirements:
               *
               * - input must fit into 88 bits
               *
               * _Available since v4.7._
               */
              function toInt88(int256 value) internal pure returns (int88) {
                  require(value >= type(int88).min && value <= type(int88).max, "SafeCast: value doesn't fit in 88 bits");
                  return int88(value);
              }
              /**
               * @dev Returns the downcasted int80 from int256, reverting on
               * overflow (when the input is less than smallest int80 or
               * greater than largest int80).
               *
               * Counterpart to Solidity's `int80` operator.
               *
               * Requirements:
               *
               * - input must fit into 80 bits
               *
               * _Available since v4.7._
               */
              function toInt80(int256 value) internal pure returns (int80) {
                  require(value >= type(int80).min && value <= type(int80).max, "SafeCast: value doesn't fit in 80 bits");
                  return int80(value);
              }
              /**
               * @dev Returns the downcasted int72 from int256, reverting on
               * overflow (when the input is less than smallest int72 or
               * greater than largest int72).
               *
               * Counterpart to Solidity's `int72` operator.
               *
               * Requirements:
               *
               * - input must fit into 72 bits
               *
               * _Available since v4.7._
               */
              function toInt72(int256 value) internal pure returns (int72) {
                  require(value >= type(int72).min && value <= type(int72).max, "SafeCast: value doesn't fit in 72 bits");
                  return int72(value);
              }
              /**
               * @dev Returns the downcasted int64 from int256, reverting on
               * overflow (when the input is less than smallest int64 or
               * greater than largest int64).
               *
               * Counterpart to Solidity's `int64` operator.
               *
               * Requirements:
               *
               * - input must fit into 64 bits
               *
               * _Available since v3.1._
               */
              function toInt64(int256 value) internal pure returns (int64) {
                  require(value >= type(int64).min && value <= type(int64).max, "SafeCast: value doesn't fit in 64 bits");
                  return int64(value);
              }
              /**
               * @dev Returns the downcasted int56 from int256, reverting on
               * overflow (when the input is less than smallest int56 or
               * greater than largest int56).
               *
               * Counterpart to Solidity's `int56` operator.
               *
               * Requirements:
               *
               * - input must fit into 56 bits
               *
               * _Available since v4.7._
               */
              function toInt56(int256 value) internal pure returns (int56) {
                  require(value >= type(int56).min && value <= type(int56).max, "SafeCast: value doesn't fit in 56 bits");
                  return int56(value);
              }
              /**
               * @dev Returns the downcasted int48 from int256, reverting on
               * overflow (when the input is less than smallest int48 or
               * greater than largest int48).
               *
               * Counterpart to Solidity's `int48` operator.
               *
               * Requirements:
               *
               * - input must fit into 48 bits
               *
               * _Available since v4.7._
               */
              function toInt48(int256 value) internal pure returns (int48) {
                  require(value >= type(int48).min && value <= type(int48).max, "SafeCast: value doesn't fit in 48 bits");
                  return int48(value);
              }
              /**
               * @dev Returns the downcasted int40 from int256, reverting on
               * overflow (when the input is less than smallest int40 or
               * greater than largest int40).
               *
               * Counterpart to Solidity's `int40` operator.
               *
               * Requirements:
               *
               * - input must fit into 40 bits
               *
               * _Available since v4.7._
               */
              function toInt40(int256 value) internal pure returns (int40) {
                  require(value >= type(int40).min && value <= type(int40).max, "SafeCast: value doesn't fit in 40 bits");
                  return int40(value);
              }
              /**
               * @dev Returns the downcasted int32 from int256, reverting on
               * overflow (when the input is less than smallest int32 or
               * greater than largest int32).
               *
               * Counterpart to Solidity's `int32` operator.
               *
               * Requirements:
               *
               * - input must fit into 32 bits
               *
               * _Available since v3.1._
               */
              function toInt32(int256 value) internal pure returns (int32) {
                  require(value >= type(int32).min && value <= type(int32).max, "SafeCast: value doesn't fit in 32 bits");
                  return int32(value);
              }
              /**
               * @dev Returns the downcasted int24 from int256, reverting on
               * overflow (when the input is less than smallest int24 or
               * greater than largest int24).
               *
               * Counterpart to Solidity's `int24` operator.
               *
               * Requirements:
               *
               * - input must fit into 24 bits
               *
               * _Available since v4.7._
               */
              function toInt24(int256 value) internal pure returns (int24) {
                  require(value >= type(int24).min && value <= type(int24).max, "SafeCast: value doesn't fit in 24 bits");
                  return int24(value);
              }
              /**
               * @dev Returns the downcasted int16 from int256, reverting on
               * overflow (when the input is less than smallest int16 or
               * greater than largest int16).
               *
               * Counterpart to Solidity's `int16` operator.
               *
               * Requirements:
               *
               * - input must fit into 16 bits
               *
               * _Available since v3.1._
               */
              function toInt16(int256 value) internal pure returns (int16) {
                  require(value >= type(int16).min && value <= type(int16).max, "SafeCast: value doesn't fit in 16 bits");
                  return int16(value);
              }
              /**
               * @dev Returns the downcasted int8 from int256, reverting on
               * overflow (when the input is less than smallest int8 or
               * greater than largest int8).
               *
               * Counterpart to Solidity's `int8` operator.
               *
               * Requirements:
               *
               * - input must fit into 8 bits
               *
               * _Available since v3.1._
               */
              function toInt8(int256 value) internal pure returns (int8) {
                  require(value >= type(int8).min && value <= type(int8).max, "SafeCast: value doesn't fit in 8 bits");
                  return int8(value);
              }
              /**
               * @dev Converts an unsigned uint256 into a signed int256.
               *
               * Requirements:
               *
               * - input must be less than or equal to maxInt256.
               *
               * _Available since v3.0._
               */
              function toInt256(uint256 value) internal pure returns (int256) {
                  // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
                  require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256");
                  return int256(value);
              }
          }
          // 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.0;
          /**
           * @title This interface is a mixin for IFilteredMinterExpSettlementV<version>
           * interfaces to use when defining settlement minter interfaces.
           * @author Art Blocks Inc.
           */
          interface IFilteredMinterDAExpSettlement_Mixin {
              /// Auction details cleared for project `projectId`.
              /// At time of reset, the project has had `numPurchases` purchases on this
              /// minter, with a most recent purchase price of `latestPurchasePrice`. If
              /// the number of purchases is 0, the latest purchase price will have a
              /// dummy value of 0.
              event ResetAuctionDetails(
                  uint256 indexed projectId,
                  uint256 numPurchases,
                  uint256 latestPurchasePrice
              );
              /// sellout price updated for project `projectId`.
              /// @dev does not use generic event because likely will trigger additional
              /// actions in indexing layer
              event SelloutPriceUpdated(
                  uint256 indexed _projectId,
                  uint256 _selloutPrice
              );
              /// artist and admin have withdrawn revenues from settleable purchases for
              /// project `projectId`.
              /// @dev does not use generic event because likely will trigger additional
              /// actions in indexing layer
              event ArtistAndAdminRevenuesWithdrawn(uint256 indexed _projectId);
              /// receipt has an updated state
              event ReceiptUpdated(
                  address indexed _purchaser,
                  uint256 indexed _projectId,
                  uint256 _numPurchased,
                  uint256 _netPosted
              );
              /// returns latest purchase price for project `_projectId`, or 0 if no
              /// purchases have been made.
              function getProjectLatestPurchasePrice(
                  uint256 _projectId
              ) external view returns (uint256 latestPurchasePrice);
              /// returns the number of settleable invocations for project `_projectId`.
              function getNumSettleableInvocations(
                  uint256 _projectId
              ) external view returns (uint256 numSettleableInvocations);
              /// Returns the current excess settlement funds on project `_projectId`
              /// for address `_walletAddress`.
              function getProjectExcessSettlementFunds(
                  uint256 _projectId,
                  address _walletAddress
              ) external view returns (uint256 excessSettlementFundsInWei);
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          import "./IFilteredMinterDAExpSettlement_Mixin.sol";
          import "./IFilteredMinterV1.sol";
          import "./IFilteredMinterDAExpV0.sol";
          pragma solidity ^0.8.0;
          /**
           * @title This interface combines the set of interfaces that add support for
           * a Dutch Auction with Settlement minter.
           * @author Art Blocks Inc.
           */
          interface IFilteredMinterDAExpSettlementV0 is
              IFilteredMinterDAExpSettlement_Mixin,
              IFilteredMinterV1,
              IFilteredMinterDAExpV0
          {
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          import "./IFilteredMinterV0.sol";
          pragma solidity ^0.8.0;
          /**
           * @title This interface extends the IFilteredMinterV0 interface in order to
           * add support for exponential descending auctions.
           * @author Art Blocks Inc.
           */
          interface IFilteredMinterDAExpV0 is IFilteredMinterV0 {
              /// Auction details updated for project `projectId`.
              event SetAuctionDetails(
                  uint256 indexed projectId,
                  uint256 _auctionTimestampStart,
                  uint256 _priceDecayHalfLifeSeconds,
                  uint256 _startPrice,
                  uint256 _basePrice
              );
              /// Auction details cleared for project `projectId`.
              event ResetAuctionDetails(uint256 indexed projectId);
              /// Maximum and minimum allowed price decay half lifes updated.
              event AuctionHalfLifeRangeSecondsUpdated(
                  uint256 _minimumPriceDecayHalfLifeSeconds,
                  uint256 _maximumPriceDecayHalfLifeSeconds
              );
              function minimumPriceDecayHalfLifeSeconds() external view returns (uint256);
              function maximumPriceDecayHalfLifeSeconds() external view returns (uint256);
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.0;
          interface IFilteredMinterV0 {
              /**
               * @notice Price per token in wei updated for project `_projectId` to
               * `_pricePerTokenInWei`.
               */
              event PricePerTokenInWeiUpdated(
                  uint256 indexed _projectId,
                  uint256 indexed _pricePerTokenInWei
              );
              /**
               * @notice Currency updated for project `_projectId` to symbol
               * `_currencySymbol` and address `_currencyAddress`.
               */
              event ProjectCurrencyInfoUpdated(
                  uint256 indexed _projectId,
                  address indexed _currencyAddress,
                  string _currencySymbol
              );
              /// togglePurchaseToDisabled updated
              event PurchaseToDisabledUpdated(
                  uint256 indexed _projectId,
                  bool _purchaseToDisabled
              );
              // getter function of public variable
              function minterType() external view returns (string memory);
              function genArt721CoreAddress() external returns (address);
              function minterFilterAddress() external returns (address);
              // Triggers a purchase of a token from the desired project, to the
              // TX-sending address.
              function purchase(
                  uint256 _projectId
              ) external payable returns (uint256 tokenId);
              // Triggers a purchase of a token from the desired project, to the specified
              // receiving address.
              function purchaseTo(
                  address _to,
                  uint256 _projectId
              ) external payable returns (uint256 tokenId);
              // Toggles the ability for `purchaseTo` to be called directly with a
              // specified receiving address that differs from the TX-sending address.
              function togglePurchaseToDisabled(uint256 _projectId) external;
              // Called to make the minter contract aware of the max invocations for a
              // given project.
              function setProjectMaxInvocations(uint256 _projectId) external;
              // Gets if token price is configured, token price in wei, currency symbol,
              // and currency address, assuming this is project's minter.
              // Supersedes any defined core price.
              function getPriceInfo(
                  uint256 _projectId
              )
                  external
                  view
                  returns (
                      bool isConfigured,
                      uint256 tokenPriceInWei,
                      string memory currencySymbol,
                      address currencyAddress
                  );
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          import "./IFilteredMinterV0.sol";
          pragma solidity ^0.8.0;
          /**
           * @title This interface extends the IFilteredMinterV0 interface in order to
           * add support for generic project minter configuration updates.
           * @dev keys represent strings of finite length encoded in bytes32 to minimize
           * gas.
           * @author Art Blocks Inc.
           */
          interface IFilteredMinterV1 is IFilteredMinterV0 {
              /// ANY
              /**
               * @notice Generic project minter configuration event. Removes key `_key`
               * for project `_projectId`.
               */
              event ConfigKeyRemoved(uint256 indexed _projectId, bytes32 _key);
              /// BOOL
              /**
               * @notice Generic project minter configuration event. Sets value of key
               * `_key` to `_value` for project `_projectId`.
               */
              event ConfigValueSet(uint256 indexed _projectId, bytes32 _key, bool _value);
              /// UINT256
              /**
               * @notice Generic project minter configuration event. Sets value of key
               * `_key` to `_value` for project `_projectId`.
               */
              event ConfigValueSet(
                  uint256 indexed _projectId,
                  bytes32 _key,
                  uint256 _value
              );
              /**
               * @notice Generic project minter configuration event. Adds value `_value`
               * to the set of uint256 at key `_key` for project `_projectId`.
               */
              event ConfigValueAddedToSet(
                  uint256 indexed _projectId,
                  bytes32 _key,
                  uint256 _value
              );
              /**
               * @notice Generic project minter configuration event. Removes value
               * `_value` to the set of uint256 at key `_key` for project `_projectId`.
               */
              event ConfigValueRemovedFromSet(
                  uint256 indexed _projectId,
                  bytes32 _key,
                  uint256 _value
              );
              /// ADDRESS
              /**
               * @notice Generic project minter configuration event. Sets value of key
               * `_key` to `_value` for project `_projectId`.
               */
              event ConfigValueSet(
                  uint256 indexed _projectId,
                  bytes32 _key,
                  address _value
              );
              /**
               * @notice Generic project minter configuration event. Adds value `_value`
               * to the set of addresses at key `_key` for project `_projectId`.
               */
              event ConfigValueAddedToSet(
                  uint256 indexed _projectId,
                  bytes32 _key,
                  address _value
              );
              /**
               * @notice Generic project minter configuration event. Removes value
               * `_value` to the set of addresses at key `_key` for project `_projectId`.
               */
              event ConfigValueRemovedFromSet(
                  uint256 indexed _projectId,
                  bytes32 _key,
                  address _value
              );
              /// BYTES32
              /**
               * @notice Generic project minter configuration event. Sets value of key
               * `_key` to `_value` for project `_projectId`.
               */
              event ConfigValueSet(
                  uint256 indexed _projectId,
                  bytes32 _key,
                  bytes32 _value
              );
              /**
               * @notice Generic project minter configuration event. Adds value `_value`
               * to the set of bytes32 at key `_key` for project `_projectId`.
               */
              event ConfigValueAddedToSet(
                  uint256 indexed _projectId,
                  bytes32 _key,
                  bytes32 _value
              );
              /**
               * @notice Generic project minter configuration event. Removes value
               * `_value` to the set of bytes32 at key `_key` for project `_projectId`.
               */
              event ConfigValueRemovedFromSet(
                  uint256 indexed _projectId,
                  bytes32 _key,
                  bytes32 _value
              );
              /**
               * @dev Strings not supported. Recommend conversion of (short) strings to
               * bytes32 to remain gas-efficient.
               */
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          import "./IFilteredMinterV1.sol";
          pragma solidity ^0.8.0;
          /**
           * @title This interface extends the IFilteredMinterV1 interface in order to
           * add support for manually setting project max invocations.
           * @author Art Blocks Inc.
           */
          interface IFilteredMinterV2 is IFilteredMinterV1 {
              /**
               * @notice Local max invocations for project `_projectId`, tied to core contract `_coreContractAddress`,
               * updated to `_maxInvocations`.
               */
              event ProjectMaxInvocationsLimitUpdated(
                  uint256 indexed _projectId,
                  uint256 _maxInvocations
              );
              // Sets the local max invocations for a given project, checking that the provided max invocations is
              // less than or equal to the global max invocations for the project set on the core contract.
              // This does not impact the max invocations value defined on the core contract.
              function manuallyLimitProjectMaxInvocations(
                  uint256 _projectId,
                  uint256 _maxInvocations
              ) 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);
              // @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 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 for a given tokenId
              function tokenIdToHash(uint256 _tokenId) external view returns (bytes32);
              // function to read the hash-seed for a given tokenId
              function tokenIdToHashSeed(
                  uint256 _tokenId
              ) external view returns (bytes12);
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.0;
          import "./IAdminACLV0.sol";
          import "./IGenArt721CoreContractV3_Base.sol";
          /**
           * @title This interface extends IGenArt721CoreContractV3_Base with functions
           * that are part of the Art Blocks Flagship core contract.
           * @author Art Blocks Inc.
           */
          // This interface extends IGenArt721CoreContractV3_Base with functions that are
          // in part of the Art Blocks Flagship core contract.
          interface IGenArt721CoreContractV3 is IGenArt721CoreContractV3_Base {
              // @dev new function in V3
              function getPrimaryRevenueSplits(
                  uint256 _projectId,
                  uint256 _price
              )
                  external
                  view
                  returns (
                      uint256 artblocksRevenue_,
                      address payable artblocksAddress_,
                      uint256 artistRevenue_,
                      address payable artistAddress_,
                      uint256 additionalPayeePrimaryRevenue_,
                      address payable additionalPayeePrimaryAddress_
                  );
              // @dev Art Blocks primary sales payment address
              function artblocksPrimarySalesAddress()
                  external
                  view
                  returns (address payable);
              /**
               * @notice Backwards-compatible (pre-V3) function returning Art Blocks
               * primary sales payment address (now called artblocksPrimarySalesAddress).
               */
              function artblocksAddress() external view returns (address payable);
              // @dev Percentage of primary sales allocated to Art Blocks
              function artblocksPrimarySalesPercentage() external view returns (uint256);
              /**
               * @notice Backwards-compatible (pre-V3) function returning Art Blocks
               * primary sales percentage (now called artblocksPrimarySalesPercentage).
               */
              function artblocksPercentage() external view returns (uint256);
              // @dev Art Blocks secondary sales royalties payment address
              function artblocksSecondarySalesAddress()
                  external
                  view
                  returns (address payable);
              // @dev Basis points of secondary sales allocated to Art Blocks
              function artblocksSecondarySalesBPS() external view returns (uint256);
              /**
               * @notice Backwards-compatible (pre-V3) function  that gets artist +
               * artist's additional payee royalty data for token ID `_tokenId`.
               * WARNING: Does not include Art Blocks portion of royalties.
               */
              function getRoyaltyData(
                  uint256 _tokenId
              )
                  external
                  view
                  returns (
                      address artistAddress,
                      address additionalPayee,
                      uint256 additionalPayeePercentage,
                      uint256 royaltyFeeByID
                  );
          }
          // 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
          // Created By: Art Blocks Inc.
          import "./IFilteredMinterV2.sol";
          pragma solidity ^0.8.0;
          /**
           * @title This interface defines any events or functions required for a minter
           * to conform to the MinterBase contract.
           * @dev The MinterBase contract was not implemented from the beginning of the
           * MinterSuite contract suite, therefore early versions of some minters may not
           * conform to this interface.
           * @author Art Blocks Inc.
           */
          interface IMinterBaseV0 {
              // Function that returns if a minter is configured to integrate with a V3 flagship or V3 engine contract.
              // Returns true only if the minter is configured to integrate with an engine contract.
              function isEngine() external returns (bool isEngine);
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.0;
          interface IMinterFilterV0 {
              /**
               * @notice Approved minter `_minterAddress`.
               */
              event MinterApproved(address indexed _minterAddress, string _minterType);
              /**
               * @notice Revoked approval for minter `_minterAddress`
               */
              event MinterRevoked(address indexed _minterAddress);
              /**
               * @notice Minter `_minterAddress` of type `_minterType`
               * registered for project `_projectId`.
               */
              event ProjectMinterRegistered(
                  uint256 indexed _projectId,
                  address indexed _minterAddress,
                  string _minterType
              );
              /**
               * @notice Any active minter removed for project `_projectId`.
               */
              event ProjectMinterRemoved(uint256 indexed _projectId);
              function genArt721CoreAddress() external returns (address);
              function setMinterForProject(uint256, address) external;
              function removeMinterForProject(uint256) external;
              function mint(
                  address _to,
                  uint256 _projectId,
                  address sender
              ) external returns (uint256);
              function getMinterForProject(uint256) external view returns (address);
              function projectHasMinter(uint256) external view returns (bool);
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          import "../../interfaces/0.8.x/IMinterBaseV0.sol";
          import "../../interfaces/0.8.x/IGenArt721CoreContractV3_Base.sol";
          import "../../interfaces/0.8.x/IGenArt721CoreContractV3.sol";
          import "../../interfaces/0.8.x/IGenArt721CoreContractV3_Engine.sol";
          import "@openzeppelin-4.7/contracts/token/ERC20/IERC20.sol";
          pragma solidity ^0.8.0;
          /**
           * @title Art Blocks Minter Base Class
           * @notice A base class for Art Blocks minter contracts that provides common
           * functionality used across minter contracts.
           * This contract is not intended to be deployed directly, but rather to be
           * inherited by other minter contracts.
           * From a design perspective, this contract is intended to remain simple and
           * easy to understand. It is not intended to cause a complex inheritance tree,
           * and instead should keep minter contracts as readable as possible for
           * collectors and developers.
           * @dev Semantic versioning is used in the solidity file name, and is therefore
           * controlled by contracts importing the appropriate filename version.
           * @author Art Blocks Inc.
           */
          abstract contract MinterBase is IMinterBaseV0 {
              /// state variable that tracks whether this contract's associated core
              /// contract is an Engine contract, where Engine contracts have an
              /// additional revenue split for the platform provider
              bool public immutable isEngine;
              // @dev we do not track an initialization state, as the only state variable
              // is immutable, which the compiler enforces to be assigned during
              // construction.
              /**
               * @notice Initializes contract to ensure state variable `isEngine` is set
               * appropriately based on the minter's associated core contract address.
               * @param genArt721Address Art Blocks core contract address for
               * which this contract will be a minter.
               */
              constructor(address genArt721Address) {
                  // set state variable isEngine
                  isEngine = _getV3CoreIsEngine(genArt721Address);
              }
              /**
               * @notice splits ETH funds between sender (if refund), providers,
               * artist, and artist's additional payee for a token purchased on
               * project `_projectId`.
               * WARNING: This function uses msg.value and msg.sender to determine
               * refund amounts, and therefore may not be applicable to all use cases
               * (e.g. do not use with Dutch Auctions with on-chain settlement).
               * @dev possible DoS during splits is acknowledged, and mitigated by
               * business practices, including end-to-end testing on mainnet, and
               * admin-accepted artist payment addresses.
               * @param projectId Project ID for which funds shall be split.
               * @param pricePerTokenInWei Current price of token, in Wei.
               */
              function splitFundsETH(
                  uint256 projectId,
                  uint256 pricePerTokenInWei,
                  address genArt721CoreAddress
              ) internal {
                  if (msg.value > 0) {
                      bool success_;
                      // send refund to sender
                      uint256 refund = msg.value - pricePerTokenInWei;
                      if (refund > 0) {
                          (success_, ) = msg.sender.call{value: refund}("");
                          require(success_, "Refund failed");
                      }
                      // split revenues
                      splitRevenuesETH(
                          projectId,
                          pricePerTokenInWei,
                          genArt721CoreAddress
                      );
                  }
              }
              /**
               * @notice splits ETH revenues between providers, artist, and artist's
               * additional payee for revenue generated by project `_projectId`.
               * @dev possible DoS during splits is acknowledged, and mitigated by
               * business practices, including end-to-end testing on mainnet, and
               * admin-accepted artist payment addresses.
               * @param projectId Project ID for which funds shall be split.
               * @param valueInWei Value to be split, in Wei.
               */
              function splitRevenuesETH(
                  uint256 projectId,
                  uint256 valueInWei,
                  address genArtCoreContract
              ) internal {
                  if (valueInWei <= 0) {
                      return; // return early
                  }
                  bool success;
                  // split funds between platforms, artist, and artist's
                  // additional payee
                  uint256 renderProviderRevenue_;
                  address payable renderProviderAddress_;
                  uint256 artistRevenue_;
                  address payable artistAddress_;
                  uint256 additionalPayeePrimaryRevenue_;
                  address payable additionalPayeePrimaryAddress_;
                  if (isEngine) {
                      // get engine splits
                      uint256 platformProviderRevenue_;
                      address payable platformProviderAddress_;
                      (
                          renderProviderRevenue_,
                          renderProviderAddress_,
                          platformProviderRevenue_,
                          platformProviderAddress_,
                          artistRevenue_,
                          artistAddress_,
                          additionalPayeePrimaryRevenue_,
                          additionalPayeePrimaryAddress_
                      ) = IGenArt721CoreContractV3_Engine(genArtCoreContract)
                          .getPrimaryRevenueSplits(projectId, valueInWei);
                      // Platform Provider payment (only possible if engine)
                      if (platformProviderRevenue_ > 0) {
                          (success, ) = platformProviderAddress_.call{
                              value: platformProviderRevenue_
                          }("");
                          require(success, "Platform Provider payment failed");
                      }
                  } else {
                      // get flagship splits
                      (
                          renderProviderRevenue_, // artblocks revenue
                          renderProviderAddress_, // artblocks address
                          artistRevenue_,
                          artistAddress_,
                          additionalPayeePrimaryRevenue_,
                          additionalPayeePrimaryAddress_
                      ) = IGenArt721CoreContractV3(genArtCoreContract)
                          .getPrimaryRevenueSplits(projectId, valueInWei);
                  }
                  // Render Provider / Art Blocks payment
                  if (renderProviderRevenue_ > 0) {
                      (success, ) = renderProviderAddress_.call{
                          value: renderProviderRevenue_
                      }("");
                      require(success, "Render Provider payment failed");
                  }
                  // artist payment
                  if (artistRevenue_ > 0) {
                      (success, ) = artistAddress_.call{value: artistRevenue_}("");
                      require(success, "Artist payment failed");
                  }
                  // additional payee payment
                  if (additionalPayeePrimaryRevenue_ > 0) {
                      (success, ) = additionalPayeePrimaryAddress_.call{
                          value: additionalPayeePrimaryRevenue_
                      }("");
                      require(success, "Additional Payee payment failed");
                  }
              }
              /**
               * @notice splits ERC-20 funds between providers, artist, and artist's
               * additional payee, for a token purchased on project `_projectId`.
               * @dev possible DoS during splits is acknowledged, and mitigated by
               * business practices, including end-to-end testing on mainnet, and
               * admin-accepted artist payment addresses.
               */
              function splitFundsERC20(
                  uint256 projectId,
                  uint256 pricePerTokenInWei,
                  address currencyAddress,
                  address genArtCoreContract
              ) internal {
                  IERC20 _projectCurrency = IERC20(currencyAddress);
                  // split remaining funds between foundation, artist, and artist's
                  // additional payee
                  uint256 renderProviderRevenue_;
                  address payable renderProviderAddress_;
                  uint256 artistRevenue_;
                  address payable artistAddress_;
                  uint256 additionalPayeePrimaryRevenue_;
                  address payable additionalPayeePrimaryAddress_;
                  if (isEngine) {
                      // get engine splits
                      uint256 platformProviderRevenue_;
                      address payable platformProviderAddress_;
                      (
                          renderProviderRevenue_,
                          renderProviderAddress_,
                          platformProviderRevenue_,
                          platformProviderAddress_,
                          artistRevenue_,
                          artistAddress_,
                          additionalPayeePrimaryRevenue_,
                          additionalPayeePrimaryAddress_
                      ) = IGenArt721CoreContractV3_Engine(genArtCoreContract)
                          .getPrimaryRevenueSplits(projectId, pricePerTokenInWei);
                      // Platform Provider payment (only possible if engine)
                      if (platformProviderRevenue_ > 0) {
                          _projectCurrency.transferFrom(
                              msg.sender,
                              platformProviderAddress_,
                              platformProviderRevenue_
                          );
                      }
                  } else {
                      // get flagship splits
                      (
                          renderProviderRevenue_, // artblocks revenue
                          renderProviderAddress_, // artblocks address
                          artistRevenue_,
                          artistAddress_,
                          additionalPayeePrimaryRevenue_,
                          additionalPayeePrimaryAddress_
                      ) = IGenArt721CoreContractV3(genArtCoreContract)
                          .getPrimaryRevenueSplits(projectId, pricePerTokenInWei);
                  }
                  // Art Blocks payment
                  if (renderProviderRevenue_ > 0) {
                      _projectCurrency.transferFrom(
                          msg.sender,
                          renderProviderAddress_,
                          renderProviderRevenue_
                      );
                  }
                  // artist payment
                  if (artistRevenue_ > 0) {
                      _projectCurrency.transferFrom(
                          msg.sender,
                          artistAddress_,
                          artistRevenue_
                      );
                  }
                  // additional payee payment
                  if (additionalPayeePrimaryRevenue_ > 0) {
                      _projectCurrency.transferFrom(
                          msg.sender,
                          additionalPayeePrimaryAddress_,
                          additionalPayeePrimaryRevenue_
                      );
                  }
              }
              /**
               * @notice Returns whether a V3 core contract is an Art Blocks Engine
               * contract or not. Return value of false indicates that the core is a
               * flagship contract.
               * @dev this function reverts if a core contract does not return the
               * expected number of return values from getPrimaryRevenueSplits() for
               * either a flagship or engine core contract.
               * @dev this function uses the length of the return data (in bytes) to
               * determine whether the core is an engine or not.
               * @param genArt721CoreV3 The address of the deployed core contract.
               */
              function _getV3CoreIsEngine(
                  address genArt721CoreV3
              ) private returns (bool) {
                  // call getPrimaryRevenueSplits() on core contract
                  bytes memory payload = abi.encodeWithSignature(
                      "getPrimaryRevenueSplits(uint256,uint256)",
                      0,
                      0
                  );
                  (bool success, bytes memory returnData) = genArt721CoreV3.call(payload);
                  require(success, "getPrimaryRevenueSplits() call failed");
                  // determine whether core is engine or not, based on return data length
                  uint256 returnDataLength = returnData.length;
                  if (returnDataLength == 6 * 32) {
                      // 6 32-byte words returned if flagship (not engine)
                      // @dev 6 32-byte words are expected because the non-engine core
                      // contracts return a payout address and uint256 payment value for
                      // the artist, and artist's additional payee, and Art Blocks.
                      // also note that per Solidity ABI encoding, the address return
                      // values are padded to 32 bytes.
                      return false;
                  } else if (returnDataLength == 8 * 32) {
                      // 8 32-byte words returned if engine
                      // @dev 8 32-byte words are expected because the engine core
                      // contracts return a payout address and uint256 payment value for
                      // the artist, artist's additional payee, render provider
                      // typically Art Blocks, and platform provider (partner).
                      // also note that per Solidity ABI encoding, the address return
                      // values are padded to 32 bytes.
                      return true;
                  } else {
                      // unexpected return value length
                      revert("Unexpected revenue split bytes");
                  }
              }
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          import "../../../interfaces/0.8.x/IGenArt721CoreContractV3_Base.sol";
          import "../../../interfaces/0.8.x/IMinterFilterV0.sol";
          import "../../../interfaces/0.8.x/IFilteredMinterDAExpSettlementV0.sol";
          import "../MinterBase_v0_1_1.sol";
          import "@openzeppelin-4.7/contracts/security/ReentrancyGuard.sol";
          import "@openzeppelin-4.7/contracts/utils/math/SafeCast.sol";
          pragma solidity 0.8.17;
          /**
           * @title Filtered Minter contract that allows tokens to be minted with ETH.
           * Pricing is achieved using an automated Dutch-auction mechanism, with a
           * settlement mechanism for tokens purchased before the auction ends.
           * This is designed to be used with GenArt721CoreContractV3 flagship or
           * engine contracts.
           * @author Art Blocks Inc.
           * @notice Privileged Roles and Ownership:
           * This contract is designed to be managed, with limited powers.
           * Privileged roles and abilities are controlled by the core contract's Admin
           * ACL contract and a project's artist. Both of these roles hold extensive
           * power and can modify minter details.
           * 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.
           * Additionally, the purchaser of a token has some trust assumptions regarding
           * settlement, beyond typical minter Art Blocks trust assumptions. In general,
           * Artists and Admin are trusted to not abuse their powers in a way that
           * would artifically inflate the sellout price of a project. They are
           * incentivized to not do so, as it would diminish their reputation and
           * ability to sell future projects. Agreements between Admin and Artist
           * may or may not be in place to further dissuade artificial inflation of an
           * auction's sellout price.
           * ----------------------------------------------------------------------------
           * The following functions are restricted to the core contract's Admin ACL
           * contract:
           * - setAllowablePriceDecayHalfLifeRangeSeconds (note: this range is only
           *   enforced when creating new auctions)
           * - resetAuctionDetails (note: this will prevent minting until a new auction
           *   is created)
           * - adminEmergencyReduceSelloutPrice
           * ----------------------------------------------------------------------------
           * The following functions are restricted to a project's artist or the core
           * contract's Admin ACL contract:
           * - withdrawArtistAndAdminRevenues (note: this may only be called after an
           *   auction has sold out or has reached base price)
           * ----------------------------------------------------------------------------
           * The following functions are restricted to a project's artist:
           * - setAuctionDetails (note: this may only be called when there is no active
           *   auction, and must start at a price less than or equal to any previously
           *   made purchases)
           * ----------------------------------------------------------------------------
           * Additional admin and artist privileged roles may be described on other
           * contracts that this minter integrates with.
           *
           * @dev Note that while this minter makes use of `block.timestamp` and it is
           * technically possible that this value is manipulated by block producers via
           * denial of service (in PoS), such manipulation will not have material impact
           * on the price values of this minter given the business practices for how
           * pricing is congfigured for this minter and that variations on the order of
           * less than a minute should not meaningfully impact price given the minimum
           * allowable price decay rate that this minter intends to support.
           */
          contract MinterDAExpSettlementV1 is
              ReentrancyGuard,
              MinterBase,
              IFilteredMinterDAExpSettlementV0
          {
              using SafeCast for uint256;
              /// Core contract address this minter interacts with
              address public immutable genArt721CoreAddress;
              /// The core contract integrates with V3 contracts
              IGenArt721CoreContractV3_Base private immutable genArtCoreContract_Base;
              /// Minter filter address this minter interacts with
              address public immutable minterFilterAddress;
              /// Minter filter this minter may interact with.
              IMinterFilterV0 private immutable minterFilter;
              /// minterType for this minter
              string public constant minterType = "MinterDAExpSettlementV1";
              uint256 constant ONE_MILLION = 1_000_000;
              struct ProjectConfig {
                  // on this minter, hasMaxBeenInvoked is updated only during every
                  // purchase, and is only true if this minter minted the final token.
                  // this enables the minter to know when a sellout price is greater than
                  // the auction's base price.
                  bool maxHasBeenInvoked;
                  // set to true only after artist + admin revenues have been collected
                  bool auctionRevenuesCollected;
                  // number of tokens minted that have potential of future settlement.
                  // max uint24 > 16.7 million tokens > 1 million tokens/project max
                  uint24 numSettleableInvocations;
                  // max uint64 ~= 1.8e19 sec ~= 570 billion years
                  uint64 timestampStart;
                  uint64 priceDecayHalfLifeSeconds;
                  // Prices are packed internally as uint128, resulting in a maximum
                  // allowed price of ~3.4e20 ETH. This is many orders of magnitude
                  // greater than current ETH supply.
                  uint128 startPrice;
                  // base price is non-zero for all configured auctions on this minter
                  uint128 basePrice;
                  // This value is only zero if no purchases have been made on this
                  // minter.
                  // When non-zero, this value is used as a reference when an auction is
                  // reset by admin, and then a new auction is configured by an artist.
                  // In that case, the new auction will be required to have a starting
                  // price less than or equal to this value, if one or more purchases
                  // have been made on this minter.
                  uint256 latestPurchasePrice;
              }
              mapping(uint256 => ProjectConfig) public projectConfig;
              /// Minimum price decay half life: price must decay with a half life of at
              /// least this amount (must cut in half at least every N seconds).
              uint256 public minimumPriceDecayHalfLifeSeconds = 300; // 5 minutes
              /// Maximum price decay half life: price may decay with a half life of no
              /// more than this amount (may cut in half at no more than every N seconds).
              uint256 public maximumPriceDecayHalfLifeSeconds = 3600; // 60 minutes
              struct Receipt {
                  // max uint232 allows for > 1e51 ETH (much more than max supply)
                  uint232 netPosted;
                  // max uint24 still allows for > max project supply of 1 million tokens
                  uint24 numPurchased;
              }
              /// user address => project ID => receipt
              mapping(address => mapping(uint256 => Receipt)) receipts;
              // modifier to restrict access to only AdminACL or the artist
              modifier onlyCoreAdminACLOrArtist(uint256 _projectId, bytes4 _selector) {
                  require(
                      (msg.sender ==
                          genArtCoreContract_Base.projectIdToArtistAddress(_projectId)) ||
                          (
                              genArtCoreContract_Base.adminACLAllowed(
                                  msg.sender,
                                  address(this),
                                  _selector
                              )
                          ),
                      "Only Artist or Admin ACL"
                  );
                  _;
              }
              // modifier to restrict access to only AdminACL allowed calls
              // @dev defers which ACL contract is used to the core contract
              modifier onlyCoreAdminACL(bytes4 _selector) {
                  require(
                      genArtCoreContract_Base.adminACLAllowed(
                          msg.sender,
                          address(this),
                          _selector
                      ),
                      "Only Core AdminACL allowed"
                  );
                  _;
              }
              modifier onlyArtist(uint256 _projectId) {
                  require(
                      (msg.sender ==
                          genArtCoreContract_Base.projectIdToArtistAddress(_projectId)),
                      "Only Artist"
                  );
                  _;
              }
              /**
               * @notice Initializes contract to be a Filtered Minter for
               * `_minterFilter`, integrated with Art Blocks core contract
               * at address `_genArt721Address`.
               * @param _genArt721Address Art Blocks core contract address for
               * which this contract will be a minter.
               * @param _minterFilter Minter filter for which
               * this will a filtered minter.
               */
              constructor(
                  address _genArt721Address,
                  address _minterFilter
              ) ReentrancyGuard() MinterBase(_genArt721Address) {
                  genArt721CoreAddress = _genArt721Address;
                  // always populate immutable engine contracts, but only use appropriate
                  // interface based on isEngine in the rest of the contract
                  genArtCoreContract_Base = IGenArt721CoreContractV3_Base(
                      _genArt721Address
                  );
                  minterFilterAddress = _minterFilter;
                  minterFilter = IMinterFilterV0(_minterFilter);
                  require(
                      minterFilter.genArt721CoreAddress() == _genArt721Address,
                      "Illegal contract pairing"
                  );
              }
              /**
               * @notice This function is not implemented on this minter, and exists only
               * for interface conformance reasons. This minter checks if max invocations
               * have been reached during every purchase to determine if a sellout has
               * occurred. Therefore, the local caching of max invocations is not
               * beneficial or necessary.
               */
              function setProjectMaxInvocations(uint256 /*_projectId*/) external pure {
                  // not implemented because maxInvocations must be checked during every mint
                  // to know if final price should be set
                  revert(
                      "setProjectMaxInvocations not implemented - updated during every mint"
                  );
              }
              /**
               * @notice Warning: Disabling purchaseTo is not supported on this minter.
               * This method exists purely for interface-conformance purposes.
               */
              function togglePurchaseToDisabled(
                  uint256 _projectId
              ) external view onlyArtist(_projectId) {
                  revert("Action not supported");
              }
              /**
               * @notice projectId => has project reached its maximum number of
               * invocations while being minted with this minter?
               * Note that this returns a local cache of the core contract's
               * state, and may be out of sync with the core contract. This is
               * intentional. A false negative will only result in a gas cost increase,
               * since the core contract will still enforce max invocations during during
               * minting. A false negative will also only occur if the max invocations
               * was either reduced on the core contract to equal current invocations, or
               * if the max invocations was reached by minting on a different minter.
               * In both of these cases, we expect the net purchase price (after
               * settlement) shall be the base price of the project's auction. This
               * prevents an artist from benefiting by reducing max invocations on the
               * core mid-auction, or by minting on a different minter.
               * Note that if an artist wishes to reduce the max invocations on the core
               * to something less than the current invocations, but more than max
               * invocations (with the hope of increasing the sellout price), an admin
               * function is provided to manually reduce the sellout price to a lower
               * value, if desired, in the `adminEmergencyReduceSelloutPrice`
               * function.
               * @param _projectId projectId to be queried
               *
               */
              function projectMaxHasBeenInvoked(
                  uint256 _projectId
              ) external view returns (bool) {
                  return projectConfig[_projectId].maxHasBeenInvoked;
              }
              /**
               * @notice projectId => auction parameters
               */
              function projectAuctionParameters(
                  uint256 _projectId
              )
                  external
                  view
                  returns (
                      uint256 timestampStart,
                      uint256 priceDecayHalfLifeSeconds,
                      uint256 startPrice,
                      uint256 basePrice
                  )
              {
                  ProjectConfig storage _projectConfig = projectConfig[_projectId];
                  return (
                      _projectConfig.timestampStart,
                      _projectConfig.priceDecayHalfLifeSeconds,
                      _projectConfig.startPrice,
                      _projectConfig.basePrice
                  );
              }
              /**
               * @notice Sets the minimum and maximum values that are settable for
               * `_priceDecayHalfLifeSeconds` across all projects.
               * @param _minimumPriceDecayHalfLifeSeconds Minimum price decay half life
               * (in seconds).
               * @param _maximumPriceDecayHalfLifeSeconds Maximum price decay half life
               * (in seconds).
               */
              function setAllowablePriceDecayHalfLifeRangeSeconds(
                  uint256 _minimumPriceDecayHalfLifeSeconds,
                  uint256 _maximumPriceDecayHalfLifeSeconds
              )
                  external
                  onlyCoreAdminACL(
                      this.setAllowablePriceDecayHalfLifeRangeSeconds.selector
                  )
              {
                  require(
                      _maximumPriceDecayHalfLifeSeconds >
                          _minimumPriceDecayHalfLifeSeconds,
                      "Maximum half life must be greater than minimum"
                  );
                  require(
                      _minimumPriceDecayHalfLifeSeconds > 0,
                      "Half life of zero not allowed"
                  );
                  minimumPriceDecayHalfLifeSeconds = _minimumPriceDecayHalfLifeSeconds;
                  maximumPriceDecayHalfLifeSeconds = _maximumPriceDecayHalfLifeSeconds;
                  emit AuctionHalfLifeRangeSecondsUpdated(
                      _minimumPriceDecayHalfLifeSeconds,
                      _maximumPriceDecayHalfLifeSeconds
                  );
              }
              ////// Auction Functions
              /**
               * @notice Sets auction details for project `_projectId`.
               * @param _projectId Project ID to set auction details for.
               * @param _auctionTimestampStart Timestamp at which to start the auction.
               * @param _priceDecayHalfLifeSeconds The half life with which to decay the
               *  price (in seconds).
               * @param _startPrice Price at which to start the auction, in Wei.
               * If a previous auction existed on this minter and at least one settleable
               * purchase has been made, this value must be less than or equal to the
               * price when the previous auction was paused. This enforces an overall
               * monatonically decreasing auction. Must be greater than or equal to
               * max(uint128) for internal storage packing purposes.
               * @param _basePrice Resting price of the auction, in Wei. Must be greater
               * than or equal to max(uint128) for internal storage packing purposes.
               * @dev Note that setting the auction price explicitly to `0` is
               * intentionally not allowed. This allows the minter to use the assumption
               * that a price of `0` indicates that the auction is not configured.
               * @dev Note that prices must be <= max(128) for internal storage packing
               * efficiency purposes only. This function's interface remains unchanged
               * for interface conformance purposes.
               */
              function setAuctionDetails(
                  uint256 _projectId,
                  uint256 _auctionTimestampStart,
                  uint256 _priceDecayHalfLifeSeconds,
                  uint256 _startPrice,
                  uint256 _basePrice
              ) external onlyArtist(_projectId) {
                  // CHECKS
                  ProjectConfig storage _projectConfig = projectConfig[_projectId];
                  require(
                      _projectConfig.timestampStart == 0 ||
                          block.timestamp < _projectConfig.timestampStart,
                      "No modifications mid-auction"
                  );
                  require(
                      block.timestamp < _auctionTimestampStart,
                      "Only future auctions"
                  );
                  require(
                      _startPrice > _basePrice,
                      "Auction start price must be greater than auction end price"
                  );
                  // require _basePrice is non-zero to simplify logic of this minter
                  require(_basePrice > 0, "Base price must be non-zero");
                  // If previous purchases have been made, require monotonically
                  // decreasing purchase prices to preserve settlement and revenue
                  // claiming logic. Since base price is always non-zero, if
                  // latestPurchasePrice is zero, then no previous purchases have been
                  // made, and startPrice may be set to any value.
                  require(
                      _projectConfig.latestPurchasePrice == 0 || // never purchased
                          _startPrice <= _projectConfig.latestPurchasePrice,
                      "Auction start price must be <= latest purchase price"
                  );
                  require(
                      (_priceDecayHalfLifeSeconds >= minimumPriceDecayHalfLifeSeconds) &&
                          (_priceDecayHalfLifeSeconds <=
                              maximumPriceDecayHalfLifeSeconds),
                      "Price decay half life must fall between min and max allowable values"
                  );
                  // EFFECTS
                  _projectConfig.timestampStart = _auctionTimestampStart.toUint64();
                  _projectConfig.priceDecayHalfLifeSeconds = _priceDecayHalfLifeSeconds
                      .toUint64();
                  _projectConfig.startPrice = _startPrice.toUint128();
                  _projectConfig.basePrice = _basePrice.toUint128();
                  emit SetAuctionDetails(
                      _projectId,
                      _auctionTimestampStart,
                      _priceDecayHalfLifeSeconds,
                      _startPrice,
                      _basePrice
                  );
              }
              /**
               * @notice Resets auction details for project `_projectId`, zero-ing out all
               * relevant auction fields. Not intended to be used in normal auction
               * operation, but rather only in case of the need to reset an ongoing
               * auction. An expected time this might occur would be when a frontend
               * issue was occuring, and many typical users are actively being prevented
               * from easily minting (even though minting would technically be possible
               * directly from the contract).
               * This function is only callable by the core admin during an active
               * auction, before revenues have been collected.
               * The price at the time of the reset will be the maximum starting price
               * when re-configuring the next auction if one or more settleable purchases
               * have been made.
               * This is to ensure that purchases up through the block that this is
               * called on will remain settleable, and that revenue claimed does not
               * surpass (payments - excess_settlement_funds) for a given project.
               * @param _projectId Project ID to set auction details for.
               */
              function resetAuctionDetails(
                  uint256 _projectId
              ) external onlyCoreAdminACL(this.resetAuctionDetails.selector) {
                  // CHECKS
                  ProjectConfig storage _projectConfig = projectConfig[_projectId];
                  require(_projectConfig.startPrice != 0, "Auction must be configured");
                  // no reset after revenues collected, since that solidifies amount due
                  require(
                      !_projectConfig.auctionRevenuesCollected,
                      "Only before revenues collected"
                  );
                  // EFFECTS
                  // reset to initial values
                  _projectConfig.timestampStart = 0;
                  _projectConfig.priceDecayHalfLifeSeconds = 0;
                  _projectConfig.startPrice = 0;
                  _projectConfig.basePrice = 0;
                  // Since auction revenues have not been collected, we can safely assume
                  // that numSettleableInvocations is the number of purchases made on
                  // this minter. A dummy value of 0 is used for latest purchase price if
                  // no purchases have been made.
                  emit ResetAuctionDetails(
                      _projectId,
                      _projectConfig.numSettleableInvocations,
                      _projectConfig.latestPurchasePrice
                  );
              }
              /**
               * @notice This represents an admin stepping in and reducing the sellout
               * price of an auction. This is only callable by the core admin, only
               * after the auction is complete, but before project revenues are
               * withdrawn.
               * This is only intended to be used in the case where for some reason,
               * whether malicious or accidental, the sellout price was too high.
               * Examples of this include:
               *  - The artist reducing a project's maxInvocations on the core contract
               *    after an auction has started, but before it ends, eliminating the
               *    ability of purchasers to fairly determine market price under the
               *    original, expected auction parameters.
               *  - Any other reason the admin deems to be a valid reason to reduce the
               *    sellout price of an auction, prior to marking it as valid.
               * @param _projectId Project ID to reduce auction sellout price for.
               * @param _newSelloutPrice New sellout price to set for the auction. Must
               * be less than the current sellout price.
               */
              function adminEmergencyReduceSelloutPrice(
                  uint256 _projectId,
                  uint256 _newSelloutPrice
              )
                  external
                  onlyCoreAdminACL(this.adminEmergencyReduceSelloutPrice.selector)
              {
                  ProjectConfig storage _projectConfig = projectConfig[_projectId];
                  require(_projectConfig.maxHasBeenInvoked, "Auction must be complete");
                  // @dev no need to check that auction max invocations has been reached,
                  // because if it was, the sellout price will be zero, and the following
                  // check will fail.
                  require(
                      _newSelloutPrice < _projectConfig.latestPurchasePrice,
                      "May only reduce sellout price"
                  );
                  require(
                      _newSelloutPrice >= _projectConfig.basePrice,
                      "May only reduce sellout price to base price or greater"
                  );
                  // ensure latestPurchasePrice is non-zero if any purchases on minter
                  // @dev only possible to fail this if auction is in a reset state
                  require(_newSelloutPrice > 0, "Only sellout prices > 0");
                  require(
                      !_projectConfig.auctionRevenuesCollected,
                      "Only before revenues collected"
                  );
                  _projectConfig.latestPurchasePrice = _newSelloutPrice;
                  emit SelloutPriceUpdated(_projectId, _newSelloutPrice);
              }
              /**
               * @notice This withdraws project revenues for the artist and admin.
               * This function is only callable by the artist or admin, and only after
               * one of the following is true:
               * - the auction has sold out above base price
               * - the auction has reached base price
               * Note that revenues are not claimable if in a temporary state after
               * an auction is reset.
               * Revenues may only be collected a single time per project.
               * After revenues are collected, auction parameters will never be allowed
               * to be reset, and excess settlement funds will become immutable and fully
               * deterministic.
               */
              function withdrawArtistAndAdminRevenues(
                  uint256 _projectId
              )
                  external
                  nonReentrant
                  onlyCoreAdminACLOrArtist(
                      _projectId,
                      this.withdrawArtistAndAdminRevenues.selector
                  )
              {
                  ProjectConfig storage _projectConfig = projectConfig[_projectId];
                  // CHECKS
                  // require revenues to not have already been collected
                  require(
                      !_projectConfig.auctionRevenuesCollected,
                      "Revenues already collected"
                  );
                  // get the current net price of the auction - reverts if no auction
                  // is configured.
                  // @dev _getPrice is guaranteed <= _projectConfig.latestPurchasePrice,
                  // since this minter enforces monotonically decreasing purchase prices.
                  uint256 _price = _getPrice(_projectId);
                  // if the price is not base price, require that the auction have
                  // reached max invocations. This prevents premature withdrawl
                  // before final auction price is possible to know.
                  if (_price != _projectConfig.basePrice) {
                      // prefer to use locally cached value of maxHasBeenInvoked, which
                      // is only updated when a purchase is made. This is to handle the
                      // case where an artist reduced max invocations to current
                      // invocations on the core contract mid-auction. In that case, the
                      // the following _projectConfig.maxHasBeenInvoked check will fail
                      // (only a local cache is used). This is a valid state, and in that
                      // somewhat suspicious case, the artist must wait until the auction
                      // reaches base price before withdrawing funds, at which point the
                      // latestPurchasePrice will be set to base price, maximizing
                      // purchaser excess settlement amounts, and minimizing artist/admin
                      // revenue.
                      require(
                          _projectConfig.maxHasBeenInvoked,
                          "Active auction not yet sold out"
                      );
                  } else {
                      // update the latest purchase price to the base price, to ensure
                      // the base price is used for all future settlement calculations
                      _projectConfig.latestPurchasePrice = _projectConfig.basePrice;
                  }
                  // EFFECTS
                  _projectConfig.auctionRevenuesCollected = true;
                  // if the price is base price, the auction is valid and may be claimed
                  // calculate the artist and admin revenues (no check requuired)
                  uint256 netRevenues = _projectConfig.numSettleableInvocations * _price;
                  // INTERACTIONS
                  splitRevenuesETH(_projectId, netRevenues, genArt721CoreAddress);
                  emit ArtistAndAdminRevenuesWithdrawn(_projectId);
              }
              /**
               * @notice Purchases a token from project `_projectId`.
               * @param _projectId Project ID to mint a token on.
               * @return tokenId Token ID of minted token
               */
              function purchase(
                  uint256 _projectId
              ) external payable returns (uint256 tokenId) {
                  tokenId = purchaseTo_do6(msg.sender, _projectId);
                  return tokenId;
              }
              /**
               * @notice gas-optimized version of purchase(uint256).
               */
              function purchase_H4M(
                  uint256 _projectId
              ) external payable returns (uint256 tokenId) {
                  tokenId = purchaseTo_do6(msg.sender, _projectId);
                  return tokenId;
              }
              /**
               * @notice Purchases a token from project `_projectId` and sets
               * the token's owner to `_to`.
               * @param _to Address to be the new token's owner.
               * @param _projectId Project ID to mint a token on.
               * @return tokenId Token ID of minted token
               */
              function purchaseTo(
                  address _to,
                  uint256 _projectId
              ) external payable returns (uint256 tokenId) {
                  return purchaseTo_do6(_to, _projectId);
              }
              /**
               * @notice gas-optimized version of purchaseTo(address, uint256).
               */
              function purchaseTo_do6(
                  address _to,
                  uint256 _projectId
              ) public payable nonReentrant returns (uint256 tokenId) {
                  // CHECKS
                  ProjectConfig storage _projectConfig = projectConfig[_projectId];
                  // Note that `maxHasBeenInvoked` is only checked here to reduce gas
                  // consumption after a project has been fully minted.
                  // `_projectConfig.maxHasBeenInvoked` is locally cached during every
                  // purchase to reduce gas consumption and enable recording of sellout
                  // price, but if not in sync with the core contract's value,
                  // the core contract also enforces its own max invocation check during
                  // minting.
                  require(
                      !_projectConfig.maxHasBeenInvoked,
                      "Maximum number of invocations reached"
                  );
                  // _getPrice reverts if auction is unconfigured or has not started
                  uint256 currentPriceInWei = _getPrice(_projectId);
                  // EFFECTS
                  // update the purchaser's receipt and require sufficient net payment
                  Receipt storage receipt = receipts[msg.sender][_projectId];
                  // in memory copy + update
                  uint256 netPosted = receipt.netPosted + msg.value;
                  uint256 numPurchased = receipt.numPurchased + 1;
                  // require sufficient payment on project
                  require(
                      netPosted >= numPurchased * currentPriceInWei,
                      "Must send minimum value to mint"
                  );
                  // update Receipt in storage
                  // @dev overflow checks are not required since the added values cannot
                  // be enough to overflow due to maximum invocations or supply of ETH
                  receipt.netPosted = uint232(netPosted);
                  receipt.numPurchased = uint24(numPurchased);
                  // emit event indicating new receipt state
                  emit ReceiptUpdated(msg.sender, _projectId, numPurchased, netPosted);
                  // update latest purchase price (on this minter) in storage
                  // @dev this is used to enforce monotonically decreasing purchase price
                  // across multiple auctions
                  _projectConfig.latestPurchasePrice = currentPriceInWei;
                  tokenId = minterFilter.mint(_to, _projectId, msg.sender);
                  // Note that this requires that the core contract's maxInvocations
                  // be accurate to ensure that the minters maxHasBeenInvoked is
                  // accurate, so we get the value from the core contract directly.
                  uint256 maxInvocations;
                  (, maxInvocations, , , , ) = genArtCoreContract_Base.projectStateData(
                      _projectId
                  );
                  // okay if this underflows because if statement will always eval false.
                  // this is only for gas optimization and recording sellout price in
                  // an event (core enforces maxInvocations).
                  unchecked {
                      if (tokenId % ONE_MILLION == maxInvocations - 1) {
                          _projectConfig.maxHasBeenInvoked = true;
                          emit SelloutPriceUpdated(_projectId, currentPriceInWei);
                      }
                  }
                  // INTERACTIONS
                  if (_projectConfig.auctionRevenuesCollected) {
                      // if revenues have been collected, split funds immediately.
                      // @dev note that we are guaranteed to be at auction base price,
                      // since we know we didn't sellout prior to this tx.
                      // note that we don't refund msg.sender here, since a separate
                      // settlement mechanism is provided on this minter, unrelated to
                      // msg.value
                      splitRevenuesETH(
                          _projectId,
                          currentPriceInWei,
                          genArt721CoreAddress
                      );
                  } else {
                      // increment the number of settleable invocations that will be
                      // claimable by the artist and admin once auction is validated.
                      // do not split revenue here since will be claimed at a later time.
                      _projectConfig.numSettleableInvocations++;
                  }
                  return tokenId;
              }
              /**
               * @notice Reclaims the sender's payment above current settled price for
               * project `_projectId`. The current settled price is the the price paid
               * for the most recently purchased token, or the base price if the artist
               * has withdrawn revenues after the auction reached base price.
               * This function is callable at any point, but is expected to typically be
               * called after auction has sold out above base price or after the auction
               * has been purchased at base price. This minimizes the amount of gas
               * required to send all excess settlement funds to the sender.
               * Sends excess settlement funds to msg.sender.
               * @param _projectId Project ID to reclaim excess settlement funds on.
               */
              function reclaimProjectExcessSettlementFunds(uint256 _projectId) external {
                  reclaimProjectExcessSettlementFundsTo(payable(msg.sender), _projectId);
              }
              /**
               * @notice Reclaims the sender's payment above current settled price for
               * project `_projectId`. The current settled price is the the price paid
               * for the most recently purchased token, or the base price if the artist
               * has withdrawn revenues after the auction reached base price.
               * This function is callable at any point, but is expected to typically be
               * called after auction has sold out above base price or after the auction
               * has been purchased at base price. This minimizes the amount of gas
               * required to send all excess settlement funds.
               * Sends excess settlement funds to address `_to`.
               * @param _to Address to send excess settlement funds to.
               * @param _projectId Project ID to reclaim excess settlement funds on.
               */
              function reclaimProjectExcessSettlementFundsTo(
                  address payable _to,
                  uint256 _projectId
              ) public nonReentrant {
                  ProjectConfig storage _projectConfig = projectConfig[_projectId];
                  Receipt storage receipt = receipts[msg.sender][_projectId];
                  uint256 numPurchased = receipt.numPurchased;
                  // CHECKS
                  // input validation
                  require(_to != address(0), "No claiming to the zero address");
                  // require that a user has purchased at least one token on this project
                  require(numPurchased > 0, "No purchases made by this address");
                  // get the latestPurchasePrice, which returns the sellout price if the
                  // auction sold out before reaching base price, or returns the base
                  // price if auction has reached base price and artist has withdrawn
                  // revenues.
                  // @dev if user is eligible for a reclaiming, they have purchased a
                  // token, therefore we are guaranteed to have a populated
                  // latestPurchasePrice
                  uint256 currentSettledTokenPrice = _projectConfig.latestPurchasePrice;
                  // EFFECTS
                  // calculate the excess settlement funds amount
                  // implicit overflow/underflow checks in solidity ^0.8
                  uint256 requiredAmountPosted = numPurchased * currentSettledTokenPrice;
                  uint256 excessSettlementFunds = receipt.netPosted -
                      requiredAmountPosted;
                  // update Receipt in storage
                  receipt.netPosted = requiredAmountPosted.toUint232();
                  // emit event indicating new receipt state
                  emit ReceiptUpdated(
                      msg.sender,
                      _projectId,
                      numPurchased,
                      requiredAmountPosted
                  );
                  // INTERACTIONS
                  bool success_;
                  (success_, ) = _to.call{value: excessSettlementFunds}("");
                  require(success_, "Reclaiming failed");
              }
              /**
               * @notice Reclaims the sender's payment above current settled price for
               * projects in `_projectIds`. The current settled price is the the price
               * paid for the most recently purchased token, or the base price if the
               * artist has withdrawn revenues after the auction reached base price.
               * This function is callable at any point, but is expected to typically be
               * called after auction has sold out above base price or after the auction
               * has been purchased at base price. This minimizes the amount of gas
               * required to send all excess settlement funds to the sender.
               * Sends total of all excess settlement funds to msg.sender in a single
               * chunk. Entire transaction reverts if any excess settlement calculation
               * fails.
               * @param _projectIds Array of project IDs to reclaim excess settlement
               * funds on.
               */
              function reclaimProjectsExcessSettlementFunds(
                  uint256[] calldata _projectIds
              ) external {
                  reclaimProjectsExcessSettlementFundsTo(
                      payable(msg.sender),
                      _projectIds
                  );
              }
              /**
               * @notice Reclaims the sender's payment above current settled price for
               * projects in `_projectIds`. The current settled price is the the price
               * paid for the most recently purchased token, or the base price if the
               * artist has withdrawn revenues after the auction reached base price.
               * This function is callable at any point, but is expected to typically be
               * called after auction has sold out above base price or after the auction
               * has been purchased at base price. This minimizes the amount of gas
               * required to send all excess settlement funds to the sender.
               * Sends total of all excess settlement funds to `_to` in a single
               * chunk. Entire transaction reverts if any excess settlement calculation
               * fails.
               * @param _to Address to send excess settlement funds to.
               * @param _projectIds Array of project IDs to reclaim excess settlement
               * funds on.
               */
              function reclaimProjectsExcessSettlementFundsTo(
                  address payable _to,
                  uint256[] memory _projectIds
              ) public nonReentrant {
                  // CHECKS
                  // input validation
                  require(_to != address(0), "No claiming to the zero address");
                  // EFFECTS
                  // for each project, tally up the excess settlement funds and update
                  // the receipt in storage
                  uint256 excessSettlementFunds;
                  uint256 projectIdsLength = _projectIds.length;
                  for (uint256 i; i < projectIdsLength; ) {
                      uint256 projectId = _projectIds[i];
                      ProjectConfig storage _projectConfig = projectConfig[projectId];
                      Receipt storage receipt = receipts[msg.sender][projectId];
                      uint256 numPurchased = receipt.numPurchased;
                      // input validation
                      // require that a user has purchased at least one token on this project
                      require(numPurchased > 0, "No purchases made by this address");
                      // get the latestPurchasePrice, which returns the sellout price if the
                      // auction sold out before reaching base price, or returns the base
                      // price if auction has reached base price and artist has withdrawn
                      // revenues.
                      // @dev if user is eligible for a claim, they have purchased a token,
                      // therefore we are guaranteed to have a populated
                      // latestPurchasePrice
                      uint256 currentSettledTokenPrice = _projectConfig
                          .latestPurchasePrice;
                      // calculate the excessSettlementFunds amount
                      // implicit overflow/underflow checks in solidity ^0.8
                      uint256 requiredAmountPosted = numPurchased *
                          currentSettledTokenPrice;
                      excessSettlementFunds += (receipt.netPosted - requiredAmountPosted);
                      // reduce the netPosted (in storage) to value after excess settlement
                      // funds deducted
                      receipt.netPosted = requiredAmountPosted.toUint232();
                      // emit event indicating new receipt state
                      emit ReceiptUpdated(
                          msg.sender,
                          projectId,
                          numPurchased,
                          requiredAmountPosted
                      );
                      // gas efficiently increment i
                      // won't overflow due to for loop, as well as gas limts
                      unchecked {
                          ++i;
                      }
                  }
                  // INTERACTIONS
                  // send excess settlement funds in a single chunk for all
                  // projects
                  bool success_;
                  (success_, ) = _to.call{value: excessSettlementFunds}("");
                  require(success_, "Reclaiming failed");
              }
              /**
               * @notice Gets price of minting a token on project `_projectId` given
               * the project's AuctionParameters and current block timestamp.
               * Reverts if auction has not yet started or auction is unconfigured.
               * Returns auction last purchase price if auction sold out before reaching
               * base price.
               * @param _projectId Project ID to get price of token for.
               * @return current price of token in Wei
               * @dev This method calculates price decay using a linear interpolation
               * of exponential decay based on the artist-provided half-life for price
               * decay, `_priceDecayHalfLifeSeconds`.
               */
              function _getPrice(uint256 _projectId) private view returns (uint256) {
                  ProjectConfig storage _projectConfig = projectConfig[_projectId];
                  // if auction sold out on this minter, return the latest purchase
                  // price (which is the sellout price). This is the price that is due
                  // after an auction is complete.
                  if (_projectConfig.maxHasBeenInvoked) {
                      return _projectConfig.latestPurchasePrice;
                  }
                  // otherwise calculate price based on current block timestamp and
                  // auction configuration (will revert if auction has not started)
                  // move parameters to memory if used more than once
                  uint256 _timestampStart = uint256(_projectConfig.timestampStart);
                  uint256 _priceDecayHalfLifeSeconds = uint256(
                      _projectConfig.priceDecayHalfLifeSeconds
                  );
                  uint256 _basePrice = _projectConfig.basePrice;
                  require(block.timestamp > _timestampStart, "Auction not yet started");
                  require(_priceDecayHalfLifeSeconds > 0, "Only configured auctions");
                  uint256 decayedPrice = _projectConfig.startPrice;
                  uint256 elapsedTimeSeconds;
                  unchecked {
                      // already checked that block.timestamp > _timestampStart above
                      elapsedTimeSeconds = block.timestamp - _timestampStart;
                  }
                  // Divide by two (via bit-shifting) for the number of entirely completed
                  // half-lives that have elapsed since auction start time.
                  unchecked {
                      // already required _priceDecayHalfLifeSeconds > 0
                      decayedPrice >>= elapsedTimeSeconds / _priceDecayHalfLifeSeconds;
                  }
                  // Perform a linear interpolation between partial half-life points, to
                  // approximate the current place on a perfect exponential decay curve.
                  unchecked {
                      // value of expression is provably always less than decayedPrice,
                      // so no underflow is possible when the subtraction assignment
                      // operator is used on decayedPrice.
                      decayedPrice -=
                          (decayedPrice *
                              (elapsedTimeSeconds % _priceDecayHalfLifeSeconds)) /
                          _priceDecayHalfLifeSeconds /
                          2;
                  }
                  if (decayedPrice < _basePrice) {
                      // Price may not decay below stay `basePrice`.
                      return _basePrice;
                  }
                  return decayedPrice;
              }
              /**
               * @notice Gets the current excess settlement funds on project `_projectId`
               * for address `_walletAddress`. The returned value is expected to change
               * throughtout an auction, since the latest purchase price is used when
               * determining excess settlement funds.
               * A user may claim excess settlement funds by calling the function
               * `reclaimProjectExcessSettlementFunds(_projectId)`.
               * @param _projectId Project ID to query.
               * @param _walletAddress Account address for which the excess posted funds
               * is being queried.
               * @return excessSettlementFundsInWei Amount of excess settlement funds, in
               * wei
               */
              function getProjectExcessSettlementFunds(
                  uint256 _projectId,
                  address _walletAddress
              ) external view returns (uint256 excessSettlementFundsInWei) {
                  // input validation
                  require(_walletAddress != address(0), "No zero address");
                  // load struct from storage
                  ProjectConfig storage _projectConfig = projectConfig[_projectId];
                  Receipt storage receipt = receipts[_walletAddress][_projectId];
                  // require that a user has purchased at least one token on this project
                  require(receipt.numPurchased > 0, "No purchases made by this address");
                  // get the latestPurchasePrice, which returns the sellout price if the
                  // auction sold out before reaching base price, or returns the base
                  // price if auction has reached base price and artist has withdrawn
                  // revenues.
                  // @dev if user is eligible for a reclaiming, they have purchased a
                  // token, therefore we are guaranteed to have a populated
                  // latestPurchasePrice
                  uint256 currentSettledTokenPrice = _projectConfig.latestPurchasePrice;
                  // EFFECTS
                  // calculate the excess settlement funds amount and return
                  // implicit overflow/underflow checks in solidity ^0.8
                  uint256 requiredAmountPosted = receipt.numPurchased *
                      currentSettledTokenPrice;
                  excessSettlementFundsInWei = receipt.netPosted - requiredAmountPosted;
                  return excessSettlementFundsInWei;
              }
              /**
               * @notice Gets the latest purchase price for project `_projectId`, or 0 if
               * no purchases have been made.
               */
              function getProjectLatestPurchasePrice(
                  uint256 _projectId
              ) external view returns (uint256 latestPurchasePrice) {
                  return projectConfig[_projectId].latestPurchasePrice;
              }
              /**
               * @notice Gets the number of settleable invocations for project `_projectId`.
               */
              function getNumSettleableInvocations(
                  uint256 _projectId
              ) external view returns (uint256 numSettleableInvocations) {
                  return projectConfig[_projectId].numSettleableInvocations;
              }
              /**
               * @notice Gets if price of token is configured, price of minting a
               * token on project `_projectId`, and currency symbol and address to be
               * used as payment. Supersedes any core contract price information.
               * @param _projectId Project ID to get price information for.
               * @return isConfigured true only if project's auction parameters have been
               * configured on this minter
               * @return tokenPriceInWei current price of token on this minter - invalid
               * if auction has not yet been configured
               * @return currencySymbol currency symbol for purchases of project on this
               * minter. This minter always returns "ETH"
               * @return currencyAddress currency address for purchases of project on
               * this minter. This minter always returns null address, reserved for ether
               */
              function getPriceInfo(
                  uint256 _projectId
              )
                  external
                  view
                  returns (
                      bool isConfigured,
                      uint256 tokenPriceInWei,
                      string memory currencySymbol,
                      address currencyAddress
                  )
              {
                  ProjectConfig storage _projectConfig = projectConfig[_projectId];
                  isConfigured = (_projectConfig.startPrice > 0);
                  if (block.timestamp <= _projectConfig.timestampStart) {
                      // Provide a reasonable value for `tokenPriceInWei` when it would
                      // otherwise revert, using the starting price before auction starts.
                      tokenPriceInWei = _projectConfig.startPrice;
                  } else if (_projectConfig.startPrice == 0) {
                      // In the case of unconfigured auction, return price of zero when
                      // it would otherwise revert
                      tokenPriceInWei = 0;
                  } else {
                      tokenPriceInWei = _getPrice(_projectId);
                  }
                  currencySymbol = "ETH";
                  currencyAddress = address(0);
              }
          }
          

          File 2 of 3: GenArt721CoreV3
          // 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/IGenArt721CoreContractV3.sol";
          import "./interfaces/0.8.x/IManifold.sol";
          import "@openzeppelin-4.7/contracts/utils/Strings.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 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:
           * - updateArtblocksCurationRegistryAddress
           * - updateArtblocksDependencyRegistryAddress
           * - updateArtblocksPrimarySalesAddress
           * - updateArtblocksSecondarySalesAddress
           * - updateArtblocksPrimarySalesPercentage (up to 25%)
           * - updateArtblocksSecondarySalesBPS (up to 100%)
           * - updateMinterContract
           * - updateRandomizerAddress
           * - toggleProjectIsActive
           * - addProject
           * - forbidNewProjects (forever forbidding new projects)
           * - updateDefaultBaseURI (used to initialize new project base URIs)
           * ----------------------------------------------------------------------------
           * 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 address:
           * - 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.)
           * - 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 function is restricted to owner calling directly:
           * - transferOwnership
           * - renounceOwnership
           * ----------------------------------------------------------------------------
           * Additional admin and artist privileged roles may be described on minters,
           * registries, and other contracts that may interact with this core contract.
           */
          contract GenArt721CoreV3 is
              ERC721_PackedHashSeed,
              Ownable,
              IGenArt721CoreContractV3
          {
              using BytecodeStorage for string;
              using BytecodeStorage for address;
              using Bytes32Strings for bytes32;
              using Strings for uint256;
              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 ART_BLOCKS_MAX_PRIMARY_SALES_PERCENTAGE = 25; // 25%
              uint256 constant ART_BLOCKS_MAX_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_ARTBLOCKS_PRIMARY_SALES_ADDRESS =
                  "artblocksPrimarySalesAddress";
              bytes32 constant FIELD_ARTBLOCKS_SECONDARY_SALES_ADDRESS =
                  "artblocksSecondarySalesAddress";
              bytes32 constant FIELD_RANDOMIZER_ADDRESS = "randomizerAddress";
              bytes32 constant FIELD_ARTBLOCKS_CURATION_REGISTRY_ADDRESS =
                  "curationRegistryAddress";
              bytes32 constant FIELD_ARTBLOCKS_DEPENDENCY_REGISTRY_ADDRESS =
                  "dependencyRegistryAddress";
              bytes32 constant FIELD_ARTBLOCKS_PRIMARY_SALES_PERCENTAGE =
                  "artblocksPrimaryPercentage";
              bytes32 constant FIELD_ARTBLOCKS_SECONDARY_SALES_BPS =
                  "artblocksSecondaryBPS";
              // 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";
              // Art Blocks previous flagship ERC721 token addresses (for reference)
              /// Art Blocks Project ID range: [0-2]
              address public constant ART_BLOCKS_ERC721TOKEN_ADDRESS_V0 =
                  0x059EDD72Cd353dF5106D2B9cC5ab83a52287aC3a;
              /// Art Blocks Project ID range: [3-373]
              address public constant ART_BLOCKS_ERC721TOKEN_ADDRESS_V1 =
                  0xa7d8d9ef8D8Ce8992Df33D8b8CF4Aebabd5bD270;
              /// Curation registry managed by Art Blocks
              address public artblocksCurationRegistryAddress;
              /// 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;
              }
              mapping(uint256 => Project) projects;
              /// 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;
              /// Art Blocks payment address for all primary sales revenues (packed)
              address payable public artblocksPrimarySalesAddress;
              /// Percentage of primary sales revenue allocated to Art Blocks (packed)
              // packed uint: max of 25, max uint8 = 255
              uint8 private _artblocksPrimarySalesPercentage = 10;
              /// Art Blocks payment address for all secondary sales royalty revenues
              address payable public artblocksSecondarySalesAddress;
              /// Basis Points of secondary sales royalties allocated to Art Blocks
              uint256 public artblocksSecondarySalesBPS = 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;
              /// version & type of this core contract
              string public constant coreVersion = "v3.0.0";
              string public constant coreType = "GenArt721CoreV3";
              /// default base URI to initialize all new project projectBaseURI values to
              string public defaultBaseURI;
              modifier onlyNonZeroAddress(address _address) {
                  require(_address != address(0), "Must input non-zero address");
                  _;
              }
              modifier onlyNonEmptyString(string memory _string) {
                  require(bytes(_string).length != 0, "Must input non-empty string");
                  _;
              }
              modifier onlyValidTokenId(uint256 _tokenId) {
                  require(_exists(_tokenId), "Token ID does not exist");
                  _;
              }
              modifier onlyValidProjectId(uint256 _projectId) {
                  require(
                      (_projectId >= startingProjectId) && (_projectId < _nextProjectId),
                      "Project ID does not exist"
                  );
                  _;
              }
              modifier onlyUnlocked(uint256 _projectId) {
                  // Note: calling `_projectUnlocked` enforces that the `_projectId`
                  //       passed in is valid.`
                  require(_projectUnlocked(_projectId), "Only if unlocked");
                  _;
              }
              modifier onlyAdminACL(bytes4 _selector) {
                  require(
                      adminACLAllowed(msg.sender, address(this), _selector),
                      "Only Admin ACL allowed"
                  );
                  _;
              }
              modifier onlyArtist(uint256 _projectId) {
                  require(
                      msg.sender == projectIdToFinancials[_projectId].artistAddress,
                      "Only artist"
                  );
                  _;
              }
              modifier onlyArtistOrAdminACL(uint256 _projectId, bytes4 _selector) {
                  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.
               */
              modifier onlyAdminACLOrRenouncedArtist(
                  uint256 _projectId,
                  bytes4 _selector
              ) {
                  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.
               * @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 _randomizerContract,
                  address _adminACLContract,
                  uint248 _startingProjectId
              )
                  ERC721_PackedHashSeed(_tokenName, _tokenSymbol)
                  onlyNonZeroAddress(_randomizerContract)
              {
                  // record contracts starting project ID
                  // casting-up is safe
                  startingProjectId = uint256(_startingProjectId);
                  _updateArtblocksPrimarySalesAddress(msg.sender);
                  _updateArtblocksSecondarySalesAddress(msg.sender);
                  _updateRandomizerAddress(_randomizerContract);
                  // set AdminACL management contract as owner
                  _transferOwnership(_adminACLContract);
                  // initialize default base URI
                  _updateDefaultBaseURI("https://token.artblocks.io/");
                  // initialize next project ID
                  _nextProjectId = _startingProjectId;
                  emit PlatformUpdated(FIELD_NEXT_PROJECT_ID);
              }
              /**
               * @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
               * defined Art Blocks 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 Curation Registry contract.
               * @param _artblocksCurationRegistryAddress Address of new Curation
               * Registry.
               */
              function updateArtblocksCurationRegistryAddress(
                  address _artblocksCurationRegistryAddress
              )
                  external
                  onlyAdminACL(this.updateArtblocksCurationRegistryAddress.selector)
                  onlyNonZeroAddress(_artblocksCurationRegistryAddress)
              {
                  artblocksCurationRegistryAddress = _artblocksCurationRegistryAddress;
                  emit PlatformUpdated(FIELD_ARTBLOCKS_CURATION_REGISTRY_ADDRESS);
              }
              /**
               * @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 artblocksPrimarySalesAddress to
               * `_artblocksPrimarySalesAddress`.
               * @param _artblocksPrimarySalesAddress Address of new primary sales
               * payment address.
               */
              function updateArtblocksPrimarySalesAddress(
                  address payable _artblocksPrimarySalesAddress
              )
                  external
                  onlyAdminACL(this.updateArtblocksPrimarySalesAddress.selector)
                  onlyNonZeroAddress(_artblocksPrimarySalesAddress)
              {
                  _updateArtblocksPrimarySalesAddress(_artblocksPrimarySalesAddress);
              }
              /**
               * @notice Updates Art Blocks secondary sales royalty payment address to
               * `_artblocksSecondarySalesAddress`.
               * @param _artblocksSecondarySalesAddress Address of new secondary sales
               * payment address.
               */
              function updateArtblocksSecondarySalesAddress(
                  address payable _artblocksSecondarySalesAddress
              )
                  external
                  onlyAdminACL(this.updateArtblocksSecondarySalesAddress.selector)
                  onlyNonZeroAddress(_artblocksSecondarySalesAddress)
              {
                  _updateArtblocksSecondarySalesAddress(_artblocksSecondarySalesAddress);
              }
              /**
               * @notice Updates Art Blocks primary sales revenue percentage to
               * `artblocksPrimarySalesPercentage_`.
               * @param artblocksPrimarySalesPercentage_ New primary sales revenue
               * percentage.
               */
              function updateArtblocksPrimarySalesPercentage(
                  uint256 artblocksPrimarySalesPercentage_
              )
                  external
                  onlyAdminACL(this.updateArtblocksPrimarySalesPercentage.selector)
              {
                  require(
                      artblocksPrimarySalesPercentage_ <=
                          ART_BLOCKS_MAX_PRIMARY_SALES_PERCENTAGE,
                      "Max of ART_BLOCKS_MAX_PRIMARY_SALES_PERCENTAGE percent"
                  );
                  _artblocksPrimarySalesPercentage = uint8(
                      artblocksPrimarySalesPercentage_
                  );
                  emit PlatformUpdated(FIELD_ARTBLOCKS_PRIMARY_SALES_PERCENTAGE);
              }
              /**
               * @notice Updates Art Blocks secondary sales royalty Basis Points to
               * `_artblocksSecondarySalesBPS`.
               * @param _artblocksSecondarySalesBPS 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 updateArtblocksSecondarySalesBPS(
                  uint256 _artblocksSecondarySalesBPS
              ) external onlyAdminACL(this.updateArtblocksSecondarySalesBPS.selector) {
                  require(
                      _artblocksSecondarySalesBPS <= ART_BLOCKS_MAX_SECONDARY_SALES_BPS,
                      "Max of ART_BLOCKS_MAX_SECONDARY_SALES_BPS BPS"
                  );
                  artblocksSecondarySalesBPS = _artblocksSecondarySalesBPS;
                  emit PlatformUpdated(FIELD_ARTBLOCKS_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 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)
                  onlyArtist(_projectId)
                  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.
                  // store proposal hash on-chain, only if not automatic accept
                  bool 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 cannot change during automatic accept)
                      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
                  onlyArtist(_projectId)
              {
                  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 Art Blocks; this 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 onlyArtist(_projectId) {
                  require(
                      _secondMarketRoyalty <= ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE,
                      "Max of ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE 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 onlyArtist(_projectId) {
                  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 onlyArtist(_projectId) {
                  // CHECKS
                  Project storage project = projects[_projectId];
                  uint256 _invocations = project.invocations;
                  require(
                      (_maxInvocations < project.maxInvocations),
                      "maxInvocations may only be decreased"
                  );
                  require(
                      _maxInvocations >= _invocations,
                      "Only max invocations gte current 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.
               */
              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.
               */
              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");
                  // purge old contract bytecode contract from the blockchain state
                  project.scriptBytecodeAddresses[_scriptId].purgeBytecode();
                  // 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, "there are no scripts to remove");
                  // purge old contract bytecode contract from the blockchain state
                  project.scriptBytecodeAddresses[project.scriptCount - 1].purgeBytecode();
                  // 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
                  onlyArtist(_projectId)
                  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 View function returning Art Blocks portion of primary sales, in
               * percent.
               * @return uint256 Art Blocks portion of primary sales, in percent.
               */
              function artblocksPrimarySalesPercentage() external view returns (uint256) {
                  return _artblocksPrimarySalesPercentage;
              }
              /**
               * @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 Art Blocks portion 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 by
               * `artblocksSecondarySalesBPS`.
               */
              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
                  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 Backwards-compatible (pre-V3) function returning Art Blocks
               * primary sales payment address (now called artblocksPrimarySalesAddress).
               * @return address payable Art Blocks primary sales payment address
               */
              function artblocksAddress() external view returns (address payable) {
                  return artblocksPrimarySalesAddress;
              }
              /**
               * @notice Backwards-compatible (pre-V3) function returning Art Blocks
               * primary sales percentage (now called artblocksPrimarySalesPercentage).
               * @return uint256 Art Blocks primary sales percentage
               */
              function artblocksPercentage() external view returns (uint256) {
                  return _artblocksPrimarySalesPercentage;
              }
              /**
               * @notice Backwards-compatible (pre-V3) function.
               * Gets artist + artist's additional payee royalty data for token ID
               `_tokenId`.
               * WARNING: Does not include Art Blocks portion of royalties.
               * @param _tokenId Token ID to be queried.
               * @return artistAddress Artist's payment address
               * @return additionalPayee Additional payee's payment address
               * @return additionalPayeePercentage Percentage of artist revenue
               * to be sent to the additional payee's address
               * @return royaltyFeeByID Total royalty percentage to be sent to
               * combination of artist and additional payee
               * @dev Does not include Art Blocks portion of royalties.
               */
              function getRoyaltyData(uint256 _tokenId)
                  external
                  view
                  returns (
                      address artistAddress,
                      address additionalPayee,
                      uint256 additionalPayeePercentage,
                      uint256 royaltyFeeByID
                  )
              {
                  uint256 projectId = tokenIdToProjectId(_tokenId);
                  ProjectFinance storage projectFinance = projectIdToFinancials[
                      projectId
                  ];
                  artistAddress = projectFinance.artistAddress;
                  additionalPayee = projectFinance.additionalPayeeSecondarySales;
                  additionalPayeePercentage = projectFinance
                      .additionalPayeeSecondarySalesPercentage;
                  royaltyFeeByID = projectFinance.secondaryMarketRoyaltyPercentage;
              }
              /**
               * @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
                  onlyValidTokenId(_tokenId)
                  returns (address payable[] memory recipients, uint256[] memory bps)
              {
                  // initialize arrays with maximum potential length
                  recipients = new address payable[](3);
                  bps = new uint256[](3);
                  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 artblocksBPS = artblocksSecondarySalesBPS;
                  // 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 (artblocksBPS > 0) {
                      recipients[payeeCount] = artblocksSecondarySalesAddress;
                      bps[payeeCount++] = artblocksBPS;
                  }
                  // trim arrays if necessary
                  if (3 > payeeCount) {
                      assembly {
                          let decrease := sub(3, 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 Art Blocks, Artist, and Artist's additional primary sales
               * payee given a sale price of `_price` on project `_projectId`.
               * This always returns three revenue amounts and three 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 artblocksRevenue_ amount of revenue to be sent to Art Blocks
               * @return artblocksAddress_ address to send Art Blocks 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 three addresses and three 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 artblocksRevenue_,
                      address payable artblocksAddress_,
                      uint256 artistRevenue_,
                      address payable artistAddress_,
                      uint256 additionalPayeePrimaryRevenue_,
                      address payable additionalPayeePrimaryAddress_
                  )
              {
                  ProjectFinance storage projectFinance = projectIdToFinancials[
                      _projectId
                  ];
                  // calculate revenues
                  artblocksRevenue_ =
                      (_price * uint256(_artblocksPrimarySalesPercentage)) /
                      ONE_HUNDRED;
                  uint256 projectFunds;
                  unchecked {
                      // artblocksRevenue_ is always <=25, so guaranteed to never underflow
                      projectFunds = _price - artblocksRevenue_;
                  }
                  additionalPayeePrimaryRevenue_ =
                      (projectFunds *
                          projectFinance.additionalPayeePrimarySalesPercentage) /
                      ONE_HUNDRED;
                  unchecked {
                      // projectIdToAdditionalPayeePrimarySalesPercentage is always
                      // <=100, so guaranteed to never underflow
                      artistRevenue_ = projectFunds - additionalPayeePrimaryRevenue_;
                  }
                  // set addresses from storage
                  artblocksAddress_ = artblocksPrimarySalesAddress;
                  if (artistRevenue_ > 0) {
                      artistAddress_ = projectFinance.artistAddress;
                  }
                  if (additionalPayeePrimaryRevenue_ > 0) {
                      additionalPayeePrimaryAddress_ = projectFinance
                          .additionalPayeePrimarySales;
                  }
              }
              /**
               * @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)
                  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
                  onlyValidTokenId(_tokenId)
                  returns (string memory)
              {
                  string memory _projectBaseURI = projects[tokenIdToProjectId(_tokenId)]
                      .projectBaseURI;
                  return string.concat(_projectBaseURI, _tokenId.toString());
              }
              /**
               * @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 Art Blocks payment address to `_artblocksPrimarySalesAddress`.
               * @param _artblocksPrimarySalesAddress New Art Blocks 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 _updateArtblocksPrimarySalesAddress(
                  address _artblocksPrimarySalesAddress
              ) internal {
                  artblocksPrimarySalesAddress = payable(_artblocksPrimarySalesAddress);
                  emit PlatformUpdated(FIELD_ARTBLOCKS_PRIMARY_SALES_ADDRESS);
              }
              /**
               * @notice Updates Art Blocks secondary sales royalty payment address to
               * `_artblocksSecondarySalesAddress`.
               * @param _artblocksSecondarySalesAddress New Art Blocks 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 _updateArtblocksSecondarySalesAddress(
                  address _artblocksSecondarySalesAddress
              ) internal {
                  artblocksSecondarySalesAddress = payable(
                      _artblocksSecondarySalesAddress
                  );
                  emit PlatformUpdated(FIELD_ARTBLOCKS_SECONDARY_SALES_ADDRESS);
              }
              /**
               * @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
                  onlyValidProjectId(_projectId)
                  returns (bool)
              {
                  uint256 projectCompletedTimestamp = projects[_projectId]
                      .completedTimestamp;
                  bool projectOpen = projectCompletedTimestamp == 0;
                  return
                      projectOpen ||
                      (block.timestamp - projectCompletedTimestamp <
                          FOUR_WEEKS_IN_SECONDS);
              }
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Creatd By: Art Blocks Inc.
          pragma solidity ^0.8.0;
          import "./IGenArt721CoreContractV3.sol";
          interface IRandomizerV2 {
              // The core contract that may interact with this randomizer contract.
              function genArt721Core() external view returns (IGenArt721CoreContractV3);
              // 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.0;
          import "./IAdminACLV0.sol";
          /// use the Royalty Registry's IManifold interface for token royalties
          import "./IManifold.sol";
          interface IGenArt721CoreContractV3 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);
              // @dev new function in V3
              function getPrimaryRevenueSplits(uint256 _projectId, uint256 _price)
                  external
                  view
                  returns (
                      uint256 artblocksRevenue_,
                      address payable artblocksAddress_,
                      uint256 artistRevenue_,
                      address payable artistAddress_,
                      uint256 additionalPayeePrimaryRevenue_,
                      address payable additionalPayeePrimaryAddress_
                  );
              // @dev new function in V3
              function projectStateData(uint256 _projectId)
                  external
                  view
                  returns (
                      uint256 invocations,
                      uint256 maxInvocations,
                      bool active,
                      bool paused,
                      uint256 completedTimestamp,
                      bool locked
                  );
              // @dev Art Blocks primary sales payment address
              function artblocksPrimarySalesAddress()
                  external
                  view
                  returns (address payable);
              /**
               * @notice Backwards-compatible (pre-V3) function returning Art Blocks
               * primary sales payment address (now called artblocksPrimarySalesAddress).
               */
              function artblocksAddress() external view returns (address payable);
              // @dev Percentage of primary sales allocated to Art Blocks
              function artblocksPrimarySalesPercentage() external view returns (uint256);
              /**
               * @notice Backwards-compatible (pre-V3) function returning Art Blocks
               * primary sales percentage (now called artblocksPrimarySalesPercentage).
               */
              function artblocksPercentage() external view returns (uint256);
              // @dev Art Blocks secondary sales royalties payment address
              function artblocksSecondarySalesAddress()
                  external
                  view
                  returns (address payable);
              // @dev Basis points of secondary sales allocated to Art Blocks
              function artblocksSecondarySalesBPS() external view returns (uint256);
              // 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);
              /**
               * @notice Backwards-compatible (pre-V3) function  that gets artist +
               * artist's additional payee royalty data for token ID `_tokenId`.
               * WARNING: Does not include Art Blocks portion of royalties.
               */
              function getRoyaltyData(uint256 _tokenId)
                  external
                  view
                  returns (
                      address artistAddress,
                      address additionalPayee,
                      uint256 additionalPayeePercentage,
                      uint256 royaltyFeeByID
                  );
          }
          // 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) (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 (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
               * @return address_ address of deployed contract with bytecode containing concat(gated-cleanup-logic, 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, 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
                      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, 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
               * @param _address address of deployed contract with bytecode containing concat(gated-cleanup-logic, 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 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 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);
          }
          

          File 3 of 3: MinterFilterV1
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          import "../../interfaces/0.8.x/IMinterFilterV0.sol";
          import "../../interfaces/0.8.x/IFilteredMinterV0.sol";
          import "../../interfaces/0.8.x/IAdminACLV0.sol";
          import "../../interfaces/0.8.x/IGenArt721CoreContractV3.sol";
          import "@openzeppelin-4.5/contracts/utils/structs/EnumerableMap.sol";
          pragma solidity 0.8.17;
          /**
           * @title Minter filter contract that allows filtered minters to be set
           * on a per-project basis.
           * This is designed to be used with IGenArt721CoreContractV3 contracts.
           * @author Art Blocks Inc.
           * @notice Privileged Roles and Ownership:
           * This contract is designed to be managed, with limited powers.
           * Privileged roles and abilities are controlled by the core contract's Admin
           * ACL contract and a project's artist. Both of these roles hold extensive
           * power and can modify a project's current minter.
           * 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 core contract's Admin ACL
           * contract:
           * - addApprovedMinters
           * - removeApprovedMinters
           * - removeMintersForProjects
           * ----------------------------------------------------------------------------
           * The following functions are restricted to the core contract's Admin ACL
           * contract or a project's artist:
           * - setMinterForProject
           * - removeMinterForProject
           * ----------------------------------------------------------------------------
           * Additional admin and artist privileged roles may be described on minters
           */
          contract MinterFilterV1 is IMinterFilterV0 {
              // add Enumerable Map methods
              using EnumerableMap for EnumerableMap.UintToAddressMap;
              /// Core contract address this minter interacts with
              address public immutable genArt721CoreAddress;
              /// This contract integrates with the IV3 core contract
              IGenArt721CoreContractV3 private immutable genArtCoreContract;
              /// projectId => minter address
              EnumerableMap.UintToAddressMap private minterForProject;
              /// minter address => qty projects currently using minter
              mapping(address => uint256) public numProjectsUsingMinter;
              /// minter address => is an approved minter?
              mapping(address => bool) public isApprovedMinter;
              modifier onlyNonZeroAddress(address _address) {
                  require(_address != address(0), "Must input non-zero address");
                  _;
              }
              // modifier to restrict access to only AdminACL allowed calls
              // @dev defers which ACL contract is used to the core contract
              modifier onlyCoreAdminACL(bytes4 _selector) {
                  require(_coreAdminACLAllowed(_selector), "Only Core AdminACL allowed");
                  _;
              }
              // modifier to restrict access to only the artist of `_projectId`, or
              // AdminACL allowed calls
              // @dev defers which ACL contract is used to the core contract
              modifier onlyCoreAdminACLOrArtist(uint256 _projectId, bytes4 _selector) {
                  require(
                      msg.sender ==
                          genArtCoreContract.projectIdToArtistAddress(_projectId) ||
                          _coreAdminACLAllowed(_selector),
                      "Only Core AdminACL or Artist"
                  );
                  _;
              }
              modifier onlyValidProjectId(uint256 _projectId) {
                  require(
                      _projectId < genArtCoreContract.nextProjectId(),
                      "Only existing projects"
                  );
                  _;
              }
              modifier usingApprovedMinter(address _minterAddress) {
                  require(
                      isApprovedMinter[_minterAddress],
                      "Only approved minters are allowed"
                  );
                  _;
              }
              /**
               * @notice Initializes contract to be a Minter for `_genArt721Address`.
               * @param _genArt721Address Art Blocks core contract address
               * this contract will be a minter for. Can never be updated.
               */
              constructor(address _genArt721Address)
                  onlyNonZeroAddress(_genArt721Address)
              {
                  genArt721CoreAddress = _genArt721Address;
                  genArtCoreContract = IGenArt721CoreContractV3(_genArt721Address);
              }
              /**
               * @notice Internal function that determines if msg.sender is allowed to
               * call a function on this contract. Defers to core contract's
               * adminACLAllowed function.
               */
              function _coreAdminACLAllowed(bytes4 _selector) internal returns (bool) {
                  return
                      genArtCoreContract.adminACLAllowed(
                          msg.sender,
                          address(this),
                          _selector
                      );
              }
              /**
               * @notice Approves minter `_minterAddress`.
               * @param _minterAddress Minter to be added as an approved minter.
               */
              function addApprovedMinter(address _minterAddress)
                  external
                  onlyCoreAdminACL(this.addApprovedMinter.selector)
                  onlyNonZeroAddress(_minterAddress)
              {
                  isApprovedMinter[_minterAddress] = true;
                  emit MinterApproved(
                      _minterAddress,
                      IFilteredMinterV0(_minterAddress).minterType()
                  );
              }
              /**
               * @notice Removes previously approved minter `_minterAddress`.
               * @param _minterAddress Minter to remove.
               */
              function removeApprovedMinter(address _minterAddress)
                  external
                  onlyCoreAdminACL(this.removeApprovedMinter.selector)
              {
                  require(isApprovedMinter[_minterAddress], "Only approved minters");
                  require(
                      numProjectsUsingMinter[_minterAddress] == 0,
                      "Only unused minters"
                  );
                  isApprovedMinter[_minterAddress] = false;
                  emit MinterRevoked(_minterAddress);
              }
              /**
               * @notice Sets minter for project `_projectId` to minter
               * `_minterAddress`.
               * @param _projectId Project ID to set minter for.
               * @param _minterAddress Minter to be the project's minter.
               */
              function setMinterForProject(uint256 _projectId, address _minterAddress)
                  external
                  onlyCoreAdminACLOrArtist(_projectId, this.setMinterForProject.selector)
                  usingApprovedMinter(_minterAddress)
                  onlyValidProjectId(_projectId)
              {
                  // decrement number of projects using a previous minter
                  (bool hasPreviousMinter, address previousMinter) = minterForProject
                      .tryGet(_projectId);
                  if (hasPreviousMinter) {
                      numProjectsUsingMinter[previousMinter]--;
                  }
                  // add new minter
                  numProjectsUsingMinter[_minterAddress]++;
                  minterForProject.set(_projectId, _minterAddress);
                  emit ProjectMinterRegistered(
                      _projectId,
                      _minterAddress,
                      IFilteredMinterV0(_minterAddress).minterType()
                  );
              }
              /**
               * @notice Updates project `_projectId` to have no configured minter.
               * @param _projectId Project ID to remove minter.
               * @dev requires project to have an assigned minter
               */
              function removeMinterForProject(uint256 _projectId)
                  external
                  onlyCoreAdminACLOrArtist(
                      _projectId,
                      this.removeMinterForProject.selector
                  )
              {
                  _removeMinterForProject(_projectId);
              }
              /**
               * @notice Updates an array of project IDs to have no configured minter.
               * @param _projectIds Array of project IDs to remove minters for.
               * @dev requires all project IDs to have an assigned minter
               * @dev caution with respect to single tx gas limits
               */
              function removeMintersForProjects(uint256[] calldata _projectIds)
                  external
                  onlyCoreAdminACL(this.removeMintersForProjects.selector)
              {
                  uint256 numProjects = _projectIds.length;
                  for (uint256 i; i < numProjects; i++) {
                      _removeMinterForProject(_projectIds[i]);
                  }
              }
              /**
               * @notice Updates project `_projectId` to have no configured minter
               * (reverts tx if project does not have an assigned minter).
               * @param _projectId Project ID to remove minter.
               */
              function _removeMinterForProject(uint256 _projectId) private {
                  // remove minter for project and emit
                  // `minterForProject.get()` reverts tx if no minter set for project
                  numProjectsUsingMinter[minterForProject.get(_projectId)]--;
                  minterForProject.remove(_projectId);
                  emit ProjectMinterRemoved(_projectId);
              }
              /**
               * @notice Mint a token from project `_projectId` to `_to`, originally
               * purchased by `sender`.
               * @param _to The new token's owner.
               * @param _projectId Project ID to mint a new token on.
               * @param sender Address purchasing a new token.
               * @return _tokenId Token ID of minted token
               * @dev reverts w/nonexistent key error when project has no assigned minter
               */
              function mint(
                  address _to,
                  uint256 _projectId,
                  address sender
              ) external returns (uint256 _tokenId) {
                  // CHECKS
                  // minter is the project's minter
                  require(
                      msg.sender == minterForProject.get(_projectId),
                      "Only assigned minter"
                  );
                  // EFFECTS
                  uint256 tokenId = genArtCoreContract.mint_Ecf(_to, _projectId, sender);
                  return tokenId;
              }
              /**
               * @notice Gets the assigned minter for project `_projectId`.
               * @param _projectId Project ID to query.
               * @return address Minter address assigned to project `_projectId`
               * @dev requires project to have an assigned minter
               */
              function getMinterForProject(uint256 _projectId)
                  external
                  view
                  onlyValidProjectId(_projectId)
                  returns (address)
              {
                  (bool _hasMinter, address _currentMinter) = minterForProject.tryGet(
                      _projectId
                  );
                  require(_hasMinter, "No minter assigned");
                  return _currentMinter;
              }
              /**
               * @notice Queries if project `_projectId` has an assigned minter.
               * @param _projectId Project ID to query.
               * @return bool true if project has an assigned minter, else false
               */
              function projectHasMinter(uint256 _projectId)
                  external
                  view
                  onlyValidProjectId(_projectId)
                  returns (bool)
              {
                  (bool _hasMinter, ) = minterForProject.tryGet(_projectId);
                  return _hasMinter;
              }
              /**
               * @notice Gets quantity of projects that have assigned minters.
               * @return uint256 quantity of projects that have assigned minters
               */
              function getNumProjectsWithMinters() external view returns (uint256) {
                  return minterForProject.length();
              }
              /**
               * @notice Get project ID and minter address at index `_index` of
               * enumerable map.
               * @param _index enumerable map index to query.
               * @return projectId project ID at index `_index`
               * @return minterAddress minter address for project at index `_index`
               * @return minterType minter type of minter at minterAddress
               * @dev index must be < quantity of projects that have assigned minters
               */
              function getProjectAndMinterInfoAt(uint256 _index)
                  external
                  view
                  returns (
                      uint256 projectId,
                      address minterAddress,
                      string memory minterType
                  )
              {
                  (projectId, minterAddress) = minterForProject.at(_index);
                  minterType = IFilteredMinterV0(minterAddress).minterType();
                  return (projectId, minterAddress, minterType);
              }
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.0;
          interface IMinterFilterV0 {
              /**
               * @notice Approved minter `_minterAddress`.
               */
              event MinterApproved(address indexed _minterAddress, string _minterType);
              /**
               * @notice Revoked approval for minter `_minterAddress`
               */
              event MinterRevoked(address indexed _minterAddress);
              /**
               * @notice Minter `_minterAddress` of type `_minterType`
               * registered for project `_projectId`.
               */
              event ProjectMinterRegistered(
                  uint256 indexed _projectId,
                  address indexed _minterAddress,
                  string _minterType
              );
              /**
               * @notice Any active minter removed for project `_projectId`.
               */
              event ProjectMinterRemoved(uint256 indexed _projectId);
              function genArt721CoreAddress() external returns (address);
              function setMinterForProject(uint256, address) external;
              function removeMinterForProject(uint256) external;
              function mint(
                  address _to,
                  uint256 _projectId,
                  address sender
              ) external returns (uint256);
              function getMinterForProject(uint256) external view returns (address);
              function projectHasMinter(uint256) external view returns (bool);
          }
          // SPDX-License-Identifier: LGPL-3.0-only
          // Created By: Art Blocks Inc.
          pragma solidity ^0.8.0;
          interface IFilteredMinterV0 {
              /**
               * @notice Price per token in wei updated for project `_projectId` to
               * `_pricePerTokenInWei`.
               */
              event PricePerTokenInWeiUpdated(
                  uint256 indexed _projectId,
                  uint256 indexed _pricePerTokenInWei
              );
              /**
               * @notice Currency updated for project `_projectId` to symbol
               * `_currencySymbol` and address `_currencyAddress`.
               */
              event ProjectCurrencyInfoUpdated(
                  uint256 indexed _projectId,
                  address indexed _currencyAddress,
                  string _currencySymbol
              );
              /// togglePurchaseToDisabled updated
              event PurchaseToDisabledUpdated(
                  uint256 indexed _projectId,
                  bool _purchaseToDisabled
              );
              // getter function of public variable
              function minterType() external view returns (string memory);
              function genArt721CoreAddress() external returns (address);
              function minterFilterAddress() external returns (address);
              // Triggers a purchase of a token from the desired project, to the
              // TX-sending address.
              function purchase(uint256 _projectId)
                  external
                  payable
                  returns (uint256 tokenId);
              // Triggers a purchase of a token from the desired project, to the specified
              // receiving address.
              function purchaseTo(address _to, uint256 _projectId)
                  external
                  payable
                  returns (uint256 tokenId);
              // Toggles the ability for `purchaseTo` to be called directly with a
              // specified receiving address that differs from the TX-sending address.
              function togglePurchaseToDisabled(uint256 _projectId) external;
              // Called to make the minter contract aware of the max invocations for a
              // given project.
              function setProjectMaxInvocations(uint256 _projectId) external;
              // Gets if token price is configured, token price in wei, currency symbol,
              // and currency address, assuming this is project's minter.
              // Supersedes any defined core price.
              function getPriceInfo(uint256 _projectId)
                  external
                  view
                  returns (
                      bool isConfigured,
                      uint256 tokenPriceInWei,
                      string memory currencySymbol,
                      address currencyAddress
                  );
          }
          // 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.0;
          import "./IAdminACLV0.sol";
          /// use the Royalty Registry's IManifold interface for token royalties
          import "./IManifold.sol";
          interface IGenArt721CoreContractV3 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);
              // @dev new function in V3
              function getPrimaryRevenueSplits(uint256 _projectId, uint256 _price)
                  external
                  view
                  returns (
                      uint256 artblocksRevenue_,
                      address payable artblocksAddress_,
                      uint256 artistRevenue_,
                      address payable artistAddress_,
                      uint256 additionalPayeePrimaryRevenue_,
                      address payable additionalPayeePrimaryAddress_
                  );
              // @dev new function in V3
              function projectStateData(uint256 _projectId)
                  external
                  view
                  returns (
                      uint256 invocations,
                      uint256 maxInvocations,
                      bool active,
                      bool paused,
                      uint256 completedTimestamp,
                      bool locked
                  );
              // @dev Art Blocks primary sales payment address
              function artblocksPrimarySalesAddress()
                  external
                  view
                  returns (address payable);
              /**
               * @notice Backwards-compatible (pre-V3) function returning Art Blocks
               * primary sales payment address (now called artblocksPrimarySalesAddress).
               */
              function artblocksAddress() external view returns (address payable);
              // @dev Percentage of primary sales allocated to Art Blocks
              function artblocksPrimarySalesPercentage() external view returns (uint256);
              /**
               * @notice Backwards-compatible (pre-V3) function returning Art Blocks
               * primary sales percentage (now called artblocksPrimarySalesPercentage).
               */
              function artblocksPercentage() external view returns (uint256);
              // @dev Art Blocks secondary sales royalties payment address
              function artblocksSecondarySalesAddress()
                  external
                  view
                  returns (address payable);
              // @dev Basis points of secondary sales allocated to Art Blocks
              function artblocksSecondarySalesBPS() external view returns (uint256);
              // 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);
              /**
               * @notice Backwards-compatible (pre-V3) function  that gets artist +
               * artist's additional payee royalty data for token ID `_tokenId`.
               * WARNING: Does not include Art Blocks portion of royalties.
               */
              function getRoyaltyData(uint256 _tokenId)
                  external
                  view
                  returns (
                      address artistAddress,
                      address additionalPayee,
                      uint256 additionalPayeePercentage,
                      uint256 royaltyFeeByID
                  );
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableMap.sol)
          pragma solidity ^0.8.0;
          import "./EnumerableSet.sol";
          /**
           * @dev Library for managing an enumerable variant of Solidity's
           * https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`]
           * type.
           *
           * Maps have the following properties:
           *
           * - Entries are added, removed, and checked for existence in constant time
           * (O(1)).
           * - Entries are enumerated in O(n). No guarantees are made on the ordering.
           *
           * ```
           * contract Example {
           *     // Add the library methods
           *     using EnumerableMap for EnumerableMap.UintToAddressMap;
           *
           *     // Declare a set state variable
           *     EnumerableMap.UintToAddressMap private myMap;
           * }
           * ```
           *
           * As of v3.0.0, only maps of type `uint256 -> address` (`UintToAddressMap`) are
           * supported.
           */
          library EnumerableMap {
              using EnumerableSet for EnumerableSet.Bytes32Set;
              // To implement this library for multiple types with as little code
              // repetition as possible, we write it in terms of a generic Map type with
              // bytes32 keys and values.
              // The Map implementation uses private functions, and user-facing
              // implementations (such as Uint256ToAddressMap) are just wrappers around
              // the underlying Map.
              // This means that we can only create new EnumerableMaps for types that fit
              // in bytes32.
              struct Map {
                  // Storage of keys
                  EnumerableSet.Bytes32Set _keys;
                  mapping(bytes32 => bytes32) _values;
              }
              /**
               * @dev Adds a key-value pair to a map, or updates the value for an existing
               * key. O(1).
               *
               * Returns true if the key was added to the map, that is if it was not
               * already present.
               */
              function _set(
                  Map storage map,
                  bytes32 key,
                  bytes32 value
              ) private returns (bool) {
                  map._values[key] = value;
                  return map._keys.add(key);
              }
              /**
               * @dev Removes a key-value pair from a map. O(1).
               *
               * Returns true if the key was removed from the map, that is if it was present.
               */
              function _remove(Map storage map, bytes32 key) private returns (bool) {
                  delete map._values[key];
                  return map._keys.remove(key);
              }
              /**
               * @dev Returns true if the key is in the map. O(1).
               */
              function _contains(Map storage map, bytes32 key) private view returns (bool) {
                  return map._keys.contains(key);
              }
              /**
               * @dev Returns the number of key-value pairs in the map. O(1).
               */
              function _length(Map storage map) private view returns (uint256) {
                  return map._keys.length();
              }
              /**
               * @dev Returns the key-value pair stored at position `index` in the map. O(1).
               *
               * Note that there are no guarantees on the ordering of entries inside the
               * array, and it may change when more entries are added or removed.
               *
               * Requirements:
               *
               * - `index` must be strictly less than {length}.
               */
              function _at(Map storage map, uint256 index) private view returns (bytes32, bytes32) {
                  bytes32 key = map._keys.at(index);
                  return (key, map._values[key]);
              }
              /**
               * @dev Tries to returns the value associated with `key`.  O(1).
               * Does not revert if `key` is not in the map.
               */
              function _tryGet(Map storage map, bytes32 key) private view returns (bool, bytes32) {
                  bytes32 value = map._values[key];
                  if (value == bytes32(0)) {
                      return (_contains(map, key), bytes32(0));
                  } else {
                      return (true, value);
                  }
              }
              /**
               * @dev Returns the value associated with `key`.  O(1).
               *
               * Requirements:
               *
               * - `key` must be in the map.
               */
              function _get(Map storage map, bytes32 key) private view returns (bytes32) {
                  bytes32 value = map._values[key];
                  require(value != 0 || _contains(map, key), "EnumerableMap: nonexistent key");
                  return value;
              }
              /**
               * @dev Same as {_get}, with a custom error message when `key` is not in the map.
               *
               * CAUTION: This function is deprecated because it requires allocating memory for the error
               * message unnecessarily. For custom revert reasons use {_tryGet}.
               */
              function _get(
                  Map storage map,
                  bytes32 key,
                  string memory errorMessage
              ) private view returns (bytes32) {
                  bytes32 value = map._values[key];
                  require(value != 0 || _contains(map, key), errorMessage);
                  return value;
              }
              // UintToAddressMap
              struct UintToAddressMap {
                  Map _inner;
              }
              /**
               * @dev Adds a key-value pair to a map, or updates the value for an existing
               * key. O(1).
               *
               * Returns true if the key was added to the map, that is if it was not
               * already present.
               */
              function set(
                  UintToAddressMap storage map,
                  uint256 key,
                  address value
              ) internal returns (bool) {
                  return _set(map._inner, bytes32(key), bytes32(uint256(uint160(value))));
              }
              /**
               * @dev Removes a value from a set. O(1).
               *
               * Returns true if the key was removed from the map, that is if it was present.
               */
              function remove(UintToAddressMap storage map, uint256 key) internal returns (bool) {
                  return _remove(map._inner, bytes32(key));
              }
              /**
               * @dev Returns true if the key is in the map. O(1).
               */
              function contains(UintToAddressMap storage map, uint256 key) internal view returns (bool) {
                  return _contains(map._inner, bytes32(key));
              }
              /**
               * @dev Returns the number of elements in the map. O(1).
               */
              function length(UintToAddressMap storage map) internal view returns (uint256) {
                  return _length(map._inner);
              }
              /**
               * @dev Returns the element 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(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) {
                  (bytes32 key, bytes32 value) = _at(map._inner, index);
                  return (uint256(key), address(uint160(uint256(value))));
              }
              /**
               * @dev Tries to returns the value associated with `key`.  O(1).
               * Does not revert if `key` is not in the map.
               *
               * _Available since v3.4._
               */
              function tryGet(UintToAddressMap storage map, uint256 key) internal view returns (bool, address) {
                  (bool success, bytes32 value) = _tryGet(map._inner, bytes32(key));
                  return (success, address(uint160(uint256(value))));
              }
              /**
               * @dev Returns the value associated with `key`.  O(1).
               *
               * Requirements:
               *
               * - `key` must be in the map.
               */
              function get(UintToAddressMap storage map, uint256 key) internal view returns (address) {
                  return address(uint160(uint256(_get(map._inner, bytes32(key)))));
              }
              /**
               * @dev Same as {get}, with a custom error message when `key` is not in the map.
               *
               * CAUTION: This function is deprecated because it requires allocating memory for the error
               * message unnecessarily. For custom revert reasons use {tryGet}.
               */
              function get(
                  UintToAddressMap storage map,
                  uint256 key,
                  string memory errorMessage
              ) internal view returns (address) {
                  return address(uint160(uint256(_get(map._inner, bytes32(key), errorMessage))));
              }
          }
          // 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 v4.4.1 (utils/structs/EnumerableSet.sol)
          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.
           */
          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) {
                  return _values(set._inner);
              }
              // 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;
                  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 on 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;
                  assembly {
                      result := store
                  }
                  return result;
              }
          }