ETH Price: $2,078.86 (-3.39%)

Transaction Decoder

Block:
16953930 at Apr-01-2023 11:13:47 AM +UTC
Transaction Fee:
0.00246844336568796 ETH $5.13
Gas Used:
136,520 Gas / 18.081184923 Gwei

Emitted Events:

216 0xf1557d9322dc97fee13ba5897863c8df09366213.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000007e7475980e970090b9c228fe66d8635afd0e5e74, 0x0000000000000000000000000000000000000000000000000000000000000004 )
217 TransparentUpgradeableProxy.0x05ebbb6b0ce7d564230ba625dd7a0e5108786b0852d6060de6099e1778203e34( 0x05ebbb6b0ce7d564230ba625dd7a0e5108786b0852d6060de6099e1778203e34, 0x000000000000000000000000f1557d9322dc97fee13ba5897863c8df09366213, 0x0000000000000000000000007e7475980e970090b9c228fe66d8635afd0e5e74, 0x0000000000000000000000000000000000000000000000000000000000000004, 0000000000000000000000000000000000000000000000000000000000000001, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000 )

Account State Difference:

  Address   Before After State Difference Code
0x53F45116...261B2334A
(Foundation: Drop Market)
0x7e747598...AFd0e5e74
0.047270197780788438 Eth
Nonce: 159
0.044801754415100478 Eth
Nonce: 160
0.00246844336568796
(Flashbots: Builder)
1.179945601869161365 Eth1.179959253869161365 Eth0.000013652
0xf1557d93...f09366213

Execution Trace

TransparentUpgradeableProxy.d782d491( )
  • NFTDropMarket.mintFromFixedPriceSaleWithEarlyAccessAllowlist( ) => ( firstTokenId=4 )
    • 0xf1557d9322dc97fee13ba5897863c8df09366213.d1151249( )
      • NFTTimedEditionCollection.mintCountTo( count=1, to=0x7e7475980e970090B9C228fE66d8635AFd0e5e74 ) => ( firstTokenId=4 )
      • AdminUpgradeabilityProxy.55daed3e( )
        • NFTMarket.getExhibitionPaymentDetails( exhibitionId=315 ) => ( curator=0x2CE48F39C5440BF2216ecd04663C563caE49CcAc, takeRateInBasisPoints=500 )
          mintFromFixedPriceSaleWithEarlyAccessAllowlist[NFTDropMarketFixedPriceSale (ln:2925)]
          File 1 of 5: TransparentUpgradeableProxy
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
          import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
          import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
          // Kept for backwards compatibility with older versions of Hardhat and Truffle plugins.
          contract AdminUpgradeabilityProxy is TransparentUpgradeableProxy {
              constructor(address logic, address admin, bytes memory data) payable TransparentUpgradeableProxy(logic, admin, data) {}
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          import "../Proxy.sol";
          import "./ERC1967Upgrade.sol";
          /**
           * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
           * implementation address that can be changed. This address is stored in storage in the location specified by
           * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
           * implementation behind the proxy.
           */
          contract ERC1967Proxy is Proxy, ERC1967Upgrade {
              /**
               * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
               *
               * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded
               * function call, and allows initializating the storage of the proxy like a Solidity constructor.
               */
              constructor(address _logic, bytes memory _data) payable {
                  assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1));
                  _upgradeToAndCall(_logic, _data, false);
              }
              /**
               * @dev Returns the current implementation address.
               */
              function _implementation() internal view virtual override returns (address impl) {
                  return ERC1967Upgrade._getImplementation();
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          import "../ERC1967/ERC1967Proxy.sol";
          /**
           * @dev This contract implements a proxy that is upgradeable by an admin.
           *
           * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
           * clashing], which can potentially be used in an attack, this contract uses the
           * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
           * things that go hand in hand:
           *
           * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
           * that call matches one of the admin functions exposed by the proxy itself.
           * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the
           * implementation. If the admin tries to call a function on the implementation it will fail with an error that says
           * "admin cannot fallback to proxy target".
           *
           * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing
           * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due
           * to sudden errors when trying to call a function from the proxy implementation.
           *
           * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,
           * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.
           */
          contract TransparentUpgradeableProxy is ERC1967Proxy {
              /**
               * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and
               * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.
               */
              constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) {
                  assert(_ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1));
                  _changeAdmin(admin_);
              }
              /**
               * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.
               */
              modifier ifAdmin() {
                  if (msg.sender == _getAdmin()) {
                      _;
                  } else {
                      _fallback();
                  }
              }
              /**
               * @dev Returns the current admin.
               *
               * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.
               *
               * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
               * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
               * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
               */
              function admin() external ifAdmin returns (address admin_) {
                  admin_ = _getAdmin();
              }
              /**
               * @dev Returns the current implementation.
               *
               * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.
               *
               * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
               * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
               * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
               */
              function implementation() external ifAdmin returns (address implementation_) {
                  implementation_ = _implementation();
              }
              /**
               * @dev Changes the admin of the proxy.
               *
               * Emits an {AdminChanged} event.
               *
               * NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}.
               */
              function changeAdmin(address newAdmin) external virtual ifAdmin {
                  _changeAdmin(newAdmin);
              }
              /**
               * @dev Upgrade the implementation of the proxy.
               *
               * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.
               */
              function upgradeTo(address newImplementation) external ifAdmin {
                  _upgradeToAndCall(newImplementation, bytes(""), false);
              }
              /**
               * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified
               * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the
               * proxied contract.
               *
               * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.
               */
              function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {
                  _upgradeToAndCall(newImplementation, data, true);
              }
              /**
               * @dev Returns the current admin.
               */
              function _admin() internal view virtual returns (address) {
                  return _getAdmin();
              }
              /**
               * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.
               */
              function _beforeFallback() internal virtual override {
                  require(msg.sender != _getAdmin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target");
                  super._beforeFallback();
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          import "./TransparentUpgradeableProxy.sol";
          import "../../access/Ownable.sol";
          /**
           * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an
           * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.
           */
          contract ProxyAdmin is Ownable {
              /**
               * @dev Returns the current implementation of `proxy`.
               *
               * Requirements:
               *
               * - This contract must be the admin of `proxy`.
               */
              function getProxyImplementation(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
                  // We need to manually run the static call since the getter cannot be flagged as view
                  // bytes4(keccak256("implementation()")) == 0x5c60da1b
                  (bool success, bytes memory returndata) = address(proxy).staticcall(hex"5c60da1b");
                  require(success);
                  return abi.decode(returndata, (address));
              }
              /**
               * @dev Returns the current admin of `proxy`.
               *
               * Requirements:
               *
               * - This contract must be the admin of `proxy`.
               */
              function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
                  // We need to manually run the static call since the getter cannot be flagged as view
                  // bytes4(keccak256("admin()")) == 0xf851a440
                  (bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440");
                  require(success);
                  return abi.decode(returndata, (address));
              }
              /**
               * @dev Changes the admin of `proxy` to `newAdmin`.
               *
               * Requirements:
               *
               * - This contract must be the current admin of `proxy`.
               */
              function changeProxyAdmin(TransparentUpgradeableProxy proxy, address newAdmin) public virtual onlyOwner {
                  proxy.changeAdmin(newAdmin);
              }
              /**
               * @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}.
               *
               * Requirements:
               *
               * - This contract must be the admin of `proxy`.
               */
              function upgrade(TransparentUpgradeableProxy proxy, address implementation) public virtual onlyOwner {
                  proxy.upgradeTo(implementation);
              }
              /**
               * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See
               * {TransparentUpgradeableProxy-upgradeToAndCall}.
               *
               * Requirements:
               *
               * - This contract must be the admin of `proxy`.
               */
              function upgradeAndCall(TransparentUpgradeableProxy proxy, address implementation, bytes memory data) public payable virtual onlyOwner {
                  proxy.upgradeToAndCall{value: msg.value}(implementation, data);
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          /**
           * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
           * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
           * be specified by overriding the virtual {_implementation} function.
           *
           * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
           * different contract through the {_delegate} function.
           *
           * The success and return data of the delegated call will be returned back to the caller of the proxy.
           */
          abstract contract Proxy {
              /**
               * @dev Delegates the current call to `implementation`.
               *
               * This function does not return to its internall call site, it will return directly to the external caller.
               */
              function _delegate(address implementation) internal virtual {
                  // solhint-disable-next-line no-inline-assembly
                  assembly {
                      // Copy msg.data. We take full control of memory in this inline assembly
                      // block because it will not return to Solidity code. We overwrite the
                      // Solidity scratch pad at memory position 0.
                      calldatacopy(0, 0, calldatasize())
                      // Call the implementation.
                      // out and outsize are 0 because we don't know the size yet.
                      let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
                      // Copy the returned data.
                      returndatacopy(0, 0, returndatasize())
                      switch result
                      // delegatecall returns 0 on error.
                      case 0 { revert(0, returndatasize()) }
                      default { return(0, returndatasize()) }
                  }
              }
              /**
               * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function
               * and {_fallback} should delegate.
               */
              function _implementation() internal view virtual returns (address);
              /**
               * @dev Delegates the current call to the address returned by `_implementation()`.
               *
               * This function does not return to its internall call site, it will return directly to the external caller.
               */
              function _fallback() internal virtual {
                  _beforeFallback();
                  _delegate(_implementation());
              }
              /**
               * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
               * function in the contract matches the call data.
               */
              fallback () external payable virtual {
                  _fallback();
              }
              /**
               * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
               * is empty.
               */
              receive () external payable virtual {
                  _fallback();
              }
              /**
               * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
               * call, or as part of the Solidity `fallback` or `receive` functions.
               *
               * If overriden should call `super._beforeFallback()`.
               */
              function _beforeFallback() internal virtual {
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.2;
          import "../beacon/IBeacon.sol";
          import "../../utils/Address.sol";
          import "../../utils/StorageSlot.sol";
          /**
           * @dev This abstract contract provides getters and event emitting update functions for
           * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
           *
           * _Available since v4.1._
           *
           * @custom:oz-upgrades-unsafe-allow delegatecall
           */
          abstract contract ERC1967Upgrade {
              // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
              bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;
              /**
               * @dev Storage slot with the address of the current implementation.
               * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
               * validated in the constructor.
               */
              bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
              /**
               * @dev Emitted when the implementation is upgraded.
               */
              event Upgraded(address indexed implementation);
              /**
               * @dev Returns the current implementation address.
               */
              function _getImplementation() internal view returns (address) {
                  return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
              }
              /**
               * @dev Stores a new address in the EIP1967 implementation slot.
               */
              function _setImplementation(address newImplementation) private {
                  require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
                  StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
              }
              /**
               * @dev Perform implementation upgrade
               *
               * Emits an {Upgraded} event.
               */
              function _upgradeTo(address newImplementation) internal {
                  _setImplementation(newImplementation);
                  emit Upgraded(newImplementation);
              }
              /**
               * @dev Perform implementation upgrade with additional setup call.
               *
               * Emits an {Upgraded} event.
               */
              function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal {
                  _setImplementation(newImplementation);
                  emit Upgraded(newImplementation);
                  if (data.length > 0 || forceCall) {
                      Address.functionDelegateCall(newImplementation, data);
                  }
              }
              /**
               * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
               *
               * Emits an {Upgraded} event.
               */
              function _upgradeToAndCallSecure(address newImplementation, bytes memory data, bool forceCall) internal {
                  address oldImplementation = _getImplementation();
                  // Initial upgrade and setup call
                  _setImplementation(newImplementation);
                  if (data.length > 0 || forceCall) {
                      Address.functionDelegateCall(newImplementation, data);
                  }
                  // Perform rollback test if not already in progress
                  StorageSlot.BooleanSlot storage rollbackTesting = StorageSlot.getBooleanSlot(_ROLLBACK_SLOT);
                  if (!rollbackTesting.value) {
                      // Trigger rollback using upgradeTo from the new implementation
                      rollbackTesting.value = true;
                      Address.functionDelegateCall(
                          newImplementation,
                          abi.encodeWithSignature(
                              "upgradeTo(address)",
                              oldImplementation
                          )
                      );
                      rollbackTesting.value = false;
                      // Check rollback was effective
                      require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades");
                      // Finally reset to the new implementation and log the upgrade
                      _setImplementation(newImplementation);
                      emit Upgraded(newImplementation);
                  }
              }
              /**
               * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
               * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
               *
               * Emits a {BeaconUpgraded} event.
               */
              function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal {
                  _setBeacon(newBeacon);
                  emit BeaconUpgraded(newBeacon);
                  if (data.length > 0 || forceCall) {
                      Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
                  }
              }
              /**
               * @dev Storage slot with the admin of the contract.
               * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
               * validated in the constructor.
               */
              bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
              /**
               * @dev Emitted when the admin account has changed.
               */
              event AdminChanged(address previousAdmin, address newAdmin);
              /**
               * @dev Returns the current admin.
               */
              function _getAdmin() internal view returns (address) {
                  return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;
              }
              /**
               * @dev Stores a new address in the EIP1967 admin slot.
               */
              function _setAdmin(address newAdmin) private {
                  require(newAdmin != address(0), "ERC1967: new admin is the zero address");
                  StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
              }
              /**
               * @dev Changes the admin of the proxy.
               *
               * Emits an {AdminChanged} event.
               */
              function _changeAdmin(address newAdmin) internal {
                  emit AdminChanged(_getAdmin(), newAdmin);
                  _setAdmin(newAdmin);
              }
              /**
               * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
               * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
               */
              bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
              /**
               * @dev Emitted when the beacon is upgraded.
               */
              event BeaconUpgraded(address indexed beacon);
              /**
               * @dev Returns the current beacon.
               */
              function _getBeacon() internal view returns (address) {
                  return StorageSlot.getAddressSlot(_BEACON_SLOT).value;
              }
              /**
               * @dev Stores a new beacon in the EIP1967 beacon slot.
               */
              function _setBeacon(address newBeacon) private {
                  require(
                      Address.isContract(newBeacon),
                      "ERC1967: new beacon is not a contract"
                  );
                  require(
                      Address.isContract(IBeacon(newBeacon).implementation()),
                      "ERC1967: beacon implementation is not a contract"
                  );
                  StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          /**
           * @dev This is the interface that {BeaconProxy} expects of its beacon.
           */
          interface IBeacon {
              /**
               * @dev Must return an address that can be used as a delegate call target.
               *
               * {BeaconProxy} will check that this address is a contract.
               */
              function implementation() external view returns (address);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          /**
           * @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
               * ====
               */
              function isContract(address account) internal view returns (bool) {
                  // This method relies on extcodesize, which returns 0 for contracts in
                  // construction, since the code is only stored at the end of the
                  // constructor execution.
                  uint256 size;
                  // solhint-disable-next-line no-inline-assembly
                  assembly { size := extcodesize(account) }
                  return size > 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");
                  // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
                  (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");
                  // solhint-disable-next-line avoid-low-level-calls
                  (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");
                  // solhint-disable-next-line avoid-low-level-calls
                  (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");
                  // solhint-disable-next-line avoid-low-level-calls
                  (bool success, bytes memory returndata) = target.delegatecall(data);
                  return _verifyCallResult(success, returndata, errorMessage);
              }
              function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private 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
                          // solhint-disable-next-line no-inline-assembly
                          assembly {
                              let returndata_size := mload(returndata)
                              revert(add(32, returndata), returndata_size)
                          }
                      } else {
                          revert(errorMessage);
                      }
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          /**
           * @dev Library for reading and writing primitive types to specific storage slots.
           *
           * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
           * This library helps with reading and writing to such slots without the need for inline assembly.
           *
           * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
           *
           * Example usage to set ERC1967 implementation slot:
           * ```
           * contract ERC1967 {
           *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
           *
           *     function _getImplementation() internal view returns (address) {
           *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
           *     }
           *
           *     function _setImplementation(address newImplementation) internal {
           *         require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
           *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
           *     }
           * }
           * ```
           *
           * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._
           */
          library StorageSlot {
              struct AddressSlot {
                  address value;
              }
              struct BooleanSlot {
                  bool value;
              }
              struct Bytes32Slot {
                  bytes32 value;
              }
              struct Uint256Slot {
                  uint256 value;
              }
              /**
               * @dev Returns an `AddressSlot` with member `value` located at `slot`.
               */
              function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
                  assembly {
                      r.slot := slot
                  }
              }
              /**
               * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
               */
              function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
                  assembly {
                      r.slot := slot
                  }
              }
              /**
               * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
               */
              function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
                  assembly {
                      r.slot := slot
                  }
              }
              /**
               * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
               */
              function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
                  assembly {
                      r.slot := slot
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          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 () {
                  address msgSender = _msgSender();
                  _owner = msgSender;
                  emit OwnershipTransferred(address(0), msgSender);
              }
              /**
               * @dev Returns the address of the current owner.
               */
              function owner() public view virtual returns (address) {
                  return _owner;
              }
              /**
               * @dev Throws if called by any account other than the owner.
               */
              modifier onlyOwner() {
                  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 {
                  emit OwnershipTransferred(_owner, address(0));
                  _owner = 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");
                  emit OwnershipTransferred(_owner, newOwner);
                  _owner = newOwner;
              }
          }
          // SPDX-License-Identifier: MIT
          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) {
                  this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                  return msg.data;
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          import "../ERC1967/ERC1967Upgrade.sol";
          /**
           * @dev Base contract for building openzeppelin-upgrades compatible implementations for the {ERC1967Proxy}. It includes
           * publicly available upgrade functions that are called by the plugin and by the secure upgrade mechanism to verify
           * continuation of the upgradability.
           *
           * The {_authorizeUpgrade} function MUST be overridden to include access restriction to the upgrade mechanism.
           *
           * _Available since v4.1._
           */
          abstract contract UUPSUpgradeable is ERC1967Upgrade {
              function upgradeTo(address newImplementation) external virtual {
                  _authorizeUpgrade(newImplementation);
                  _upgradeToAndCallSecure(newImplementation, bytes(""), false);
              }
              function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual {
                  _authorizeUpgrade(newImplementation);
                  _upgradeToAndCallSecure(newImplementation, data, true);
              }
              function _authorizeUpgrade(address newImplementation) internal virtual;
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.2;
          import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
          abstract contract Proxiable is UUPSUpgradeable {
              function _authorizeUpgrade(address newImplementation) internal override {
                  _beforeUpgrade(newImplementation);
              }
              function _beforeUpgrade(address newImplementation) internal virtual;
          }
          contract ChildOfProxiable is Proxiable {
              function _beforeUpgrade(address newImplementation) internal virtual override {}
          }
          

          File 2 of 5: NFTDropMarket
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          /// @author: manifold.xyz
          import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
          /**
           * @dev Royalty registry interface
           */
          interface IRoyaltyRegistry is IERC165 {
              event RoyaltyOverride(address owner, address tokenAddress, address royaltyAddress);
              /**
               * Override the location of where to look up royalty information for a given token contract.
               * Allows for backwards compatibility and implementation of royalty logic for contracts that did not previously support them.
               *
               * @param tokenAddress    - The token address you wish to override
               * @param royaltyAddress  - The royalty override address
               */
              function setRoyaltyLookupAddress(address tokenAddress, address royaltyAddress) external returns (bool);
              /**
               * Returns royalty address location.  Returns the tokenAddress by default, or the override if it exists
               *
               * @param tokenAddress    - The token address you are looking up the royalty for
               */
              function getRoyaltyLookupAddress(address tokenAddress) external view returns (address);
              /**
               * Returns the token address that an overrideAddress is set for.
               * Note: will not be accurate if the override was created before this function was added.
               *
               * @param overrideAddress - The override address you are looking up the token for
               */
              function getOverrideLookupTokenAddress(address overrideAddress) external view returns (address);
              /**
               * Whether or not the message sender can override the royalty address for the given token address
               *
               * @param tokenAddress    - The token address you are looking up the royalty for
               */
              function overrideAllowed(address tokenAddress) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.1) (proxy/utils/Initializable.sol)
          pragma solidity ^0.8.2;
          import "../../utils/AddressUpgradeable.sol";
          /**
           * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
           * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
           * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
           * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
           *
           * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
           * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
           * case an upgrade adds a module that needs to be initialized.
           *
           * For example:
           *
           * [.hljs-theme-light.nopadding]
           * ```
           * contract MyToken is ERC20Upgradeable {
           *     function initialize() initializer public {
           *         __ERC20_init("MyToken", "MTK");
           *     }
           * }
           * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
           *     function initializeV2() reinitializer(2) public {
           *         __ERC20Permit_init("MyToken");
           *     }
           * }
           * ```
           *
           * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
           * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
           *
           * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
           * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
           *
           * [CAUTION]
           * ====
           * Avoid leaving a contract uninitialized.
           *
           * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
           * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
           * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
           *
           * [.hljs-theme-light.nopadding]
           * ```
           * /// @custom:oz-upgrades-unsafe-allow constructor
           * constructor() {
           *     _disableInitializers();
           * }
           * ```
           * ====
           */
          abstract contract Initializable {
              /**
               * @dev Indicates that the contract has been initialized.
               * @custom:oz-retyped-from bool
               */
              uint8 private _initialized;
              /**
               * @dev Indicates that the contract is in the process of being initialized.
               */
              bool private _initializing;
              /**
               * @dev Triggered when the contract has been initialized or reinitialized.
               */
              event Initialized(uint8 version);
              /**
               * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
               * `onlyInitializing` functions can be used to initialize parent contracts.
               *
               * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
               * constructor.
               *
               * Emits an {Initialized} event.
               */
              modifier initializer() {
                  bool isTopLevelCall = !_initializing;
                  require(
                      (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
                      "Initializable: contract is already initialized"
                  );
                  _initialized = 1;
                  if (isTopLevelCall) {
                      _initializing = true;
                  }
                  _;
                  if (isTopLevelCall) {
                      _initializing = false;
                      emit Initialized(1);
                  }
              }
              /**
               * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
               * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
               * used to initialize parent contracts.
               *
               * A reinitializer may be used after the original initialization step. This is essential to configure modules that
               * are added through upgrades and that require initialization.
               *
               * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
               * cannot be nested. If one is invoked in the context of another, execution will revert.
               *
               * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
               * a contract, executing them in the right order is up to the developer or operator.
               *
               * WARNING: setting the version to 255 will prevent any future reinitialization.
               *
               * Emits an {Initialized} event.
               */
              modifier reinitializer(uint8 version) {
                  require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
                  _initialized = version;
                  _initializing = true;
                  _;
                  _initializing = false;
                  emit Initialized(version);
              }
              /**
               * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
               * {initializer} and {reinitializer} modifiers, directly or indirectly.
               */
              modifier onlyInitializing() {
                  require(_initializing, "Initializable: contract is not initializing");
                  _;
              }
              /**
               * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
               * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
               * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
               * through proxies.
               *
               * Emits an {Initialized} event the first time it is successfully executed.
               */
              function _disableInitializers() internal virtual {
                  require(!_initializing, "Initializable: contract is initializing");
                  if (_initialized < type(uint8).max) {
                      _initialized = type(uint8).max;
                      emit Initialized(type(uint8).max);
                  }
              }
              /**
               * @dev Returns the highest version that has been initialized. See {reinitializer}.
               */
              function _getInitializedVersion() internal view returns (uint8) {
                  return _initialized;
              }
              /**
               * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
               */
              function _isInitializing() internal view returns (bool) {
                  return _initializing;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (security/ReentrancyGuard.sol)
          pragma solidity ^0.8.0;
          import "../proxy/utils/Initializable.sol";
          /**
           * @dev Contract module that helps prevent reentrant calls to a function.
           *
           * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
           * available, which can be applied to functions to make sure there are no nested
           * (reentrant) calls to them.
           *
           * Note that because there is a single `nonReentrant` guard, functions marked as
           * `nonReentrant` may not call one another. This can be worked around by making
           * those functions `private`, and then adding `external` `nonReentrant` entry
           * points to them.
           *
           * TIP: If you would like to learn more about reentrancy and alternative ways
           * to protect against it, check out our blog post
           * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
           */
          abstract contract ReentrancyGuardUpgradeable is Initializable {
              // Booleans are more expensive than uint256 or any type that takes up a full
              // word because each write operation emits an extra SLOAD to first read the
              // slot's contents, replace the bits taken up by the boolean, and then write
              // back. This is the compiler's defense against contract upgrades and
              // pointer aliasing, and it cannot be disabled.
              // The values being non-zero value makes deployment a bit more expensive,
              // but in exchange the refund on every call to nonReentrant will be lower in
              // amount. Since refunds are capped to a percentage of the total
              // transaction's gas, it is best to keep them low in cases like this one, to
              // increase the likelihood of the full refund coming into effect.
              uint256 private constant _NOT_ENTERED = 1;
              uint256 private constant _ENTERED = 2;
              uint256 private _status;
              function __ReentrancyGuard_init() internal onlyInitializing {
                  __ReentrancyGuard_init_unchained();
              }
              function __ReentrancyGuard_init_unchained() internal onlyInitializing {
                  _status = _NOT_ENTERED;
              }
              /**
               * @dev Prevents a contract from calling itself, directly or indirectly.
               * Calling a `nonReentrant` function from another `nonReentrant`
               * function is not supported. It is possible to prevent this from happening
               * by making the `nonReentrant` function external, and making it call a
               * `private` function that does the actual work.
               */
              modifier nonReentrant() {
                  _nonReentrantBefore();
                  _;
                  _nonReentrantAfter();
              }
              function _nonReentrantBefore() private {
                  // On the first call to nonReentrant, _status will be _NOT_ENTERED
                  require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
                  // Any calls to nonReentrant after this point will fail
                  _status = _ENTERED;
              }
              function _nonReentrantAfter() private {
                  // By storing the original value once again, a refund is triggered (see
                  // https://eips.ethereum.org/EIPS/eip-2200)
                  _status = _NOT_ENTERED;
              }
              /**
               * @dev This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[49] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)
          pragma solidity ^0.8.1;
          /**
           * @dev Collection of functions related to the address type
           */
          library AddressUpgradeable {
              /**
               * @dev Returns true if `account` is a contract.
               *
               * [IMPORTANT]
               * ====
               * It is unsafe to assume that an address for which this function returns
               * false is an externally-owned account (EOA) and not a contract.
               *
               * Among others, `isContract` will return false for the following
               * types of addresses:
               *
               *  - an externally-owned account
               *  - a contract in construction
               *  - an address where a contract will be created
               *  - an address where a contract lived, but was destroyed
               * ====
               *
               * [IMPORTANT]
               * ====
               * You shouldn't rely on `isContract` to protect against flash loan attacks!
               *
               * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
               * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
               * constructor.
               * ====
               */
              function isContract(address account) internal view returns (bool) {
                  // This method relies on extcodesize/address.code.length, which returns 0
                  // for contracts in construction, since the code is only stored at the end
                  // of the constructor execution.
                  return account.code.length > 0;
              }
              /**
               * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
               * `recipient`, forwarding all available gas and reverting on errors.
               *
               * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
               * of certain opcodes, possibly making contracts go over the 2300 gas limit
               * imposed by `transfer`, making them unable to receive funds via
               * `transfer`. {sendValue} removes this limitation.
               *
               * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
               *
               * IMPORTANT: because control is transferred to `recipient`, care must be
               * taken to not create reentrancy vulnerabilities. Consider using
               * {ReentrancyGuard} or the
               * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
               */
              function sendValue(address payable recipient, uint256 amount) internal {
                  require(address(this).balance >= amount, "Address: insufficient balance");
                  (bool success, ) = recipient.call{value: amount}("");
                  require(success, "Address: unable to send value, recipient may have reverted");
              }
              /**
               * @dev Performs a Solidity function call using a low level `call`. A
               * plain `call` is an unsafe replacement for a function call: use this
               * function instead.
               *
               * If `target` reverts with a revert reason, it is bubbled up by this
               * function (like regular Solidity function calls).
               *
               * Returns the raw returned data. To convert to the expected return value,
               * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
               *
               * Requirements:
               *
               * - `target` must be a contract.
               * - calling `target` with `data` must not revert.
               *
               * _Available since v3.1._
               */
              function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, 0, "Address: low-level call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
               * `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, 0, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but also transferring `value` wei to `target`.
               *
               * Requirements:
               *
               * - the calling contract must have an ETH balance of at least `value`.
               * - the called Solidity function must be `payable`.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
              }
              /**
               * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
               * with `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  require(address(this).balance >= value, "Address: insufficient balance for call");
                  (bool success, bytes memory returndata) = target.call{value: value}(data);
                  return verifyCallResultFromTarget(target, success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                  return functionStaticCall(target, data, "Address: low-level static call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal view returns (bytes memory) {
                  (bool success, bytes memory returndata) = target.staticcall(data);
                  return verifyCallResultFromTarget(target, success, returndata, errorMessage);
              }
              /**
               * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
               * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
               *
               * _Available since v4.8._
               */
              function verifyCallResultFromTarget(
                  address target,
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal view returns (bytes memory) {
                  if (success) {
                      if (returndata.length == 0) {
                          // only check isContract if the call was successful and the return data is empty
                          // otherwise we already know that it was a contract
                          require(isContract(target), "Address: call to non-contract");
                      }
                      return returndata;
                  } else {
                      _revert(returndata, errorMessage);
                  }
              }
              /**
               * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
               * revert reason or using the provided one.
               *
               * _Available since v4.3._
               */
              function verifyCallResult(
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal pure returns (bytes memory) {
                  if (success) {
                      return returndata;
                  } else {
                      _revert(returndata, errorMessage);
                  }
              }
              function _revert(bytes memory returndata, string memory errorMessage) private pure {
                  // Look for revert reason and bubble it up if present
                  if (returndata.length > 0) {
                      // The easiest way to bubble the revert reason is using memory via assembly
                      /// @solidity memory-safe-assembly
                      assembly {
                          let returndata_size := mload(returndata)
                          revert(add(32, returndata), returndata_size)
                      }
                  } else {
                      revert(errorMessage);
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev External interface of AccessControl declared to support ERC165 detection.
           */
          interface IAccessControl {
              /**
               * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
               *
               * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
               * {RoleAdminChanged} not being emitted signaling this.
               *
               * _Available since v3.1._
               */
              event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
              /**
               * @dev Emitted when `account` is granted `role`.
               *
               * `sender` is the account that originated the contract call, an admin role
               * bearer except when using {AccessControl-_setupRole}.
               */
              event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
              /**
               * @dev Emitted when `account` is revoked `role`.
               *
               * `sender` is the account that originated the contract call:
               *   - if using `revokeRole`, it is the admin role bearer
               *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
               */
              event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
              /**
               * @dev Returns `true` if `account` has been granted `role`.
               */
              function hasRole(bytes32 role, address account) external view returns (bool);
              /**
               * @dev Returns the admin role that controls `role`. See {grantRole} and
               * {revokeRole}.
               *
               * To change a role's admin, use {AccessControl-_setRoleAdmin}.
               */
              function getRoleAdmin(bytes32 role) external view returns (bytes32);
              /**
               * @dev Grants `role` to `account`.
               *
               * If `account` had not been already granted `role`, emits a {RoleGranted}
               * event.
               *
               * Requirements:
               *
               * - the caller must have ``role``'s admin role.
               */
              function grantRole(bytes32 role, address account) external;
              /**
               * @dev Revokes `role` from `account`.
               *
               * If `account` had been granted `role`, emits a {RoleRevoked} event.
               *
               * Requirements:
               *
               * - the caller must have ``role``'s admin role.
               */
              function revokeRole(bytes32 role, address account) external;
              /**
               * @dev Revokes `role` from the calling account.
               *
               * Roles are often managed via {grantRole} and {revokeRole}: this function's
               * purpose is to provide a mechanism for accounts to lose their privileges
               * if they are compromised (such as when a trusted device is misplaced).
               *
               * If the calling account had been granted `role`, emits a {RoleRevoked}
               * event.
               *
               * Requirements:
               *
               * - the caller must be `account`.
               */
              function renounceRole(bytes32 role, address account) external;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol)
          pragma solidity ^0.8.0;
          import "../../utils/introspection/IERC165.sol";
          /**
           * @dev Required interface of an ERC721 compliant contract.
           */
          interface IERC721 is IERC165 {
              /**
               * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
               */
              event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
              /**
               * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
               */
              event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
              /**
               * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
               */
              event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
              /**
               * @dev Returns the number of tokens in ``owner``'s account.
               */
              function balanceOf(address owner) external view returns (uint256 balance);
              /**
               * @dev Returns the owner of the `tokenId` token.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               */
              function ownerOf(uint256 tokenId) external view returns (address owner);
              /**
               * @dev Safely transfers `tokenId` token from `from` to `to`.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must exist and be owned by `from`.
               * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
               * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes calldata data
              ) external;
              /**
               * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
               * are aware of the ERC721 protocol to prevent tokens from being forever locked.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must exist and be owned by `from`.
               * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
               * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) external;
              /**
               * @dev Transfers `tokenId` token from `from` to `to`.
               *
               * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
               * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
               * understand this adds an external call which potentially creates a reentrancy vulnerability.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must be owned by `from`.
               * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
               *
               * Emits a {Transfer} event.
               */
              function transferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) external;
              /**
               * @dev Gives permission to `to` to transfer `tokenId` token to another account.
               * The approval is cleared when the token is transferred.
               *
               * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
               *
               * Requirements:
               *
               * - The caller must own the token or be an approved operator.
               * - `tokenId` must exist.
               *
               * Emits an {Approval} event.
               */
              function approve(address to, uint256 tokenId) external;
              /**
               * @dev Approve or remove `operator` as an operator for the caller.
               * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
               *
               * Requirements:
               *
               * - The `operator` cannot be the caller.
               *
               * Emits an {ApprovalForAll} event.
               */
              function setApprovalForAll(address operator, bool _approved) external;
              /**
               * @dev Returns the account approved for `tokenId` token.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               */
              function getApproved(uint256 tokenId) external view returns (address operator);
              /**
               * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
               *
               * See {setApprovalForAll}
               */
              function isApprovedForAll(address owner, address operator) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Provides information about the current execution context, including the
           * sender of the transaction and its data. While these are generally available
           * via msg.sender and msg.data, they should not be accessed in such a direct
           * manner, since when dealing with meta-transactions the account sending and
           * paying for execution may not be the actual sender (as far as an application
           * is concerned).
           *
           * This contract is only required for intermediate, library-like contracts.
           */
          abstract contract Context {
              function _msgSender() internal view virtual returns (address) {
                  return msg.sender;
              }
              function _msgData() internal view virtual returns (bytes calldata) {
                  return msg.data;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/MerkleProof.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev These functions deal with verification of Merkle Tree proofs.
           *
           * The tree and the proofs can be generated using our
           * https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
           * You will find a quickstart guide in the readme.
           *
           * WARNING: You should avoid using leaf values that are 64 bytes long prior to
           * hashing, or use a hash function other than keccak256 for hashing leaves.
           * This is because the concatenation of a sorted pair of internal nodes in
           * the merkle tree could be reinterpreted as a leaf value.
           * OpenZeppelin's JavaScript library generates merkle trees that are safe
           * against this attack out of the box.
           */
          library MerkleProof {
              /**
               * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
               * defined by `root`. For this, a `proof` must be provided, containing
               * sibling hashes on the branch from the leaf to the root of the tree. Each
               * pair of leaves and each pair of pre-images are assumed to be sorted.
               */
              function verify(
                  bytes32[] memory proof,
                  bytes32 root,
                  bytes32 leaf
              ) internal pure returns (bool) {
                  return processProof(proof, leaf) == root;
              }
              /**
               * @dev Calldata version of {verify}
               *
               * _Available since v4.7._
               */
              function verifyCalldata(
                  bytes32[] calldata proof,
                  bytes32 root,
                  bytes32 leaf
              ) internal pure returns (bool) {
                  return processProofCalldata(proof, leaf) == root;
              }
              /**
               * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
               * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
               * hash matches the root of the tree. When processing the proof, the pairs
               * of leafs & pre-images are assumed to be sorted.
               *
               * _Available since v4.4._
               */
              function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
                  bytes32 computedHash = leaf;
                  for (uint256 i = 0; i < proof.length; i++) {
                      computedHash = _hashPair(computedHash, proof[i]);
                  }
                  return computedHash;
              }
              /**
               * @dev Calldata version of {processProof}
               *
               * _Available since v4.7._
               */
              function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
                  bytes32 computedHash = leaf;
                  for (uint256 i = 0; i < proof.length; i++) {
                      computedHash = _hashPair(computedHash, proof[i]);
                  }
                  return computedHash;
              }
              /**
               * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a merkle tree defined by
               * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
               *
               * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
               *
               * _Available since v4.7._
               */
              function multiProofVerify(
                  bytes32[] memory proof,
                  bool[] memory proofFlags,
                  bytes32 root,
                  bytes32[] memory leaves
              ) internal pure returns (bool) {
                  return processMultiProof(proof, proofFlags, leaves) == root;
              }
              /**
               * @dev Calldata version of {multiProofVerify}
               *
               * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
               *
               * _Available since v4.7._
               */
              function multiProofVerifyCalldata(
                  bytes32[] calldata proof,
                  bool[] calldata proofFlags,
                  bytes32 root,
                  bytes32[] memory leaves
              ) internal pure returns (bool) {
                  return processMultiProofCalldata(proof, proofFlags, leaves) == root;
              }
              /**
               * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
               * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
               * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
               * respectively.
               *
               * CAUTION: Not all merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
               * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
               * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
               *
               * _Available since v4.7._
               */
              function processMultiProof(
                  bytes32[] memory proof,
                  bool[] memory proofFlags,
                  bytes32[] memory leaves
              ) internal pure returns (bytes32 merkleRoot) {
                  // This function rebuild the root hash by traversing the tree up from the leaves. The root is rebuilt by
                  // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
                  // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
                  // the merkle tree.
                  uint256 leavesLen = leaves.length;
                  uint256 totalHashes = proofFlags.length;
                  // Check proof validity.
                  require(leavesLen + proof.length - 1 == totalHashes, "MerkleProof: invalid multiproof");
                  // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
                  // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
                  bytes32[] memory hashes = new bytes32[](totalHashes);
                  uint256 leafPos = 0;
                  uint256 hashPos = 0;
                  uint256 proofPos = 0;
                  // At each step, we compute the next hash using two values:
                  // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
                  //   get the next hash.
                  // - depending on the flag, either another value for the "main queue" (merging branches) or an element from the
                  //   `proof` array.
                  for (uint256 i = 0; i < totalHashes; i++) {
                      bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
                      bytes32 b = proofFlags[i] ? leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++] : proof[proofPos++];
                      hashes[i] = _hashPair(a, b);
                  }
                  if (totalHashes > 0) {
                      return hashes[totalHashes - 1];
                  } else if (leavesLen > 0) {
                      return leaves[0];
                  } else {
                      return proof[0];
                  }
              }
              /**
               * @dev Calldata version of {processMultiProof}.
               *
               * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
               *
               * _Available since v4.7._
               */
              function processMultiProofCalldata(
                  bytes32[] calldata proof,
                  bool[] calldata proofFlags,
                  bytes32[] memory leaves
              ) internal pure returns (bytes32 merkleRoot) {
                  // This function rebuild the root hash by traversing the tree up from the leaves. The root is rebuilt by
                  // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
                  // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
                  // the merkle tree.
                  uint256 leavesLen = leaves.length;
                  uint256 totalHashes = proofFlags.length;
                  // Check proof validity.
                  require(leavesLen + proof.length - 1 == totalHashes, "MerkleProof: invalid multiproof");
                  // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
                  // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
                  bytes32[] memory hashes = new bytes32[](totalHashes);
                  uint256 leafPos = 0;
                  uint256 hashPos = 0;
                  uint256 proofPos = 0;
                  // At each step, we compute the next hash using two values:
                  // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
                  //   get the next hash.
                  // - depending on the flag, either another value for the "main queue" (merging branches) or an element from the
                  //   `proof` array.
                  for (uint256 i = 0; i < totalHashes; i++) {
                      bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
                      bytes32 b = proofFlags[i] ? leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++] : proof[proofPos++];
                      hashes[i] = _hashPair(a, b);
                  }
                  if (totalHashes > 0) {
                      return hashes[totalHashes - 1];
                  } else if (leavesLen > 0) {
                      return leaves[0];
                  } else {
                      return proof[0];
                  }
              }
              function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
                  return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
              }
              function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
                  /// @solidity memory-safe-assembly
                  assembly {
                      mstore(0x00, a)
                      mstore(0x20, b)
                      value := keccak256(0x00, 0x40)
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/introspection/ERC165Checker.sol)
          pragma solidity ^0.8.0;
          import "./IERC165.sol";
          /**
           * @dev Library used to query support of an interface declared via {IERC165}.
           *
           * Note that these functions return the actual result of the query: they do not
           * `revert` if an interface is not supported. It is up to the caller to decide
           * what to do in these cases.
           */
          library ERC165Checker {
              // As per the EIP-165 spec, no interface should ever match 0xffffffff
              bytes4 private constant _INTERFACE_ID_INVALID = 0xffffffff;
              /**
               * @dev Returns true if `account` supports the {IERC165} interface.
               */
              function supportsERC165(address account) internal view returns (bool) {
                  // Any contract that implements ERC165 must explicitly indicate support of
                  // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid
                  return
                      supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId) &&
                      !supportsERC165InterfaceUnchecked(account, _INTERFACE_ID_INVALID);
              }
              /**
               * @dev Returns true if `account` supports the interface defined by
               * `interfaceId`. Support for {IERC165} itself is queried automatically.
               *
               * See {IERC165-supportsInterface}.
               */
              function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) {
                  // query support of both ERC165 as per the spec and support of _interfaceId
                  return supportsERC165(account) && supportsERC165InterfaceUnchecked(account, interfaceId);
              }
              /**
               * @dev Returns a boolean array where each value corresponds to the
               * interfaces passed in and whether they're supported or not. This allows
               * you to batch check interfaces for a contract where your expectation
               * is that some interfaces may not be supported.
               *
               * See {IERC165-supportsInterface}.
               *
               * _Available since v3.4._
               */
              function getSupportedInterfaces(address account, bytes4[] memory interfaceIds)
                  internal
                  view
                  returns (bool[] memory)
              {
                  // an array of booleans corresponding to interfaceIds and whether they're supported or not
                  bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length);
                  // query support of ERC165 itself
                  if (supportsERC165(account)) {
                      // query support of each interface in interfaceIds
                      for (uint256 i = 0; i < interfaceIds.length; i++) {
                          interfaceIdsSupported[i] = supportsERC165InterfaceUnchecked(account, interfaceIds[i]);
                      }
                  }
                  return interfaceIdsSupported;
              }
              /**
               * @dev Returns true if `account` supports all the interfaces defined in
               * `interfaceIds`. Support for {IERC165} itself is queried automatically.
               *
               * Batch-querying can lead to gas savings by skipping repeated checks for
               * {IERC165} support.
               *
               * See {IERC165-supportsInterface}.
               */
              function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) {
                  // query support of ERC165 itself
                  if (!supportsERC165(account)) {
                      return false;
                  }
                  // query support of each interface in interfaceIds
                  for (uint256 i = 0; i < interfaceIds.length; i++) {
                      if (!supportsERC165InterfaceUnchecked(account, interfaceIds[i])) {
                          return false;
                      }
                  }
                  // all interfaces supported
                  return true;
              }
              /**
               * @notice Query if a contract implements an interface, does not check ERC165 support
               * @param account The address of the contract to query for support of an interface
               * @param interfaceId The interface identifier, as specified in ERC-165
               * @return true if the contract at account indicates support of the interface with
               * identifier interfaceId, false otherwise
               * @dev Assumes that account contains a contract that supports ERC165, otherwise
               * the behavior of this method is undefined. This precondition can be checked
               * with {supportsERC165}.
               * Interface identification is specified in ERC-165.
               */
              function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) {
                  // prepare call
                  bytes memory encodedParams = abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId);
                  // perform static call
                  bool success;
                  uint256 returnSize;
                  uint256 returnValue;
                  assembly {
                      success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20)
                      returnSize := returndatasize()
                      returnValue := mload(0x00)
                  }
                  return success && returnSize >= 0x20 && returnValue > 0;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Interface of the ERC165 standard, as defined in the
           * https://eips.ethereum.org/EIPS/eip-165[EIP].
           *
           * Implementers can declare support of contract interfaces, which can then be
           * queried by others ({ERC165Checker}).
           *
           * For an implementation, see {ERC165}.
           */
          interface IERC165 {
              /**
               * @dev Returns true if this contract implements the interface defined by
               * `interfaceId`. See the corresponding
               * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
               * to learn more about how these ids are created.
               *
               * This function call must use less than 30 000 gas.
               */
              function supportsInterface(bytes4 interfaceId) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SafeCast.sol)
          // This file was procedurally generated from scripts/generate/templates/SafeCast.js.
          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 downcasted) {
                  downcasted = int248(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 248 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int240(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 240 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int232(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 232 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int224(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 224 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int216(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 216 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int208(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 208 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int200(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 200 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int192(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 192 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int184(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 184 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int176(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 176 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int168(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 168 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int160(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 160 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int152(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 152 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int144(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 144 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int136(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 136 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int128(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 128 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int120(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 120 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int112(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 112 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int104(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 104 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int96(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 96 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int88(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 88 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int80(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 80 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int72(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 72 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int64(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 64 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int56(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 56 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int48(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 48 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int40(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 40 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int32(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 32 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int24(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 24 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int16(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 16 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int8(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 8 bits");
              }
              /**
               * @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: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @notice Interface for functions the market uses in FETH.
           * @author batu-inal & HardlyDifficult
           */
          interface IFethMarket {
            function depositFor(address account) external payable;
            function marketLockupFor(address account, uint256 amount) external payable returns (uint256 expiration);
            function marketWithdrawFrom(address from, uint256 amount) external;
            function marketWithdrawLocked(address account, uint256 expiration, uint256 amount) external;
            function marketUnlockFor(address account, uint256 expiration, uint256 amount) external;
            function marketChangeLockup(
              address unlockFrom,
              uint256 unlockExpiration,
              uint256 unlockAmount,
              address lockupFor,
              uint256 lockupAmount
            ) external payable returns (uint256 expiration);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @title Declares the type of the collection contract.
           * @dev This interface is declared as an ERC-165 interface.
           * @author reggieag
           */
          interface INFTCollectionType {
            function getNFTCollectionType() external view returns (string memory collectionType);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @title The required interface for collections to support minting from the NFTDropMarket.
           * @dev This interface must be registered as a ERC165 supported interface.
           * @author batu-inal & HardlyDifficult
           */
          interface INFTLazyMintedCollectionMintCountTo {
            function mintCountTo(uint16 count, address to) external returns (uint256 firstTokenId);
            /**
             * @notice Get the number of tokens which can still be minted.
             * @return count The max number of additional NFTs that can be minted by this collection.
             */
            function numberOfTokensAvailableToMint() external view returns (uint256 count);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @notice The required interface for collections in the NFTDropMarket to support exhibitions.
           * @author philbirt
           */
          interface INFTMarketExhibition {
            function isAllowedSellerForExhibition(
              uint256 exhibitionId,
              address seller
            ) external view returns (bool allowedSeller);
            function getExhibitionPaymentDetails(
              uint256 exhibitionId
            ) external view returns (address payable curator, uint16 takeRateInBasisPoints);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @notice Interface for AdminRole which wraps the default admin role from
           * OpenZeppelin's AccessControl for easy integration.
           * @author batu-inal & HardlyDifficult
           */
          interface IAdminRole {
            function isAdmin(address account) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @notice Interface for OperatorRole which wraps a role from
           * OpenZeppelin's AccessControl for easy integration.
           * @author batu-inal & HardlyDifficult
           */
          interface IOperatorRole {
            function isOperator(address account) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @title Interface for routing calls to the NFT Drop Market to create fixed price sales.
           * @author HardlyDifficult & reggieag
           */
          interface INFTDropMarketFixedPriceSale {
            function createFixedPriceSaleV3(
              address nftContract,
              uint256 exhibitionId,
              uint256 price,
              uint256 limitPerAccount,
              uint256 generalAvailabilityStartTime,
              uint256 txDeadlineTime
            ) external;
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @notice An interface for communicating fees to 3rd party marketplaces.
           * @dev Originally implemented in mainnet contract 0x44d6e8933f8271abcf253c72f9ed7e0e4c0323b3
           */
          interface IGetFees {
            /**
             * @notice Get the recipient addresses to which creator royalties should be sent.
             * @dev The expected royalty amounts are communicated with `getFeeBps`.
             * @param tokenId The ID of the NFT to get royalties for.
             * @return recipients An array of addresses to which royalties should be sent.
             */
            function getFeeRecipients(uint256 tokenId) external view returns (address payable[] memory recipients);
            /**
             * @notice Get the creator royalty amounts to be sent to each recipient, in basis points.
             * @dev The expected recipients are communicated with `getFeeRecipients`.
             * @param tokenId The ID of the NFT to get royalties for.
             * @return royaltiesInBasisPoints The array of fees to be sent to each recipient, in basis points.
             */
            function getFeeBps(uint256 tokenId) external view returns (uint256[] memory royaltiesInBasisPoints);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          interface IGetRoyalties {
            /**
             * @notice Get the creator royalties to be sent.
             * @dev The data is the same as when calling `getFeeRecipients` and `getFeeBps` separately.
             * @param tokenId The ID of the NFT to get royalties for.
             * @return recipients An array of addresses to which royalties should be sent.
             * @return royaltiesInBasisPoints The array of fees to be sent to each recipient, in basis points.
             */
            function getRoyalties(
              uint256 tokenId
            ) external view returns (address payable[] memory recipients, uint256[] memory royaltiesInBasisPoints);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.12;
          interface IOwnable {
            /**
             * @dev Returns the address of the current owner.
             */
            function owner() external view returns (address);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @notice Interface for EIP-2981: NFT Royalty Standard.
           * For more see: https://eips.ethereum.org/EIPS/eip-2981.
           */
          interface IRoyaltyInfo {
            /**
             * @notice Get the creator royalties to be sent.
             * @param tokenId The ID of the NFT to get royalties for.
             * @param salePrice The total price of the sale.
             * @return receiver The address to which royalties should be sent.
             * @return royaltyAmount The total amount that should be sent to the `receiver`.
             */
            function royaltyInfo(
              uint256 tokenId,
              uint256 salePrice
            ) external view returns (address receiver, uint256 royaltyAmount);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          interface ITokenCreator {
            /**
             * @notice Returns the creator of this NFT collection.
             * @param tokenId The ID of the NFT to get the creator payment address for.
             * @return creator The creator of this collection.
             */
            function tokenCreator(uint256 tokenId) external view returns (address payable creator);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @title Helper functions for arrays.
           * @author batu-inal & HardlyDifficult
           */
          library ArrayLibrary {
            /**
             * @notice Reduces the size of an array if it's greater than the specified max size,
             * using the first maxSize elements.
             */
            function capLength(address payable[] memory data, uint256 maxLength) internal pure {
              if (data.length > maxLength) {
                assembly {
                  mstore(data, maxLength)
                }
              }
            }
            /**
             * @notice Reduces the size of an array if it's greater than the specified max size,
             * using the first maxSize elements.
             */
            function capLength(uint256[] memory data, uint256 maxLength) internal pure {
              if (data.length > maxLength) {
                assembly {
                  mstore(data, maxLength)
                }
              }
            }
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
          /**
           * @title Helper library for interacting with Merkle trees & proofs.
           * @author batu-inal & HardlyDifficult & reggieag
           */
          library MerkleAddressLibrary {
            using MerkleProof for bytes32[];
            /**
             * @notice Gets the root for a merkle tree comprised only of addresses.
             */
            function getMerkleRootForAddress(address user, bytes32[] calldata proof) internal pure returns (bytes32 root) {
              bytes32 leaf = keccak256(abi.encodePacked(user));
              root = proof.processProofCalldata(leaf);
            }
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @title Helpers for working with time.
           * @author batu-inal & HardlyDifficult
           */
          library TimeLibrary {
            /**
             * @notice Checks if the given timestamp is in the past.
             * @dev This helper ensures a consistent interpretation of expiry across the codebase.
             * This is different than `hasBeenReached` in that it will return false if the expiry is now.
             */
            function hasExpired(uint256 expiry) internal view returns (bool) {
              return expiry < block.timestamp;
            }
            /**
             * @notice Checks if the given timestamp is now or in the past.
             * @dev This helper ensures a consistent interpretation of expiry across the codebase.
             * This is different from `hasExpired` in that it will return true if the timestamp is now.
             */
            function hasBeenReached(uint256 timestamp) internal view returns (bool) {
              return timestamp <= block.timestamp;
            }
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @title A place for common modifiers and functions used by various market mixins, if any.
           * @dev This also leaves a gap which can be used to add a new mixin to the top of the inheritance tree.
           * @author batu-inal & HardlyDifficult
           */
          abstract contract NFTDropMarketCore {
            /**
             * @notice This empty reserved space is put in place to allow future versions to add new
             * variables without shifting down storage in the inheritance chain.
             * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
             */
            uint256[1_000] private __gap;
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts/utils/Context.sol";
          import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
          import "../../interfaces/internal/INFTMarketExhibition.sol";
          error NFTDropMarketExhibition_Exhibition_Does_Not_Exist();
          error NFTDropMarketExhibition_NFT_Market_Is_Not_A_Contract();
          error NFTDropMarketExhibition_Seller_Not_Allowed_In_Exhibition();
          /**
           * @title Enables a curation surface for sellers to exhibit their NFTs in the drop market.
           * @author HardlyDifficult & philbirt
           */
          abstract contract NFTDropMarketExhibition is Context {
            using AddressUpgradeable for address;
            /// @notice Maps a collection to the exhibition it was listed with.
            mapping(address => uint256) private nftContractToExhibitionId;
            /// @notice The NFT Market contract address, containing exhibition definitions.
            address private immutable _nftMarket;
            /**
             * @notice Emitted when a collection is added to an exhibition.
             * @param nftContract The contract address of the collection.
             * @param exhibitionId The ID of the exhibition the collection was added to.
             */
            event CollectionAddedToExhibition(address indexed nftContract, uint256 indexed exhibitionId);
            /**
             * @notice Set immutable variables for the implementation contract.
             * @dev Using immutable instead of constants allows us to use different values on testnet.
             * @param nftMarket The NFT Market contract address, containing exhibition definitions.
             */
            constructor(address nftMarket) {
              if (!nftMarket.isContract()) {
                revert NFTDropMarketExhibition_NFT_Market_Is_Not_A_Contract();
              }
              _nftMarket = nftMarket;
            }
            /**
             * @notice Adds a collection to an exhibition, if the ID provided is not 0.
             */
            function _addCollectionToExhibition(address nftContract, uint256 exhibitionId) internal {
              if (exhibitionId != 0) {
                // If there is an exhibition, make sure the seller is allowed to list in it
                if (
                  !INFTMarketExhibition(_nftMarket).isAllowedSellerForExhibition({
                    exhibitionId: exhibitionId,
                    seller: _msgSender()
                  })
                ) {
                  (address curator, ) = INFTMarketExhibition(_nftMarket).getExhibitionPaymentDetails(exhibitionId);
                  if (curator == address(0)) {
                    // Provides a more useful error when an exhibition never existed or has since been deleted.
                    revert NFTDropMarketExhibition_Exhibition_Does_Not_Exist();
                  }
                  revert NFTDropMarketExhibition_Seller_Not_Allowed_In_Exhibition();
                }
                nftContractToExhibitionId[nftContract] = exhibitionId;
                emit CollectionAddedToExhibition(nftContract, exhibitionId);
              }
            }
            /**
             * @notice Returns the exhibition ID for a given Collection.
             * @param nftContract The contract address of the Collection.
             * @return exhibitionId The ID of the exhibition this Collection is assigned to, or 0 if it's
             * not assigned to an exhibition.
             */
            function getExhibitionIdForCollection(address nftContract) external view returns (uint256 exhibitionId) {
              exhibitionId = nftContractToExhibitionId[nftContract];
            }
            /**
             * @notice Returns the contract which contains the exhibition definitions.
             * @return nftMarket The NFT Market contract address.
             */
            function getNftMarket() external view returns (address nftMarket) {
              nftMarket = _nftMarket;
            }
            /**
             * @notice Returns the exhibition payment details for a given Collection.
             * @dev The payment address and take rate will be 0 if the collection is not assigned to an exhibition or if the
             * exhibition has seen been deleted.
             */
            function _getExhibitionByCollection(
              address nftContract
            ) internal view returns (address payable curator, uint16 takeRateInBasisPoints) {
              uint256 exhibitionId = nftContractToExhibitionId[nftContract];
              if (exhibitionId != 0) {
                (curator, takeRateInBasisPoints) = INFTMarketExhibition(_nftMarket).getExhibitionPaymentDetails(exhibitionId);
              }
            }
            /**
             * @notice This empty reserved space is put in place to allow future versions to add new
             * variables without shifting down storage in the inheritance chain.
             * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
             * @dev This mixin uses 1,000 slots in total.
             */
            uint256[999] private __gap;
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
          import "@openzeppelin/contracts/access/IAccessControl.sol";
          import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
          import "@openzeppelin/contracts/utils/Context.sol";
          import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
          import "@openzeppelin/contracts/utils/math/SafeCast.sol";
          import "../../interfaces/internal/INFTLazyMintedCollectionMintCountTo.sol";
          import "../../interfaces/internal/routes/INFTDropMarketFixedPriceSale.sol";
          import "../../libraries/MerkleAddressLibrary.sol";
          import "../../libraries/TimeLibrary.sol";
          import "../shared/Constants.sol";
          import "../shared/MarketFees.sol";
          import "../shared/TxDeadline.sol";
          import "./NFTDropMarketExhibition.sol";
          /// @param limitPerAccount The limit of tokens an account can purchase.
          error NFTDropMarketFixedPriceSale_Cannot_Buy_More_Than_Limit(uint256 limitPerAccount);
          /// @param earlyAccessStartTime The time when early access starts, in seconds since the Unix epoch.
          error NFTDropMarketFixedPriceSale_Early_Access_Not_Open(uint256 earlyAccessStartTime);
          error NFTDropMarketFixedPriceSale_Early_Access_Start_Time_Has_Expired();
          error NFTDropMarketFixedPriceSale_General_Access_Is_Open();
          /// @param generalAvailabilityStartTime The start time of the general availability period, in seconds since the Unix
          /// epoch.
          error NFTDropMarketFixedPriceSale_General_Access_Not_Open(uint256 generalAvailabilityStartTime);
          error NFTDropMarketFixedPriceSale_General_Availability_Start_Time_Has_Expired();
          error NFTDropMarketFixedPriceSale_Invalid_Merkle_Proof();
          error NFTDropMarketFixedPriceSale_Invalid_Merkle_Root();
          error NFTDropMarketFixedPriceSale_Invalid_Merkle_Tree_URI();
          error NFTDropMarketFixedPriceSale_Limit_Per_Account_Must_Be_Set();
          error NFTDropMarketFixedPriceSale_Mint_Permission_Required();
          error NFTDropMarketFixedPriceSale_Must_Be_Listed_For_Sale();
          error NFTDropMarketFixedPriceSale_Must_Buy_At_Least_One_Token();
          error NFTDropMarketFixedPriceSale_Must_Have_Non_Zero_Early_Access_Duration();
          error NFTDropMarketFixedPriceSale_Must_Not_Be_Sold_Out();
          error NFTDropMarketFixedPriceSale_Must_Not_Have_Pending_Sale();
          error NFTDropMarketFixedPriceSale_Must_Support_Collection_Mint_Interface();
          error NFTDropMarketFixedPriceSale_Must_Support_ERC721();
          error NFTDropMarketFixedPriceSale_Only_Callable_By_Collection_Owner();
          error NFTDropMarketFixedPriceSale_Start_Time_Too_Far_In_The_Future();
          /// @param mintCost The total cost for this purchase.
          error NFTDropMarketFixedPriceSale_Too_Much_Value_Provided(uint256 mintCost);
          /**
           * @title Allows creators to list a drop collection for sale at a fixed price point.
           * @dev Listing a collection for sale in this market requires the collection to implement
           * the functions in `INFTLazyMintedCollectionMintCountTo` and to register that interface with ERC165.
           * Additionally the collection must implement access control, or more specifically:
           * `hasRole(bytes32(0), msg.sender)` must return true when called from the creator or admin's account
           * and `hasRole(keccak256("MINTER_ROLE", address(this)))` must return true for this market's address.
           * @author batu-inal & HardlyDifficult & philbirt & reggieag
           */
          abstract contract NFTDropMarketFixedPriceSale is
            INFTDropMarketFixedPriceSale,
            TxDeadline,
            MarketFees,
            NFTDropMarketExhibition
          {
            using AddressUpgradeable for address;
            using AddressUpgradeable for address payable;
            using ERC165Checker for address;
            using MerkleAddressLibrary for address;
            using SafeCast for uint256;
            using TimeLibrary for uint32;
            using TimeLibrary for uint256;
            /**
             * @notice Configuration for the terms of the sale.
             */
            struct FixedPriceSaleConfig {
              /****** Slot 0 (of this struct) ******/
              /// @notice The seller for the drop.
              address payable seller;
              /// @notice The fixed price per NFT in the collection.
              /// @dev The maximum price that can be set on an NFT is ~1.2M (2^80/10^18) ETH.
              uint80 price;
              /// @notice The max number of NFTs an account may mint in this sale.
              uint16 limitPerAccount;
              /****** Slot 1 ******/
              /// @notice Tracks how many NFTs a given user has already minted.
              mapping(address => uint256) userToMintedCount;
              /****** Slot 2 ******/
              /// @notice The start time of the general availability period, in seconds since the Unix epoch.
              /// @dev This must be >= `earlyAccessStartTime`.
              /// When set to 0, general availability was not scheduled and started as soon as the price was set.
              uint32 generalAvailabilityStartTime;
              /// @notice The time when early access purchasing may begin, in seconds since the Unix epoch.
              /// @dev This must be <= `generalAvailabilityStartTime`.
              /// When set to 0, early access was not scheduled and started as soon as the price was set.
              uint32 earlyAccessStartTime;
              // 192-bits available in this slot
              /****** Slot 3 ******/
              /// @notice Merkle roots representing which users have access to purchase during the early access period.
              /// @dev There may be many roots supported per sale where each is considered additive as any root may be used to
              /// purchase.
              mapping(bytes32 => bool) earlyAccessMerkleRoots;
            }
            /// @notice Stores the current sale information for all drop contracts.
            mapping(address => FixedPriceSaleConfig) private nftContractToFixedPriceSaleConfig;
            /**
             * @dev Protocol fee for edition mints in basis points.
             */
            uint256 private constant EDITION_PROTOCOL_FEE_IN_BASIS_POINTS = 500;
            /**
             * @notice Hash of the edition type name.
             * @dev This is precalculated in order to save gas on use.
             * `keccak256(abi.encodePacked(NFT_TIMED_EDITION_COLLECTION_TYPE))`
             */
            bytes32 private constant editionTypeHash = 0xee2afa3f960e108aca17013728aafa363a0f4485661d9b6f41c6b4ddb55008ee;
            /**
             * @notice The `role` type used to validate drop collections have granted this market access to mint.
             * @return `keccak256("MINTER_ROLE")`
             */
            bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
            /**
             * @notice Emitted when an early access merkle root is added to a fixed price sale early access period.
             * @param nftContract The address of the NFT drop collection.
             * @param merkleRoot The merkleRoot used to authorize early access purchases.
             * @param merkleTreeUri The URI for the merkle tree represented by the merkleRoot.
             */
            event AddMerkleRootToFixedPriceSale(address indexed nftContract, bytes32 merkleRoot, string merkleTreeUri);
            /**
             * @notice Emitted when a collection is listed for sale.
             * @param nftContract The address of the NFT drop collection.
             * @param seller The address for the seller which listed this for sale.
             * @param price The price per NFT minted.
             * @param limitPerAccount The max number of NFTs an account may mint in this sale.
             * @param generalAvailabilityStartTime The time at which general purchases are available, in seconds since Unix epoch.
             * Can not be more than two years from the creation block timestamp.
             * @param earlyAccessStartTime The time at which early access purchases are available, in seconds since Unix epoch.
             * Can not be more than two years from the creation block timestamp.
             * @param merkleRoot The merkleRoot used to authorize early access purchases, or 0 if n/a.
             * @param merkleTreeUri The URI for the merkle tree represented by the merkleRoot, or empty if n/a.
             */
            event CreateFixedPriceSale(
              address indexed nftContract,
              address indexed seller,
              uint256 price,
              uint256 limitPerAccount,
              uint256 generalAvailabilityStartTime,
              uint256 earlyAccessStartTime,
              bytes32 merkleRoot,
              string merkleTreeUri
            );
            /**
             * @notice Emitted when NFTs are minted from the drop.
             * @dev The total price paid by the buyer is `totalFees + creatorRev`.
             * @param nftContract The address of the NFT drop collection.
             * @param buyer The address of the buyer.
             * @param firstTokenId The tokenId for the first NFT minted.
             * The other minted tokens are assigned sequentially, so `firstTokenId` - `firstTokenId + count - 1` were minted.
             * @param count The number of NFTs minted.
             * @param totalFees The amount of ETH that was sent to Foundation & referrals for this sale.
             * @param creatorRev The amount of ETH that was sent to the creator for this sale.
             */
            event MintFromFixedPriceDrop(
              address indexed nftContract,
              address indexed buyer,
              uint256 indexed firstTokenId,
              uint256 count,
              uint256 totalFees,
              uint256 creatorRev
            );
            /// @notice Requires the given NFT contract can mint at least 1 more NFT.
            modifier notSoldOut(address nftContract) {
              if (INFTLazyMintedCollectionMintCountTo(nftContract).numberOfTokensAvailableToMint() == 0) {
                revert NFTDropMarketFixedPriceSale_Must_Not_Be_Sold_Out();
              }
              _;
            }
            /// @notice Requires the msg.sender has the admin role on the given NFT contract.
            modifier onlyCollectionAdmin(address nftContract) {
              if (!IAccessControl(nftContract).hasRole(DEFAULT_ADMIN_ROLE, _msgSender())) {
                revert NFTDropMarketFixedPriceSale_Only_Callable_By_Collection_Owner();
              }
              _;
            }
            /// @notice Requires the given NFT contract supports the interfaces currently required by this market for minting,
            /// and that it has granted this market the MINTER_ROLE.
            modifier onlySupportedCollectionType(address nftContract) {
              if (!nftContract.supportsInterface(type(INFTLazyMintedCollectionMintCountTo).interfaceId)) {
                // Must support the mint interface this market depends on.
                revert NFTDropMarketFixedPriceSale_Must_Support_Collection_Mint_Interface();
              }
              if (!nftContract.supportsERC165InterfaceUnchecked(type(IERC721).interfaceId)) {
                // Must be an ERC-721 NFT collection.
                revert NFTDropMarketFixedPriceSale_Must_Support_ERC721();
              }
              if (!IAccessControl(nftContract).hasRole(MINTER_ROLE, address(this))) {
                revert NFTDropMarketFixedPriceSale_Mint_Permission_Required();
              }
              _;
            }
            /// @notice Requires the merkle params have been assigned non-zero values.
            modifier onlyValidMerkle(bytes32 merkleRoot, string calldata merkleTreeUri) {
              if (merkleRoot == bytes32(0)) {
                revert NFTDropMarketFixedPriceSale_Invalid_Merkle_Root();
              }
              if (bytes(merkleTreeUri).length == 0) {
                revert NFTDropMarketFixedPriceSale_Invalid_Merkle_Tree_URI();
              }
              _;
            }
            /// @notice Requires that the time provided is not more than 2 years in the future.
            modifier onlyValidStartTime(uint256 startTime) {
              if (startTime > block.timestamp + MAX_SCHEDULED_TIME_IN_THE_FUTURE) {
                // Prevent arbitrarily large values from accidentally being set.
                revert NFTDropMarketFixedPriceSale_Start_Time_Too_Far_In_The_Future();
              }
              _;
            }
            /**
             * @notice Add a merkle root to an existing fixed price sale early access period.
             * @param nftContract The address of the NFT drop collection.
             * @param merkleRoot The merkleRoot used to authorize early access purchases.
             * @param merkleTreeUri The URI for the merkle tree represented by the merkleRoot.
             * @dev If you accidentally pass in the wrong merkleTreeUri for a merkleRoot,
             * you can call this function again to emit a new event with a new merkleTreeUri.
             */
            function addMerkleRootToFixedPriceSale(
              address nftContract,
              bytes32 merkleRoot,
              string calldata merkleTreeUri
            ) external notSoldOut(nftContract) onlyCollectionAdmin(nftContract) onlyValidMerkle(merkleRoot, merkleTreeUri) {
              FixedPriceSaleConfig storage saleConfig = nftContractToFixedPriceSaleConfig[nftContract];
              if (saleConfig.generalAvailabilityStartTime.hasBeenReached()) {
                // Start time may be 0, check if this collection has been listed to provide a better error message.
                if (saleConfig.seller == payable(0)) {
                  revert NFTDropMarketFixedPriceSale_Must_Be_Listed_For_Sale();
                }
                // Adding users to the allow list is unnecessary when general access is open.
                revert NFTDropMarketFixedPriceSale_General_Access_Is_Open();
              }
              if (saleConfig.generalAvailabilityStartTime == saleConfig.earlyAccessStartTime) {
                // Must have non-zero early access duration, otherwise merkle roots are unnecessary.
                revert NFTDropMarketFixedPriceSale_Must_Have_Non_Zero_Early_Access_Duration();
              }
              saleConfig.earlyAccessMerkleRoots[merkleRoot] = true;
              emit AddMerkleRootToFixedPriceSale(nftContract, merkleRoot, merkleTreeUri);
            }
            /**
             * @notice [DEPRECATED] use `createFixedPriceSaleV3` instead.
             * Create a fixed price sale drop without an early access period.
             * @param nftContract The address of the NFT drop collection.
             * @param price The price per NFT minted.
             * Set price to 0 for a first come first serve airdrop-like drop.
             * @param limitPerAccount The max number of NFTs an account may mint in this sale.
             * @dev Notes:
             *   a) The sale is final and can not be updated or canceled.
             *   b) The sale is immediately kicked off.
             *   c) Any collection that abides by `INFTLazyMintedCollectionMintCountTo` and `IAccessControl` is supported.
             */
            function createFixedPriceSale(address nftContract, uint80 price, uint16 limitPerAccount) external {
              _createFixedPriceSale({
                nftContract: nftContract,
                exhibitionId: 0,
                price: price,
                limitPerAccount: limitPerAccount,
                generalAvailabilityStartTime: block.timestamp,
                earlyAccessStartTime: block.timestamp,
                merkleRoot: bytes32(0),
                merkleTreeUri: ""
              });
            }
            /**
             * @notice [DEPRECATED] use `createFixedPriceSaleV3` instead.
             * Create a fixed price sale drop without an early access period,
             * optionally scheduling the sale to start sometime in the future.
             * @param nftContract The address of the NFT drop collection.
             * @param price The price per NFT minted.
             * Set price to 0 for a first come first serve airdrop-like drop.
             * @param limitPerAccount The max number of NFTs an account may mint in this sale.
             * @param generalAvailabilityStartTime The time at which general purchases are available, in seconds since Unix epoch.
             * Set this to 0 in order to have general availability begin as soon as the transaction is mined.
             * Can not be more than two years from the creation block timestamp.
             * @param txDeadlineTime The deadline timestamp for the transaction to be mined, in seconds since Unix epoch.
             * Set this to 0 to send the transaction without a deadline.
             * @dev Notes:
             *   a) The sale is final and can not be updated or canceled.
             *   b) Any collection that abides by `INFTLazyMintedCollectionMintCountTo` and `IAccessControl` is supported.
             */
            function createFixedPriceSaleV2(
              address nftContract,
              uint256 price,
              uint256 limitPerAccount,
              uint256 generalAvailabilityStartTime,
              uint256 txDeadlineTime
            ) external {
              createFixedPriceSaleV3({
                nftContract: nftContract,
                exhibitionId: 0,
                price: price,
                limitPerAccount: limitPerAccount,
                generalAvailabilityStartTime: generalAvailabilityStartTime,
                txDeadlineTime: txDeadlineTime
              });
            }
            /**
             * @notice Create a fixed price sale drop without an early access period,
             * optionally scheduling the sale to start sometime in the future.
             * @param nftContract The address of the NFT drop collection.
             * @param exhibitionId The exhibition to associate this fix priced sale to.
             * Set this to 0 to exist outside of an exhibition.
             * @param price The price per NFT minted.
             * Set price to 0 for a first come first serve airdrop-like drop.
             * @param limitPerAccount The max number of NFTs an account may mint in this sale.
             * @param generalAvailabilityStartTime The time at which general purchases are available, in seconds since Unix epoch.
             * Set this to 0 in order to have general availability begin as soon as the transaction is mined.
             * Can not be more than two years from the creation block timestamp.
             * @param txDeadlineTime The deadline timestamp for the transaction to be mined, in seconds since Unix epoch.
             * Set this to 0 to send the transaction without a deadline.
             * @dev Notes:
             *   a) The sale is final and can not be updated or canceled.
             *   b) Any collection that abides by `INFTLazyMintedCollectionMintCountTo` and `IAccessControl` is supported.
             */
            function createFixedPriceSaleV3(
              address nftContract,
              uint256 exhibitionId,
              uint256 price,
              uint256 limitPerAccount,
              uint256 generalAvailabilityStartTime,
              uint256 txDeadlineTime
            ) public txDeadlineNotExpired(txDeadlineTime) {
              // When generalAvailabilityStartTime is not specified, default to now.
              if (generalAvailabilityStartTime == 0) {
                generalAvailabilityStartTime = block.timestamp;
              } else if (generalAvailabilityStartTime.hasExpired()) {
                // The start time must be now or in the future.
                revert NFTDropMarketFixedPriceSale_General_Availability_Start_Time_Has_Expired();
              }
              _createFixedPriceSale({
                nftContract: nftContract,
                exhibitionId: exhibitionId,
                price: price,
                limitPerAccount: limitPerAccount,
                generalAvailabilityStartTime: generalAvailabilityStartTime,
                earlyAccessStartTime: generalAvailabilityStartTime,
                merkleRoot: bytes32(0),
                merkleTreeUri: ""
              });
            }
            /**
             * @notice [DEPRECATED] use `createFixedPriceSaleWithEarlyAccessAllowlistV2` instead.
             * Create a fixed price sale drop with an early access period.
             * @param nftContract The address of the NFT drop collection.
             * @param price The price per NFT minted.
             * Set price to 0 for a first come first serve airdrop-like drop.
             * @param limitPerAccount The max number of NFTs an account may mint in this sale.
             * @param generalAvailabilityStartTime The time at which general purchases are available, in seconds since Unix epoch.
             * This value must be > `earlyAccessStartTime`.
             * @param earlyAccessStartTime The time at which early access purchases are available, in seconds since Unix epoch.
             * Set this to 0 in order to have early access begin as soon as the transaction is mined.
             * @param merkleRoot The merkleRoot used to authorize early access purchases.
             * @param merkleTreeUri The URI for the merkle tree represented by the merkleRoot.
             * @param txDeadlineTime The deadline timestamp for the transaction to be mined, in seconds since Unix epoch.
             * Set this to 0 to send the transaction without a deadline.
             * @dev Notes:
             *   a) The sale is final and can not be updated or canceled.
             *   b) Any collection that abides by `INFTLazyMintedCollectionMintCountTo` and `IAccessControl` is supported.
             */
            function createFixedPriceSaleWithEarlyAccessAllowlist(
              address nftContract,
              uint256 price,
              uint256 limitPerAccount,
              uint256 generalAvailabilityStartTime,
              uint256 earlyAccessStartTime,
              bytes32 merkleRoot,
              string calldata merkleTreeUri,
              uint256 txDeadlineTime
            ) external {
              createFixedPriceSaleWithEarlyAccessAllowlistV2({
                nftContract: nftContract,
                exhibitionId: 0,
                price: price,
                limitPerAccount: limitPerAccount,
                generalAvailabilityStartTime: generalAvailabilityStartTime,
                earlyAccessStartTime: earlyAccessStartTime,
                merkleRoot: merkleRoot,
                merkleTreeUri: merkleTreeUri,
                txDeadlineTime: txDeadlineTime
              });
            }
            /**
             * @notice Create a fixed price sale drop with an early access period.
             * @param nftContract The address of the NFT drop collection.
             * @param exhibitionId The exhibition to associate this fix priced sale to.
             * Set this to 0 to exist outside of an exhibition.
             * @param price The price per NFT minted.
             * Set price to 0 for a first come first serve airdrop-like drop.
             * @param limitPerAccount The max number of NFTs an account may mint in this sale.
             * @param generalAvailabilityStartTime The time at which general purchases are available, in seconds since Unix epoch.
             * This value must be > `earlyAccessStartTime`.
             * @param earlyAccessStartTime The time at which early access purchases are available, in seconds since Unix epoch.
             * Set this to 0 in order to have early access begin as soon as the transaction is mined.
             * @param merkleRoot The merkleRoot used to authorize early access purchases.
             * @param merkleTreeUri The URI for the merkle tree represented by the merkleRoot.
             * @param txDeadlineTime The deadline timestamp for the transaction to be mined, in seconds since Unix epoch.
             * Set this to 0 to send the transaction without a deadline.
             * @dev Notes:
             *   a) The sale is final and can not be updated or canceled.
             *   b) Any collection that abides by `INFTLazyMintedCollectionMintCountTo` and `IAccessControl` is supported.
             */
            function createFixedPriceSaleWithEarlyAccessAllowlistV2(
              address nftContract,
              uint256 exhibitionId,
              uint256 price,
              uint256 limitPerAccount,
              uint256 generalAvailabilityStartTime,
              uint256 earlyAccessStartTime,
              bytes32 merkleRoot,
              string calldata merkleTreeUri,
              uint256 txDeadlineTime
            ) public txDeadlineNotExpired(txDeadlineTime) onlyValidMerkle(merkleRoot, merkleTreeUri) {
              // When earlyAccessStartTime is not specified, default to now.
              if (earlyAccessStartTime == 0) {
                earlyAccessStartTime = block.timestamp;
              } else if (earlyAccessStartTime.hasExpired()) {
                // The start time must be now or in the future.
                revert NFTDropMarketFixedPriceSale_Early_Access_Start_Time_Has_Expired();
              }
              if (earlyAccessStartTime >= generalAvailabilityStartTime) {
                // Early access period must start before GA period.
                revert NFTDropMarketFixedPriceSale_Must_Have_Non_Zero_Early_Access_Duration();
              }
              _createFixedPriceSale(
                nftContract,
                exhibitionId,
                price,
                limitPerAccount,
                generalAvailabilityStartTime,
                earlyAccessStartTime,
                merkleRoot,
                merkleTreeUri
              );
            }
            /**
             * @notice Used to mint `count` number of NFTs from the collection during general availability.
             * @param nftContract The address of the NFT drop collection.
             * @param count The number of NFTs to mint.
             * @param buyReferrer The address which referred this purchase, or address(0) if n/a.
             * @return firstTokenId The tokenId for the first NFT minted.
             * The other minted tokens are assigned sequentially, so `firstTokenId` - `firstTokenId + count - 1` were minted.
             * @dev This call may revert if the collection has sold out, has an insufficient number of tokens available,
             * or if the market's minter permissions were removed.
             * If insufficient msg.value is included, the msg.sender's available FETH token balance will be used.
             */
            function mintFromFixedPriceSale(
              address nftContract,
              uint16 count,
              address payable buyReferrer
            ) external payable returns (uint256 firstTokenId) {
              FixedPriceSaleConfig storage saleConfig = nftContractToFixedPriceSaleConfig[nftContract];
              // Must be in general access period.
              if (!saleConfig.generalAvailabilityStartTime.hasBeenReached()) {
                revert NFTDropMarketFixedPriceSale_General_Access_Not_Open(saleConfig.generalAvailabilityStartTime);
              }
              firstTokenId = _mintFromFixedPriceSale(saleConfig, nftContract, count, buyReferrer);
            }
            /**
             * @notice Used to mint `count` number of NFTs from the collection during early access.
             * @param nftContract The address of the NFT drop collection.
             * @param count The number of NFTs to mint.
             * @param buyReferrer The address which referred this purchase, or address(0) if n/a.
             * @param proof The merkle proof used to authorize this purchase.
             * @return firstTokenId The tokenId for the first NFT minted.
             * The other minted tokens are assigned sequentially, so `firstTokenId` - `firstTokenId + count - 1` were minted.
             * @dev This call may revert if the collection has sold out, has an insufficient number of tokens available,
             * or if the market's minter permissions were removed.
             * If insufficient msg.value is included, the msg.sender's available FETH token balance will be used.
             */
            function mintFromFixedPriceSaleWithEarlyAccessAllowlist(
              address nftContract,
              uint256 count,
              address payable buyReferrer,
              bytes32[] calldata proof
            ) external payable returns (uint256 firstTokenId) {
              FixedPriceSaleConfig storage saleConfig = nftContractToFixedPriceSaleConfig[nftContract];
              // Skip proof check if in general access period.
              if (!saleConfig.generalAvailabilityStartTime.hasBeenReached()) {
                // Must be in early access period or beyond.
                if (!saleConfig.earlyAccessStartTime.hasBeenReached()) {
                  if (saleConfig.earlyAccessStartTime == saleConfig.generalAvailabilityStartTime) {
                    // This just provides a more targeted error message for the case where early access is not enabled.
                    revert NFTDropMarketFixedPriceSale_Must_Have_Non_Zero_Early_Access_Duration();
                  }
                  revert NFTDropMarketFixedPriceSale_Early_Access_Not_Open(saleConfig.earlyAccessStartTime);
                }
                bytes32 root = _msgSender().getMerkleRootForAddress(proof);
                if (!saleConfig.earlyAccessMerkleRoots[root]) {
                  revert NFTDropMarketFixedPriceSale_Invalid_Merkle_Proof();
                }
              }
              firstTokenId = _mintFromFixedPriceSale(saleConfig, nftContract, count, buyReferrer);
            }
            function _createFixedPriceSale(
              address nftContract,
              uint256 exhibitionId,
              uint256 price,
              uint256 limitPerAccount,
              uint256 generalAvailabilityStartTime,
              uint256 earlyAccessStartTime,
              bytes32 merkleRoot,
              string memory merkleTreeUri
            )
              private
              onlySupportedCollectionType(nftContract)
              notSoldOut(nftContract)
              onlyCollectionAdmin(nftContract)
              onlyValidStartTime(generalAvailabilityStartTime)
            {
              // Validate input params.
              if (limitPerAccount == 0) {
                // A non-zero limit is required.
                revert NFTDropMarketFixedPriceSale_Limit_Per_Account_Must_Be_Set();
              }
              // Confirm this collection has not already been listed.
              FixedPriceSaleConfig storage saleConfig = nftContractToFixedPriceSaleConfig[nftContract];
              if (saleConfig.seller != payable(0)) {
                revert NFTDropMarketFixedPriceSale_Must_Not_Have_Pending_Sale();
              }
              // Save the sale details.
              address payable sender = payable(_msgSender());
              saleConfig.seller = sender;
              // Any price is supported, including 0.
              saleConfig.price = price.toUint80();
              saleConfig.limitPerAccount = limitPerAccount.toUint16();
              if (generalAvailabilityStartTime != block.timestamp) {
                // If starting now we don't need to write to storage
                // Safe cast is not required since onlyValidStartTime confirms the max is within range.
                saleConfig.generalAvailabilityStartTime = uint32(generalAvailabilityStartTime);
              }
              if (earlyAccessStartTime != block.timestamp) {
                // If starting now we don't need to write to storage
                // Safe cast is not required since callers require earlyAccessStartTime <= generalAvailabilityStartTime.
                saleConfig.earlyAccessStartTime = uint32(earlyAccessStartTime);
              }
              // Store the merkle root if there's an early access period
              if (merkleRoot != 0) {
                saleConfig.earlyAccessMerkleRoots[merkleRoot] = true;
              }
              _addCollectionToExhibition(nftContract, exhibitionId);
              emit CreateFixedPriceSale({
                nftContract: nftContract,
                seller: sender,
                price: price,
                limitPerAccount: limitPerAccount,
                generalAvailabilityStartTime: generalAvailabilityStartTime,
                earlyAccessStartTime: earlyAccessStartTime,
                merkleRoot: merkleRoot,
                merkleTreeUri: merkleTreeUri
              });
            }
            function _mintFromFixedPriceSale(
              FixedPriceSaleConfig storage saleConfig,
              address nftContract,
              uint256 count,
              address payable buyReferrer
            ) private returns (uint256 firstTokenId) {
              // Validate input params.
              if (count == 0) {
                revert NFTDropMarketFixedPriceSale_Must_Buy_At_Least_One_Token();
              }
              // Confirm that the buyer will not exceed the limit specified after minting.
              address sender = _msgSender();
              uint256 minted = saleConfig.userToMintedCount[sender] + count;
              if (minted > saleConfig.limitPerAccount) {
                if (saleConfig.limitPerAccount == 0) {
                  // Provide a more targeted error if the collection has not been listed.
                  revert NFTDropMarketFixedPriceSale_Must_Be_Listed_For_Sale();
                }
                revert NFTDropMarketFixedPriceSale_Cannot_Buy_More_Than_Limit(saleConfig.limitPerAccount);
              }
              saleConfig.userToMintedCount[sender] = minted;
              // Calculate the total cost, considering the `count` requested.
              uint256 mintCost;
              unchecked {
                // Can not overflow as 2^80 * 2^16 == 2^96 max which fits in 256 bits.
                mintCost = uint256(saleConfig.price) * count;
              }
              // The sale price is immutable so the buyer is aware of how much they will be paying when their tx is broadcasted.
              if (msg.value > mintCost) {
                // Since price is known ahead of time, if too much ETH is sent then something went wrong.
                revert NFTDropMarketFixedPriceSale_Too_Much_Value_Provided(mintCost);
              }
              // Withdraw from the user's available FETH balance if insufficient msg.value was included.
              _tryUseFETHBalance({ totalAmount: mintCost, shouldRefundSurplus: false });
              // Mint the NFTs.
              // Safe cast is not required, above confirms count <= limitPerAccount which is uint16.
              firstTokenId = INFTLazyMintedCollectionMintCountTo(nftContract).mintCountTo(uint16(count), sender);
              (address payable curator, uint16 takeRateInBasisPoints) = _getExhibitionByCollection(nftContract);
              // Distribute revenue from this sale.
              (uint256 totalFees, uint256 creatorRev, ) = _distributeFunds({
                nftContract: nftContract,
                tokenId: firstTokenId,
                seller: saleConfig.seller,
                price: mintCost,
                buyReferrer: buyReferrer,
                sellerReferrerPaymentAddress: curator,
                sellerReferrerTakeRateInBasisPoints: takeRateInBasisPoints
              });
              emit MintFromFixedPriceDrop({
                nftContract: nftContract,
                buyer: sender,
                firstTokenId: firstTokenId,
                count: count,
                totalFees: totalFees,
                creatorRev: creatorRev
              });
            }
            /**
             * @notice Returns the max number of NFTs a given account may mint.
             * @param nftContract The address of the NFT drop collection.
             * @param user The address of the user which will be minting.
             * @return numberThatCanBeMinted How many NFTs the user can mint.
             */
            function getAvailableCountFromFixedPriceSale(
              address nftContract,
              address user
            ) external view returns (uint256 numberThatCanBeMinted) {
              (, , uint256 limitPerAccount, uint256 numberOfTokensAvailableToMint, bool marketCanMint, , ) = getFixedPriceSale(
                nftContract
              );
              if (!marketCanMint) {
                // No one can mint in the current state.
                return 0;
              }
              uint256 mintedCount = nftContractToFixedPriceSaleConfig[nftContract].userToMintedCount[user];
              if (mintedCount >= limitPerAccount) {
                // User has exhausted their limit.
                return 0;
              }
              unchecked {
                // Safe math is not required due to the if statement directly above.
                numberThatCanBeMinted = limitPerAccount - mintedCount;
              }
              if (numberThatCanBeMinted > numberOfTokensAvailableToMint) {
                // User has more tokens available than the collection has available.
                numberThatCanBeMinted = numberOfTokensAvailableToMint;
              }
            }
            /**
             * @notice Returns details for a drop collection's fixed price sale.
             * @param nftContract The address of the NFT drop collection.
             * @return seller The address of the seller which listed this drop for sale.
             * This value will be address(0) if the collection is not listed or has sold out.
             * @return price The price per NFT minted.
             * @return limitPerAccount The max number of NFTs an account may mint in this sale.
             * @return numberOfTokensAvailableToMint The number of NFTs available to mint.
             * @return marketCanMint True if this contract has permissions to mint from the given collection.
             * @return generalAvailabilityStartTime The time at which general availability starts.
             * When set to 0, general availability was not scheduled and started as soon as the price was set.
             * @return earlyAccessStartTime The timestamp at which the allowlist period starts.
             * When set to 0, early access was not scheduled and started as soon as the price was set.
             */
            function getFixedPriceSale(
              address nftContract
            )
              public
              view
              returns (
                address payable seller,
                uint256 price,
                uint256 limitPerAccount,
                uint256 numberOfTokensAvailableToMint,
                bool marketCanMint,
                uint256 generalAvailabilityStartTime,
                uint256 earlyAccessStartTime
              )
            {
              try INFTLazyMintedCollectionMintCountTo(nftContract).numberOfTokensAvailableToMint() returns (uint256 count) {
                if (count != 0) {
                  try IAccessControl(nftContract).hasRole(MINTER_ROLE, address(this)) returns (bool hasRole) {
                    FixedPriceSaleConfig storage saleConfig = nftContractToFixedPriceSaleConfig[nftContract];
                    seller = saleConfig.seller;
                    price = saleConfig.price;
                    limitPerAccount = saleConfig.limitPerAccount;
                    numberOfTokensAvailableToMint = count;
                    marketCanMint = hasRole;
                    earlyAccessStartTime = saleConfig.earlyAccessStartTime;
                    generalAvailabilityStartTime = saleConfig.generalAvailabilityStartTime;
                  } catch {
                    // The contract is not supported - return default values.
                  }
                }
                // Else minted completed -- return default values.
              } catch {
                // Contract not supported or self destructed - return default values
              }
            }
            /**
             * @notice Checks if a given merkle root has been authorized to purchase from a given drop collection.
             * @param nftContract The address of the NFT drop collection.
             * @param merkleRoot The merkle root to check.
             * @return supported True if the merkle root has been authorized.
             */
            function getFixedPriceSaleEarlyAccessAllowlistSupported(
              address nftContract,
              bytes32 merkleRoot
            ) external view returns (bool supported) {
              supported = nftContractToFixedPriceSaleConfig[nftContract].earlyAccessMerkleRoots[merkleRoot];
            }
            /**
             * @inheritdoc MarketFees
             * @dev Offers a reduced protocol fee for NFT edition collection sales.
             */
            function _getProtocolFee(
              address nftContract
            ) internal view virtual override returns (uint256 protocolFeeInBasisPoints) {
              try INFTCollectionType(nftContract).getNFTCollectionType() returns (string memory nftCollectionType) {
                if (keccak256(abi.encodePacked(nftCollectionType)) == editionTypeHash) {
                  return EDITION_PROTOCOL_FEE_IN_BASIS_POINTS;
                }
              } catch {
                // Fall through to use the default fee of 15% instead.
                // If the collection implements a fallback function, decoding will revert and cause the sale to fail.
              }
              return super._getProtocolFee(nftContract);
            }
            /**
             * @inheritdoc MarketSharedCore
             * @dev Returns the seller for a collection if listed and not already sold out.
             */
            function _getSellerOf(
              address nftContract,
              uint256 /* tokenId */
            ) internal view virtual override returns (address payable seller) {
              (seller, , , , , , ) = getFixedPriceSale(nftContract);
            }
            /**
             * @notice This empty reserved space is put in place to allow future versions to add new
             * variables without shifting down storage in the inheritance chain.
             * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
             */
            uint256[1_000] private __gap;
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /// Constant values shared across mixins.
          /**
           * @dev 100% in basis points.
           */
          uint256 constant BASIS_POINTS = 10_000;
          /**
           * @dev The default admin role defined by OZ ACL modules.
           */
          bytes32 constant DEFAULT_ADMIN_ROLE = 0x00;
          ////////////////////////////////////////////////////////////////
          // Royalties & Take Rates
          ////////////////////////////////////////////////////////////////
          /**
           * @dev The max take rate an exhibition can have.
           */
          uint256 constant MAX_EXHIBITION_TAKE_RATE = 5_000;
          /**
           * @dev Cap the number of royalty recipients.
           * A cap is required to ensure gas costs are not too high when a sale is settled.
           */
          uint256 constant MAX_ROYALTY_RECIPIENTS = 5;
          /**
           * @dev Default royalty cut paid out on secondary sales.
           * Set to 10% of the secondary sale.
           */
          uint96 constant ROYALTY_IN_BASIS_POINTS = 1_000;
          /**
           * @dev 10%, expressed as a denominator for more efficient calculations.
           */
          uint256 constant ROYALTY_RATIO = BASIS_POINTS / ROYALTY_IN_BASIS_POINTS;
          ////////////////////////////////////////////////////////////////
          // Gas Limits
          ////////////////////////////////////////////////////////////////
          /**
           * @dev The gas limit used when making external read-only calls.
           * This helps to ensure that external calls does not prevent the market from executing.
           */
          uint256 constant READ_ONLY_GAS_LIMIT = 40_000;
          /**
           * @dev The gas limit to send ETH to multiple recipients, enough for a 5-way split.
           */
          uint256 constant SEND_VALUE_GAS_LIMIT_MULTIPLE_RECIPIENTS = 210_000;
          /**
           * @dev The gas limit to send ETH to a single recipient, enough for a contract with a simple receiver.
           */
          uint256 constant SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT = 20_000;
          ////////////////////////////////////////////////////////////////
          // Collection Type Names
          ////////////////////////////////////////////////////////////////
          /**
           * @dev The NFT collection type.
           */
          string constant NFT_COLLECTION_TYPE = "NFT Collection";
          /**
           * @dev The NFT drop collection type.
           */
          string constant NFT_DROP_COLLECTION_TYPE = "NFT Drop Collection";
          /**
           * @dev The NFT edition collection type.
           */
          string constant NFT_TIMED_EDITION_COLLECTION_TYPE = "NFT Timed Edition Collection";
          ////////////////////////////////////////////////////////////////
          // Business Logic
          ////////////////////////////////////////////////////////////////
          /**
           * @dev Limits scheduled start/end times to be less than 2 years in the future.
           */
          uint256 constant MAX_SCHEDULED_TIME_IN_THE_FUTURE = 365 days * 2;
          /**
           * @dev The minimum increase of 10% required when making an offer or placing a bid.
           */
          uint256 constant MIN_PERCENT_INCREMENT_DENOMINATOR = BASIS_POINTS / 1_000;
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts/utils/Context.sol";
          import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
          import "../../interfaces/internal/IFethMarket.sol";
          error FETHNode_FETH_Address_Is_Not_A_Contract();
          error FETHNode_Only_FETH_Can_Transfer_ETH();
          /**
           * @title A mixin for interacting with the FETH contract.
           * @author batu-inal & HardlyDifficult
           */
          abstract contract FETHNode is Context {
            using AddressUpgradeable for address;
            using AddressUpgradeable for address payable;
            /// @notice The FETH ERC-20 token for managing escrow and lockup.
            IFethMarket internal immutable feth;
            constructor(address _feth) {
              if (!_feth.isContract()) {
                revert FETHNode_FETH_Address_Is_Not_A_Contract();
              }
              feth = IFethMarket(_feth);
            }
            /**
             * @notice Only used by FETH. Any direct transfer from users will revert.
             */
            receive() external payable {
              if (msg.sender != address(feth)) {
                revert FETHNode_Only_FETH_Can_Transfer_ETH();
              }
            }
            /**
             * @notice Withdraw the msg.sender's available FETH balance if they requested more than the msg.value provided.
             * @dev This may revert if the msg.sender is non-receivable.
             * This helper should not be used anywhere that may lead to locked assets.
             * @param totalAmount The total amount of ETH required (including the msg.value).
             * @param shouldRefundSurplus If true, refund msg.value - totalAmount to the msg.sender.
             */
            function _tryUseFETHBalance(uint256 totalAmount, bool shouldRefundSurplus) internal {
              if (totalAmount > msg.value) {
                // Withdraw additional ETH required from the user's available FETH balance.
                unchecked {
                  // The if above ensures delta will not underflow.
                  // Withdraw ETH from the user's account in the FETH token contract,
                  // making the funds available in this contract as ETH.
                  feth.marketWithdrawFrom(_msgSender(), totalAmount - msg.value);
                }
              } else if (shouldRefundSurplus && totalAmount < msg.value) {
                // Return any surplus ETH to the user.
                unchecked {
                  // The if above ensures this will not underflow
                  payable(_msgSender()).sendValue(msg.value - totalAmount);
                }
              }
            }
            /**
             * @notice Gets the FETH contract used to escrow offer funds.
             * @return fethAddress The FETH contract address.
             */
            function getFethAddress() external view returns (address fethAddress) {
              fethAddress = address(feth);
            }
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
          import "../../interfaces/internal/roles/IAdminRole.sol";
          import "../../interfaces/internal/roles/IOperatorRole.sol";
          error FoundationTreasuryNode_Address_Is_Not_A_Contract();
          error FoundationTreasuryNode_Caller_Not_Admin();
          error FoundationTreasuryNode_Caller_Not_Operator();
          /**
           * @title A mixin that stores a reference to the Foundation treasury contract.
           * @notice The treasury collects fees and defines admin/operator roles.
           * @author batu-inal & HardlyDifficult
           */
          abstract contract FoundationTreasuryNode {
            using AddressUpgradeable for address payable;
            /// @dev This value was replaced with an immutable version.
            address payable private __gap_was_treasury;
            /// @notice The address of the treasury contract.
            address payable private immutable treasury;
            /// @notice Requires the caller is a Foundation admin.
            modifier onlyFoundationAdmin() {
              if (!IAdminRole(treasury).isAdmin(msg.sender)) {
                revert FoundationTreasuryNode_Caller_Not_Admin();
              }
              _;
            }
            /// @notice Requires the caller is a Foundation operator.
            modifier onlyFoundationOperator() {
              if (!IOperatorRole(treasury).isOperator(msg.sender)) {
                revert FoundationTreasuryNode_Caller_Not_Operator();
              }
              _;
            }
            /**
             * @notice Set immutable variables for the implementation contract.
             * @dev Assigns the treasury contract address.
             */
            constructor(address payable _treasury) {
              if (!_treasury.isContract()) {
                revert FoundationTreasuryNode_Address_Is_Not_A_Contract();
              }
              treasury = _treasury;
            }
            /**
             * @notice Gets the Foundation treasury contract.
             * @dev This call is used in the royalty registry contract.
             * @return treasuryAddress The address of the Foundation treasury contract.
             */
            function getFoundationTreasury() public view returns (address payable treasuryAddress) {
              treasuryAddress = treasury;
            }
            /**
             * @notice This empty reserved space is put in place to allow future versions to add new
             * variables without shifting down storage in the inheritance chain.
             * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
             */
            uint256[2_000] private __gap;
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @title A placeholder contract leaving room for new mixins to be added to the future.
           * @author HardlyDifficult
           */
          abstract contract Gap500 {
            /**
             * @notice This empty reserved space is put in place to allow future versions to add new
             * variables without shifting down storage in the inheritance chain.
             * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
             */
            uint256[500] private __gap;
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @title A placeholder contract leaving room for new mixins to be added to the future.
           * @author HardlyDifficult
           */
          abstract contract Gap9000 {
            /**
             * @notice This empty reserved space is put in place to allow future versions to add new
             * variables without shifting down storage in the inheritance chain.
             * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
             */
            uint256[9_000] private __gap;
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts/utils/Context.sol";
          import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
          import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
          import "@manifoldxyz/royalty-registry-solidity/contracts/IRoyaltyRegistry.sol";
          import "../../interfaces/internal/INFTCollectionType.sol";
          import "../../interfaces/standards/royalties/IGetFees.sol";
          import "../../interfaces/standards/royalties/IGetRoyalties.sol";
          import "../../interfaces/standards/royalties/IOwnable.sol";
          import "../../interfaces/standards/royalties/IRoyaltyInfo.sol";
          import "../../interfaces/standards/royalties/ITokenCreator.sol";
          import "../../libraries/ArrayLibrary.sol";
          import "./Constants.sol";
          import "./FoundationTreasuryNode.sol";
          import "./SendValueWithFallbackWithdraw.sol";
          import "./MarketSharedCore.sol";
          error NFTMarketFees_Royalty_Registry_Is_Not_A_Contract();
          error NFTMarketFees_Invalid_Protocol_Fee();
          /**
           * @title A mixin to distribute funds when an NFT is sold.
           * @author batu-inal & HardlyDifficult
           */
          abstract contract MarketFees is FoundationTreasuryNode, Context, MarketSharedCore, SendValueWithFallbackWithdraw {
            using AddressUpgradeable for address;
            using ArrayLibrary for address payable[];
            using ArrayLibrary for uint256[];
            using ERC165Checker for address;
            /**
             * @dev Removing old unused variables in an upgrade safe way. Was:
             * uint256 private _primaryFoundationFeeBasisPoints;
             * uint256 private _secondaryFoundationFeeBasisPoints;
             * uint256 private _secondaryCreatorFeeBasisPoints;
             * mapping(address => mapping(uint256 => bool)) private _nftContractToTokenIdToFirstSaleCompleted;
             */
            uint256[4] private __gap_was_fees;
            /// @notice The royalties sent to creator recipients on secondary sales.
            uint256 private constant CREATOR_ROYALTY_DENOMINATOR = BASIS_POINTS / 1_000; // 10%
            /// @notice The fee collected by Foundation for sales facilitated by this market contract.
            uint256 private immutable DEFAULT_PROTOCOL_FEE_IN_BASIS_POINTS;
            /// @notice The fee collected by the buy referrer for sales facilitated by this market contract.
            ///         This fee is calculated from the total protocol fee.
            uint256 private constant BUY_REFERRER_FEE_DENOMINATOR = BASIS_POINTS / 100; // 1%
            /// @notice The address of the royalty registry which may be used to define royalty overrides for some collections.
            IRoyaltyRegistry private immutable royaltyRegistry;
            /// @notice The address of this contract's implementation.
            /// @dev This is used when making stateless external calls to this contract,
            /// saving gas over hopping through the proxy which is only necessary when accessing state.
            MarketFees private immutable implementationAddress;
            /// @notice True for the Drop market which only performs primary sales. False if primary & secondary are supported.
            bool private immutable assumePrimarySale;
            /**
             * @notice Emitted when an NFT sold with a referrer.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @param buyReferrer The account which received the buy referral incentive.
             * @param buyReferrerFee The portion of the protocol fee collected by the buy referrer.
             * @param buyReferrerSellerFee The portion of the owner revenue collected by the buy referrer (not implemented).
             */
            event BuyReferralPaid(
              address indexed nftContract,
              uint256 indexed tokenId,
              address buyReferrer,
              uint256 buyReferrerFee,
              uint256 buyReferrerSellerFee
            );
            /**
             * @notice Emitted when an NFT is sold when associated with a sell referrer.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @param sellerReferrer The account which received the sell referral incentive.
             * @param sellerReferrerFee The portion of the seller revenue collected by the sell referrer.
             */
            event SellerReferralPaid(
              address indexed nftContract,
              uint256 indexed tokenId,
              address sellerReferrer,
              uint256 sellerReferrerFee
            );
            /**
             * @notice Configures the registry allowing for royalty overrides to be defined.
             * @param _royaltyRegistry The registry to use for royalty overrides.
             * @param _assumePrimarySale True for the Drop market which only performs primary sales.
             * False if primary & secondary are supported.
             */
            constructor(uint16 protocolFeeInBasisPoints, address _royaltyRegistry, bool _assumePrimarySale) {
              if (
                protocolFeeInBasisPoints < BASIS_POINTS / BUY_REFERRER_FEE_DENOMINATOR ||
                protocolFeeInBasisPoints + BASIS_POINTS / CREATOR_ROYALTY_DENOMINATOR >= BASIS_POINTS - MAX_EXHIBITION_TAKE_RATE
              ) {
                /* If the protocol fee is invalid, revert:
                 * Protocol fee must be greater than the buy referrer fee since referrer fees are deducted from the protocol fee.
                 * The protocol fee must leave room for the creator royalties and the max exhibition take rate.
                 */
                revert NFTMarketFees_Invalid_Protocol_Fee();
              }
              DEFAULT_PROTOCOL_FEE_IN_BASIS_POINTS = protocolFeeInBasisPoints;
              if (!_royaltyRegistry.isContract()) {
                // Not using a 165 check since mainnet and goerli are not using the same versions of the registry.
                revert NFTMarketFees_Royalty_Registry_Is_Not_A_Contract();
              }
              royaltyRegistry = IRoyaltyRegistry(_royaltyRegistry);
              assumePrimarySale = _assumePrimarySale;
              // In the constructor, `this` refers to the implementation address. Everywhere else it'll be the proxy.
              implementationAddress = this;
            }
            /**
             * @notice Distributes funds to foundation, creator recipients, and NFT owner after a sale.
             */
            function _distributeFunds(
              address nftContract,
              uint256 tokenId,
              address payable seller,
              uint256 price,
              address payable buyReferrer,
              address payable sellerReferrerPaymentAddress,
              uint16 sellerReferrerTakeRateInBasisPoints
            ) internal returns (uint256 totalFees, uint256 creatorRev, uint256 sellerRev) {
              if (price == 0) {
                // When the sale price is 0, there are no revenue to distribute.
                return (0, 0, 0);
              }
              address payable[] memory creatorRecipients;
              uint256[] memory creatorShares;
              uint256 buyReferrerFee;
              uint256 sellerReferrerFee;
              (totalFees, creatorRecipients, creatorShares, sellerRev, buyReferrerFee, sellerReferrerFee) = _getFees(
                nftContract,
                tokenId,
                seller,
                price,
                buyReferrer,
                sellerReferrerTakeRateInBasisPoints
              );
              // Pay the creator(s)
              // If just a single recipient was defined, use a larger gas limit in order to support in-contract split logic.
              uint256 creatorGasLimit = creatorRecipients.length == 1
                ? SEND_VALUE_GAS_LIMIT_MULTIPLE_RECIPIENTS
                : SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT;
              unchecked {
                for (uint256 i = 0; i < creatorRecipients.length; ++i) {
                  _sendValueWithFallbackWithdraw(creatorRecipients[i], creatorShares[i], creatorGasLimit);
                  // Sum the total creator rev from shares
                  // creatorShares is in ETH so creatorRev will not overflow here.
                  creatorRev += creatorShares[i];
                }
              }
              // Pay the seller
              _sendValueWithFallbackWithdraw(seller, sellerRev, SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT);
              // Pay the protocol fee
              _sendValueWithFallbackWithdraw(getFoundationTreasury(), totalFees, SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT);
              // Pay the buy referrer fee
              if (buyReferrerFee != 0) {
                _sendValueWithFallbackWithdraw(buyReferrer, buyReferrerFee, SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT);
                emit BuyReferralPaid({
                  nftContract: nftContract,
                  tokenId: tokenId,
                  buyReferrer: buyReferrer,
                  buyReferrerFee: buyReferrerFee,
                  buyReferrerSellerFee: 0
                });
                unchecked {
                  // Add the referrer fee back into the total fees so that all 3 return fields sum to the total price for events
                  totalFees += buyReferrerFee;
                }
              }
              if (sellerReferrerPaymentAddress != address(0)) {
                if (sellerReferrerFee != 0) {
                  // Add the seller referrer fee back to revenue so that all 3 return fields sum to the total price for events.
                  unchecked {
                    if (sellerRev == 0) {
                      // When sellerRev is 0, this is a primary sale and all revenue is attributed to the "creator".
                      creatorRev += sellerReferrerFee;
                    } else {
                      sellerRev += sellerReferrerFee;
                    }
                  }
                  _sendValueWithFallbackWithdraw(
                    sellerReferrerPaymentAddress,
                    sellerReferrerFee,
                    SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT
                  );
                }
                emit SellerReferralPaid(nftContract, tokenId, sellerReferrerPaymentAddress, sellerReferrerFee);
              }
            }
            /**
             * @notice Returns how funds will be distributed for a sale at the given price point.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @param price The sale price to calculate the fees for.
             * @return totalFees How much will be sent to the Foundation treasury and/or referrals.
             * @return creatorRev How much will be sent across all the `creatorRecipients` defined.
             * @return creatorRecipients The addresses of the recipients to receive a portion of the creator fee.
             * @return creatorShares The percentage of the creator fee to be distributed to each `creatorRecipient`.
             * If there is only one `creatorRecipient`, this may be an empty array.
             * Otherwise `creatorShares.length` == `creatorRecipients.length`.
             * @return sellerRev How much will be sent to the owner/seller of the NFT.
             * If the NFT is being sold by the creator, this may be 0 and the full revenue will appear as `creatorRev`.
             * @return seller The address of the owner of the NFT.
             * If `sellerRev` is 0, this may be `address(0)`.
             */
            function getFeesAndRecipients(
              address nftContract,
              uint256 tokenId,
              uint256 price
            )
              external
              view
              returns (
                uint256 totalFees,
                uint256 creatorRev,
                address payable[] memory creatorRecipients,
                uint256[] memory creatorShares,
                uint256 sellerRev,
                address payable seller
              )
            {
              seller = _getSellerOrOwnerOf(nftContract, tokenId);
              (totalFees, creatorRecipients, creatorShares, sellerRev, , ) = _getFees({
                nftContract: nftContract,
                tokenId: tokenId,
                seller: seller,
                price: price,
                // Notice: Setting this value is a breaking change for the FNDMiddleware contract.
                // Will be wired in an upcoming release to communicate the buy referral information.
                buyReferrer: payable(0),
                sellerReferrerTakeRateInBasisPoints: 0
              });
              // Sum the total creator rev from shares
              unchecked {
                for (uint256 i = 0; i < creatorShares.length; ++i) {
                  creatorRev += creatorShares[i];
                }
              }
            }
            /**
             * @notice Returns the address of the registry allowing for royalty configuration overrides.
             * @dev See https://royaltyregistry.xyz/
             * @return registry The address of the royalty registry contract.
             */
            function getRoyaltyRegistry() external view returns (address registry) {
              registry = address(royaltyRegistry);
            }
            /**
             * @notice **For internal use only.**
             * @dev This function is external to allow using try/catch but is not intended for external use.
             * This checks the token creator.
             */
            function internalGetTokenCreator(
              address nftContract,
              uint256 tokenId
            ) external view returns (address payable creator) {
              creator = ITokenCreator(nftContract).tokenCreator{ gas: READ_ONLY_GAS_LIMIT }(tokenId);
            }
            /**
             * @notice **For internal use only.**
             * @dev This function is external to allow using try/catch but is not intended for external use.
             * If ERC2981 royalties (or getRoyalties) are defined by the NFT contract, allow this standard to define immutable
             * royalties that cannot be later changed via the royalty registry.
             */
            function internalGetImmutableRoyalties(
              address nftContract,
              uint256 tokenId
            ) external view returns (address payable[] memory recipients, uint256[] memory splitPerRecipientInBasisPoints) {
              // 1st priority: ERC-2981
              if (nftContract.supportsERC165InterfaceUnchecked(type(IRoyaltyInfo).interfaceId)) {
                try IRoyaltyInfo(nftContract).royaltyInfo{ gas: READ_ONLY_GAS_LIMIT }(tokenId, BASIS_POINTS) returns (
                  address receiver,
                  uint256 royaltyAmount
                ) {
                  // Manifold contracts return (address(this), 0) when royalties are not defined
                  // - so ignore results when the amount is 0
                  if (royaltyAmount > 0) {
                    recipients = new address payable[](1);
                    recipients[0] = payable(receiver);
                    splitPerRecipientInBasisPoints = new uint256[](1);
                    // The split amount is assumed to be 100% when only 1 recipient is returned
                    return (recipients, splitPerRecipientInBasisPoints);
                  }
                } catch {
                  // Fall through
                }
              }
              // 2nd priority: getRoyalties
              if (nftContract.supportsERC165InterfaceUnchecked(type(IGetRoyalties).interfaceId)) {
                try IGetRoyalties(nftContract).getRoyalties{ gas: READ_ONLY_GAS_LIMIT }(tokenId) returns (
                  address payable[] memory _recipients,
                  uint256[] memory recipientBasisPoints
                ) {
                  if (_recipients.length != 0 && _recipients.length == recipientBasisPoints.length) {
                    return (_recipients, recipientBasisPoints);
                  }
                } catch {
                  // Fall through
                }
              }
            }
            /**
             * @notice **For internal use only.**
             * @dev This function is external to allow using try/catch but is not intended for external use.
             * This checks for royalties defined in the royalty registry or via a non-standard royalty API.
             */
            function internalGetMutableRoyalties(
              address nftContract,
              uint256 tokenId,
              address payable creator
            ) external view returns (address payable[] memory recipients, uint256[] memory splitPerRecipientInBasisPoints) {
              /* Overrides must support ERC-165 when registered, except for overrides defined by the registry owner.
                 If that results in an override w/o 165 we may need to upgrade the market to support or ignore that override. */
              // The registry requires overrides are not 0 and contracts when set.
              // If no override is set, the nftContract address is returned.
              try royaltyRegistry.getRoyaltyLookupAddress{ gas: READ_ONLY_GAS_LIMIT }(nftContract) returns (
                address overrideContract
              ) {
                if (overrideContract != nftContract) {
                  nftContract = overrideContract;
                  // The functions above are repeated here if an override is set.
                  // 3rd priority: ERC-2981 override
                  if (nftContract.supportsERC165InterfaceUnchecked(type(IRoyaltyInfo).interfaceId)) {
                    try IRoyaltyInfo(nftContract).royaltyInfo{ gas: READ_ONLY_GAS_LIMIT }(tokenId, BASIS_POINTS) returns (
                      address receiver,
                      uint256 royaltyAmount
                    ) {
                      // Manifold contracts return (address(this), 0) when royalties are not defined
                      // - so ignore results when the amount is 0
                      if (royaltyAmount != 0) {
                        recipients = new address payable[](1);
                        recipients[0] = payable(receiver);
                        splitPerRecipientInBasisPoints = new uint256[](1);
                        // The split amount is assumed to be 100% when only 1 recipient is returned
                        return (recipients, splitPerRecipientInBasisPoints);
                      }
                    } catch {
                      // Fall through
                    }
                  }
                  // 4th priority: getRoyalties override
                  if (recipients.length == 0 && nftContract.supportsERC165InterfaceUnchecked(type(IGetRoyalties).interfaceId)) {
                    try IGetRoyalties(nftContract).getRoyalties{ gas: READ_ONLY_GAS_LIMIT }(tokenId) returns (
                      address payable[] memory _recipients,
                      uint256[] memory recipientBasisPoints
                    ) {
                      if (_recipients.length != 0 && _recipients.length == recipientBasisPoints.length) {
                        return (_recipients, recipientBasisPoints);
                      }
                    } catch {
                      // Fall through
                    }
                  }
                }
              } catch {
                // Ignore out of gas errors and continue using the nftContract address
              }
              // 5th priority: getFee* from contract or override
              if (nftContract.supportsERC165InterfaceUnchecked(type(IGetFees).interfaceId)) {
                try IGetFees(nftContract).getFeeRecipients{ gas: READ_ONLY_GAS_LIMIT }(tokenId) returns (
                  address payable[] memory _recipients
                ) {
                  if (_recipients.length != 0) {
                    try IGetFees(nftContract).getFeeBps{ gas: READ_ONLY_GAS_LIMIT }(tokenId) returns (
                      uint256[] memory recipientBasisPoints
                    ) {
                      if (_recipients.length == recipientBasisPoints.length) {
                        return (_recipients, recipientBasisPoints);
                      }
                    } catch {
                      // Fall through
                    }
                  }
                } catch {
                  // Fall through
                }
              }
              // 6th priority: tokenCreator w/ or w/o requiring 165 from contract or override
              if (creator != address(0)) {
                // Only pay the tokenCreator if there wasn't another royalty defined
                recipients = new address payable[](1);
                recipients[0] = creator;
                splitPerRecipientInBasisPoints = new uint256[](1);
                // The split amount is assumed to be 100% when only 1 recipient is returned
                return (recipients, splitPerRecipientInBasisPoints);
              }
              // 7th priority: owner from contract or override
              try IOwnable(nftContract).owner{ gas: READ_ONLY_GAS_LIMIT }() returns (address owner) {
                if (owner != address(0)) {
                  // Only pay the owner if there wasn't another royalty defined
                  recipients = new address payable[](1);
                  recipients[0] = payable(owner);
                  splitPerRecipientInBasisPoints = new uint256[](1);
                  // The split amount is assumed to be 100% when only 1 recipient is returned
                  return (recipients, splitPerRecipientInBasisPoints);
                }
              } catch {
                // Fall through
              }
              // If no valid payment address or creator is found, return 0 recipients
            }
            /**
             * @notice Calculates how funds should be distributed for the given sale details.
             * @dev When the NFT is being sold by the `tokenCreator`, all the seller revenue will
             * be split with the royalty recipients defined for that NFT.
             */
            function _getFees(
              address nftContract,
              uint256 tokenId,
              address payable seller,
              uint256 price,
              address payable buyReferrer,
              uint16 sellerReferrerTakeRateInBasisPoints
            )
              private
              view
              returns (
                uint256 totalFees,
                address payable[] memory creatorRecipients,
                uint256[] memory creatorShares,
                uint256 sellerRev,
                uint256 buyReferrerFee,
                uint256 sellerReferrerFee
              )
            {
              // Calculate the protocol fee
              totalFees = (price * _getProtocolFee(nftContract)) / BASIS_POINTS;
              address payable creator;
              try implementationAddress.internalGetTokenCreator(nftContract, tokenId) returns (address payable _creator) {
                creator = _creator;
              } catch {
                // Fall through
              }
              try implementationAddress.internalGetImmutableRoyalties(nftContract, tokenId) returns (
                address payable[] memory _recipients,
                uint256[] memory _splitPerRecipientInBasisPoints
              ) {
                (creatorRecipients, creatorShares) = (_recipients, _splitPerRecipientInBasisPoints);
              } catch {
                // Fall through
              }
              if (creatorRecipients.length == 0) {
                // Check mutable royalties only if we didn't find results from the immutable API
                try implementationAddress.internalGetMutableRoyalties(nftContract, tokenId, creator) returns (
                  address payable[] memory _recipients,
                  uint256[] memory _splitPerRecipientInBasisPoints
                ) {
                  (creatorRecipients, creatorShares) = (_recipients, _splitPerRecipientInBasisPoints);
                } catch {
                  // Fall through
                }
              }
              if (creatorRecipients.length != 0 || assumePrimarySale) {
                uint256 creatorRev;
                if (assumePrimarySale) {
                  // All revenue should go to the creator recipients
                  unchecked {
                    // totalFees is always < price.
                    creatorRev = price - totalFees;
                  }
                  if (creatorRecipients.length == 0) {
                    // If no creators were found via the royalty APIs, then set that recipient to the seller's address
                    creatorRecipients = new address payable[](1);
                    creatorRecipients[0] = seller;
                    creatorShares = new uint256[](1);
                    // The split amount is assumed to be 100% when only 1 recipient is returned
                  }
                } else if (seller == creator || (creatorRecipients.length != 0 && seller == creatorRecipients[0])) {
                  // When sold by the creator, all revenue is split if applicable.
                  unchecked {
                    // totalFees is always < price.
                    creatorRev = price - totalFees;
                  }
                } else {
                  // Rounding favors the owner first, then creator, and foundation last.
                  unchecked {
                    // Safe math is not required when dividing by a non-zero constant.
                    creatorRev = price / CREATOR_ROYALTY_DENOMINATOR;
                  }
                  sellerRev = price - totalFees - creatorRev;
                }
                // Cap the max number of recipients supported
                creatorRecipients.capLength(MAX_ROYALTY_RECIPIENTS);
                creatorShares.capLength(MAX_ROYALTY_RECIPIENTS);
                // Calculate the seller referrer fee when some revenue is awarded to the creator
                if (sellerReferrerTakeRateInBasisPoints != 0) {
                  sellerReferrerFee = (price * sellerReferrerTakeRateInBasisPoints) / BASIS_POINTS;
                  // Subtract the seller referrer fee from the seller revenue so we do not double pay.
                  if (sellerRev == 0) {
                    // If the seller revenue is 0, this is a primary sale where all seller revenue is attributed to the "creator".
                    creatorRev -= sellerReferrerFee;
                  } else {
                    sellerRev -= sellerReferrerFee;
                  }
                }
                // Sum the total shares defined
                uint256 totalShares;
                if (creatorRecipients.length > 1) {
                  unchecked {
                    for (uint256 i = 0; i < creatorRecipients.length; ++i) {
                      if (creatorRecipients[i] == seller) {
                        // If the seller is any of the recipients defined, assume a primary sale
                        creatorRev += sellerRev;
                        sellerRev = 0;
                      }
                      if (totalShares != type(uint256).max) {
                        if (creatorShares[i] > BASIS_POINTS) {
                          // If the numbers are >100% we ignore the fee recipients and pay just the first instead
                          totalShares = type(uint256).max;
                          // Continue the loop in order to detect a potential primary sale condition
                        } else {
                          totalShares += creatorShares[i];
                        }
                      }
                    }
                  }
                  if (totalShares == 0 || totalShares == type(uint256).max) {
                    // If no shares were defined or shares were out of bounds, pay only the first recipient
                    creatorRecipients.capLength(1);
                    creatorShares.capLength(1);
                  }
                }
                // Send payouts to each additional recipient if more than 1 was defined
                uint256 totalRoyaltiesDistributed;
                for (uint256 i = 1; i < creatorRecipients.length; ) {
                  uint256 royalty = (creatorRev * creatorShares[i]) / totalShares;
                  totalRoyaltiesDistributed += royalty;
                  creatorShares[i] = royalty;
                  unchecked {
                    ++i;
                  }
                }
                // Send the remainder to the 1st creator, rounding in their favor
                creatorShares[0] = creatorRev - totalRoyaltiesDistributed;
              } else {
                // No royalty recipients found.
                unchecked {
                  // totalFees is always < price.
                  sellerRev = price - totalFees;
                }
                // Calculate the seller referrer fee when there is no creator royalty
                if (sellerReferrerTakeRateInBasisPoints != 0) {
                  sellerReferrerFee = (price * sellerReferrerTakeRateInBasisPoints) / BASIS_POINTS;
                  sellerRev -= sellerReferrerFee;
                }
              }
              if (buyReferrer != address(0) && buyReferrer != _msgSender() && buyReferrer != seller && buyReferrer != creator) {
                unchecked {
                  buyReferrerFee = price / BUY_REFERRER_FEE_DENOMINATOR;
                  // buyReferrerFee is always <= totalFees
                  totalFees -= buyReferrerFee;
                }
              }
            }
            /**
             * @notice Calculates the protocol fee for the given NFT contract.
             * @dev This returns the contract's default fee but may be overridden to change fees based on the collection type.
             */
            function _getProtocolFee(address /* nftContract */) internal view virtual returns (uint256 protocolFeeInBasisPoints) {
              protocolFeeInBasisPoints = DEFAULT_PROTOCOL_FEE_IN_BASIS_POINTS;
            }
            /**
             * @notice This empty reserved space is put in place to allow future versions to add new
             * variables without shifting down storage in the inheritance chain.
             * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
             */
            uint256[500] private __gap;
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "./FETHNode.sol";
          /**
           * @title A place for common modifiers and functions used by various market mixins, if any.
           * @dev This also leaves a gap which can be used to add a new mixin to the top of the inheritance tree.
           * @author batu-inal & HardlyDifficult
           */
          abstract contract MarketSharedCore is FETHNode {
            /**
             * @notice Checks who the seller for an NFT is if listed in this market.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @return seller The seller which listed this NFT for sale, or address(0) if not listed.
             */
            function getSellerOf(address nftContract, uint256 tokenId) external view returns (address payable seller) {
              seller = _getSellerOf(nftContract, tokenId);
            }
            /**
             * @notice Checks who the seller for an NFT is if listed in this market.
             */
            function _getSellerOf(address nftContract, uint256 tokenId) internal view virtual returns (address payable seller);
            /**
             * @notice Checks who the seller for an NFT is if listed in this market or returns the current owner.
             */
            function _getSellerOrOwnerOf(
              address nftContract,
              uint256 tokenId
            ) internal view virtual returns (address payable sellerOrOwner);
            /**
             * @notice This empty reserved space is put in place to allow future versions to add new
             * variables without shifting down storage in the inheritance chain.
             * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
             */
            uint256[500] private __gap;
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts/utils/Context.sol";
          import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
          error RouterContext_Not_A_Contract();
          /**
           * @title Enables a trusted router contract to override the usual msg.sender address.
           * @author HardlyDifficult
           */
          abstract contract RouterContext is Context {
            using AddressUpgradeable for address;
            address private immutable approvedRouter;
            constructor(address router) {
              if (!router.isContract()) {
                revert RouterContext_Not_A_Contract();
              }
              approvedRouter = router;
            }
            /**
             * @notice Returns the router contract which is able to override the msg.sender address.
             * @return router The address of the trusted router.
             */
            function getApprovedRouterAddress() external view returns (address router) {
              router = approvedRouter;
            }
            /**
             * @notice Returns the sender of the transaction.
             * @dev If the msg.sender is the trusted router contract, then the last 20 bytes of the calldata is the authorized
             * sender.
             */
            function _msgSender() internal view virtual override returns (address sender) {
              sender = super._msgSender();
              if (sender == approvedRouter) {
                assembly {
                  // The router appends the msg.sender to the end of the calldata
                  // source: https://github.com/opengsn/gsn/blob/v3.0.0-beta.3/packages/contracts/src/ERC2771Recipient.sol#L48
                  sender := shr(96, calldataload(sub(calldatasize(), 20)))
                }
              }
            }
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
          import "./FETHNode.sol";
          /**
           * @title A mixin for sending ETH with a fallback withdraw mechanism.
           * @notice Attempt to send ETH and if the transfer fails or runs out of gas, store the balance
           * in the FETH token contract for future withdrawal instead.
           * @dev This mixin was recently switched to escrow funds in FETH.
           * Once we have confirmed all pending balances have been withdrawn, we can remove the escrow tracking here.
           * @author batu-inal & HardlyDifficult
           */
          abstract contract SendValueWithFallbackWithdraw is FETHNode {
            using AddressUpgradeable for address payable;
            /// @dev Removing old unused variables in an upgrade safe way.
            uint256 private __gap_was_pendingWithdrawals;
            /**
             * @notice Emitted when escrowed funds are withdrawn to FETH.
             * @param user The account which has withdrawn ETH.
             * @param amount The amount of ETH which has been withdrawn.
             */
            event WithdrawalToFETH(address indexed user, uint256 amount);
            /**
             * @notice Attempt to send a user or contract ETH.
             * If it fails store the amount owned for later withdrawal in FETH.
             * @dev This may fail when sending ETH to a contract that is non-receivable or exceeds the gas limit specified.
             */
            function _sendValueWithFallbackWithdraw(address payable user, uint256 amount, uint256 gasLimit) internal {
              if (amount == 0) {
                return;
              }
              // Cap the gas to prevent consuming all available gas to block a tx from completing successfully
              // solhint-disable-next-line avoid-low-level-calls
              (bool success, ) = user.call{ value: amount, gas: gasLimit }("");
              if (!success) {
                // Store the funds that failed to send for the user in the FETH token
                feth.depositFor{ value: amount }(user);
                emit WithdrawalToFETH(user, amount);
              }
            }
            /**
             * @notice This empty reserved space is put in place to allow future versions to add new
             * variables without shifting down storage in the inheritance chain.
             * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
             */
            uint256[999] private __gap;
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "../../libraries/TimeLibrary.sol";
          error TxDeadline_Tx_Deadline_Expired();
          /**
           * @title A mixin that provides a modifier to check that a transaction deadline has not expired.
           * @author HardlyDifficult
           */
          abstract contract TxDeadline {
            using TimeLibrary for uint256;
            /// @notice Requires the deadline provided is 0, now, or in the future.
            modifier txDeadlineNotExpired(uint256 txDeadlineTime) {
              // No transaction deadline when set to 0.
              if (txDeadlineTime != 0 && txDeadlineTime.hasExpired()) {
                revert TxDeadline_Tx_Deadline_Expired();
              }
              _;
            }
            // This mixin does not use any storage.
          }
          /*
            ・
             * ★
                ・ 。
                   ・ ゚☆ 。
                * ★ ゚・。 *  。
                      * ☆ 。・゚*.。
                   ゚ *.。☆。★ ・
          ​
                                `                     .-:::::-.`              `-::---...```
                               `-:`               .:+ssssoooo++//:.`       .-/+shhhhhhhhhhhhhyyyssooo:
                              .--::.            .+ossso+/////++/:://-`   .////+shhhhhhhhhhhhhhhhhhhhhy
                            `-----::.         `/+////+++///+++/:--:/+/-  -////+shhhhhhhhhhhhhhhhhhhhhy
                           `------:::-`      `//-.``.-/+ooosso+:-.-/oso- -////+shhhhhhhhhhhhhhhhhhhhhy
                          .--------:::-`     :+:.`  .-/osyyyyyyso++syhyo.-////+shhhhhhhhhhhhhhhhhhhhhy
                        `-----------:::-.    +o+:-.-:/oyhhhhhhdhhhhhdddy:-////+shhhhhhhhhhhhhhhhhhhhhy
                       .------------::::--  `oys+/::/+shhhhhhhdddddddddy/-////+shhhhhhhhhhhhhhhhhhhhhy
                      .--------------:::::-` +ys+////+yhhhhhhhddddddddhy:-////+yhhhhhhhhhhhhhhhhhhhhhy
                    `----------------::::::-`.ss+/:::+oyhhhhhhhhhhhhhhho`-////+shhhhhhhhhhhhhhhhhhhhhy
                   .------------------:::::::.-so//::/+osyyyhhhhhhhhhys` -////+shhhhhhhhhhhhhhhhhhhhhy
                 `.-------------------::/:::::..+o+////+oosssyyyyyyys+`  .////+shhhhhhhhhhhhhhhhhhhhhy
                 .--------------------::/:::.`   -+o++++++oooosssss/.     `-//+shhhhhhhhhhhhhhhhhhhhyo
               .-------   ``````.......--`        `-/+ooooosso+/-`          `./++++///:::--...``hhhhyo
                                                        `````
             * 
                ・ 。
              ・  ゚☆ 。
                * ★ ゚・。 *  。
                      * ☆ 。・゚*.。
                   ゚ *.。☆。★ ・
              *  ゚。·*・。 ゚*
               ☆゚・。°*. ゚
            ・ ゚*。・゚★。
            ・ *゚。   *
           ・゚*。★・
           ☆∴。 *
          ・ 。
          */
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts/utils/Context.sol";
          import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
          import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
          import "./mixins/shared/FETHNode.sol";
          import "./mixins/shared/FoundationTreasuryNode.sol";
          import "./mixins/shared/Gap9000.sol";
          import "./mixins/shared/Gap500.sol";
          import "./mixins/shared/MarketFees.sol";
          import "./mixins/shared/MarketSharedCore.sol";
          import "./mixins/shared/RouterContext.sol";
          import "./mixins/shared/SendValueWithFallbackWithdraw.sol";
          import "./mixins/shared/TxDeadline.sol";
          import "./mixins/nftDropMarket/NFTDropMarketCore.sol";
          import "./mixins/nftDropMarket/NFTDropMarketFixedPriceSale.sol";
          import "./mixins/nftDropMarket/NFTDropMarketExhibition.sol";
          error NFTDropMarket_NFT_Already_Minted();
          /**
           * @title A market for minting NFTs with Foundation.
           * @author batu-inal & HardlyDifficult & philbirt & reggieag
           */
          contract NFTDropMarket is
            TxDeadline,
            Initializable,
            FoundationTreasuryNode,
            Context,
            RouterContext,
            FETHNode,
            MarketSharedCore,
            NFTDropMarketCore,
            ReentrancyGuardUpgradeable,
            SendValueWithFallbackWithdraw,
            MarketFees,
            Gap500,
            Gap9000,
            NFTDropMarketExhibition,
            NFTDropMarketFixedPriceSale
          {
            /**
             * @notice Set immutable variables for the implementation contract.
             * @dev Using immutable instead of constants allows us to use different values on testnet.
             * @param treasury The Foundation Treasury contract address.
             * @param feth The FETH ERC-20 token contract address.
             * @param royaltyRegistry The Royalty Registry contract address.
             * @param nftMarket The NFT Market contract address, containing exhibition definitions.
             * @param router The trusted router contract address.
             */
            constructor(
              address payable treasury,
              address feth,
              address royaltyRegistry,
              address nftMarket,
              address router
            )
              FoundationTreasuryNode(treasury)
              FETHNode(feth)
              MarketFees(
                /* protocolFeeInBasisPoints: */
                1500,
                royaltyRegistry,
                /* assumePrimarySale: */
                true
              )
              NFTDropMarketExhibition(nftMarket)
              RouterContext(router)
            {
              _disableInitializers();
            }
            /**
             * @notice Called once to configure the contract after the initial proxy deployment.
             * @dev This farms the initialize call out to inherited contracts as needed to initialize mutable variables.
             */
            function initialize() external initializer {
              ReentrancyGuardUpgradeable.__ReentrancyGuard_init();
            }
            /**
             * @inheritdoc MarketSharedCore
             * @dev Returns address(0) if the NFT has already been sold, otherwise checks for a listing in this market.
             */
            function _getSellerOf(
              address nftContract,
              uint256 tokenId
            ) internal view override(MarketSharedCore, NFTDropMarketFixedPriceSale) returns (address payable seller) {
              // Check the current owner first in case it has been sold.
              try IERC721(nftContract).ownerOf(tokenId) returns (address owner) {
                if (owner != address(0)) {
                  // If sold, return address(0) since that owner cannot sell via this market.
                  return payable(address(0));
                }
              } catch {
                // Fall through
              }
              return super._getSellerOf(nftContract, tokenId);
            }
            /**
             * @inheritdoc NFTDropMarketFixedPriceSale
             */
            function _getProtocolFee(
              address nftContract
            ) internal view override(MarketFees, NFTDropMarketFixedPriceSale) returns (uint256 protocolFeeInBasisPoints) {
              protocolFeeInBasisPoints = super._getProtocolFee(nftContract);
            }
            /**
             * @inheritdoc MarketSharedCore
             * @dev Reverts if the NFT has already been sold, otherwise checks for a listing in this market.
             */
            function _getSellerOrOwnerOf(
              address nftContract,
              uint256 tokenId
            ) internal view override returns (address payable sellerOrOwner) {
              // Check the current owner first in case it has been sold.
              try IERC721(nftContract).ownerOf(tokenId) returns (address owner) {
                if (owner != address(0)) {
                  // Once an NFT has been minted, it cannot be sold through this contract.
                  revert NFTDropMarket_NFT_Already_Minted();
                }
              } catch {
                // Fall through
              }
              sellerOrOwner = super._getSellerOf(nftContract, tokenId);
            }
            /**
             * @inheritdoc RouterContext
             */
            function _msgSender() internal view override(Context, RouterContext) returns (address sender) {
              sender = super._msgSender();
            }
          }
          

          File 3 of 5: NFTTimedEditionCollection
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (access/AccessControl.sol)
          pragma solidity ^0.8.0;
          import "./IAccessControlUpgradeable.sol";
          import "../utils/ContextUpgradeable.sol";
          import "../utils/StringsUpgradeable.sol";
          import "../utils/introspection/ERC165Upgradeable.sol";
          import "../proxy/utils/Initializable.sol";
          /**
           * @dev Contract module that allows children to implement role-based access
           * control mechanisms. This is a lightweight version that doesn't allow enumerating role
           * members except through off-chain means by accessing the contract event logs. Some
           * applications may benefit from on-chain enumerability, for those cases see
           * {AccessControlEnumerable}.
           *
           * Roles are referred to by their `bytes32` identifier. These should be exposed
           * in the external API and be unique. The best way to achieve this is by
           * using `public constant` hash digests:
           *
           * ```
           * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
           * ```
           *
           * Roles can be used to represent a set of permissions. To restrict access to a
           * function call, use {hasRole}:
           *
           * ```
           * function foo() public {
           *     require(hasRole(MY_ROLE, msg.sender));
           *     ...
           * }
           * ```
           *
           * Roles can be granted and revoked dynamically via the {grantRole} and
           * {revokeRole} functions. Each role has an associated admin role, and only
           * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
           *
           * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
           * that only accounts with this role will be able to grant or revoke other
           * roles. More complex role relationships can be created by using
           * {_setRoleAdmin}.
           *
           * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
           * grant and revoke this role. Extra precautions should be taken to secure
           * accounts that have been granted it.
           */
          abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControlUpgradeable, ERC165Upgradeable {
              function __AccessControl_init() internal onlyInitializing {
              }
              function __AccessControl_init_unchained() internal onlyInitializing {
              }
              struct RoleData {
                  mapping(address => bool) members;
                  bytes32 adminRole;
              }
              mapping(bytes32 => RoleData) private _roles;
              bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
              /**
               * @dev Modifier that checks that an account has a specific role. Reverts
               * with a standardized message including the required role.
               *
               * The format of the revert reason is given by the following regular expression:
               *
               *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
               *
               * _Available since v4.1._
               */
              modifier onlyRole(bytes32 role) {
                  _checkRole(role);
                  _;
              }
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                  return interfaceId == type(IAccessControlUpgradeable).interfaceId || super.supportsInterface(interfaceId);
              }
              /**
               * @dev Returns `true` if `account` has been granted `role`.
               */
              function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
                  return _roles[role].members[account];
              }
              /**
               * @dev Revert with a standard message if `_msgSender()` is missing `role`.
               * Overriding this function changes the behavior of the {onlyRole} modifier.
               *
               * Format of the revert message is described in {_checkRole}.
               *
               * _Available since v4.6._
               */
              function _checkRole(bytes32 role) internal view virtual {
                  _checkRole(role, _msgSender());
              }
              /**
               * @dev Revert with a standard message if `account` is missing `role`.
               *
               * The format of the revert reason is given by the following regular expression:
               *
               *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
               */
              function _checkRole(bytes32 role, address account) internal view virtual {
                  if (!hasRole(role, account)) {
                      revert(
                          string(
                              abi.encodePacked(
                                  "AccessControl: account ",
                                  StringsUpgradeable.toHexString(account),
                                  " is missing role ",
                                  StringsUpgradeable.toHexString(uint256(role), 32)
                              )
                          )
                      );
                  }
              }
              /**
               * @dev Returns the admin role that controls `role`. See {grantRole} and
               * {revokeRole}.
               *
               * To change a role's admin, use {_setRoleAdmin}.
               */
              function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
                  return _roles[role].adminRole;
              }
              /**
               * @dev Grants `role` to `account`.
               *
               * If `account` had not been already granted `role`, emits a {RoleGranted}
               * event.
               *
               * Requirements:
               *
               * - the caller must have ``role``'s admin role.
               *
               * May emit a {RoleGranted} event.
               */
              function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
                  _grantRole(role, account);
              }
              /**
               * @dev Revokes `role` from `account`.
               *
               * If `account` had been granted `role`, emits a {RoleRevoked} event.
               *
               * Requirements:
               *
               * - the caller must have ``role``'s admin role.
               *
               * May emit a {RoleRevoked} event.
               */
              function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
                  _revokeRole(role, account);
              }
              /**
               * @dev Revokes `role` from the calling account.
               *
               * Roles are often managed via {grantRole} and {revokeRole}: this function's
               * purpose is to provide a mechanism for accounts to lose their privileges
               * if they are compromised (such as when a trusted device is misplaced).
               *
               * If the calling account had been revoked `role`, emits a {RoleRevoked}
               * event.
               *
               * Requirements:
               *
               * - the caller must be `account`.
               *
               * May emit a {RoleRevoked} event.
               */
              function renounceRole(bytes32 role, address account) public virtual override {
                  require(account == _msgSender(), "AccessControl: can only renounce roles for self");
                  _revokeRole(role, account);
              }
              /**
               * @dev Grants `role` to `account`.
               *
               * If `account` had not been already granted `role`, emits a {RoleGranted}
               * event. Note that unlike {grantRole}, this function doesn't perform any
               * checks on the calling account.
               *
               * May emit a {RoleGranted} event.
               *
               * [WARNING]
               * ====
               * This function should only be called from the constructor when setting
               * up the initial roles for the system.
               *
               * Using this function in any other way is effectively circumventing the admin
               * system imposed by {AccessControl}.
               * ====
               *
               * NOTE: This function is deprecated in favor of {_grantRole}.
               */
              function _setupRole(bytes32 role, address account) internal virtual {
                  _grantRole(role, account);
              }
              /**
               * @dev Sets `adminRole` as ``role``'s admin role.
               *
               * Emits a {RoleAdminChanged} event.
               */
              function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
                  bytes32 previousAdminRole = getRoleAdmin(role);
                  _roles[role].adminRole = adminRole;
                  emit RoleAdminChanged(role, previousAdminRole, adminRole);
              }
              /**
               * @dev Grants `role` to `account`.
               *
               * Internal function without access restriction.
               *
               * May emit a {RoleGranted} event.
               */
              function _grantRole(bytes32 role, address account) internal virtual {
                  if (!hasRole(role, account)) {
                      _roles[role].members[account] = true;
                      emit RoleGranted(role, account, _msgSender());
                  }
              }
              /**
               * @dev Revokes `role` from `account`.
               *
               * Internal function without access restriction.
               *
               * May emit a {RoleRevoked} event.
               */
              function _revokeRole(bytes32 role, address account) internal virtual {
                  if (hasRole(role, account)) {
                      _roles[role].members[account] = false;
                      emit RoleRevoked(role, account, _msgSender());
                  }
              }
              /**
               * @dev This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[49] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev External interface of AccessControl declared to support ERC165 detection.
           */
          interface IAccessControlUpgradeable {
              /**
               * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
               *
               * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
               * {RoleAdminChanged} not being emitted signaling this.
               *
               * _Available since v3.1._
               */
              event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
              /**
               * @dev Emitted when `account` is granted `role`.
               *
               * `sender` is the account that originated the contract call, an admin role
               * bearer except when using {AccessControl-_setupRole}.
               */
              event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
              /**
               * @dev Emitted when `account` is revoked `role`.
               *
               * `sender` is the account that originated the contract call:
               *   - if using `revokeRole`, it is the admin role bearer
               *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
               */
              event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
              /**
               * @dev Returns `true` if `account` has been granted `role`.
               */
              function hasRole(bytes32 role, address account) external view returns (bool);
              /**
               * @dev Returns the admin role that controls `role`. See {grantRole} and
               * {revokeRole}.
               *
               * To change a role's admin, use {AccessControl-_setRoleAdmin}.
               */
              function getRoleAdmin(bytes32 role) external view returns (bytes32);
              /**
               * @dev Grants `role` to `account`.
               *
               * If `account` had not been already granted `role`, emits a {RoleGranted}
               * event.
               *
               * Requirements:
               *
               * - the caller must have ``role``'s admin role.
               */
              function grantRole(bytes32 role, address account) external;
              /**
               * @dev Revokes `role` from `account`.
               *
               * If `account` had been granted `role`, emits a {RoleRevoked} event.
               *
               * Requirements:
               *
               * - the caller must have ``role``'s admin role.
               */
              function revokeRole(bytes32 role, address account) external;
              /**
               * @dev Revokes `role` from the calling account.
               *
               * Roles are often managed via {grantRole} and {revokeRole}: this function's
               * purpose is to provide a mechanism for accounts to lose their privileges
               * if they are compromised (such as when a trusted device is misplaced).
               *
               * If the calling account had been granted `role`, emits a {RoleRevoked}
               * event.
               *
               * Requirements:
               *
               * - the caller must be `account`.
               */
              function renounceRole(bytes32 role, address account) external;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.1) (proxy/utils/Initializable.sol)
          pragma solidity ^0.8.2;
          import "../../utils/AddressUpgradeable.sol";
          /**
           * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
           * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
           * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
           * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
           *
           * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
           * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
           * case an upgrade adds a module that needs to be initialized.
           *
           * For example:
           *
           * [.hljs-theme-light.nopadding]
           * ```
           * contract MyToken is ERC20Upgradeable {
           *     function initialize() initializer public {
           *         __ERC20_init("MyToken", "MTK");
           *     }
           * }
           * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
           *     function initializeV2() reinitializer(2) public {
           *         __ERC20Permit_init("MyToken");
           *     }
           * }
           * ```
           *
           * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
           * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
           *
           * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
           * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
           *
           * [CAUTION]
           * ====
           * Avoid leaving a contract uninitialized.
           *
           * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
           * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
           * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
           *
           * [.hljs-theme-light.nopadding]
           * ```
           * /// @custom:oz-upgrades-unsafe-allow constructor
           * constructor() {
           *     _disableInitializers();
           * }
           * ```
           * ====
           */
          abstract contract Initializable {
              /**
               * @dev Indicates that the contract has been initialized.
               * @custom:oz-retyped-from bool
               */
              uint8 private _initialized;
              /**
               * @dev Indicates that the contract is in the process of being initialized.
               */
              bool private _initializing;
              /**
               * @dev Triggered when the contract has been initialized or reinitialized.
               */
              event Initialized(uint8 version);
              /**
               * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
               * `onlyInitializing` functions can be used to initialize parent contracts.
               *
               * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
               * constructor.
               *
               * Emits an {Initialized} event.
               */
              modifier initializer() {
                  bool isTopLevelCall = !_initializing;
                  require(
                      (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
                      "Initializable: contract is already initialized"
                  );
                  _initialized = 1;
                  if (isTopLevelCall) {
                      _initializing = true;
                  }
                  _;
                  if (isTopLevelCall) {
                      _initializing = false;
                      emit Initialized(1);
                  }
              }
              /**
               * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
               * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
               * used to initialize parent contracts.
               *
               * A reinitializer may be used after the original initialization step. This is essential to configure modules that
               * are added through upgrades and that require initialization.
               *
               * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
               * cannot be nested. If one is invoked in the context of another, execution will revert.
               *
               * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
               * a contract, executing them in the right order is up to the developer or operator.
               *
               * WARNING: setting the version to 255 will prevent any future reinitialization.
               *
               * Emits an {Initialized} event.
               */
              modifier reinitializer(uint8 version) {
                  require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
                  _initialized = version;
                  _initializing = true;
                  _;
                  _initializing = false;
                  emit Initialized(version);
              }
              /**
               * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
               * {initializer} and {reinitializer} modifiers, directly or indirectly.
               */
              modifier onlyInitializing() {
                  require(_initializing, "Initializable: contract is not initializing");
                  _;
              }
              /**
               * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
               * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
               * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
               * through proxies.
               *
               * Emits an {Initialized} event the first time it is successfully executed.
               */
              function _disableInitializers() internal virtual {
                  require(!_initializing, "Initializable: contract is initializing");
                  if (_initialized < type(uint8).max) {
                      _initialized = type(uint8).max;
                      emit Initialized(type(uint8).max);
                  }
              }
              /**
               * @dev Returns the highest version that has been initialized. See {reinitializer}.
               */
              function _getInitializedVersion() internal view returns (uint8) {
                  return _initialized;
              }
              /**
               * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
               */
              function _isInitializing() internal view returns (bool) {
                  return _initializing;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/ERC721.sol)
          pragma solidity ^0.8.0;
          import "./IERC721Upgradeable.sol";
          import "./IERC721ReceiverUpgradeable.sol";
          import "./extensions/IERC721MetadataUpgradeable.sol";
          import "../../utils/AddressUpgradeable.sol";
          import "../../utils/ContextUpgradeable.sol";
          import "../../utils/StringsUpgradeable.sol";
          import "../../utils/introspection/ERC165Upgradeable.sol";
          import "../../proxy/utils/Initializable.sol";
          /**
           * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
           * the Metadata extension, but not including the Enumerable extension, which is available separately as
           * {ERC721Enumerable}.
           */
          contract ERC721Upgradeable is Initializable, ContextUpgradeable, ERC165Upgradeable, IERC721Upgradeable, IERC721MetadataUpgradeable {
              using AddressUpgradeable for address;
              using StringsUpgradeable for uint256;
              // Token name
              string private _name;
              // Token symbol
              string private _symbol;
              // Mapping from token ID to owner address
              mapping(uint256 => address) private _owners;
              // Mapping owner address to token count
              mapping(address => uint256) private _balances;
              // Mapping from token ID to approved address
              mapping(uint256 => address) private _tokenApprovals;
              // Mapping from owner to operator approvals
              mapping(address => mapping(address => bool)) private _operatorApprovals;
              /**
               * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
               */
              function __ERC721_init(string memory name_, string memory symbol_) internal onlyInitializing {
                  __ERC721_init_unchained(name_, symbol_);
              }
              function __ERC721_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {
                  _name = name_;
                  _symbol = symbol_;
              }
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165Upgradeable, IERC165Upgradeable) returns (bool) {
                  return
                      interfaceId == type(IERC721Upgradeable).interfaceId ||
                      interfaceId == type(IERC721MetadataUpgradeable).interfaceId ||
                      super.supportsInterface(interfaceId);
              }
              /**
               * @dev See {IERC721-balanceOf}.
               */
              function balanceOf(address owner) public view virtual override returns (uint256) {
                  require(owner != address(0), "ERC721: address zero is not a valid owner");
                  return _balances[owner];
              }
              /**
               * @dev See {IERC721-ownerOf}.
               */
              function ownerOf(uint256 tokenId) public view virtual override returns (address) {
                  address owner = _ownerOf(tokenId);
                  require(owner != address(0), "ERC721: invalid token ID");
                  return owner;
              }
              /**
               * @dev See {IERC721Metadata-name}.
               */
              function name() public view virtual override returns (string memory) {
                  return _name;
              }
              /**
               * @dev See {IERC721Metadata-symbol}.
               */
              function symbol() public view virtual override returns (string memory) {
                  return _symbol;
              }
              /**
               * @dev See {IERC721Metadata-tokenURI}.
               */
              function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
                  _requireMinted(tokenId);
                  string memory baseURI = _baseURI();
                  return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
              }
              /**
               * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
               * token will be the concatenation of the `baseURI` and the `tokenId`. Empty
               * by default, can be overridden in child contracts.
               */
              function _baseURI() internal view virtual returns (string memory) {
                  return "";
              }
              /**
               * @dev See {IERC721-approve}.
               */
              function approve(address to, uint256 tokenId) public virtual override {
                  address owner = ERC721Upgradeable.ownerOf(tokenId);
                  require(to != owner, "ERC721: approval to current owner");
                  require(
                      _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
                      "ERC721: approve caller is not token owner or approved for all"
                  );
                  _approve(to, tokenId);
              }
              /**
               * @dev See {IERC721-getApproved}.
               */
              function getApproved(uint256 tokenId) public view virtual override returns (address) {
                  _requireMinted(tokenId);
                  return _tokenApprovals[tokenId];
              }
              /**
               * @dev See {IERC721-setApprovalForAll}.
               */
              function setApprovalForAll(address operator, bool approved) public virtual override {
                  _setApprovalForAll(_msgSender(), operator, approved);
              }
              /**
               * @dev See {IERC721-isApprovedForAll}.
               */
              function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
                  return _operatorApprovals[owner][operator];
              }
              /**
               * @dev See {IERC721-transferFrom}.
               */
              function transferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) public virtual override {
                  //solhint-disable-next-line max-line-length
                  require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
                  _transfer(from, to, tokenId);
              }
              /**
               * @dev See {IERC721-safeTransferFrom}.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) public virtual override {
                  safeTransferFrom(from, to, tokenId, "");
              }
              /**
               * @dev See {IERC721-safeTransferFrom}.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory data
              ) public virtual override {
                  require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
                  _safeTransfer(from, to, tokenId, data);
              }
              /**
               * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
               * are aware of the ERC721 protocol to prevent tokens from being forever locked.
               *
               * `data` is additional data, it has no specified format and it is sent in call to `to`.
               *
               * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
               * implement alternative mechanisms to perform token transfer, such as signature-based.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must exist and be owned by `from`.
               * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function _safeTransfer(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory data
              ) internal virtual {
                  _transfer(from, to, tokenId);
                  require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer");
              }
              /**
               * @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist
               */
              function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
                  return _owners[tokenId];
              }
              /**
               * @dev Returns whether `tokenId` exists.
               *
               * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
               *
               * Tokens start existing when they are minted (`_mint`),
               * and stop existing when they are burned (`_burn`).
               */
              function _exists(uint256 tokenId) internal view virtual returns (bool) {
                  return _ownerOf(tokenId) != address(0);
              }
              /**
               * @dev Returns whether `spender` is allowed to manage `tokenId`.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               */
              function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
                  address owner = ERC721Upgradeable.ownerOf(tokenId);
                  return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
              }
              /**
               * @dev Safely mints `tokenId` and transfers it to `to`.
               *
               * Requirements:
               *
               * - `tokenId` must not exist.
               * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function _safeMint(address to, uint256 tokenId) internal virtual {
                  _safeMint(to, tokenId, "");
              }
              /**
               * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
               * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
               */
              function _safeMint(
                  address to,
                  uint256 tokenId,
                  bytes memory data
              ) internal virtual {
                  _mint(to, tokenId);
                  require(
                      _checkOnERC721Received(address(0), to, tokenId, data),
                      "ERC721: transfer to non ERC721Receiver implementer"
                  );
              }
              /**
               * @dev Mints `tokenId` and transfers it to `to`.
               *
               * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
               *
               * Requirements:
               *
               * - `tokenId` must not exist.
               * - `to` cannot be the zero address.
               *
               * Emits a {Transfer} event.
               */
              function _mint(address to, uint256 tokenId) internal virtual {
                  require(to != address(0), "ERC721: mint to the zero address");
                  require(!_exists(tokenId), "ERC721: token already minted");
                  _beforeTokenTransfer(address(0), to, tokenId, 1);
                  // Check that tokenId was not minted by `_beforeTokenTransfer` hook
                  require(!_exists(tokenId), "ERC721: token already minted");
                  unchecked {
                      // Will not overflow unless all 2**256 token ids are minted to the same owner.
                      // Given that tokens are minted one by one, it is impossible in practice that
                      // this ever happens. Might change if we allow batch minting.
                      // The ERC fails to describe this case.
                      _balances[to] += 1;
                  }
                  _owners[tokenId] = to;
                  emit Transfer(address(0), to, tokenId);
                  _afterTokenTransfer(address(0), to, tokenId, 1);
              }
              /**
               * @dev Destroys `tokenId`.
               * The approval is cleared when the token is burned.
               * This is an internal function that does not check if the sender is authorized to operate on the token.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               *
               * Emits a {Transfer} event.
               */
              function _burn(uint256 tokenId) internal virtual {
                  address owner = ERC721Upgradeable.ownerOf(tokenId);
                  _beforeTokenTransfer(owner, address(0), tokenId, 1);
                  // Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook
                  owner = ERC721Upgradeable.ownerOf(tokenId);
                  // Clear approvals
                  delete _tokenApprovals[tokenId];
                  unchecked {
                      // Cannot overflow, as that would require more tokens to be burned/transferred
                      // out than the owner initially received through minting and transferring in.
                      _balances[owner] -= 1;
                  }
                  delete _owners[tokenId];
                  emit Transfer(owner, address(0), tokenId);
                  _afterTokenTransfer(owner, address(0), tokenId, 1);
              }
              /**
               * @dev Transfers `tokenId` from `from` to `to`.
               *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
               *
               * Requirements:
               *
               * - `to` cannot be the zero address.
               * - `tokenId` token must be owned by `from`.
               *
               * Emits a {Transfer} event.
               */
              function _transfer(
                  address from,
                  address to,
                  uint256 tokenId
              ) internal virtual {
                  require(ERC721Upgradeable.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
                  require(to != address(0), "ERC721: transfer to the zero address");
                  _beforeTokenTransfer(from, to, tokenId, 1);
                  // Check that tokenId was not transferred by `_beforeTokenTransfer` hook
                  require(ERC721Upgradeable.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
                  // Clear approvals from the previous owner
                  delete _tokenApprovals[tokenId];
                  unchecked {
                      // `_balances[from]` cannot overflow for the same reason as described in `_burn`:
                      // `from`'s balance is the number of token held, which is at least one before the current
                      // transfer.
                      // `_balances[to]` could overflow in the conditions described in `_mint`. That would require
                      // all 2**256 token ids to be minted, which in practice is impossible.
                      _balances[from] -= 1;
                      _balances[to] += 1;
                  }
                  _owners[tokenId] = to;
                  emit Transfer(from, to, tokenId);
                  _afterTokenTransfer(from, to, tokenId, 1);
              }
              /**
               * @dev Approve `to` to operate on `tokenId`
               *
               * Emits an {Approval} event.
               */
              function _approve(address to, uint256 tokenId) internal virtual {
                  _tokenApprovals[tokenId] = to;
                  emit Approval(ERC721Upgradeable.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 IERC721ReceiverUpgradeable(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
                          return retval == IERC721ReceiverUpgradeable.onERC721Received.selector;
                      } catch (bytes memory reason) {
                          if (reason.length == 0) {
                              revert("ERC721: transfer to non ERC721Receiver implementer");
                          } else {
                              /// @solidity memory-safe-assembly
                              assembly {
                                  revert(add(32, reason), mload(reason))
                              }
                          }
                      }
                  } else {
                      return true;
                  }
              }
              /**
               * @dev Hook that is called before any token transfer. This includes minting and burning. If {ERC721Consecutive} is
               * used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
               *
               * Calling conditions:
               *
               * - When `from` and `to` are both non-zero, ``from``'s tokens will be transferred to `to`.
               * - When `from` is zero, the tokens will be minted for `to`.
               * - When `to` is zero, ``from``'s tokens will be burned.
               * - `from` and `to` are never both zero.
               * - `batchSize` is non-zero.
               *
               * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
               */
              function _beforeTokenTransfer(
                  address from,
                  address to,
                  uint256, /* firstTokenId */
                  uint256 batchSize
              ) internal virtual {
                  if (batchSize > 1) {
                      if (from != address(0)) {
                          _balances[from] -= batchSize;
                      }
                      if (to != address(0)) {
                          _balances[to] += batchSize;
                      }
                  }
              }
              /**
               * @dev Hook that is called after any token transfer. This includes minting and burning. If {ERC721Consecutive} is
               * used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
               *
               * Calling conditions:
               *
               * - When `from` and `to` are both non-zero, ``from``'s tokens were transferred to `to`.
               * - When `from` is zero, the tokens were minted for `to`.
               * - When `to` is zero, ``from``'s tokens were burned.
               * - `from` and `to` are never both zero.
               * - `batchSize` is non-zero.
               *
               * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
               */
              function _afterTokenTransfer(
                  address from,
                  address to,
                  uint256 firstTokenId,
                  uint256 batchSize
              ) internal virtual {}
              /**
               * @dev This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[44] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/extensions/ERC721Burnable.sol)
          pragma solidity ^0.8.0;
          import "../ERC721Upgradeable.sol";
          import "../../../utils/ContextUpgradeable.sol";
          import "../../../proxy/utils/Initializable.sol";
          /**
           * @title ERC721 Burnable Token
           * @dev ERC721 Token that can be burned (destroyed).
           */
          abstract contract ERC721BurnableUpgradeable is Initializable, ContextUpgradeable, ERC721Upgradeable {
              function __ERC721Burnable_init() internal onlyInitializing {
              }
              function __ERC721Burnable_init_unchained() internal onlyInitializing {
              }
              /**
               * @dev Burns `tokenId`. See {ERC721-_burn}.
               *
               * Requirements:
               *
               * - The caller must own `tokenId` or be an approved operator.
               */
              function burn(uint256 tokenId) public virtual {
                  //solhint-disable-next-line max-line-length
                  require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
                  _burn(tokenId);
              }
              /**
               * @dev This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[50] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)
          pragma solidity ^0.8.0;
          import "../IERC721Upgradeable.sol";
          /**
           * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
           * @dev See https://eips.ethereum.org/EIPS/eip-721
           */
          interface IERC721MetadataUpgradeable is IERC721Upgradeable {
              /**
               * @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.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 IERC721ReceiverUpgradeable {
              /**
               * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
               * by `operator` from `from`, this function is called.
               *
               * It must return its Solidity selector to confirm the token transfer.
               * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
               *
               * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
               */
              function onERC721Received(
                  address operator,
                  address from,
                  uint256 tokenId,
                  bytes calldata data
              ) external returns (bytes4);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol)
          pragma solidity ^0.8.0;
          import "../../utils/introspection/IERC165Upgradeable.sol";
          /**
           * @dev Required interface of an ERC721 compliant contract.
           */
          interface IERC721Upgradeable is IERC165Upgradeable {
              /**
               * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
               */
              event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
              /**
               * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
               */
              event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
              /**
               * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
               */
              event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
              /**
               * @dev Returns the number of tokens in ``owner``'s account.
               */
              function balanceOf(address owner) external view returns (uint256 balance);
              /**
               * @dev Returns the owner of the `tokenId` token.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               */
              function ownerOf(uint256 tokenId) external view returns (address owner);
              /**
               * @dev Safely transfers `tokenId` token from `from` to `to`.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must exist and be owned by `from`.
               * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
               * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes calldata data
              ) external;
              /**
               * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
               * are aware of the ERC721 protocol to prevent tokens from being forever locked.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must exist and be owned by `from`.
               * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
               * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) external;
              /**
               * @dev Transfers `tokenId` token from `from` to `to`.
               *
               * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
               * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
               * understand this adds an external call which potentially creates a reentrancy vulnerability.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must be owned by `from`.
               * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
               *
               * Emits a {Transfer} event.
               */
              function transferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) external;
              /**
               * @dev Gives permission to `to` to transfer `tokenId` token to another account.
               * The approval is cleared when the token is transferred.
               *
               * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
               *
               * Requirements:
               *
               * - The caller must own the token or be an approved operator.
               * - `tokenId` must exist.
               *
               * Emits an {Approval} event.
               */
              function approve(address to, uint256 tokenId) external;
              /**
               * @dev Approve or remove `operator` as an operator for the caller.
               * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
               *
               * Requirements:
               *
               * - The `operator` cannot be the caller.
               *
               * Emits an {ApprovalForAll} event.
               */
              function setApprovalForAll(address operator, bool _approved) external;
              /**
               * @dev Returns the account approved for `tokenId` token.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               */
              function getApproved(uint256 tokenId) external view returns (address operator);
              /**
               * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
               *
               * See {setApprovalForAll}
               */
              function isApprovedForAll(address owner, address operator) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)
          pragma solidity ^0.8.1;
          /**
           * @dev Collection of functions related to the address type
           */
          library AddressUpgradeable {
              /**
               * @dev Returns true if `account` is a contract.
               *
               * [IMPORTANT]
               * ====
               * It is unsafe to assume that an address for which this function returns
               * false is an externally-owned account (EOA) and not a contract.
               *
               * Among others, `isContract` will return false for the following
               * types of addresses:
               *
               *  - an externally-owned account
               *  - a contract in construction
               *  - an address where a contract will be created
               *  - an address where a contract lived, but was destroyed
               * ====
               *
               * [IMPORTANT]
               * ====
               * You shouldn't rely on `isContract` to protect against flash loan attacks!
               *
               * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
               * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
               * constructor.
               * ====
               */
              function isContract(address account) internal view returns (bool) {
                  // This method relies on extcodesize/address.code.length, which returns 0
                  // for contracts in construction, since the code is only stored at the end
                  // of the constructor execution.
                  return account.code.length > 0;
              }
              /**
               * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
               * `recipient`, forwarding all available gas and reverting on errors.
               *
               * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
               * of certain opcodes, possibly making contracts go over the 2300 gas limit
               * imposed by `transfer`, making them unable to receive funds via
               * `transfer`. {sendValue} removes this limitation.
               *
               * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
               *
               * IMPORTANT: because control is transferred to `recipient`, care must be
               * taken to not create reentrancy vulnerabilities. Consider using
               * {ReentrancyGuard} or the
               * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
               */
              function sendValue(address payable recipient, uint256 amount) internal {
                  require(address(this).balance >= amount, "Address: insufficient balance");
                  (bool success, ) = recipient.call{value: amount}("");
                  require(success, "Address: unable to send value, recipient may have reverted");
              }
              /**
               * @dev Performs a Solidity function call using a low level `call`. A
               * plain `call` is an unsafe replacement for a function call: use this
               * function instead.
               *
               * If `target` reverts with a revert reason, it is bubbled up by this
               * function (like regular Solidity function calls).
               *
               * Returns the raw returned data. To convert to the expected return value,
               * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
               *
               * Requirements:
               *
               * - `target` must be a contract.
               * - calling `target` with `data` must not revert.
               *
               * _Available since v3.1._
               */
              function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, 0, "Address: low-level call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
               * `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, 0, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but also transferring `value` wei to `target`.
               *
               * Requirements:
               *
               * - the calling contract must have an ETH balance of at least `value`.
               * - the called Solidity function must be `payable`.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
              }
              /**
               * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
               * with `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  require(address(this).balance >= value, "Address: insufficient balance for call");
                  (bool success, bytes memory returndata) = target.call{value: value}(data);
                  return verifyCallResultFromTarget(target, success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                  return functionStaticCall(target, data, "Address: low-level static call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal view returns (bytes memory) {
                  (bool success, bytes memory returndata) = target.staticcall(data);
                  return verifyCallResultFromTarget(target, success, returndata, errorMessage);
              }
              /**
               * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
               * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
               *
               * _Available since v4.8._
               */
              function verifyCallResultFromTarget(
                  address target,
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal view returns (bytes memory) {
                  if (success) {
                      if (returndata.length == 0) {
                          // only check isContract if the call was successful and the return data is empty
                          // otherwise we already know that it was a contract
                          require(isContract(target), "Address: call to non-contract");
                      }
                      return returndata;
                  } else {
                      _revert(returndata, errorMessage);
                  }
              }
              /**
               * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
               * revert reason or using the provided one.
               *
               * _Available since v4.3._
               */
              function verifyCallResult(
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal pure returns (bytes memory) {
                  if (success) {
                      return returndata;
                  } else {
                      _revert(returndata, errorMessage);
                  }
              }
              function _revert(bytes memory returndata, string memory errorMessage) private pure {
                  // Look for revert reason and bubble it up if present
                  if (returndata.length > 0) {
                      // The easiest way to bubble the revert reason is using memory via assembly
                      /// @solidity memory-safe-assembly
                      assembly {
                          let returndata_size := mload(returndata)
                          revert(add(32, returndata), returndata_size)
                      }
                  } else {
                      revert(errorMessage);
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
          pragma solidity ^0.8.0;
          import "../proxy/utils/Initializable.sol";
          /**
           * @dev Provides information about the current execution context, including the
           * sender of the transaction and its data. While these are generally available
           * via msg.sender and msg.data, they should not be accessed in such a direct
           * manner, since when dealing with meta-transactions the account sending and
           * paying for execution may not be the actual sender (as far as an application
           * is concerned).
           *
           * This contract is only required for intermediate, library-like contracts.
           */
          abstract contract ContextUpgradeable is Initializable {
              function __Context_init() internal onlyInitializing {
              }
              function __Context_init_unchained() internal onlyInitializing {
              }
              function _msgSender() internal view virtual returns (address) {
                  return msg.sender;
              }
              function _msgData() internal view virtual returns (bytes calldata) {
                  return msg.data;
              }
              /**
               * @dev This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[50] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
          pragma solidity ^0.8.0;
          import "./IERC165Upgradeable.sol";
          import "../../proxy/utils/Initializable.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 ERC165Upgradeable is Initializable, IERC165Upgradeable {
              function __ERC165_init() internal onlyInitializing {
              }
              function __ERC165_init_unchained() internal onlyInitializing {
              }
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                  return interfaceId == type(IERC165Upgradeable).interfaceId;
              }
              /**
               * @dev This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[50] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Interface of the ERC165 standard, as defined in the
           * https://eips.ethereum.org/EIPS/eip-165[EIP].
           *
           * Implementers can declare support of contract interfaces, which can then be
           * queried by others ({ERC165Checker}).
           *
           * For an implementation, see {ERC165}.
           */
          interface IERC165Upgradeable {
              /**
               * @dev Returns true if this contract implements the interface defined by
               * `interfaceId`. See the corresponding
               * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
               * to learn more about how these ids are created.
               *
               * This function call must use less than 30 000 gas.
               */
              function supportsInterface(bytes4 interfaceId) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Standard math utilities missing in the Solidity language.
           */
          library MathUpgradeable {
              enum Rounding {
                  Down, // Toward negative infinity
                  Up, // Toward infinity
                  Zero // Toward zero
              }
              /**
               * @dev Returns the largest of two numbers.
               */
              function max(uint256 a, uint256 b) internal pure returns (uint256) {
                  return a > b ? a : b;
              }
              /**
               * @dev Returns the smallest of two numbers.
               */
              function min(uint256 a, uint256 b) internal pure returns (uint256) {
                  return a < b ? a : b;
              }
              /**
               * @dev Returns the average of two numbers. The result is rounded towards
               * zero.
               */
              function average(uint256 a, uint256 b) internal pure returns (uint256) {
                  // (a + b) / 2 can overflow.
                  return (a & b) + (a ^ b) / 2;
              }
              /**
               * @dev Returns the ceiling of the division of two numbers.
               *
               * This differs from standard division with `/` in that it rounds up instead
               * of rounding down.
               */
              function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
                  // (a + b - 1) / b can overflow on addition, so we distribute.
                  return a == 0 ? 0 : (a - 1) / b + 1;
              }
              /**
               * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
               * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
               * with further edits by Uniswap Labs also under MIT license.
               */
              function mulDiv(
                  uint256 x,
                  uint256 y,
                  uint256 denominator
              ) internal pure returns (uint256 result) {
                  unchecked {
                      // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                      // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                      // variables such that product = prod1 * 2^256 + prod0.
                      uint256 prod0; // Least significant 256 bits of the product
                      uint256 prod1; // Most significant 256 bits of the product
                      assembly {
                          let mm := mulmod(x, y, not(0))
                          prod0 := mul(x, y)
                          prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                      }
                      // Handle non-overflow cases, 256 by 256 division.
                      if (prod1 == 0) {
                          return prod0 / denominator;
                      }
                      // Make sure the result is less than 2^256. Also prevents denominator == 0.
                      require(denominator > prod1);
                      ///////////////////////////////////////////////
                      // 512 by 256 division.
                      ///////////////////////////////////////////////
                      // Make division exact by subtracting the remainder from [prod1 prod0].
                      uint256 remainder;
                      assembly {
                          // Compute remainder using mulmod.
                          remainder := mulmod(x, y, denominator)
                          // Subtract 256 bit number from 512 bit number.
                          prod1 := sub(prod1, gt(remainder, prod0))
                          prod0 := sub(prod0, remainder)
                      }
                      // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
                      // See https://cs.stackexchange.com/q/138556/92363.
                      // Does not overflow because the denominator cannot be zero at this stage in the function.
                      uint256 twos = denominator & (~denominator + 1);
                      assembly {
                          // Divide denominator by twos.
                          denominator := div(denominator, twos)
                          // Divide [prod1 prod0] by twos.
                          prod0 := div(prod0, twos)
                          // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                          twos := add(div(sub(0, twos), twos), 1)
                      }
                      // Shift in bits from prod1 into prod0.
                      prod0 |= prod1 * twos;
                      // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                      // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                      // four bits. That is, denominator * inv = 1 mod 2^4.
                      uint256 inverse = (3 * denominator) ^ 2;
                      // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
                      // in modular arithmetic, doubling the correct bits in each step.
                      inverse *= 2 - denominator * inverse; // inverse mod 2^8
                      inverse *= 2 - denominator * inverse; // inverse mod 2^16
                      inverse *= 2 - denominator * inverse; // inverse mod 2^32
                      inverse *= 2 - denominator * inverse; // inverse mod 2^64
                      inverse *= 2 - denominator * inverse; // inverse mod 2^128
                      inverse *= 2 - denominator * inverse; // inverse mod 2^256
                      // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                      // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                      // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                      // is no longer required.
                      result = prod0 * inverse;
                      return result;
                  }
              }
              /**
               * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
               */
              function mulDiv(
                  uint256 x,
                  uint256 y,
                  uint256 denominator,
                  Rounding rounding
              ) internal pure returns (uint256) {
                  uint256 result = mulDiv(x, y, denominator);
                  if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
                      result += 1;
                  }
                  return result;
              }
              /**
               * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
               *
               * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
               */
              function sqrt(uint256 a) internal pure returns (uint256) {
                  if (a == 0) {
                      return 0;
                  }
                  // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
                  //
                  // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
                  // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
                  //
                  // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
                  // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
                  // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
                  //
                  // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
                  uint256 result = 1 << (log2(a) >> 1);
                  // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
                  // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
                  // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
                  // into the expected uint128 result.
                  unchecked {
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      return min(result, a / result);
                  }
              }
              /**
               * @notice Calculates sqrt(a), following the selected rounding direction.
               */
              function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = sqrt(a);
                      return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
                  }
              }
              /**
               * @dev Return the log in base 2, rounded down, of a positive value.
               * Returns 0 if given 0.
               */
              function log2(uint256 value) internal pure returns (uint256) {
                  uint256 result = 0;
                  unchecked {
                      if (value >> 128 > 0) {
                          value >>= 128;
                          result += 128;
                      }
                      if (value >> 64 > 0) {
                          value >>= 64;
                          result += 64;
                      }
                      if (value >> 32 > 0) {
                          value >>= 32;
                          result += 32;
                      }
                      if (value >> 16 > 0) {
                          value >>= 16;
                          result += 16;
                      }
                      if (value >> 8 > 0) {
                          value >>= 8;
                          result += 8;
                      }
                      if (value >> 4 > 0) {
                          value >>= 4;
                          result += 4;
                      }
                      if (value >> 2 > 0) {
                          value >>= 2;
                          result += 2;
                      }
                      if (value >> 1 > 0) {
                          result += 1;
                      }
                  }
                  return result;
              }
              /**
               * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
               * Returns 0 if given 0.
               */
              function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = log2(value);
                      return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
                  }
              }
              /**
               * @dev Return the log in base 10, rounded down, of a positive value.
               * Returns 0 if given 0.
               */
              function log10(uint256 value) internal pure returns (uint256) {
                  uint256 result = 0;
                  unchecked {
                      if (value >= 10**64) {
                          value /= 10**64;
                          result += 64;
                      }
                      if (value >= 10**32) {
                          value /= 10**32;
                          result += 32;
                      }
                      if (value >= 10**16) {
                          value /= 10**16;
                          result += 16;
                      }
                      if (value >= 10**8) {
                          value /= 10**8;
                          result += 8;
                      }
                      if (value >= 10**4) {
                          value /= 10**4;
                          result += 4;
                      }
                      if (value >= 10**2) {
                          value /= 10**2;
                          result += 2;
                      }
                      if (value >= 10**1) {
                          result += 1;
                      }
                  }
                  return result;
              }
              /**
               * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
               * Returns 0 if given 0.
               */
              function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = log10(value);
                      return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
                  }
              }
              /**
               * @dev Return the log in base 256, rounded down, of a positive value.
               * Returns 0 if given 0.
               *
               * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
               */
              function log256(uint256 value) internal pure returns (uint256) {
                  uint256 result = 0;
                  unchecked {
                      if (value >> 128 > 0) {
                          value >>= 128;
                          result += 16;
                      }
                      if (value >> 64 > 0) {
                          value >>= 64;
                          result += 8;
                      }
                      if (value >> 32 > 0) {
                          value >>= 32;
                          result += 4;
                      }
                      if (value >> 16 > 0) {
                          value >>= 16;
                          result += 2;
                      }
                      if (value >> 8 > 0) {
                          result += 1;
                      }
                  }
                  return result;
              }
              /**
               * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
               * Returns 0 if given 0.
               */
              function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = log256(value);
                      return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)
          pragma solidity ^0.8.0;
          import "./math/MathUpgradeable.sol";
          /**
           * @dev String operations.
           */
          library StringsUpgradeable {
              bytes16 private constant _SYMBOLS = "0123456789abcdef";
              uint8 private constant _ADDRESS_LENGTH = 20;
              /**
               * @dev Converts a `uint256` to its ASCII `string` decimal representation.
               */
              function toString(uint256 value) internal pure returns (string memory) {
                  unchecked {
                      uint256 length = MathUpgradeable.log10(value) + 1;
                      string memory buffer = new string(length);
                      uint256 ptr;
                      /// @solidity memory-safe-assembly
                      assembly {
                          ptr := add(buffer, add(32, length))
                      }
                      while (true) {
                          ptr--;
                          /// @solidity memory-safe-assembly
                          assembly {
                              mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                          }
                          value /= 10;
                          if (value == 0) break;
                      }
                      return buffer;
                  }
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
               */
              function toHexString(uint256 value) internal pure returns (string memory) {
                  unchecked {
                      return toHexString(value, MathUpgradeable.log256(value) + 1);
                  }
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
               */
              function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                  bytes memory buffer = new bytes(2 * length + 2);
                  buffer[0] = "0";
                  buffer[1] = "x";
                  for (uint256 i = 2 * length + 1; i > 1; --i) {
                      buffer[i] = _SYMBOLS[value & 0xf];
                      value >>= 4;
                  }
                  require(value == 0, "Strings: hex length insufficient");
                  return string(buffer);
              }
              /**
               * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
               */
              function toHexString(address addr) internal pure returns (string memory) {
                  return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Standard math utilities missing in the Solidity language.
           */
          library Math {
              enum Rounding {
                  Down, // Toward negative infinity
                  Up, // Toward infinity
                  Zero // Toward zero
              }
              /**
               * @dev Returns the largest of two numbers.
               */
              function max(uint256 a, uint256 b) internal pure returns (uint256) {
                  return a > b ? a : b;
              }
              /**
               * @dev Returns the smallest of two numbers.
               */
              function min(uint256 a, uint256 b) internal pure returns (uint256) {
                  return a < b ? a : b;
              }
              /**
               * @dev Returns the average of two numbers. The result is rounded towards
               * zero.
               */
              function average(uint256 a, uint256 b) internal pure returns (uint256) {
                  // (a + b) / 2 can overflow.
                  return (a & b) + (a ^ b) / 2;
              }
              /**
               * @dev Returns the ceiling of the division of two numbers.
               *
               * This differs from standard division with `/` in that it rounds up instead
               * of rounding down.
               */
              function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
                  // (a + b - 1) / b can overflow on addition, so we distribute.
                  return a == 0 ? 0 : (a - 1) / b + 1;
              }
              /**
               * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
               * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
               * with further edits by Uniswap Labs also under MIT license.
               */
              function mulDiv(
                  uint256 x,
                  uint256 y,
                  uint256 denominator
              ) internal pure returns (uint256 result) {
                  unchecked {
                      // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                      // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                      // variables such that product = prod1 * 2^256 + prod0.
                      uint256 prod0; // Least significant 256 bits of the product
                      uint256 prod1; // Most significant 256 bits of the product
                      assembly {
                          let mm := mulmod(x, y, not(0))
                          prod0 := mul(x, y)
                          prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                      }
                      // Handle non-overflow cases, 256 by 256 division.
                      if (prod1 == 0) {
                          return prod0 / denominator;
                      }
                      // Make sure the result is less than 2^256. Also prevents denominator == 0.
                      require(denominator > prod1);
                      ///////////////////////////////////////////////
                      // 512 by 256 division.
                      ///////////////////////////////////////////////
                      // Make division exact by subtracting the remainder from [prod1 prod0].
                      uint256 remainder;
                      assembly {
                          // Compute remainder using mulmod.
                          remainder := mulmod(x, y, denominator)
                          // Subtract 256 bit number from 512 bit number.
                          prod1 := sub(prod1, gt(remainder, prod0))
                          prod0 := sub(prod0, remainder)
                      }
                      // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
                      // See https://cs.stackexchange.com/q/138556/92363.
                      // Does not overflow because the denominator cannot be zero at this stage in the function.
                      uint256 twos = denominator & (~denominator + 1);
                      assembly {
                          // Divide denominator by twos.
                          denominator := div(denominator, twos)
                          // Divide [prod1 prod0] by twos.
                          prod0 := div(prod0, twos)
                          // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                          twos := add(div(sub(0, twos), twos), 1)
                      }
                      // Shift in bits from prod1 into prod0.
                      prod0 |= prod1 * twos;
                      // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                      // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                      // four bits. That is, denominator * inv = 1 mod 2^4.
                      uint256 inverse = (3 * denominator) ^ 2;
                      // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
                      // in modular arithmetic, doubling the correct bits in each step.
                      inverse *= 2 - denominator * inverse; // inverse mod 2^8
                      inverse *= 2 - denominator * inverse; // inverse mod 2^16
                      inverse *= 2 - denominator * inverse; // inverse mod 2^32
                      inverse *= 2 - denominator * inverse; // inverse mod 2^64
                      inverse *= 2 - denominator * inverse; // inverse mod 2^128
                      inverse *= 2 - denominator * inverse; // inverse mod 2^256
                      // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                      // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                      // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                      // is no longer required.
                      result = prod0 * inverse;
                      return result;
                  }
              }
              /**
               * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
               */
              function mulDiv(
                  uint256 x,
                  uint256 y,
                  uint256 denominator,
                  Rounding rounding
              ) internal pure returns (uint256) {
                  uint256 result = mulDiv(x, y, denominator);
                  if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
                      result += 1;
                  }
                  return result;
              }
              /**
               * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
               *
               * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
               */
              function sqrt(uint256 a) internal pure returns (uint256) {
                  if (a == 0) {
                      return 0;
                  }
                  // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
                  //
                  // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
                  // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
                  //
                  // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
                  // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
                  // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
                  //
                  // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
                  uint256 result = 1 << (log2(a) >> 1);
                  // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
                  // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
                  // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
                  // into the expected uint128 result.
                  unchecked {
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      return min(result, a / result);
                  }
              }
              /**
               * @notice Calculates sqrt(a), following the selected rounding direction.
               */
              function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = sqrt(a);
                      return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
                  }
              }
              /**
               * @dev Return the log in base 2, rounded down, of a positive value.
               * Returns 0 if given 0.
               */
              function log2(uint256 value) internal pure returns (uint256) {
                  uint256 result = 0;
                  unchecked {
                      if (value >> 128 > 0) {
                          value >>= 128;
                          result += 128;
                      }
                      if (value >> 64 > 0) {
                          value >>= 64;
                          result += 64;
                      }
                      if (value >> 32 > 0) {
                          value >>= 32;
                          result += 32;
                      }
                      if (value >> 16 > 0) {
                          value >>= 16;
                          result += 16;
                      }
                      if (value >> 8 > 0) {
                          value >>= 8;
                          result += 8;
                      }
                      if (value >> 4 > 0) {
                          value >>= 4;
                          result += 4;
                      }
                      if (value >> 2 > 0) {
                          value >>= 2;
                          result += 2;
                      }
                      if (value >> 1 > 0) {
                          result += 1;
                      }
                  }
                  return result;
              }
              /**
               * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
               * Returns 0 if given 0.
               */
              function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = log2(value);
                      return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
                  }
              }
              /**
               * @dev Return the log in base 10, rounded down, of a positive value.
               * Returns 0 if given 0.
               */
              function log10(uint256 value) internal pure returns (uint256) {
                  uint256 result = 0;
                  unchecked {
                      if (value >= 10**64) {
                          value /= 10**64;
                          result += 64;
                      }
                      if (value >= 10**32) {
                          value /= 10**32;
                          result += 32;
                      }
                      if (value >= 10**16) {
                          value /= 10**16;
                          result += 16;
                      }
                      if (value >= 10**8) {
                          value /= 10**8;
                          result += 8;
                      }
                      if (value >= 10**4) {
                          value /= 10**4;
                          result += 4;
                      }
                      if (value >= 10**2) {
                          value /= 10**2;
                          result += 2;
                      }
                      if (value >= 10**1) {
                          result += 1;
                      }
                  }
                  return result;
              }
              /**
               * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
               * Returns 0 if given 0.
               */
              function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = log10(value);
                      return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
                  }
              }
              /**
               * @dev Return the log in base 256, rounded down, of a positive value.
               * Returns 0 if given 0.
               *
               * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
               */
              function log256(uint256 value) internal pure returns (uint256) {
                  uint256 result = 0;
                  unchecked {
                      if (value >> 128 > 0) {
                          value >>= 128;
                          result += 16;
                      }
                      if (value >> 64 > 0) {
                          value >>= 64;
                          result += 8;
                      }
                      if (value >> 32 > 0) {
                          value >>= 32;
                          result += 4;
                      }
                      if (value >> 16 > 0) {
                          value >>= 16;
                          result += 2;
                      }
                      if (value >> 8 > 0) {
                          result += 1;
                      }
                  }
                  return result;
              }
              /**
               * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
               * Returns 0 if given 0.
               */
              function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = log256(value);
                      return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SafeCast.sol)
          // This file was procedurally generated from scripts/generate/templates/SafeCast.js.
          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 downcasted) {
                  downcasted = int248(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 248 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int240(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 240 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int232(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 232 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int224(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 224 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int216(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 216 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int208(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 208 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int200(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 200 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int192(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 192 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int184(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 184 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int176(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 176 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int168(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 168 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int160(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 160 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int152(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 152 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int144(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 144 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int136(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 136 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int128(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 128 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int120(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 120 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int112(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 112 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int104(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 104 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int96(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 96 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int88(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 88 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int80(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 80 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int72(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 72 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int64(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 64 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int56(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 56 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int48(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 48 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int40(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 40 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int32(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 32 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int24(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 24 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int16(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 16 bits");
              }
              /**
               * @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 downcasted) {
                  downcasted = int8(value);
                  require(downcasted == value, "SafeCast: value doesn't fit in 8 bits");
              }
              /**
               * @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: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)
          pragma solidity ^0.8.0;
          import "./math/Math.sol";
          /**
           * @dev String operations.
           */
          library Strings {
              bytes16 private constant _SYMBOLS = "0123456789abcdef";
              uint8 private constant _ADDRESS_LENGTH = 20;
              /**
               * @dev Converts a `uint256` to its ASCII `string` decimal representation.
               */
              function toString(uint256 value) internal pure returns (string memory) {
                  unchecked {
                      uint256 length = Math.log10(value) + 1;
                      string memory buffer = new string(length);
                      uint256 ptr;
                      /// @solidity memory-safe-assembly
                      assembly {
                          ptr := add(buffer, add(32, length))
                      }
                      while (true) {
                          ptr--;
                          /// @solidity memory-safe-assembly
                          assembly {
                              mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                          }
                          value /= 10;
                          if (value == 0) break;
                      }
                      return buffer;
                  }
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
               */
              function toHexString(uint256 value) internal pure returns (string memory) {
                  unchecked {
                      return toHexString(value, Math.log256(value) + 1);
                  }
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
               */
              function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                  bytes memory buffer = new bytes(2 * length + 2);
                  buffer[0] = "0";
                  buffer[1] = "x";
                  for (uint256 i = 2 * length + 1; i > 1; --i) {
                      buffer[i] = _SYMBOLS[value & 0xf];
                      value >>= 4;
                  }
                  require(value == 0, "Strings: hex length insufficient");
                  return string(buffer);
              }
              /**
               * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
               */
              function toHexString(address addr) internal pure returns (string memory) {
                  return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
              }
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts/utils/Strings.sol";
          import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
          import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
          import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
          import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
          import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
          import "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol";
          import "../interfaces/internal/INFTTimedEditionCollectionInitializer.sol";
          import "../libraries/TimeLibrary.sol";
          import "../mixins/collections/CollectionRoyalties.sol";
          import "../mixins/collections/LazyMintedCollection.sol";
          import "../mixins/collections/NFTCollectionType.sol";
          import "../mixins/collections/SequentialMintCollection.sol";
          import "../mixins/collections/SharedPaymentCollection.sol";
          import "../mixins/collections/TimeLimitedCollection.sol";
          import "../mixins/roles/AdminRole.sol";
          import "../mixins/roles/MinterRole.sol";
          import "../mixins/shared/Constants.sol";
          import "../mixins/shared/ContractFactory.sol";
          error NFTTimedEditionCollection_Token_URI_Not_Set();
          /**
           * @title A contract to batch mint a collection of NFTs where each token shares the same `tokenURI`.
           * @notice A 10% royalty to the creator is included which may be split with collaborators.
           * @author cori-grohman & HardlyDifficult
           */
          contract NFTTimedEditionCollection is
            INFTTimedEditionCollectionInitializer,
            ContractFactory,
            Initializable,
            ContextUpgradeable,
            ERC165Upgradeable,
            AccessControlUpgradeable,
            AdminRole,
            MinterRole,
            ERC721Upgradeable,
            ERC721BurnableUpgradeable,
            NFTCollectionType,
            SequentialMintCollection,
            CollectionRoyalties,
            LazyMintedCollection,
            TimeLimitedCollection,
            SharedPaymentCollection
          {
            using Strings for uint256;
            using TimeLibrary for uint32;
            /**
             * @notice The token URI used for all NFTs in this collection.
             */
            string private _tokenURI;
            modifier validTokenURI(string calldata tokenURI_) {
              if (bytes(tokenURI_).length == 0) {
                revert NFTTimedEditionCollection_Token_URI_Not_Set();
              }
              _;
            }
            /**
             * @notice Initialize the template's immutable variables.
             * @param _contractFactory The factory which will be used to create collection contracts.
             */
            constructor(
              address _contractFactory
            ) ContractFactory(_contractFactory) NFTCollectionType(NFT_TIMED_EDITION_COLLECTION_TYPE) {
              // The template will be initialized by the factory when it's registered for use.
            }
            /**
             * @notice Called by the contract factory on creation.
             * @param _creator The creator of this collection.
             * This account is the default admin for this collection.
             * @param _name The collection's `name`.
             * @param _symbol The collection's `symbol`.
             * @param tokenURI_ The token URI used for all NFTs in this collection.
             * @param _mintEndTime The time in seconds after which no more editions can be minted.
             * @param _approvedMinter An optional address to grant the MINTER_ROLE.
             * Set to address(0) if only admins should be granted permission to mint.
             * @param _paymentAddress The address that will receive royalties and mint payments.
             */
            function initialize(
              address payable _creator,
              string calldata _name,
              string calldata _symbol,
              string calldata tokenURI_,
              uint256 _mintEndTime,
              address _approvedMinter,
              address payable _paymentAddress
            ) external initializer onlyContractFactory validTokenURI(tokenURI_) {
              // Initialize the mixins
              __ERC721_init(_name, _symbol);
              _initializeSequentialMintCollection(_creator);
              _initializeTimeLimitedCollection(_mintEndTime);
              _initializeLazyMintedCollection(_creator, _approvedMinter);
              _initializeSharedPaymentCollection(_paymentAddress);
              // Initialize URI
              _tokenURI = tokenURI_;
            }
            /**
             * @inheritdoc LazyMintedCollection
             */
            function mintCountTo(
              uint16 count,
              address to
            ) public override(LazyMintedCollection, TimeLimitedCollection) returns (uint256 firstTokenId) {
              firstTokenId = super.mintCountTo(count, to);
            }
            /**
             * @inheritdoc ERC721Upgradeable
             * @dev The function here asserts `onlyAdmin` while the super confirms ownership.
             */
            function _burn(uint256 tokenId) internal override(ERC721Upgradeable, SequentialMintCollection) onlyAdmin {
              super._burn(tokenId);
            }
            /**
             * @inheritdoc IERC165Upgradeable
             */
            function supportsInterface(
              bytes4 interfaceId
            )
              public
              view
              override(
                ERC165Upgradeable,
                ERC721Upgradeable,
                AccessControlUpgradeable,
                NFTCollectionType,
                LazyMintedCollection,
                CollectionRoyalties,
                SharedPaymentCollection
              )
              returns (bool isSupported)
            {
              isSupported = super.supportsInterface(interfaceId);
            }
            /**
             * @inheritdoc IERC721MetadataUpgradeable
             * @dev This will return the same URI for all tokenIds, even if it has not been minted.
             */
            function tokenURI(uint256 /* tokenId */) public view override returns (string memory uri) {
              uri = _tokenURI;
            }
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @title Declares the type of the collection contract.
           * @dev This interface is declared as an ERC-165 interface.
           * @author reggieag
           */
          interface INFTCollectionType {
            function getNFTCollectionType() external view returns (string memory collectionType);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @title The required interface for collections to support minting from the NFTDropMarket.
           * @dev This interface must be registered as a ERC165 supported interface.
           * @author batu-inal & HardlyDifficult
           */
          interface INFTLazyMintedCollectionMintCountTo {
            function mintCountTo(uint16 count, address to) external returns (uint256 firstTokenId);
            /**
             * @notice Get the number of tokens which can still be minted.
             * @return count The max number of additional NFTs that can be minted by this collection.
             */
            function numberOfTokensAvailableToMint() external view returns (uint256 count);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @title Declares the interface for initializing an NFTTimedEditionCollection contract.
           * @author cori-grohman
           */
          interface INFTTimedEditionCollectionInitializer {
            function initialize(
              address payable _creator,
              string calldata _name,
              string calldata _symbol,
              string calldata tokenURI_,
              uint256 _mintEndTime,
              address _approvedMinter,
              address payable _paymentAddress
            ) external;
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @notice An interface for communicating fees to 3rd party marketplaces.
           * @dev Originally implemented in mainnet contract 0x44d6e8933f8271abcf253c72f9ed7e0e4c0323b3
           */
          interface IGetFees {
            /**
             * @notice Get the recipient addresses to which creator royalties should be sent.
             * @dev The expected royalty amounts are communicated with `getFeeBps`.
             * @param tokenId The ID of the NFT to get royalties for.
             * @return recipients An array of addresses to which royalties should be sent.
             */
            function getFeeRecipients(uint256 tokenId) external view returns (address payable[] memory recipients);
            /**
             * @notice Get the creator royalty amounts to be sent to each recipient, in basis points.
             * @dev The expected recipients are communicated with `getFeeRecipients`.
             * @param tokenId The ID of the NFT to get royalties for.
             * @return royaltiesInBasisPoints The array of fees to be sent to each recipient, in basis points.
             */
            function getFeeBps(uint256 tokenId) external view returns (uint256[] memory royaltiesInBasisPoints);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          interface IGetRoyalties {
            /**
             * @notice Get the creator royalties to be sent.
             * @dev The data is the same as when calling `getFeeRecipients` and `getFeeBps` separately.
             * @param tokenId The ID of the NFT to get royalties for.
             * @return recipients An array of addresses to which royalties should be sent.
             * @return royaltiesInBasisPoints The array of fees to be sent to each recipient, in basis points.
             */
            function getRoyalties(
              uint256 tokenId
            ) external view returns (address payable[] memory recipients, uint256[] memory royaltiesInBasisPoints);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @notice Interface for EIP-2981: NFT Royalty Standard.
           * For more see: https://eips.ethereum.org/EIPS/eip-2981.
           */
          interface IRoyaltyInfo {
            /**
             * @notice Get the creator royalties to be sent.
             * @param tokenId The ID of the NFT to get royalties for.
             * @param salePrice The total price of the sale.
             * @return receiver The address to which royalties should be sent.
             * @return royaltyAmount The total amount that should be sent to the `receiver`.
             */
            function royaltyInfo(
              uint256 tokenId,
              uint256 salePrice
            ) external view returns (address receiver, uint256 royaltyAmount);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          interface ITokenCreator {
            /**
             * @notice Returns the creator of this NFT collection.
             * @param tokenId The ID of the NFT to get the creator payment address for.
             * @return creator The creator of this collection.
             */
            function tokenCreator(uint256 tokenId) external view returns (address payable creator);
          }
          // SPDX-License-Identifier: MIT
          // Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/260e082/contracts/utils/ShortStrings.sol
          // TODO: Swap out for the OZ library version once this has been published.
          pragma solidity ^0.8.12;
          type ShortString is bytes32;
          /**
           * @dev This library provides functions to convert short memory strings
           * into a `ShortString` type that can be used as an immutable variable.
           * Strings of arbitrary length can be optimized if they are short enough by
           * the addition of a storage variable used as fallback.
           *
           * Usage example:
           *
           * ```solidity
           * contract Named {
           *     using ShortStrings for *;
           *
           *     ShortString private immutable _name;
           *     string private _nameFallback;
           *
           *     constructor(string memory contractName) {
           *         _name = contractName.toShortStringWithFallback(_nameFallback);
           *     }
           *
           *     function name() external view returns (string memory) {
           *         return _name.toStringWithFallback(_nameFallback);
           *     }
           * }
           * ```
           */
          library ShortStrings {
            error StringTooLong(string str);
            /**
             * @dev Encode a string of at most 31 chars into a `ShortString`.
             *
             * This will trigger a `StringTooLong` error is the input string is too long.
             */
            function toShortString(string memory str) internal pure returns (ShortString) {
              bytes memory bstr = bytes(str);
              if (bstr.length > 31) {
                revert StringTooLong(str);
              }
              return ShortString.wrap(bytes32(uint256(bytes32(bstr)) | bstr.length));
            }
            /**
             * @dev Decode a `ShortString` back to a "normal" string.
             */
            function toString(ShortString sstr) internal pure returns (string memory) {
              uint256 len = length(sstr);
              // using `new string(len)` would work locally but is not memory safe.
              string memory str = new string(32);
              /// @solidity memory-safe-assembly
              assembly {
                mstore(str, len)
                mstore(add(str, 0x20), sstr)
              }
              return str;
            }
            /**
             * @dev Return the length of a `ShortString`.
             */
            function length(ShortString sstr) internal pure returns (uint256) {
              return uint256(ShortString.unwrap(sstr)) & 0xFF;
            }
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @title Helpers for working with time.
           * @author batu-inal & HardlyDifficult
           */
          library TimeLibrary {
            /**
             * @notice Checks if the given timestamp is in the past.
             * @dev This helper ensures a consistent interpretation of expiry across the codebase.
             * This is different than `hasBeenReached` in that it will return false if the expiry is now.
             */
            function hasExpired(uint256 expiry) internal view returns (bool) {
              return expiry < block.timestamp;
            }
            /**
             * @notice Checks if the given timestamp is now or in the past.
             * @dev This helper ensures a consistent interpretation of expiry across the codebase.
             * This is different from `hasExpired` in that it will return true if the timestamp is now.
             */
            function hasBeenReached(uint256 timestamp) internal view returns (bool) {
              return timestamp <= block.timestamp;
            }
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol";
          import "../../interfaces/standards/royalties/IGetFees.sol";
          import "../../interfaces/standards/royalties/IGetRoyalties.sol";
          import "../../interfaces/standards/royalties/IRoyaltyInfo.sol";
          import "../../interfaces/standards/royalties/ITokenCreator.sol";
          import "../shared/Constants.sol";
          /**
           * @title Defines various royalty APIs for broad marketplace support.
           * @author batu-inal & HardlyDifficult
           */
          abstract contract CollectionRoyalties is IGetRoyalties, IGetFees, IRoyaltyInfo, ITokenCreator, ERC165Upgradeable {
            /**
             * @inheritdoc IGetFees
             */
            function getFeeRecipients(uint256 tokenId) external view returns (address payable[] memory recipients) {
              recipients = new address payable[](1);
              recipients[0] = getTokenCreatorPaymentAddress(tokenId);
            }
            /**
             * @inheritdoc IGetFees
             * @dev The tokenId param is ignored since all NFTs return the same value.
             */
            function getFeeBps(uint256 /* tokenId */) external pure returns (uint256[] memory royaltiesInBasisPoints) {
              royaltiesInBasisPoints = new uint256[](1);
              royaltiesInBasisPoints[0] = ROYALTY_IN_BASIS_POINTS;
            }
            /**
             * @inheritdoc IGetRoyalties
             */
            function getRoyalties(
              uint256 tokenId
            ) external view returns (address payable[] memory recipients, uint256[] memory royaltiesInBasisPoints) {
              recipients = new address payable[](1);
              recipients[0] = getTokenCreatorPaymentAddress(tokenId);
              royaltiesInBasisPoints = new uint256[](1);
              royaltiesInBasisPoints[0] = ROYALTY_IN_BASIS_POINTS;
            }
            /**
             * @notice The address to pay the creator proceeds/royalties for the collection.
             * @param tokenId The ID of the NFT to get the creator payment address for.
             * @return creatorPaymentAddress The address to which royalties should be paid.
             */
            function getTokenCreatorPaymentAddress(
              uint256 tokenId
            ) public view virtual returns (address payable creatorPaymentAddress);
            /**
             * @inheritdoc IRoyaltyInfo
             */
            function royaltyInfo(
              uint256 tokenId,
              uint256 salePrice
            ) external view returns (address receiver, uint256 royaltyAmount) {
              receiver = getTokenCreatorPaymentAddress(tokenId);
              unchecked {
                royaltyAmount = salePrice / ROYALTY_RATIO;
              }
            }
            /**
             * @inheritdoc IERC165Upgradeable
             * @dev Checks the supported royalty interfaces.
             */
            function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool interfaceSupported) {
              interfaceSupported = (interfaceId == type(IRoyaltyInfo).interfaceId ||
                interfaceId == type(ITokenCreator).interfaceId ||
                interfaceId == type(IGetRoyalties).interfaceId ||
                interfaceId == type(IGetFees).interfaceId ||
                super.supportsInterface(interfaceId));
            }
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "../../interfaces/internal/INFTLazyMintedCollectionMintCountTo.sol";
          import "../roles/MinterRole.sol";
          import "./SequentialMintCollection.sol";
          error DropCollection_Count_Must_Be_Greater_Than_Zero();
          /**
           * @title Common functions for collections in which all tokens are defined at the time of collection creation.
           * @dev This implements the INFTLazyMintedCollectionMintCountTo ERC-165 interface.
           * @author HardlyDifficult
           */
          abstract contract LazyMintedCollection is INFTLazyMintedCollectionMintCountTo, MinterRole, SequentialMintCollection {
            function _initializeLazyMintedCollection(address payable _creator, address _approvedMinter) internal {
              // Initialize access control
              AdminRole._initializeAdminRole(_creator);
              if (_approvedMinter != address(0)) {
                MinterRole._initializeMinterRole(_approvedMinter);
              }
            }
            /**
             * @notice Mint `count` number of NFTs for the `to` address.
             * @dev This is only callable by an address with either the MINTER_ROLE or the DEFAULT_ADMIN_ROLE.
             * @param count The number of NFTs to mint.
             * @param to The address to mint the NFTs for.
             * @return firstTokenId The tokenId for the first NFT minted.
             * The other minted tokens are assigned sequentially, so `firstTokenId` - `firstTokenId + count - 1` were minted.
             */
            function mintCountTo(uint16 count, address to) public virtual onlyMinterOrAdmin returns (uint256 firstTokenId) {
              if (count == 0) {
                revert DropCollection_Count_Must_Be_Greater_Than_Zero();
              }
              unchecked {
                // If +1 overflows then +count would also overflow, since count > 0.
                firstTokenId = latestTokenId + 1;
              }
              // If the mint will exceed uint32, the addition here will overflow. But it's not realistic to mint that many tokens.
              latestTokenId = latestTokenId + count;
              uint256 lastTokenId = latestTokenId;
              for (uint256 i = firstTokenId; i <= lastTokenId; ) {
                _safeMint(to, i);
                unchecked {
                  ++i;
                }
              }
            }
            /**
             * @notice Allows a collection admin to destroy this contract only if
             * no NFTs have been minted yet or the minted NFTs have been burned.
             * @dev Once destructed, a new collection could be deployed to this address (although that's discouraged).
             */
            function selfDestruct() external onlyAdmin {
              _selfDestruct();
            }
            /**
             * @inheritdoc IERC165Upgradeable
             */
            function supportsInterface(
              bytes4 interfaceId
            ) public view virtual override(AccessControlUpgradeable, ERC721Upgradeable) returns (bool isSupported) {
              isSupported =
                interfaceId == type(INFTLazyMintedCollectionMintCountTo).interfaceId ||
                super.supportsInterface(interfaceId);
            }
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol";
          import "../../libraries/ShortStrings.sol";
          import "../../interfaces/internal/INFTCollectionType.sol";
          /**
           * @title A mixin to add the NFTCollectionType interface to a contract.
           * @author HardlyDifficult & reggieag
           */
          abstract contract NFTCollectionType is INFTCollectionType, ERC165Upgradeable {
            using ShortStrings for string;
            using ShortStrings for ShortString;
            ShortString private immutable _collectionTypeName;
            constructor(string memory collectionTypeName) {
              _collectionTypeName = collectionTypeName.toShortString();
            }
            /**
             * @notice Returns a name of the type of collection this contract represents.
             * @return collectionType The collection type.
             */
            function getNFTCollectionType() external view returns (string memory collectionType) {
              collectionType = _collectionTypeName.toString();
            }
            /**
             * @inheritdoc IERC165Upgradeable
             */
            function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool interfaceSupported) {
              interfaceSupported = interfaceId == type(INFTCollectionType).interfaceId || super.supportsInterface(interfaceId);
            }
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
          import "../../interfaces/standards/royalties/ITokenCreator.sol";
          error SequentialMintCollection_Caller_Is_Not_Owner(address owner);
          error SequentialMintCollection_Minted_NFTs_Must_Be_Burned_First(uint256 totalSupply);
          /**
           * @title Extends the OZ ERC721 implementation for collections which mint sequential token IDs.
           * @author batu-inal & HardlyDifficult
           */
          abstract contract SequentialMintCollection is ITokenCreator, ERC721BurnableUpgradeable {
            /****** Slot 0 (after inheritance) ******/
            /**
             * @notice The creator/owner of this NFT collection.
             * @dev This is the default royalty recipient if a different `paymentAddress` was not provided.
             * @return The collection's creator/owner address.
             */
            address payable public owner;
            /**
             * @notice The tokenId of the most recently created NFT.
             * @dev Minting starts at tokenId 1. Each mint will use this value + 1.
             * @return The most recently minted tokenId, or 0 if no NFTs have been minted yet.
             */
            uint32 public latestTokenId;
            /**
             * @notice Tracks how many tokens have been burned.
             * @dev This number is used to calculate the total supply efficiently.
             */
            uint32 private burnCounter;
            // 32-bits free space
            /****** End of storage ******/
            /**
             * @notice Emitted when this collection is self destructed by the creator/owner/admin.
             * @param admin The account which requested this contract be self destructed.
             */
            event SelfDestruct(address indexed admin);
            modifier onlyOwner() {
              if (msg.sender != owner) {
                revert SequentialMintCollection_Caller_Is_Not_Owner(owner);
              }
              _;
            }
            function _initializeSequentialMintCollection(address payable _creator) internal {
              owner = _creator;
            }
            /**
             * @notice Allows the collection owner to destroy this contract only if
             * no NFTs have been minted yet or the minted NFTs have been burned.
             */
            function _selfDestruct() internal {
              if (totalSupply() != 0) {
                revert SequentialMintCollection_Minted_NFTs_Must_Be_Burned_First(totalSupply());
              }
              emit SelfDestruct(msg.sender);
              selfdestruct(payable(msg.sender));
            }
            function _burn(uint256 tokenId) internal virtual override {
              unchecked {
                // Number of burned tokens cannot exceed latestTokenId which is the same size.
                ++burnCounter;
              }
              super._burn(tokenId);
            }
            /**
             * @inheritdoc ITokenCreator
             * @dev The tokenId param is ignored since all NFTs return the same value.
             */
            function tokenCreator(uint256 /* tokenId */) external view returns (address payable creator) {
              creator = owner;
            }
            /**
             * @notice Returns the total amount of tokens stored by the contract.
             * @dev From the ERC-721 enumerable standard.
             * @return supply The total number of NFTs tracked by this contract.
             */
            function totalSupply() public view returns (uint256 supply) {
              unchecked {
                // Number of tokens minted is always >= burned tokens.
                supply = latestTokenId - burnCounter;
              }
            }
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "./CollectionRoyalties.sol";
          import "./SequentialMintCollection.sol";
          /**
           * @title Offers single payment address definition for all items in a given collection.
           * @author HardlyDifficult
           */
          abstract contract SharedPaymentCollection is SequentialMintCollection, CollectionRoyalties {
            /**
             * @notice The address to pay the proceeds/royalties for the collection.
             * @dev If this is set to address(0) then the proceeds go to the creator.
             */
            address payable private paymentAddress;
            function _initializeSharedPaymentCollection(address payable _paymentAddress) internal {
              // Initialize royalties
              if (_paymentAddress != address(0)) {
                // If no payment address was defined, `.owner` will be returned in getTokenCreatorPaymentAddress() below.
                paymentAddress = _paymentAddress;
              }
            }
            /**
             * @inheritdoc CollectionRoyalties
             */
            function getTokenCreatorPaymentAddress(
              uint256 /* tokenId */
            ) public view override returns (address payable creatorPaymentAddress) {
              creatorPaymentAddress = paymentAddress;
              if (creatorPaymentAddress == address(0)) {
                creatorPaymentAddress = owner;
              }
            }
            /**
             * @inheritdoc IERC165Upgradeable
             */
            function supportsInterface(
              bytes4 interfaceId
            ) public view virtual override(ERC721Upgradeable, CollectionRoyalties) returns (bool isSupported) {
              isSupported = super.supportsInterface(interfaceId);
            }
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts/utils/math/SafeCast.sol";
          import "../../libraries/TimeLibrary.sol";
          import "../shared/Constants.sol";
          import "../../interfaces/internal/INFTLazyMintedCollectionMintCountTo.sol";
          import "./LazyMintedCollection.sol";
          error TimeLimitedCollection_Mint_End_Time_Must_Be_In_The_Future();
          error TimeLimitedCollection_Mint_End_Time_Too_Far_In_The_Future();
          /// @param mintEndTime The time in seconds after which no more editions can be minted.
          error TimeLimitedCollection_Minting_Has_Ended(uint256 mintEndTime);
          /**
           * @title Defines an upper limit on the number of tokens which may be minted by this collection.
           * @author HardlyDifficult
           */
          abstract contract TimeLimitedCollection is LazyMintedCollection {
            using SafeCast for uint256;
            using TimeLibrary for uint32;
            using TimeLibrary for uint256;
            /**
             * @notice The time in seconds after which no more editions can be minted.
             */
            uint32 public mintEndTime;
            function _initializeTimeLimitedCollection(uint256 _mintEndTime) internal {
              if (_mintEndTime.hasBeenReached()) {
                revert TimeLimitedCollection_Mint_End_Time_Must_Be_In_The_Future();
              }
              if (_mintEndTime > block.timestamp + MAX_SCHEDULED_TIME_IN_THE_FUTURE) {
                // Prevent arbitrarily large values from accidentally being set.
                revert TimeLimitedCollection_Mint_End_Time_Too_Far_In_The_Future();
              }
              // The check above ensures this cast is safe until 2104.
              mintEndTime = uint32(_mintEndTime);
            }
            /**
             * @inheritdoc LazyMintedCollection
             */
            function mintCountTo(uint16 count, address to) public virtual override returns (uint256 firstTokenId) {
              if (mintEndTime.hasExpired()) {
                revert TimeLimitedCollection_Minting_Has_Ended(mintEndTime);
              }
              firstTokenId = super.mintCountTo(count, to);
            }
            /**
             * @notice Get the number of NFTs that can still be minted.
             * @return count Number of NFTs that can still be minted.
             * @dev An edition can have up to (2^32-1) tokens, but this function will return max uint256 until the mintEndTime has
             * passed. Returning max uint256 indicates to consumers that there are effectively unlimited tokens available to mint.
             * It's not realistic for the mints to overflow the actual uint32 number of tokens available.
             */
            function numberOfTokensAvailableToMint() external view returns (uint256 count) {
              if (!mintEndTime.hasExpired()) {
                count = type(uint256).max;
              } else {
                count = 0;
              }
            }
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
          error AdminRole_Caller_Does_Not_Have_Admin_Role();
          /**
           * @title Defines a role for admin accounts.
           * @dev Wraps the default admin role from OpenZeppelin's AccessControl for easy integration.
           * @author batu-inal & HardlyDifficult
           */
          abstract contract AdminRole is AccessControlUpgradeable {
            modifier onlyAdmin() {
              if (!hasRole(DEFAULT_ADMIN_ROLE, msg.sender)) {
                revert AdminRole_Caller_Does_Not_Have_Admin_Role();
              }
              _;
            }
            function _initializeAdminRole(address admin) internal {
              // Grant the role to a specified account
              _grantRole(DEFAULT_ADMIN_ROLE, admin);
            }
            /**
             * @notice Adds an account as an approved admin.
             * @dev Only callable by existing admins, as enforced by `grantRole`.
             * @param account The address to be approved.
             */
            function grantAdmin(address account) external {
              grantRole(DEFAULT_ADMIN_ROLE, account);
            }
            /**
             * @notice Removes an account from the set of approved admins.
             * @dev Only callable by existing admins, as enforced by `revokeRole`.
             * @param account The address to be removed.
             */
            function revokeAdmin(address account) external {
              revokeRole(DEFAULT_ADMIN_ROLE, account);
            }
            /**
             * @notice Checks if the account provided is an admin.
             * @param account The address to check.
             * @return approved True if the account is an admin.
             * @dev This call is used by the royalty registry contract.
             */
            function isAdmin(address account) public view returns (bool approved) {
              approved = hasRole(DEFAULT_ADMIN_ROLE, account);
            }
            /**
             * @notice This empty reserved space is put in place to allow future versions to add new
             * variables without shifting down storage in the inheritance chain.
             * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
             */
            uint256[1_000] private __gap;
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
          import "./AdminRole.sol";
          error MinterRole_Caller_Does_Not_Have_Minter_Or_Admin_Role();
          /**
           * @title Defines a role for minter accounts.
           * @dev Wraps a role from OpenZeppelin's AccessControl for easy integration.
           * @author batu-inal & HardlyDifficult
           */
          abstract contract MinterRole is AccessControlUpgradeable, AdminRole {
            /**
             * @notice The `role` type used for approve minters.
             * @return `keccak256("MINTER_ROLE")`
             */
            bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
            modifier onlyMinterOrAdmin() {
              if (!isMinter(msg.sender) && !isAdmin(msg.sender)) {
                revert MinterRole_Caller_Does_Not_Have_Minter_Or_Admin_Role();
              }
              _;
            }
            function _initializeMinterRole(address minter) internal {
              // Grant the role to a specified account
              _grantRole(MINTER_ROLE, minter);
            }
            /**
             * @notice Adds an account as an approved minter.
             * @dev Only callable by admins, as enforced by `grantRole`.
             * @param account The address to be approved.
             */
            function grantMinter(address account) external {
              grantRole(MINTER_ROLE, account);
            }
            /**
             * @notice Removes an account from the set of approved minters.
             * @dev Only callable by admins, as enforced by `revokeRole`.
             * @param account The address to be removed.
             */
            function revokeMinter(address account) external {
              revokeRole(MINTER_ROLE, account);
            }
            /**
             * @notice Checks if the account provided is an minter.
             * @param account The address to check.
             * @return approved True if the account is an minter.
             */
            function isMinter(address account) public view returns (bool approved) {
              approved = hasRole(MINTER_ROLE, account);
            }
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /// Constant values shared across mixins.
          /**
           * @dev 100% in basis points.
           */
          uint256 constant BASIS_POINTS = 10_000;
          /**
           * @dev The default admin role defined by OZ ACL modules.
           */
          bytes32 constant DEFAULT_ADMIN_ROLE = 0x00;
          ////////////////////////////////////////////////////////////////
          // Royalties & Take Rates
          ////////////////////////////////////////////////////////////////
          /**
           * @dev The max take rate an exhibition can have.
           */
          uint256 constant MAX_EXHIBITION_TAKE_RATE = 5_000;
          /**
           * @dev Cap the number of royalty recipients.
           * A cap is required to ensure gas costs are not too high when a sale is settled.
           */
          uint256 constant MAX_ROYALTY_RECIPIENTS = 5;
          /**
           * @dev Default royalty cut paid out on secondary sales.
           * Set to 10% of the secondary sale.
           */
          uint96 constant ROYALTY_IN_BASIS_POINTS = 1_000;
          /**
           * @dev 10%, expressed as a denominator for more efficient calculations.
           */
          uint256 constant ROYALTY_RATIO = BASIS_POINTS / ROYALTY_IN_BASIS_POINTS;
          ////////////////////////////////////////////////////////////////
          // Gas Limits
          ////////////////////////////////////////////////////////////////
          /**
           * @dev The gas limit used when making external read-only calls.
           * This helps to ensure that external calls does not prevent the market from executing.
           */
          uint256 constant READ_ONLY_GAS_LIMIT = 40_000;
          /**
           * @dev The gas limit to send ETH to multiple recipients, enough for a 5-way split.
           */
          uint256 constant SEND_VALUE_GAS_LIMIT_MULTIPLE_RECIPIENTS = 210_000;
          /**
           * @dev The gas limit to send ETH to a single recipient, enough for a contract with a simple receiver.
           */
          uint256 constant SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT = 20_000;
          ////////////////////////////////////////////////////////////////
          // Collection Type Names
          ////////////////////////////////////////////////////////////////
          /**
           * @dev The NFT collection type.
           */
          string constant NFT_COLLECTION_TYPE = "NFT Collection";
          /**
           * @dev The NFT drop collection type.
           */
          string constant NFT_DROP_COLLECTION_TYPE = "NFT Drop Collection";
          /**
           * @dev The NFT edition collection type.
           */
          string constant NFT_TIMED_EDITION_COLLECTION_TYPE = "NFT Timed Edition Collection";
          ////////////////////////////////////////////////////////////////
          // Business Logic
          ////////////////////////////////////////////////////////////////
          /**
           * @dev Limits scheduled start/end times to be less than 2 years in the future.
           */
          uint256 constant MAX_SCHEDULED_TIME_IN_THE_FUTURE = 365 days * 2;
          /**
           * @dev The minimum increase of 10% required when making an offer or placing a bid.
           */
          uint256 constant MIN_PERCENT_INCREMENT_DENOMINATOR = BASIS_POINTS / 1_000;
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
          error ContractFactory_Only_Callable_By_Factory_Contract(address contractFactory);
          error ContractFactory_Factory_Is_Not_A_Contract();
          /**
           * @title Stores a reference to the factory which is used to create contract proxies.
           * @author batu-inal & HardlyDifficult
           */
          abstract contract ContractFactory {
            using AddressUpgradeable for address;
            /**
             * @notice The address of the factory which was used to create this contract.
             * @return The factory contract address.
             */
            address public immutable contractFactory;
            modifier onlyContractFactory() {
              if (msg.sender != contractFactory) {
                revert ContractFactory_Only_Callable_By_Factory_Contract(contractFactory);
              }
              _;
            }
            /**
             * @notice Initialize the template's immutable variables.
             * @param _contractFactory The factory which will be used to create these contracts.
             */
            constructor(address _contractFactory) {
              if (!_contractFactory.isContract()) {
                revert ContractFactory_Factory_Is_Not_A_Contract();
              }
              contractFactory = _contractFactory;
            }
          }
          

          File 4 of 5: AdminUpgradeabilityProxy
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.6.0;
          import './UpgradeabilityProxy.sol';
          /**
           * @title AdminUpgradeabilityProxy
           * @dev This contract combines an upgradeability proxy with an authorization
           * mechanism for administrative tasks.
           * All external functions in this contract must be guarded by the
           * `ifAdmin` modifier. See ethereum/solidity#3864 for a Solidity
           * feature proposal that would enable this to be done automatically.
           */
          contract AdminUpgradeabilityProxy is UpgradeabilityProxy {
            /**
             * Contract constructor.
             * @param _logic address of the initial implementation.
             * @param _admin Address of the proxy administrator.
             * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
             * It should include the signature and the parameters of the function to be called, as described in
             * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
             * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
             */
            constructor(address _logic, address _admin, bytes memory _data) UpgradeabilityProxy(_logic, _data) public payable {
              assert(ADMIN_SLOT == bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1));
              _setAdmin(_admin);
            }
            /**
             * @dev Emitted when the administration has been transferred.
             * @param previousAdmin Address of the previous admin.
             * @param newAdmin Address of the new admin.
             */
            event AdminChanged(address previousAdmin, address newAdmin);
            /**
             * @dev Storage slot with the admin of the contract.
             * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
             * validated in the constructor.
             */
            bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
            /**
             * @dev Modifier to check whether the `msg.sender` is the admin.
             * If it is, it will run the function. Otherwise, it will delegate the call
             * to the implementation.
             */
            modifier ifAdmin() {
              if (msg.sender == _admin()) {
                _;
              } else {
                _fallback();
              }
            }
            /**
             * @return The address of the proxy admin.
             */
            function admin() external ifAdmin returns (address) {
              return _admin();
            }
            /**
             * @return The address of the implementation.
             */
            function implementation() external ifAdmin returns (address) {
              return _implementation();
            }
            /**
             * @dev Changes the admin of the proxy.
             * Only the current admin can call this function.
             * @param newAdmin Address to transfer proxy administration to.
             */
            function changeAdmin(address newAdmin) external ifAdmin {
              require(newAdmin != address(0), "Cannot change the admin of a proxy to the zero address");
              emit AdminChanged(_admin(), newAdmin);
              _setAdmin(newAdmin);
            }
            /**
             * @dev Upgrade the backing implementation of the proxy.
             * Only the admin can call this function.
             * @param newImplementation Address of the new implementation.
             */
            function upgradeTo(address newImplementation) external ifAdmin {
              _upgradeTo(newImplementation);
            }
            /**
             * @dev Upgrade the backing implementation of the proxy and call a function
             * on the new implementation.
             * This is useful to initialize the proxied contract.
             * @param newImplementation Address of the new implementation.
             * @param data Data to send as msg.data in the low level call.
             * It should include the signature and the parameters of the function to be called, as described in
             * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
             */
            function upgradeToAndCall(address newImplementation, bytes calldata data) payable external ifAdmin {
              _upgradeTo(newImplementation);
              (bool success,) = newImplementation.delegatecall(data);
              require(success);
            }
            /**
             * @return adm The admin slot.
             */
            function _admin() internal view returns (address adm) {
              bytes32 slot = ADMIN_SLOT;
              assembly {
                adm := sload(slot)
              }
            }
            /**
             * @dev Sets the address of the proxy admin.
             * @param newAdmin Address of the new proxy admin.
             */
            function _setAdmin(address newAdmin) internal {
              bytes32 slot = ADMIN_SLOT;
              assembly {
                sstore(slot, newAdmin)
              }
            }
            /**
             * @dev Only fall back when the sender is not the admin.
             */
            function _willFallback() internal override virtual {
              require(msg.sender != _admin(), "Cannot call fallback function from the proxy admin");
              super._willFallback();
            }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.6.0;
          import './Proxy.sol';
          import '@openzeppelin/contracts/utils/Address.sol';
          /**
           * @title UpgradeabilityProxy
           * @dev This contract implements a proxy that allows to change the
           * implementation address to which it will delegate.
           * Such a change is called an implementation upgrade.
           */
          contract UpgradeabilityProxy is Proxy {
            /**
             * @dev Contract constructor.
             * @param _logic Address of the initial implementation.
             * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
             * It should include the signature and the parameters of the function to be called, as described in
             * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
             * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
             */
            constructor(address _logic, bytes memory _data) public payable {
              assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1));
              _setImplementation(_logic);
              if(_data.length > 0) {
                (bool success,) = _logic.delegatecall(_data);
                require(success);
              }
            }  
            /**
             * @dev Emitted when the implementation is upgraded.
             * @param implementation Address of the new implementation.
             */
            event Upgraded(address indexed implementation);
            /**
             * @dev Storage slot with the address of the current implementation.
             * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
             * validated in the constructor.
             */
            bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
            /**
             * @dev Returns the current implementation.
             * @return impl Address of the current implementation
             */
            function _implementation() internal override view returns (address impl) {
              bytes32 slot = IMPLEMENTATION_SLOT;
              assembly {
                impl := sload(slot)
              }
            }
            /**
             * @dev Upgrades the proxy to a new implementation.
             * @param newImplementation Address of the new implementation.
             */
            function _upgradeTo(address newImplementation) internal {
              _setImplementation(newImplementation);
              emit Upgraded(newImplementation);
            }
            /**
             * @dev Sets the implementation address of the proxy.
             * @param newImplementation Address of the new implementation.
             */
            function _setImplementation(address newImplementation) internal {
              require(Address.isContract(newImplementation), "Cannot set a proxy implementation to a non-contract address");
              bytes32 slot = IMPLEMENTATION_SLOT;
              assembly {
                sstore(slot, newImplementation)
              }
            }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.6.0;
          /**
           * @title Proxy
           * @dev Implements delegation of calls to other contracts, with proper
           * forwarding of return values and bubbling of failures.
           * It defines a fallback function that delegates all calls to the address
           * returned by the abstract _implementation() internal function.
           */
          abstract contract Proxy {
            /**
             * @dev Fallback function.
             * Implemented entirely in `_fallback`.
             */
            fallback () payable external {
              _fallback();
            }
            /**
             * @dev Receive function.
             * Implemented entirely in `_fallback`.
             */
            receive () payable external {
              _fallback();
            }
            /**
             * @return The Address of the implementation.
             */
            function _implementation() internal virtual view returns (address);
            /**
             * @dev Delegates execution to an implementation contract.
             * This is a low level function that doesn't return to its internal call site.
             * It will return to the external caller whatever the implementation returns.
             * @param implementation Address to delegate.
             */
            function _delegate(address implementation) internal {
              assembly {
                // Copy msg.data. We take full control of memory in this inline assembly
                // block because it will not return to Solidity code. We overwrite the
                // Solidity scratch pad at memory position 0.
                calldatacopy(0, 0, calldatasize())
                // Call the implementation.
                // out and outsize are 0 because we don't know the size yet.
                let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
                // Copy the returned data.
                returndatacopy(0, 0, returndatasize())
                switch result
                // delegatecall returns 0 on error.
                case 0 { revert(0, returndatasize()) }
                default { return(0, returndatasize()) }
              }
            }
            /**
             * @dev Function that is run as the first thing in the fallback function.
             * Can be redefined in derived contracts to add functionality.
             * Redefinitions must call super._willFallback().
             */
            function _willFallback() internal virtual {
            }
            /**
             * @dev fallback implementation.
             * Extracted to enable manual triggering.
             */
            function _fallback() internal {
              _willFallback();
              _delegate(_implementation());
            }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.6.2 <0.8.0;
          /**
           * @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
               * ====
               */
              function isContract(address account) internal view returns (bool) {
                  // This method relies on extcodesize, which returns 0 for contracts in
                  // construction, since the code is only stored at the end of the
                  // constructor execution.
                  uint256 size;
                  // solhint-disable-next-line no-inline-assembly
                  assembly { size := extcodesize(account) }
                  return size > 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");
                  // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
                  (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");
                  // solhint-disable-next-line avoid-low-level-calls
                  (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");
                  // solhint-disable-next-line avoid-low-level-calls
                  (bool success, bytes memory returndata) = target.staticcall(data);
                  return _verifyCallResult(success, returndata, errorMessage);
              }
              function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private 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
                          // solhint-disable-next-line no-inline-assembly
                          assembly {
                              let returndata_size := mload(returndata)
                              revert(add(32, returndata), returndata_size)
                          }
                      } else {
                          revert(errorMessage);
                      }
                  }
              }
          }
          

          File 5 of 5: NFTMarket
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          /// @author: manifold.xyz
          import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
          /**
           * @dev Royalty registry interface
           */
          interface IRoyaltyRegistry is IERC165 {
              event RoyaltyOverride(address owner, address tokenAddress, address royaltyAddress);
              /**
               * Override the location of where to look up royalty information for a given token contract.
               * Allows for backwards compatibility and implementation of royalty logic for contracts that did not previously support them.
               *
               * @param tokenAddress    - The token address you wish to override
               * @param royaltyAddress  - The royalty override address
               */
              function setRoyaltyLookupAddress(address tokenAddress, address royaltyAddress) external returns (bool);
              /**
               * Returns royalty address location.  Returns the tokenAddress by default, or the override if it exists
               *
               * @param tokenAddress    - The token address you are looking up the royalty for
               */
              function getRoyaltyLookupAddress(address tokenAddress) external view returns (address);
              /**
               * Returns the token address that an overrideAddress is set for.
               * Note: will not be accurate if the override was created before this function was added.
               *
               * @param overrideAddress - The override address you are looking up the token for
               */
              function getOverrideLookupTokenAddress(address overrideAddress) external view returns (address);
              /**
               * Whether or not the message sender can override the royalty address for the given token address
               *
               * @param tokenAddress    - The token address you are looking up the royalty for
               */
              function overrideAllowed(address tokenAddress) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.1) (proxy/utils/Initializable.sol)
          pragma solidity ^0.8.2;
          import "../../utils/AddressUpgradeable.sol";
          /**
           * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
           * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
           * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
           * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
           *
           * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
           * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
           * case an upgrade adds a module that needs to be initialized.
           *
           * For example:
           *
           * [.hljs-theme-light.nopadding]
           * ```
           * contract MyToken is ERC20Upgradeable {
           *     function initialize() initializer public {
           *         __ERC20_init("MyToken", "MTK");
           *     }
           * }
           * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
           *     function initializeV2() reinitializer(2) public {
           *         __ERC20Permit_init("MyToken");
           *     }
           * }
           * ```
           *
           * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
           * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
           *
           * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
           * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
           *
           * [CAUTION]
           * ====
           * Avoid leaving a contract uninitialized.
           *
           * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
           * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
           * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
           *
           * [.hljs-theme-light.nopadding]
           * ```
           * /// @custom:oz-upgrades-unsafe-allow constructor
           * constructor() {
           *     _disableInitializers();
           * }
           * ```
           * ====
           */
          abstract contract Initializable {
              /**
               * @dev Indicates that the contract has been initialized.
               * @custom:oz-retyped-from bool
               */
              uint8 private _initialized;
              /**
               * @dev Indicates that the contract is in the process of being initialized.
               */
              bool private _initializing;
              /**
               * @dev Triggered when the contract has been initialized or reinitialized.
               */
              event Initialized(uint8 version);
              /**
               * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
               * `onlyInitializing` functions can be used to initialize parent contracts.
               *
               * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
               * constructor.
               *
               * Emits an {Initialized} event.
               */
              modifier initializer() {
                  bool isTopLevelCall = !_initializing;
                  require(
                      (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
                      "Initializable: contract is already initialized"
                  );
                  _initialized = 1;
                  if (isTopLevelCall) {
                      _initializing = true;
                  }
                  _;
                  if (isTopLevelCall) {
                      _initializing = false;
                      emit Initialized(1);
                  }
              }
              /**
               * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
               * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
               * used to initialize parent contracts.
               *
               * A reinitializer may be used after the original initialization step. This is essential to configure modules that
               * are added through upgrades and that require initialization.
               *
               * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
               * cannot be nested. If one is invoked in the context of another, execution will revert.
               *
               * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
               * a contract, executing them in the right order is up to the developer or operator.
               *
               * WARNING: setting the version to 255 will prevent any future reinitialization.
               *
               * Emits an {Initialized} event.
               */
              modifier reinitializer(uint8 version) {
                  require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
                  _initialized = version;
                  _initializing = true;
                  _;
                  _initializing = false;
                  emit Initialized(version);
              }
              /**
               * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
               * {initializer} and {reinitializer} modifiers, directly or indirectly.
               */
              modifier onlyInitializing() {
                  require(_initializing, "Initializable: contract is not initializing");
                  _;
              }
              /**
               * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
               * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
               * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
               * through proxies.
               *
               * Emits an {Initialized} event the first time it is successfully executed.
               */
              function _disableInitializers() internal virtual {
                  require(!_initializing, "Initializable: contract is initializing");
                  if (_initialized < type(uint8).max) {
                      _initialized = type(uint8).max;
                      emit Initialized(type(uint8).max);
                  }
              }
              /**
               * @dev Returns the highest version that has been initialized. See {reinitializer}.
               */
              function _getInitializedVersion() internal view returns (uint8) {
                  return _initialized;
              }
              /**
               * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
               */
              function _isInitializing() internal view returns (bool) {
                  return _initializing;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (security/ReentrancyGuard.sol)
          pragma solidity ^0.8.0;
          import "../proxy/utils/Initializable.sol";
          /**
           * @dev Contract module that helps prevent reentrant calls to a function.
           *
           * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
           * available, which can be applied to functions to make sure there are no nested
           * (reentrant) calls to them.
           *
           * Note that because there is a single `nonReentrant` guard, functions marked as
           * `nonReentrant` may not call one another. This can be worked around by making
           * those functions `private`, and then adding `external` `nonReentrant` entry
           * points to them.
           *
           * TIP: If you would like to learn more about reentrancy and alternative ways
           * to protect against it, check out our blog post
           * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
           */
          abstract contract ReentrancyGuardUpgradeable is Initializable {
              // Booleans are more expensive than uint256 or any type that takes up a full
              // word because each write operation emits an extra SLOAD to first read the
              // slot's contents, replace the bits taken up by the boolean, and then write
              // back. This is the compiler's defense against contract upgrades and
              // pointer aliasing, and it cannot be disabled.
              // The values being non-zero value makes deployment a bit more expensive,
              // but in exchange the refund on every call to nonReentrant will be lower in
              // amount. Since refunds are capped to a percentage of the total
              // transaction's gas, it is best to keep them low in cases like this one, to
              // increase the likelihood of the full refund coming into effect.
              uint256 private constant _NOT_ENTERED = 1;
              uint256 private constant _ENTERED = 2;
              uint256 private _status;
              function __ReentrancyGuard_init() internal onlyInitializing {
                  __ReentrancyGuard_init_unchained();
              }
              function __ReentrancyGuard_init_unchained() internal onlyInitializing {
                  _status = _NOT_ENTERED;
              }
              /**
               * @dev Prevents a contract from calling itself, directly or indirectly.
               * Calling a `nonReentrant` function from another `nonReentrant`
               * function is not supported. It is possible to prevent this from happening
               * by making the `nonReentrant` function external, and making it call a
               * `private` function that does the actual work.
               */
              modifier nonReentrant() {
                  _nonReentrantBefore();
                  _;
                  _nonReentrantAfter();
              }
              function _nonReentrantBefore() private {
                  // On the first call to nonReentrant, _status will be _NOT_ENTERED
                  require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
                  // Any calls to nonReentrant after this point will fail
                  _status = _ENTERED;
              }
              function _nonReentrantAfter() private {
                  // By storing the original value once again, a refund is triggered (see
                  // https://eips.ethereum.org/EIPS/eip-2200)
                  _status = _NOT_ENTERED;
              }
              /**
               * @dev This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[49] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)
          pragma solidity ^0.8.1;
          /**
           * @dev Collection of functions related to the address type
           */
          library AddressUpgradeable {
              /**
               * @dev Returns true if `account` is a contract.
               *
               * [IMPORTANT]
               * ====
               * It is unsafe to assume that an address for which this function returns
               * false is an externally-owned account (EOA) and not a contract.
               *
               * Among others, `isContract` will return false for the following
               * types of addresses:
               *
               *  - an externally-owned account
               *  - a contract in construction
               *  - an address where a contract will be created
               *  - an address where a contract lived, but was destroyed
               * ====
               *
               * [IMPORTANT]
               * ====
               * You shouldn't rely on `isContract` to protect against flash loan attacks!
               *
               * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
               * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
               * constructor.
               * ====
               */
              function isContract(address account) internal view returns (bool) {
                  // This method relies on extcodesize/address.code.length, which returns 0
                  // for contracts in construction, since the code is only stored at the end
                  // of the constructor execution.
                  return account.code.length > 0;
              }
              /**
               * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
               * `recipient`, forwarding all available gas and reverting on errors.
               *
               * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
               * of certain opcodes, possibly making contracts go over the 2300 gas limit
               * imposed by `transfer`, making them unable to receive funds via
               * `transfer`. {sendValue} removes this limitation.
               *
               * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
               *
               * IMPORTANT: because control is transferred to `recipient`, care must be
               * taken to not create reentrancy vulnerabilities. Consider using
               * {ReentrancyGuard} or the
               * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
               */
              function sendValue(address payable recipient, uint256 amount) internal {
                  require(address(this).balance >= amount, "Address: insufficient balance");
                  (bool success, ) = recipient.call{value: amount}("");
                  require(success, "Address: unable to send value, recipient may have reverted");
              }
              /**
               * @dev Performs a Solidity function call using a low level `call`. A
               * plain `call` is an unsafe replacement for a function call: use this
               * function instead.
               *
               * If `target` reverts with a revert reason, it is bubbled up by this
               * function (like regular Solidity function calls).
               *
               * Returns the raw returned data. To convert to the expected return value,
               * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
               *
               * Requirements:
               *
               * - `target` must be a contract.
               * - calling `target` with `data` must not revert.
               *
               * _Available since v3.1._
               */
              function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, 0, "Address: low-level call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
               * `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, 0, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but also transferring `value` wei to `target`.
               *
               * Requirements:
               *
               * - the calling contract must have an ETH balance of at least `value`.
               * - the called Solidity function must be `payable`.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
              }
              /**
               * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
               * with `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  require(address(this).balance >= value, "Address: insufficient balance for call");
                  (bool success, bytes memory returndata) = target.call{value: value}(data);
                  return verifyCallResultFromTarget(target, success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                  return functionStaticCall(target, data, "Address: low-level static call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal view returns (bytes memory) {
                  (bool success, bytes memory returndata) = target.staticcall(data);
                  return verifyCallResultFromTarget(target, success, returndata, errorMessage);
              }
              /**
               * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
               * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
               *
               * _Available since v4.8._
               */
              function verifyCallResultFromTarget(
                  address target,
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal view returns (bytes memory) {
                  if (success) {
                      if (returndata.length == 0) {
                          // only check isContract if the call was successful and the return data is empty
                          // otherwise we already know that it was a contract
                          require(isContract(target), "Address: call to non-contract");
                      }
                      return returndata;
                  } else {
                      _revert(returndata, errorMessage);
                  }
              }
              /**
               * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
               * revert reason or using the provided one.
               *
               * _Available since v4.3._
               */
              function verifyCallResult(
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal pure returns (bytes memory) {
                  if (success) {
                      return returndata;
                  } else {
                      _revert(returndata, errorMessage);
                  }
              }
              function _revert(bytes memory returndata, string memory errorMessage) private pure {
                  // Look for revert reason and bubble it up if present
                  if (returndata.length > 0) {
                      // The easiest way to bubble the revert reason is using memory via assembly
                      /// @solidity memory-safe-assembly
                      assembly {
                          let returndata_size := mload(returndata)
                          revert(add(32, returndata), returndata_size)
                      }
                  } else {
                      revert(errorMessage);
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol)
          pragma solidity ^0.8.0;
          import "../../utils/introspection/IERC165.sol";
          /**
           * @dev Required interface of an ERC721 compliant contract.
           */
          interface IERC721 is IERC165 {
              /**
               * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
               */
              event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
              /**
               * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
               */
              event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
              /**
               * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
               */
              event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
              /**
               * @dev Returns the number of tokens in ``owner``'s account.
               */
              function balanceOf(address owner) external view returns (uint256 balance);
              /**
               * @dev Returns the owner of the `tokenId` token.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               */
              function ownerOf(uint256 tokenId) external view returns (address owner);
              /**
               * @dev Safely transfers `tokenId` token from `from` to `to`.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must exist and be owned by `from`.
               * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
               * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes calldata data
              ) external;
              /**
               * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
               * are aware of the ERC721 protocol to prevent tokens from being forever locked.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must exist and be owned by `from`.
               * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
               * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) external;
              /**
               * @dev Transfers `tokenId` token from `from` to `to`.
               *
               * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
               * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
               * understand this adds an external call which potentially creates a reentrancy vulnerability.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must be owned by `from`.
               * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
               *
               * Emits a {Transfer} event.
               */
              function transferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) external;
              /**
               * @dev Gives permission to `to` to transfer `tokenId` token to another account.
               * The approval is cleared when the token is transferred.
               *
               * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
               *
               * Requirements:
               *
               * - The caller must own the token or be an approved operator.
               * - `tokenId` must exist.
               *
               * Emits an {Approval} event.
               */
              function approve(address to, uint256 tokenId) external;
              /**
               * @dev Approve or remove `operator` as an operator for the caller.
               * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
               *
               * Requirements:
               *
               * - The `operator` cannot be the caller.
               *
               * Emits an {ApprovalForAll} event.
               */
              function setApprovalForAll(address operator, bool _approved) external;
              /**
               * @dev Returns the account approved for `tokenId` token.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               */
              function getApproved(uint256 tokenId) external view returns (address operator);
              /**
               * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
               *
               * See {setApprovalForAll}
               */
              function isApprovedForAll(address owner, address operator) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Provides information about the current execution context, including the
           * sender of the transaction and its data. While these are generally available
           * via msg.sender and msg.data, they should not be accessed in such a direct
           * manner, since when dealing with meta-transactions the account sending and
           * paying for execution may not be the actual sender (as far as an application
           * is concerned).
           *
           * This contract is only required for intermediate, library-like contracts.
           */
          abstract contract Context {
              function _msgSender() internal view virtual returns (address) {
                  return msg.sender;
              }
              function _msgData() internal view virtual returns (bytes calldata) {
                  return msg.data;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.2) (utils/introspection/ERC165Checker.sol)
          pragma solidity ^0.8.0;
          import "./IERC165.sol";
          /**
           * @dev Library used to query support of an interface declared via {IERC165}.
           *
           * Note that these functions return the actual result of the query: they do not
           * `revert` if an interface is not supported. It is up to the caller to decide
           * what to do in these cases.
           */
          library ERC165Checker {
              // As per the EIP-165 spec, no interface should ever match 0xffffffff
              bytes4 private constant _INTERFACE_ID_INVALID = 0xffffffff;
              /**
               * @dev Returns true if `account` supports the {IERC165} interface.
               */
              function supportsERC165(address account) internal view returns (bool) {
                  // Any contract that implements ERC165 must explicitly indicate support of
                  // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid
                  return
                      supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId) &&
                      !supportsERC165InterfaceUnchecked(account, _INTERFACE_ID_INVALID);
              }
              /**
               * @dev Returns true if `account` supports the interface defined by
               * `interfaceId`. Support for {IERC165} itself is queried automatically.
               *
               * See {IERC165-supportsInterface}.
               */
              function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) {
                  // query support of both ERC165 as per the spec and support of _interfaceId
                  return supportsERC165(account) && supportsERC165InterfaceUnchecked(account, interfaceId);
              }
              /**
               * @dev Returns a boolean array where each value corresponds to the
               * interfaces passed in and whether they're supported or not. This allows
               * you to batch check interfaces for a contract where your expectation
               * is that some interfaces may not be supported.
               *
               * See {IERC165-supportsInterface}.
               *
               * _Available since v3.4._
               */
              function getSupportedInterfaces(address account, bytes4[] memory interfaceIds)
                  internal
                  view
                  returns (bool[] memory)
              {
                  // an array of booleans corresponding to interfaceIds and whether they're supported or not
                  bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length);
                  // query support of ERC165 itself
                  if (supportsERC165(account)) {
                      // query support of each interface in interfaceIds
                      for (uint256 i = 0; i < interfaceIds.length; i++) {
                          interfaceIdsSupported[i] = supportsERC165InterfaceUnchecked(account, interfaceIds[i]);
                      }
                  }
                  return interfaceIdsSupported;
              }
              /**
               * @dev Returns true if `account` supports all the interfaces defined in
               * `interfaceIds`. Support for {IERC165} itself is queried automatically.
               *
               * Batch-querying can lead to gas savings by skipping repeated checks for
               * {IERC165} support.
               *
               * See {IERC165-supportsInterface}.
               */
              function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) {
                  // query support of ERC165 itself
                  if (!supportsERC165(account)) {
                      return false;
                  }
                  // query support of each interface in interfaceIds
                  for (uint256 i = 0; i < interfaceIds.length; i++) {
                      if (!supportsERC165InterfaceUnchecked(account, interfaceIds[i])) {
                          return false;
                      }
                  }
                  // all interfaces supported
                  return true;
              }
              /**
               * @notice Query if a contract implements an interface, does not check ERC165 support
               * @param account The address of the contract to query for support of an interface
               * @param interfaceId The interface identifier, as specified in ERC-165
               * @return true if the contract at account indicates support of the interface with
               * identifier interfaceId, false otherwise
               * @dev Assumes that account contains a contract that supports ERC165, otherwise
               * the behavior of this method is undefined. This precondition can be checked
               * with {supportsERC165}.
               *
               * Some precompiled contracts will falsely indicate support for a given interface, so caution
               * should be exercised when using this function.
               *
               * Interface identification is specified in ERC-165.
               */
              function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) {
                  // prepare call
                  bytes memory encodedParams = abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId);
                  // perform static call
                  bool success;
                  uint256 returnSize;
                  uint256 returnValue;
                  assembly {
                      success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20)
                      returnSize := returndatasize()
                      returnValue := mload(0x00)
                  }
                  return success && returnSize >= 0x20 && returnValue > 0;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Interface of the ERC165 standard, as defined in the
           * https://eips.ethereum.org/EIPS/eip-165[EIP].
           *
           * Implementers can declare support of contract interfaces, which can then be
           * queried by others ({ERC165Checker}).
           *
           * For an implementation, see {ERC165}.
           */
          interface IERC165 {
              /**
               * @dev Returns true if this contract implements the interface defined by
               * `interfaceId`. See the corresponding
               * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
               * to learn more about how these ids are created.
               *
               * This function call must use less than 30 000 gas.
               */
              function supportsInterface(bytes4 interfaceId) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @notice Interface for functions the market uses in FETH.
           * @author batu-inal & HardlyDifficult
           */
          interface IFethMarket {
            function depositFor(address account) external payable;
            function marketLockupFor(address account, uint256 amount) external payable returns (uint256 expiration);
            function marketWithdrawFrom(address from, uint256 amount) external;
            function marketWithdrawLocked(address account, uint256 expiration, uint256 amount) external;
            function marketUnlockFor(address account, uint256 expiration, uint256 amount) external;
            function marketChangeLockup(
              address unlockFrom,
              uint256 unlockExpiration,
              uint256 unlockAmount,
              address lockupFor,
              uint256 lockupAmount
            ) external payable returns (uint256 expiration);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @title Declares the type of the collection contract.
           * @dev This interface is declared as an ERC-165 interface.
           * @author reggieag
           */
          interface INFTCollectionType {
            function getNFTCollectionType() external view returns (string memory collectionType);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @notice The required interface for collections in the NFTDropMarket to support exhibitions.
           * @author philbirt
           */
          interface INFTMarketExhibition {
            function isAllowedSellerForExhibition(
              uint256 exhibitionId,
              address seller
            ) external view returns (bool allowedSeller);
            function getExhibitionPaymentDetails(
              uint256 exhibitionId
            ) external view returns (address payable curator, uint16 takeRateInBasisPoints);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @notice Interface for AdminRole which wraps the default admin role from
           * OpenZeppelin's AccessControl for easy integration.
           * @author batu-inal & HardlyDifficult
           */
          interface IAdminRole {
            function isAdmin(address account) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @notice Interface for OperatorRole which wraps a role from
           * OpenZeppelin's AccessControl for easy integration.
           * @author batu-inal & HardlyDifficult
           */
          interface IOperatorRole {
            function isOperator(address account) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @title Interface for routing calls to the NFT Market to set buy now prices.
           * @author HardlyDifficult
           */
          interface INFTMarketBuyNow {
            function setBuyPrice(address nftContract, uint256 tokenId, uint256 price) external;
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @title Interface for routing calls to the NFT Market to create reserve auctions.
           * @author HardlyDifficult
           */
          interface INFTMarketReserveAuction {
            function createReserveAuctionV2(
              address nftContract,
              uint256 tokenId,
              uint256 reservePrice,
              uint256 exhibitionId
            ) external returns (uint256 auctionId);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @notice An interface for communicating fees to 3rd party marketplaces.
           * @dev Originally implemented in mainnet contract 0x44d6e8933f8271abcf253c72f9ed7e0e4c0323b3
           */
          interface IGetFees {
            /**
             * @notice Get the recipient addresses to which creator royalties should be sent.
             * @dev The expected royalty amounts are communicated with `getFeeBps`.
             * @param tokenId The ID of the NFT to get royalties for.
             * @return recipients An array of addresses to which royalties should be sent.
             */
            function getFeeRecipients(uint256 tokenId) external view returns (address payable[] memory recipients);
            /**
             * @notice Get the creator royalty amounts to be sent to each recipient, in basis points.
             * @dev The expected recipients are communicated with `getFeeRecipients`.
             * @param tokenId The ID of the NFT to get royalties for.
             * @return royaltiesInBasisPoints The array of fees to be sent to each recipient, in basis points.
             */
            function getFeeBps(uint256 tokenId) external view returns (uint256[] memory royaltiesInBasisPoints);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          interface IGetRoyalties {
            /**
             * @notice Get the creator royalties to be sent.
             * @dev The data is the same as when calling `getFeeRecipients` and `getFeeBps` separately.
             * @param tokenId The ID of the NFT to get royalties for.
             * @return recipients An array of addresses to which royalties should be sent.
             * @return royaltiesInBasisPoints The array of fees to be sent to each recipient, in basis points.
             */
            function getRoyalties(
              uint256 tokenId
            ) external view returns (address payable[] memory recipients, uint256[] memory royaltiesInBasisPoints);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.12;
          interface IOwnable {
            /**
             * @dev Returns the address of the current owner.
             */
            function owner() external view returns (address);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @notice Interface for EIP-2981: NFT Royalty Standard.
           * For more see: https://eips.ethereum.org/EIPS/eip-2981.
           */
          interface IRoyaltyInfo {
            /**
             * @notice Get the creator royalties to be sent.
             * @param tokenId The ID of the NFT to get royalties for.
             * @param salePrice The total price of the sale.
             * @return receiver The address to which royalties should be sent.
             * @return royaltyAmount The total amount that should be sent to the `receiver`.
             */
            function royaltyInfo(
              uint256 tokenId,
              uint256 salePrice
            ) external view returns (address receiver, uint256 royaltyAmount);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          interface ITokenCreator {
            /**
             * @notice Returns the creator of this NFT collection.
             * @param tokenId The ID of the NFT to get the creator payment address for.
             * @return creator The creator of this collection.
             */
            function tokenCreator(uint256 tokenId) external view returns (address payable creator);
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @title Helper functions for arrays.
           * @author batu-inal & HardlyDifficult
           */
          library ArrayLibrary {
            /**
             * @notice Reduces the size of an array if it's greater than the specified max size,
             * using the first maxSize elements.
             */
            function capLength(address payable[] memory data, uint256 maxLength) internal pure {
              if (data.length > maxLength) {
                assembly {
                  mstore(data, maxLength)
                }
              }
            }
            /**
             * @notice Reduces the size of an array if it's greater than the specified max size,
             * using the first maxSize elements.
             */
            function capLength(uint256[] memory data, uint256 maxLength) internal pure {
              if (data.length > maxLength) {
                assembly {
                  mstore(data, maxLength)
                }
              }
            }
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @title Helpers for working with time.
           * @author batu-inal & HardlyDifficult
           */
          library TimeLibrary {
            /**
             * @notice Checks if the given timestamp is in the past.
             * @dev This helper ensures a consistent interpretation of expiry across the codebase.
             * This is different than `hasBeenReached` in that it will return false if the expiry is now.
             */
            function hasExpired(uint256 expiry) internal view returns (bool) {
              return expiry < block.timestamp;
            }
            /**
             * @notice Checks if the given timestamp is now or in the past.
             * @dev This helper ensures a consistent interpretation of expiry across the codebase.
             * This is different from `hasExpired` in that it will return true if the timestamp is now.
             */
            function hasBeenReached(uint256 timestamp) internal view returns (bool) {
              return timestamp <= block.timestamp;
            }
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @title An abstraction layer for auctions.
           * @dev This contract can be expanded with reusable calls and data as more auction types are added.
           * @author batu-inal & HardlyDifficult
           */
          abstract contract NFTMarketAuction {
            /**
             * @notice A global id for auctions of any type.
             */
            uint256 private nextAuctionId;
            /**
             * @notice Called once to configure the contract after the initial proxy deployment.
             * @dev This sets the initial auction id to 1, making the first auction cheaper
             * and id 0 represents no auction found.
             */
            function _initializeNFTMarketAuction() internal {
              nextAuctionId = 1;
            }
            /**
             * @notice Returns id to assign to the next auction.
             */
            function _getNextAndIncrementAuctionId() internal returns (uint256) {
              // AuctionId cannot overflow 256 bits.
              unchecked {
                return nextAuctionId++;
              }
            }
            /**
             * @notice This empty reserved space is put in place to allow future versions to add new
             * variables without shifting down storage in the inheritance chain.
             * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
             */
            uint256[1_000] private __gap;
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts/utils/Context.sol";
          import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
          import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
          import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
          import "../../interfaces/internal/routes/INFTMarketBuyNow.sol";
          import "../shared/MarketFees.sol";
          import "../shared/FoundationTreasuryNode.sol";
          import "../shared/FETHNode.sol";
          import "../shared/MarketSharedCore.sol";
          import "../shared/SendValueWithFallbackWithdraw.sol";
          import "./NFTMarketCore.sol";
          import "./NFTMarketExhibition.sol";
          /// @param buyPrice The current buy price set for this NFT.
          error NFTMarketBuyPrice_Cannot_Buy_At_Lower_Price(uint256 buyPrice);
          error NFTMarketBuyPrice_Cannot_Buy_Unset_Price();
          error NFTMarketBuyPrice_Cannot_Cancel_Unset_Price();
          /// @param owner The current owner of this NFT.
          error NFTMarketBuyPrice_Only_Owner_Can_Cancel_Price(address owner);
          /// @param owner The current owner of this NFT.
          error NFTMarketBuyPrice_Only_Owner_Can_Set_Price(address owner);
          error NFTMarketBuyPrice_Price_Already_Set();
          error NFTMarketBuyPrice_Price_Too_High();
          /// @param seller The current owner of this NFT.
          error NFTMarketBuyPrice_Seller_Mismatch(address seller);
          /**
           * @title Allows sellers to set a buy price of their NFTs that may be accepted and instantly transferred to the buyer.
           * @notice NFTs with a buy price set are escrowed in the market contract.
           * @author batu-inal & HardlyDifficult
           */
          abstract contract NFTMarketBuyPrice is
            INFTMarketBuyNow,
            Initializable,
            FoundationTreasuryNode,
            Context,
            FETHNode,
            MarketSharedCore,
            NFTMarketCore,
            ReentrancyGuardUpgradeable,
            SendValueWithFallbackWithdraw,
            MarketFees,
            NFTMarketExhibition
          {
            using AddressUpgradeable for address payable;
            /// @notice Stores the buy price details for a specific NFT.
            /// @dev The struct is packed into a single slot to optimize gas.
            struct BuyPrice {
              /// @notice The current owner of this NFT which set a buy price.
              /// @dev A zero price is acceptable so a non-zero address determines whether a price has been set.
              address payable seller;
              /// @notice The current buy price set for this NFT.
              uint96 price;
            }
            /// @notice Stores the current buy price for each NFT.
            mapping(address => mapping(uint256 => BuyPrice)) private nftContractToTokenIdToBuyPrice;
            /**
             * @notice Emitted when an NFT is bought by accepting the buy price,
             * indicating that the NFT has been transferred and revenue from the sale distributed.
             * @dev The total buy price that was accepted is `totalFees` + `creatorRev` + `sellerRev`.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @param buyer The address of the collector that purchased the NFT using `buy`.
             * @param seller The address of the seller which originally set the buy price.
             * @param totalFees The amount of ETH that was sent to Foundation & referrals for this sale.
             * @param creatorRev The amount of ETH that was sent to the creator for this sale.
             * @param sellerRev The amount of ETH that was sent to the owner for this sale.
             */
            event BuyPriceAccepted(
              address indexed nftContract,
              uint256 indexed tokenId,
              address indexed seller,
              address buyer,
              uint256 totalFees,
              uint256 creatorRev,
              uint256 sellerRev
            );
            /**
             * @notice Emitted when the buy price is removed by the owner of an NFT.
             * @dev The NFT is transferred back to the owner unless it's still escrowed for another market tool,
             * e.g. listed for sale in an auction.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             */
            event BuyPriceCanceled(address indexed nftContract, uint256 indexed tokenId);
            /**
             * @notice Emitted when a buy price is invalidated due to other market activity.
             * @dev This occurs when the buy price is no longer eligible to be accepted,
             * e.g. when a bid is placed in an auction for this NFT.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             */
            event BuyPriceInvalidated(address indexed nftContract, uint256 indexed tokenId);
            /**
             * @notice Emitted when a buy price is set by the owner of an NFT.
             * @dev The NFT is transferred into the market contract for escrow unless it was already escrowed,
             * e.g. for auction listing.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @param seller The address of the NFT owner which set the buy price.
             * @param price The price of the NFT.
             */
            event BuyPriceSet(address indexed nftContract, uint256 indexed tokenId, address indexed seller, uint256 price);
            /**
             * @notice [DEPRECATED] use `buyV2` instead.
             * Buy the NFT at the set buy price.
             * `msg.value` must be <= `maxPrice` and any delta will be taken from the account's available FETH balance.
             * @dev `maxPrice` protects the buyer in case a the price is increased but allows the transaction to continue
             * when the price is reduced (and any surplus funds provided are refunded).
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @param maxPrice The maximum price to pay for the NFT.
             */
            function buy(address nftContract, uint256 tokenId, uint256 maxPrice) external payable {
              buyV2(nftContract, tokenId, maxPrice, payable(0));
            }
            /**
             * @notice Buy the NFT at the set buy price.
             * `msg.value` must be <= `maxPrice` and any delta will be taken from the account's available FETH balance.
             * @dev `maxPrice` protects the buyer in case a the price is increased but allows the transaction to continue
             * when the price is reduced (and any surplus funds provided are refunded).
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @param maxPrice The maximum price to pay for the NFT.
             * @param referrer The address of the referrer.
             */
            function buyV2(address nftContract, uint256 tokenId, uint256 maxPrice, address payable referrer) public payable {
              BuyPrice storage buyPrice = nftContractToTokenIdToBuyPrice[nftContract][tokenId];
              if (buyPrice.price > maxPrice) {
                revert NFTMarketBuyPrice_Cannot_Buy_At_Lower_Price(buyPrice.price);
              } else if (buyPrice.seller == address(0)) {
                revert NFTMarketBuyPrice_Cannot_Buy_Unset_Price();
              }
              _buy(nftContract, tokenId, referrer);
            }
            /**
             * @notice Removes the buy price set for an NFT.
             * @dev The NFT is transferred back to the owner unless it's still escrowed for another market tool,
             * e.g. listed for sale in an auction.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             */
            function cancelBuyPrice(address nftContract, uint256 tokenId) external nonReentrant {
              address seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller;
              address sender = _msgSender();
              if (seller == address(0)) {
                // This check is redundant with the next one, but done in order to provide a more clear error message.
                revert NFTMarketBuyPrice_Cannot_Cancel_Unset_Price();
              } else if (seller != sender) {
                revert NFTMarketBuyPrice_Only_Owner_Can_Cancel_Price(seller);
              }
              // Remove the buy price
              delete nftContractToTokenIdToBuyPrice[nftContract][tokenId];
              // Transfer the NFT back to the owner if it is not listed in auction.
              _transferFromEscrowIfAvailable(nftContract, tokenId, sender);
              emit BuyPriceCanceled(nftContract, tokenId);
            }
            /**
             * @notice Sets the buy price for an NFT and escrows it in the market contract.
             * A 0 price is acceptable and valid price you can set, enabling a giveaway to the first collector that calls `buy`.
             * @dev If there is an offer for this amount or higher, that will be accepted instead of setting a buy price.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @param price The price at which someone could buy this NFT.
             */
            function setBuyPrice(address nftContract, uint256 tokenId, uint256 price) external nonReentrant {
              // If there is a valid offer at this price or higher, accept that instead.
              if (_autoAcceptOffer(nftContract, tokenId, price)) {
                return;
              }
              if (price > type(uint96).max) {
                // This ensures that no data is lost when storing the price as `uint96`.
                revert NFTMarketBuyPrice_Price_Too_High();
              }
              BuyPrice storage buyPrice = nftContractToTokenIdToBuyPrice[nftContract][tokenId];
              address seller = buyPrice.seller;
              if (buyPrice.price == price && seller != address(0)) {
                revert NFTMarketBuyPrice_Price_Already_Set();
              }
              // Store the new price for this NFT.
              buyPrice.price = uint96(price);
              address payable sender = payable(_msgSender());
              if (seller == address(0)) {
                // Transfer the NFT into escrow, if it's already in escrow confirm the `msg.sender` is the owner.
                _transferToEscrow(nftContract, tokenId);
                // The price was not previously set for this NFT, store the seller.
                buyPrice.seller = sender;
              } else if (seller != sender) {
                // Buy price was previously set by a different user
                revert NFTMarketBuyPrice_Only_Owner_Can_Set_Price(seller);
              }
              emit BuyPriceSet(nftContract, tokenId, sender, price);
            }
            /**
             * @notice If there is a buy price at this price or lower, accept that and return true.
             */
            function _autoAcceptBuyPrice(
              address nftContract,
              uint256 tokenId,
              uint256 maxPrice
            ) internal override returns (bool) {
              BuyPrice storage buyPrice = nftContractToTokenIdToBuyPrice[nftContract][tokenId];
              if (buyPrice.seller == address(0) || buyPrice.price > maxPrice) {
                // No buy price was found, or the price is too high.
                return false;
              }
              _buy(nftContract, tokenId, payable(0));
              return true;
            }
            /**
             * @inheritdoc NFTMarketCore
             * @dev Invalidates the buy price on a auction start, if one is found.
             */
            function _beforeAuctionStarted(address nftContract, uint256 tokenId) internal virtual override {
              BuyPrice storage buyPrice = nftContractToTokenIdToBuyPrice[nftContract][tokenId];
              if (buyPrice.seller != address(0)) {
                // A buy price was set for this NFT, invalidate it.
                _invalidateBuyPrice(nftContract, tokenId);
              }
              super._beforeAuctionStarted(nftContract, tokenId);
            }
            /**
             * @notice Process the purchase of an NFT at the current buy price.
             * @dev The caller must confirm that the seller != address(0) before calling this function.
             */
            function _buy(address nftContract, uint256 tokenId, address payable referrer) private nonReentrant {
              BuyPrice memory buyPrice = nftContractToTokenIdToBuyPrice[nftContract][tokenId];
              // Remove the buy now price
              delete nftContractToTokenIdToBuyPrice[nftContract][tokenId];
              // Cancel the buyer's offer if there is one in order to free up their FETH balance
              // even if they don't need the FETH for this specific purchase.
              _cancelSendersOffer(nftContract, tokenId);
              _tryUseFETHBalance(buyPrice.price, true);
              address sender = _msgSender();
              (
                address payable sellerReferrerPaymentAddress,
                uint16 sellerReferrerTakeRateInBasisPoints
              ) = _getExhibitionForPayment(nftContract, tokenId);
              // Transfer the NFT to the buyer.
              // The seller was already authorized when the buyPrice was set originally set.
              _transferFromEscrow(nftContract, tokenId, sender, address(0));
              // Distribute revenue for this sale.
              (uint256 totalFees, uint256 creatorRev, uint256 sellerRev) = _distributeFunds(
                nftContract,
                tokenId,
                buyPrice.seller,
                buyPrice.price,
                referrer,
                sellerReferrerPaymentAddress,
                sellerReferrerTakeRateInBasisPoints
              );
              emit BuyPriceAccepted(nftContract, tokenId, buyPrice.seller, sender, totalFees, creatorRev, sellerRev);
            }
            /**
             * @notice Clear a buy price and emit BuyPriceInvalidated.
             * @dev The caller must confirm the buy price is set before calling this function.
             */
            function _invalidateBuyPrice(address nftContract, uint256 tokenId) private {
              delete nftContractToTokenIdToBuyPrice[nftContract][tokenId];
              emit BuyPriceInvalidated(nftContract, tokenId);
            }
            /**
             * @inheritdoc NFTMarketCore
             * @dev Invalidates the buy price if one is found before transferring the NFT.
             * This will revert if there is a buy price set but the `authorizeSeller` is not the owner.
             */
            function _transferFromEscrow(
              address nftContract,
              uint256 tokenId,
              address recipient,
              address authorizeSeller
            ) internal virtual override {
              address seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller;
              if (seller != address(0)) {
                // A buy price was set for this NFT.
                // `authorizeSeller != address(0) &&` could be added when other mixins use this flow.
                // ATM that additional check would never return false.
                if (seller != authorizeSeller) {
                  // When there is a buy price set, the `buyPrice.seller` is the owner of the NFT.
                  revert NFTMarketBuyPrice_Seller_Mismatch(seller);
                }
                // The seller authorization has been confirmed.
                authorizeSeller = address(0);
                // Invalidate the buy price as the NFT will no longer be in escrow.
                _invalidateBuyPrice(nftContract, tokenId);
              }
              super._transferFromEscrow(nftContract, tokenId, recipient, authorizeSeller);
            }
            /**
             * @inheritdoc NFTMarketCore
             * @dev Checks if there is a buy price set, if not then allow the transfer to proceed.
             */
            function _transferFromEscrowIfAvailable(
              address nftContract,
              uint256 tokenId,
              address recipient
            ) internal virtual override {
              address seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller;
              if (seller == address(0)) {
                // A buy price has been set for this NFT so it should remain in escrow.
                super._transferFromEscrowIfAvailable(nftContract, tokenId, recipient);
              }
            }
            /**
             * @inheritdoc NFTMarketCore
             * @dev Checks if the NFT is already in escrow for buy now.
             */
            function _transferToEscrow(address nftContract, uint256 tokenId) internal virtual override {
              address seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller;
              if (seller == address(0)) {
                // The NFT is not in escrow for buy now.
                super._transferToEscrow(nftContract, tokenId);
              } else if (seller != _msgSender()) {
                // When there is a buy price set, the `seller` is the owner of the NFT.
                revert NFTMarketBuyPrice_Seller_Mismatch(seller);
              }
            }
            /**
             * @notice Returns the buy price details for an NFT if one is available.
             * @dev If no price is found, seller will be address(0) and price will be max uint256.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @return seller The address of the owner that listed a buy price for this NFT.
             * Returns `address(0)` if there is no buy price set for this NFT.
             * @return price The price of the NFT.
             * Returns max uint256 if there is no buy price set for this NFT (since a price of 0 is supported).
             */
            function getBuyPrice(address nftContract, uint256 tokenId) external view returns (address seller, uint256 price) {
              seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller;
              if (seller == address(0)) {
                return (seller, type(uint256).max);
              }
              price = nftContractToTokenIdToBuyPrice[nftContract][tokenId].price;
            }
            /**
             * @inheritdoc MarketSharedCore
             * @dev Returns the seller if there is a buy price set for this NFT, otherwise
             * bubbles the call up for other considerations.
             */
            function _getSellerOf(
              address nftContract,
              uint256 tokenId
            ) internal view virtual override(MarketSharedCore, NFTMarketCore) returns (address payable seller) {
              seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller;
              if (seller == address(0)) {
                seller = super._getSellerOf(nftContract, tokenId);
              }
            }
            /**
             * @notice This empty reserved space is put in place to allow future versions to add new
             * variables without shifting down storage in the inheritance chain.
             * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
             */
            uint256[1_000] private __gap;
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
          import "@openzeppelin/contracts/utils/Context.sol";
          import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
          import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
          import "../../interfaces/internal/IFethMarket.sol";
          import "../shared/Constants.sol";
          import "../shared/MarketSharedCore.sol";
          error NFTMarketCore_Seller_Not_Found();
          /**
           * @title A place for common modifiers and functions used by various NFTMarket mixins, if any.
           * @dev This also leaves a gap which can be used to add a new mixin to the top of the inheritance tree.
           * @author batu-inal & HardlyDifficult
           */
          abstract contract NFTMarketCore is Initializable, Context, MarketSharedCore {
            using AddressUpgradeable for address;
            using AddressUpgradeable for address payable;
            /**
             * @notice If there is a buy price at this amount or lower, accept that and return true.
             */
            function _autoAcceptBuyPrice(address nftContract, uint256 tokenId, uint256 amount) internal virtual returns (bool);
            /**
             * @notice If there is a valid offer at the given price or higher, accept that and return true.
             */
            function _autoAcceptOffer(address nftContract, uint256 tokenId, uint256 minAmount) internal virtual returns (bool);
            /**
             * @notice Notify implementors when an auction has received its first bid.
             * Once a bid is received the sale is guaranteed to the auction winner
             * and other sale mechanisms become unavailable.
             * @dev Implementors of this interface should update internal state to reflect an auction has been kicked off.
             */
            function _beforeAuctionStarted(address /*nftContract*/, uint256 /*tokenId*/) internal virtual {
              // No-op
            }
            /**
             * @notice Cancel the `msg.sender`'s offer if there is one, freeing up their FETH balance.
             * @dev This should be used when it does not make sense to keep the original offer around,
             * e.g. if a collector accepts a Buy Price then keeping the offer around is not necessary.
             */
            function _cancelSendersOffer(address nftContract, uint256 tokenId) internal virtual;
            /**
             * @notice Transfers the NFT from escrow and clears any state tracking this escrowed NFT.
             * @param authorizeSeller The address of the seller pending authorization.
             * Once it's been authorized by one of the escrow managers, it should be set to address(0)
             * indicated that it's no longer pending authorization.
             */
            function _transferFromEscrow(
              address nftContract,
              uint256 tokenId,
              address recipient,
              address authorizeSeller
            ) internal virtual {
              if (authorizeSeller != address(0)) {
                revert NFTMarketCore_Seller_Not_Found();
              }
              IERC721(nftContract).transferFrom(address(this), recipient, tokenId);
            }
            /**
             * @notice Transfers the NFT from escrow unless there is another reason for it to remain in escrow.
             */
            function _transferFromEscrowIfAvailable(address nftContract, uint256 tokenId, address recipient) internal virtual {
              _transferFromEscrow(nftContract, tokenId, recipient, address(0));
            }
            /**
             * @notice Transfers an NFT into escrow,
             * if already there this requires the msg.sender is authorized to manage the sale of this NFT.
             */
            function _transferToEscrow(address nftContract, uint256 tokenId) internal virtual {
              IERC721(nftContract).transferFrom(_msgSender(), address(this), tokenId);
            }
            /**
             * @dev Determines the minimum amount when increasing an existing offer or bid.
             */
            function _getMinIncrement(uint256 currentAmount) internal pure returns (uint256) {
              uint256 minIncrement = currentAmount;
              unchecked {
                minIncrement /= MIN_PERCENT_INCREMENT_DENOMINATOR;
              }
              if (minIncrement == 0) {
                // Since minIncrement reduces from the currentAmount, this cannot overflow.
                // The next amount must be at least 1 wei greater than the current.
                return currentAmount + 1;
              }
              return minIncrement + currentAmount;
            }
            /**
             * @inheritdoc MarketSharedCore
             */
            function _getSellerOf(
              address nftContract,
              uint256 tokenId
            ) internal view virtual override returns (address payable seller) {
              // No-op by default
            }
            /**
             * @inheritdoc MarketSharedCore
             */
            function _getSellerOrOwnerOf(
              address nftContract,
              uint256 tokenId
            ) internal view override returns (address payable sellerOrOwner) {
              sellerOrOwner = _getSellerOf(nftContract, tokenId);
              if (sellerOrOwner == address(0)) {
                sellerOrOwner = payable(IERC721(nftContract).ownerOf(tokenId));
              }
            }
            /**
             * @notice Checks if an escrowed NFT is currently in active auction.
             * @return Returns false if the auction has ended, even if it has not yet been settled.
             */
            function _isInActiveAuction(address nftContract, uint256 tokenId) internal view virtual returns (bool);
            /**
             * @notice This empty reserved space is put in place to allow future versions to add new
             * variables without shifting down storage in the inheritance chain.
             * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
             * @dev 50 slots were consumed by adding `ReentrancyGuard`.
             */
            uint256[450] private __gap;
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts/utils/Context.sol";
          import "../../interfaces/internal/INFTMarketExhibition.sol";
          import "../shared/Constants.sol";
          /// @param curator The curator for this exhibition.
          error NFTMarketExhibition_Caller_Is_Not_Curator(address curator);
          error NFTMarketExhibition_Can_Not_Add_Dupe_Seller();
          error NFTMarketExhibition_Curator_Automatically_Allowed();
          error NFTMarketExhibition_Exhibition_Does_Not_Exist();
          error NFTMarketExhibition_Seller_Not_Allowed_In_Exhibition();
          error NFTMarketExhibition_Sellers_Required();
          error NFTMarketExhibition_Take_Rate_Too_High();
          /**
           * @title Enables a curation surface for sellers to exhibit their NFTs.
           * @author HardlyDifficult
           */
          abstract contract NFTMarketExhibition is INFTMarketExhibition, Context {
            /**
             * @notice Stores details about an exhibition.
             */
            struct Exhibition {
              /// @notice The curator which created this exhibition.
              address payable curator;
              /// @notice The rate of the sale which goes to the curator.
              uint16 takeRateInBasisPoints;
              // 80-bits available in the first slot
              /// @notice A name for the exhibition.
              string name;
            }
            /// @notice Tracks the next sequence ID to be assigned to an exhibition.
            uint256 private latestExhibitionId;
            /// @notice Maps the exhibition ID to their details.
            mapping(uint256 => Exhibition) private idToExhibition;
            /// @notice Maps an exhibition to the list of sellers allowed to list with it.
            mapping(uint256 => mapping(address => bool)) private exhibitionIdToSellerToIsAllowed;
            /// @notice Maps an NFT to the exhibition it was listed with.
            mapping(address => mapping(uint256 => uint256)) private nftContractToTokenIdToExhibitionId;
            /**
             * @notice Emitted when an exhibition is created.
             * @param exhibitionId The ID for this exhibition.
             * @param curator The curator which created this exhibition.
             * @param name The name for this exhibition.
             * @param takeRateInBasisPoints The rate of the sale which goes to the curator.
             */
            event ExhibitionCreated(
              uint256 indexed exhibitionId,
              address indexed curator,
              string name,
              uint16 takeRateInBasisPoints
            );
            /**
             * @notice Emitted when an exhibition is deleted.
             * @param exhibitionId The ID for the exhibition.
             */
            event ExhibitionDeleted(uint256 indexed exhibitionId);
            /**
             * @notice Emitted when an NFT is listed in an exhibition.
             * @param nftContract The contract address of the NFT.
             * @param tokenId The ID of the NFT.
             * @param exhibitionId The ID of the exhibition it was listed with.
             */
            event NftAddedToExhibition(address indexed nftContract, uint256 indexed tokenId, uint256 indexed exhibitionId);
            /**
             * @notice Emitted when an NFT is no longer associated with an exhibition for reasons other than a sale.
             * @param nftContract The contract address of the NFT.
             * @param tokenId The ID of the NFT.
             * @param exhibitionId The ID of the exhibition it was originally listed with.
             */
            event NftRemovedFromExhibition(address indexed nftContract, uint256 indexed tokenId, uint256 indexed exhibitionId);
            /**
             * @notice Emitted when sellers are granted access to list with an exhibition.
             * @param exhibitionId The ID of the exhibition.
             * @param sellers The list of sellers granted access.
             */
            event SellersAddedToExhibition(uint256 indexed exhibitionId, address[] sellers);
            /// @notice Requires the caller to be the curator of the exhibition.
            modifier onlyExhibitionCurator(uint256 exhibitionId) {
              address curator = idToExhibition[exhibitionId].curator;
              if (curator != _msgSender()) {
                if (curator == address(0)) {
                  // If the curator is not a match, check if the exhibition exists in order to provide a better error message.
                  revert NFTMarketExhibition_Exhibition_Does_Not_Exist();
                }
                revert NFTMarketExhibition_Caller_Is_Not_Curator(curator);
              }
              _;
            }
            /// @notice Requires the caller pass in some number of sellers
            modifier sellersRequired(address[] calldata sellers) {
              if (sellers.length == 0) {
                revert NFTMarketExhibition_Sellers_Required();
              }
              _;
            }
            /**
             * @notice Adds sellers to exhibition.
             * @param exhibitionId The exhibition ID.
             * @param sellers The new list of sellers to be allowed to list with this exhibition.
             */
            function addSellersToExhibition(
              uint256 exhibitionId,
              address[] calldata sellers
            ) external onlyExhibitionCurator(exhibitionId) sellersRequired(sellers) {
              _addSellersToExhibition(exhibitionId, sellers);
            }
            /**
             * @notice Creates an exhibition.
             * @param name The name for this exhibition.
             * @param takeRateInBasisPoints The rate of the sale which goes to the msg.sender as the curator of this exhibition.
             * @param sellers The list of sellers allowed to list with this exhibition.
             * @dev The list of sellers may be modified after the exhibition is created via addSellersToExhibition,
             *      which only allows for adding (not removing) new sellers.
             */
            function createExhibition(
              string calldata name,
              uint16 takeRateInBasisPoints,
              address[] calldata sellers
            ) external sellersRequired(sellers) returns (uint256 exhibitionId) {
              if (takeRateInBasisPoints > MAX_EXHIBITION_TAKE_RATE) {
                revert NFTMarketExhibition_Take_Rate_Too_High();
              }
              // Create exhibition
              unchecked {
                exhibitionId = ++latestExhibitionId;
              }
              address payable sender = payable(_msgSender());
              idToExhibition[exhibitionId] = Exhibition({
                curator: sender,
                takeRateInBasisPoints: takeRateInBasisPoints,
                name: name
              });
              emit ExhibitionCreated({
                exhibitionId: exhibitionId,
                curator: sender,
                name: name,
                takeRateInBasisPoints: takeRateInBasisPoints
              });
              _addSellersToExhibition(exhibitionId, sellers);
            }
            /**
             * @notice Deletes an exhibition created by the msg.sender.
             * @param exhibitionId The ID of the exhibition to delete.
             * @dev Once deleted, any NFTs listed with this exhibition will still be listed but will no longer be associated with
             * or share revenue with the exhibition.
             */
            function deleteExhibition(uint256 exhibitionId) external onlyExhibitionCurator(exhibitionId) {
              delete idToExhibition[exhibitionId];
              emit ExhibitionDeleted(exhibitionId);
            }
            function _addSellersToExhibition(uint256 exhibitionId, address[] calldata sellers) private {
              // Populate allow list
              for (uint256 i = 0; i < sellers.length; ) {
                address seller = sellers[i];
                if (exhibitionIdToSellerToIsAllowed[exhibitionId][seller]) {
                  revert NFTMarketExhibition_Can_Not_Add_Dupe_Seller();
                }
                if (seller == _msgSender()) {
                  revert NFTMarketExhibition_Curator_Automatically_Allowed();
                }
                exhibitionIdToSellerToIsAllowed[exhibitionId][seller] = true;
                unchecked {
                  ++i;
                }
              }
              emit SellersAddedToExhibition(exhibitionId, sellers);
            }
            /**
             * @notice Assigns an NFT to an exhibition.
             * @param nftContract The contract address of the NFT.
             * @param tokenId The ID of the NFT.
             * @param exhibitionId The ID of the exhibition to list the NFT with.
             * @dev This call is a no-op if the `exhibitionId` is 0.
             */
            function _addNftToExhibition(address nftContract, uint256 tokenId, uint256 exhibitionId) internal {
              if (exhibitionId != 0) {
                Exhibition storage exhibition = idToExhibition[exhibitionId];
                if (exhibition.curator == address(0)) {
                  revert NFTMarketExhibition_Exhibition_Does_Not_Exist();
                }
                address sender = _msgSender();
                if (!exhibitionIdToSellerToIsAllowed[exhibitionId][sender] && exhibition.curator != sender) {
                  revert NFTMarketExhibition_Seller_Not_Allowed_In_Exhibition();
                }
                nftContractToTokenIdToExhibitionId[nftContract][tokenId] = exhibitionId;
                emit NftAddedToExhibition(nftContract, tokenId, exhibitionId);
              }
            }
            /**
             * @notice Returns exhibition details if this NFT was assigned to one, and clears the assignment.
             * @return paymentAddress The address to send the payment to, or address(0) if n/a.
             * @return takeRateInBasisPoints The rate of the sale which goes to the curator, or 0 if n/a.
             * @dev This does not emit NftRemovedFromExhibition, instead it's expected that SellerReferralPaid will be emitted.
             */
            function _getExhibitionForPayment(
              address nftContract,
              uint256 tokenId
            ) internal returns (address payable paymentAddress, uint16 takeRateInBasisPoints) {
              uint256 exhibitionId = nftContractToTokenIdToExhibitionId[nftContract][tokenId];
              if (exhibitionId != 0) {
                paymentAddress = idToExhibition[exhibitionId].curator;
                takeRateInBasisPoints = idToExhibition[exhibitionId].takeRateInBasisPoints;
                delete nftContractToTokenIdToExhibitionId[nftContract][tokenId];
              }
            }
            /**
             * @notice Clears an NFT's association with an exhibition.
             */
            function _removeNftFromExhibition(address nftContract, uint256 tokenId) internal {
              uint256 exhibitionId = nftContractToTokenIdToExhibitionId[nftContract][tokenId];
              if (exhibitionId != 0) {
                delete nftContractToTokenIdToExhibitionId[nftContract][tokenId];
                emit NftRemovedFromExhibition(nftContract, tokenId, exhibitionId);
              }
            }
            /**
             * @notice Returns exhibition details for a given ID.
             * @param exhibitionId The ID of the exhibition to look up.
             * @return name The name of the exhibition.
             * @return curator The curator of the exhibition.
             * @return takeRateInBasisPoints The rate of the sale which goes to the curator.
             * @dev If the exhibition does not exist or has since been deleted, the curator will be address(0).
             */
            function getExhibition(
              uint256 exhibitionId
            ) external view returns (string memory name, address payable curator, uint16 takeRateInBasisPoints) {
              Exhibition memory exhibition = idToExhibition[exhibitionId];
              name = exhibition.name;
              curator = exhibition.curator;
              takeRateInBasisPoints = exhibition.takeRateInBasisPoints;
            }
            /**
             * @notice Returns the exhibition ID for a given NFT.
             * @param nftContract The contract address of the NFT.
             * @param tokenId The ID of the NFT.
             * @return exhibitionId The ID of the exhibition this NFT is assigned to, or 0 if it's not assigned to an exhibition.
             */
            function getExhibitionIdForNft(address nftContract, uint256 tokenId) external view returns (uint256 exhibitionId) {
              exhibitionId = nftContractToTokenIdToExhibitionId[nftContract][tokenId];
            }
            /**
             * @notice Returns exhibition payment details for a given ID.
             * @param exhibitionId The ID of the exhibition to look up.
             * @return curator The curator of the exhibition.
             * @return takeRateInBasisPoints The rate of the sale which goes to the curator.
             * @dev If the exhibition does not exist or has since been deleted, the curator will be address(0).
             */
            function getExhibitionPaymentDetails(
              uint256 exhibitionId
            ) external view returns (address payable curator, uint16 takeRateInBasisPoints) {
              Exhibition storage exhibition = idToExhibition[exhibitionId];
              curator = exhibition.curator;
              takeRateInBasisPoints = exhibition.takeRateInBasisPoints;
            }
            /**
             * @notice Checks if a given seller is approved to list with a given exhibition.
             * @param exhibitionId The ID of the exhibition to check.
             * @param seller The address of the seller to check.
             * @return allowedSeller True if the seller is approved to list with the exhibition.
             */
            function isAllowedSellerForExhibition(
              uint256 exhibitionId,
              address seller
            ) external view returns (bool allowedSeller) {
              address curator = idToExhibition[exhibitionId].curator;
              if (curator != address(0)) {
                allowedSeller = exhibitionIdToSellerToIsAllowed[exhibitionId][seller] || seller == curator;
              }
            }
            /**
             * @notice This empty reserved space is put in place to allow future versions to add new
             * variables without shifting down storage in the inheritance chain.
             * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
             * @dev This file uses a total of 500 slots.
             */
            uint256[496] private __gap;
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts/utils/Context.sol";
          import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
          import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
          import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
          import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
          import "../../libraries/TimeLibrary.sol";
          import "../shared/MarketFees.sol";
          import "../shared/FoundationTreasuryNode.sol";
          import "../shared/FETHNode.sol";
          import "../shared/SendValueWithFallbackWithdraw.sol";
          import "./NFTMarketCore.sol";
          import "./NFTMarketExhibition.sol";
          error NFTMarketOffer_Cannot_Be_Made_While_In_Auction();
          /// @param currentOfferAmount The current highest offer available for this NFT.
          error NFTMarketOffer_Offer_Below_Min_Amount(uint256 currentOfferAmount);
          /// @param expiry The time at which the offer had expired.
          error NFTMarketOffer_Offer_Expired(uint256 expiry);
          /// @param currentOfferFrom The address of the collector which has made the current highest offer.
          error NFTMarketOffer_Offer_From_Does_Not_Match(address currentOfferFrom);
          /// @param minOfferAmount The minimum amount that must be offered in order for it to be accepted.
          error NFTMarketOffer_Offer_Must_Be_At_Least_Min_Amount(uint256 minOfferAmount);
          /**
           * @title Allows collectors to make an offer for an NFT, valid for 24-25 hours.
           * @notice Funds are escrowed in the FETH ERC-20 token contract.
           * @author batu-inal & HardlyDifficult
           */
          abstract contract NFTMarketOffer is
            Initializable,
            FoundationTreasuryNode,
            Context,
            FETHNode,
            NFTMarketCore,
            ReentrancyGuardUpgradeable,
            SendValueWithFallbackWithdraw,
            MarketFees,
            NFTMarketExhibition
          {
            using AddressUpgradeable for address;
            using TimeLibrary for uint32;
            /// @notice Stores offer details for a specific NFT.
            struct Offer {
              // Slot 1: When increasing an offer, only this slot is updated.
              /// @notice The expiration timestamp of when this offer expires.
              uint32 expiration;
              /// @notice The amount, in wei, of the highest offer.
              uint96 amount;
              /// @notice First slot (of 16B) used for the offerReferrerAddress.
              // The offerReferrerAddress is the address used to pay the
              // referrer on an accepted offer.
              uint128 offerReferrerAddressSlot0;
              // Slot 2: When the buyer changes, both slots need updating
              /// @notice The address of the collector who made this offer.
              address buyer;
              /// @notice Second slot (of 4B) used for the offerReferrerAddress.
              uint32 offerReferrerAddressSlot1;
              // 96 bits (12B) are available in slot 1.
            }
            /// @notice Stores the highest offer for each NFT.
            mapping(address => mapping(uint256 => Offer)) private nftContractToIdToOffer;
            /**
             * @notice Emitted when an offer is accepted,
             * indicating that the NFT has been transferred and revenue from the sale distributed.
             * @dev The accepted total offer amount is `totalFees` + `creatorRev` + `sellerRev`.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @param buyer The address of the collector that made the offer which was accepted.
             * @param seller The address of the seller which accepted the offer.
             * @param totalFees The amount of ETH that was sent to Foundation & referrals for this sale.
             * @param creatorRev The amount of ETH that was sent to the creator for this sale.
             * @param sellerRev The amount of ETH that was sent to the owner for this sale.
             */
            event OfferAccepted(
              address indexed nftContract,
              uint256 indexed tokenId,
              address indexed buyer,
              address seller,
              uint256 totalFees,
              uint256 creatorRev,
              uint256 sellerRev
            );
            /**
             * @notice Emitted when an offer is invalidated due to other market activity.
             * When this occurs, the collector which made the offer has their FETH balance unlocked
             * and the funds are available to place other offers or to be withdrawn.
             * @dev This occurs when the offer is no longer eligible to be accepted,
             * e.g. when a bid is placed in an auction for this NFT.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             */
            event OfferInvalidated(address indexed nftContract, uint256 indexed tokenId);
            /**
             * @notice Emitted when an offer is made.
             * @dev The `amount` of the offer is locked in the FETH ERC-20 contract, guaranteeing that the funds
             * remain available until the `expiration` date.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @param buyer The address of the collector that made the offer to buy this NFT.
             * @param amount The amount, in wei, of the offer.
             * @param expiration The expiration timestamp for the offer.
             */
            event OfferMade(
              address indexed nftContract,
              uint256 indexed tokenId,
              address indexed buyer,
              uint256 amount,
              uint256 expiration
            );
            /**
             * @notice Accept the highest offer for an NFT.
             * @dev The offer must not be expired and the NFT owned + approved by the seller or
             * available in the market contract's escrow.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @param offerFrom The address of the collector that you wish to sell to.
             * If the current highest offer is not from this user, the transaction will revert.
             * This could happen if a last minute offer was made by another collector,
             * and would require the seller to try accepting again.
             * @param minAmount The minimum value of the highest offer for it to be accepted.
             * If the value is less than this amount, the transaction will revert.
             * This could happen if the original offer expires and is replaced with a smaller offer.
             */
            function acceptOffer(
              address nftContract,
              uint256 tokenId,
              address offerFrom,
              uint256 minAmount
            ) external nonReentrant {
              Offer storage offer = nftContractToIdToOffer[nftContract][tokenId];
              // Validate offer expiry and amount
              if (offer.expiration.hasExpired()) {
                revert NFTMarketOffer_Offer_Expired(offer.expiration);
              } else if (offer.amount < minAmount) {
                revert NFTMarketOffer_Offer_Below_Min_Amount(offer.amount);
              }
              // Validate the buyer
              if (offer.buyer != offerFrom) {
                revert NFTMarketOffer_Offer_From_Does_Not_Match(offer.buyer);
              }
              _acceptOffer(nftContract, tokenId);
            }
            /**
             * @notice Make an offer for any NFT which is valid for 24-25 hours.
             * The funds will be locked in the FETH token contract and become available once the offer is outbid or has expired.
             * @dev An offer may be made for an NFT before it is minted, although we generally not recommend you do that.
             * If there is a buy price set at this price or lower, that will be accepted instead of making an offer.
             * `msg.value` must be <= `amount` and any delta will be taken from the account's available FETH balance.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @param amount The amount to offer for this NFT.
             * @param referrer The referrer address for the offer.
             * @return expiration The timestamp for when this offer will expire.
             * This is provided as a return value in case another contract would like to leverage this information,
             * user's should refer to the expiration in the `OfferMade` event log.
             * If the buy price is accepted instead, `0` is returned as the expiration since that's n/a.
             */
            function makeOfferV2(
              address nftContract,
              uint256 tokenId,
              uint256 amount,
              address payable referrer
            ) external payable returns (uint256 expiration) {
              // If there is a buy price set at this price or lower, accept that instead.
              if (_autoAcceptBuyPrice(nftContract, tokenId, amount)) {
                // If the buy price is accepted, `0` is returned as the expiration since that's n/a.
                return 0;
              }
              if (_isInActiveAuction(nftContract, tokenId)) {
                revert NFTMarketOffer_Cannot_Be_Made_While_In_Auction();
              }
              Offer storage offer = nftContractToIdToOffer[nftContract][tokenId];
              address sender = _msgSender();
              if (offer.expiration.hasExpired()) {
                // This is a new offer for the NFT (no other offer found or the previous offer expired)
                // Lock the offer amount in FETH until the offer expires in 24-25 hours.
                expiration = feth.marketLockupFor{ value: msg.value }(sender, amount);
              } else {
                // A previous offer exists and has not expired
                uint256 minIncrement = _getMinIncrement(offer.amount);
                if (amount < minIncrement) {
                  // A non-trivial increase in price is required to avoid sniping
                  revert NFTMarketOffer_Offer_Must_Be_At_Least_Min_Amount(minIncrement);
                }
                // Unlock the previous offer so that the FETH tokens are available for other offers or to transfer / withdraw
                // and lock the new offer amount in FETH until the offer expires in 24-25 hours.
                expiration = feth.marketChangeLockup{ value: msg.value }(
                  offer.buyer,
                  offer.expiration,
                  offer.amount,
                  sender,
                  amount
                );
              }
              // Record offer details
              offer.buyer = sender;
              // The FETH contract guarantees that the expiration fits into 32 bits.
              offer.expiration = uint32(expiration);
              // `amount` is capped by the ETH provided, which cannot realistically overflow 96 bits.
              offer.amount = uint96(amount);
              // Set offerReferrerAddressSlot0 to the first 16B of the referrer address.
              // By shifting the referrer 32 bits to the right we obtain the first 16B.
              offer.offerReferrerAddressSlot0 = uint128(uint160(address(referrer)) >> 32);
              // Set offerReferrerAddressSlot1 to the last 4B of the referrer address.
              // By casting the referrer address to 32bits we discard the first 16B.
              offer.offerReferrerAddressSlot1 = uint32(uint160(address(referrer)));
              emit OfferMade(nftContract, tokenId, sender, amount, expiration);
            }
            /**
             * @notice Accept the highest offer for an NFT from the `msg.sender` account.
             * The NFT will be transferred to the buyer and revenue from the sale will be distributed.
             * @dev The caller must validate the expiry and amount before calling this helper.
             * This may invalidate other market tools, such as clearing the buy price if set.
             */
            function _acceptOffer(address nftContract, uint256 tokenId) private {
              Offer memory offer = nftContractToIdToOffer[nftContract][tokenId];
              // Remove offer
              delete nftContractToIdToOffer[nftContract][tokenId];
              // Withdraw ETH from the buyer's account in the FETH token contract.
              feth.marketWithdrawLocked(offer.buyer, offer.expiration, offer.amount);
              address payable sender = payable(_msgSender());
              (
                address payable sellerReferrerPaymentAddress,
                uint16 sellerReferrerTakeRateInBasisPoints
              ) = _getExhibitionForPayment(nftContract, tokenId);
              // Transfer the NFT to the buyer.
              try IERC721(nftContract).transferFrom(sender, offer.buyer, tokenId) {
                // NFT was in the seller's wallet so the transfer is complete.
              } catch {
                // If the transfer fails then attempt to transfer from escrow instead.
                // This should revert if `msg.sender` is not the owner of this NFT.
                _transferFromEscrow(nftContract, tokenId, offer.buyer, sender);
              }
              // Distribute revenue for this sale leveraging the ETH received from the FETH contract in the line above.
              (uint256 totalFees, uint256 creatorRev, uint256 sellerRev) = _distributeFunds(
                nftContract,
                tokenId,
                sender,
                offer.amount,
                _getOfferReferrerFromSlots(offer.offerReferrerAddressSlot0, offer.offerReferrerAddressSlot1),
                sellerReferrerPaymentAddress,
                sellerReferrerTakeRateInBasisPoints
              );
              emit OfferAccepted(nftContract, tokenId, offer.buyer, sender, totalFees, creatorRev, sellerRev);
            }
            /**
             * @inheritdoc NFTMarketCore
             * @dev Invalidates the highest offer when an auction is kicked off, if one is found.
             */
            function _beforeAuctionStarted(address nftContract, uint256 tokenId) internal virtual override {
              _invalidateOffer(nftContract, tokenId);
              super._beforeAuctionStarted(nftContract, tokenId);
            }
            /**
             * @inheritdoc NFTMarketCore
             */
            function _autoAcceptOffer(address nftContract, uint256 tokenId, uint256 minAmount) internal override returns (bool) {
              Offer storage offer = nftContractToIdToOffer[nftContract][tokenId];
              if (offer.expiration.hasExpired() || offer.amount < minAmount) {
                // No offer found, the most recent offer is now expired, or the highest offer is below the minimum amount.
                return false;
              }
              _acceptOffer(nftContract, tokenId);
              return true;
            }
            /**
             * @inheritdoc NFTMarketCore
             */
            function _cancelSendersOffer(address nftContract, uint256 tokenId) internal override {
              Offer storage offer = nftContractToIdToOffer[nftContract][tokenId];
              if (offer.buyer == _msgSender()) {
                _invalidateOffer(nftContract, tokenId);
              }
            }
            /**
             * @notice Invalidates the offer and frees ETH from escrow, if the offer has not already expired.
             * @dev Offers are not invalidated when the NFT is purchased by accepting the buy price unless it
             * was purchased by the same user.
             * The user which just purchased the NFT may have buyer's remorse and promptly decide they want a fast exit,
             * accepting a small loss to limit their exposure.
             */
            function _invalidateOffer(address nftContract, uint256 tokenId) private {
              if (!nftContractToIdToOffer[nftContract][tokenId].expiration.hasExpired()) {
                // An offer was found and it has not already expired
                Offer memory offer = nftContractToIdToOffer[nftContract][tokenId];
                // Remove offer
                delete nftContractToIdToOffer[nftContract][tokenId];
                // Unlock the offer so that the FETH tokens are available for other offers or to transfer / withdraw
                feth.marketUnlockFor(offer.buyer, offer.expiration, offer.amount);
                emit OfferInvalidated(nftContract, tokenId);
              }
            }
            /**
             * @notice Returns the minimum amount a collector must offer for this NFT in order for the offer to be valid.
             * @dev Offers for this NFT which are less than this value will revert.
             * Once the previous offer has expired smaller offers can be made.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @return minimum The minimum amount that must be offered for this NFT.
             */
            function getMinOfferAmount(address nftContract, uint256 tokenId) external view returns (uint256 minimum) {
              Offer storage offer = nftContractToIdToOffer[nftContract][tokenId];
              if (!offer.expiration.hasExpired()) {
                return _getMinIncrement(offer.amount);
              }
              // Absolute min is anything > 0
              return 1;
            }
            /**
             * @notice Returns details about the current highest offer for an NFT.
             * @dev Default values are returned if there is no offer or the offer has expired.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @return buyer The address of the buyer that made the current highest offer.
             * Returns `address(0)` if there is no offer or the most recent offer has expired.
             * @return expiration The timestamp that the current highest offer expires.
             * Returns `0` if there is no offer or the most recent offer has expired.
             * @return amount The amount being offered for this NFT.
             * Returns `0` if there is no offer or the most recent offer has expired.
             */
            function getOffer(
              address nftContract,
              uint256 tokenId
            ) external view returns (address buyer, uint256 expiration, uint256 amount) {
              Offer storage offer = nftContractToIdToOffer[nftContract][tokenId];
              if (offer.expiration.hasExpired()) {
                // Offer not found or has expired
                return (address(0), 0, 0);
              }
              // An offer was found and it has not yet expired.
              return (offer.buyer, offer.expiration, offer.amount);
            }
            /**
             * @notice Returns the current highest offer's referral for an NFT.
             * @dev Default value of `payable(0)` is returned if
             * there is no offer, the offer has expired or does not have a referral.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @return referrer The payable address of the referrer for the offer.
             */
            function getOfferReferrer(address nftContract, uint256 tokenId) external view returns (address payable referrer) {
              Offer storage offer = nftContractToIdToOffer[nftContract][tokenId];
              if (offer.expiration.hasExpired()) {
                // Offer not found or has expired
                return payable(0);
              }
              return _getOfferReferrerFromSlots(offer.offerReferrerAddressSlot0, offer.offerReferrerAddressSlot1);
            }
            function _getOfferReferrerFromSlots(
              uint128 offerReferrerAddressSlot0,
              uint32 offerReferrerAddressSlot1
            ) private pure returns (address payable referrer) {
              // Stitch offerReferrerAddressSlot0 and offerReferrerAddressSlot1 to obtain the payable offerReferrerAddress.
              // Left shift offerReferrerAddressSlot0 by 32 bits OR it with offerReferrerAddressSlot1.
              referrer = payable(address((uint160(offerReferrerAddressSlot0) << 32) | uint160(offerReferrerAddressSlot1)));
            }
            /**
             * @notice This empty reserved space is put in place to allow future versions to add new
             * variables without shifting down storage in the inheritance chain.
             * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
             */
            uint256[1_000] private __gap;
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /**
           * @title Reserves space previously occupied by private sales.
           * @author batu-inal & HardlyDifficult
           */
          abstract contract NFTMarketPrivateSaleGap {
            // Original data:
            // bytes32 private __gap_was_DOMAIN_SEPARATOR;
            // mapping(address => mapping(uint256 => mapping(address => mapping(address => mapping(uint256 =>
            //   mapping(uint256 => bool)))))) private privateSaleInvalidated;
            // uint256[999] private __gap;
            /**
             * @notice This empty reserved space is put in place to allow future versions to add new
             * variables without shifting down storage in the inheritance chain.
             * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
             * @dev 1 slot was consumed by privateSaleInvalidated.
             */
            uint256[1001] private __gap;
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts/utils/Context.sol";
          import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
          import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
          import "../../interfaces/internal/routes/INFTMarketReserveAuction.sol";
          import "../../libraries/TimeLibrary.sol";
          import "../shared/FoundationTreasuryNode.sol";
          import "../shared/FETHNode.sol";
          import "../shared/MarketFees.sol";
          import "../shared/MarketSharedCore.sol";
          import "../shared/SendValueWithFallbackWithdraw.sol";
          import "./NFTMarketAuction.sol";
          import "./NFTMarketCore.sol";
          import "./NFTMarketExhibition.sol";
          /// @param auctionId The already listed auctionId for this NFT.
          error NFTMarketReserveAuction_Already_Listed(uint256 auctionId);
          /// @param minAmount The minimum amount that must be bid in order for it to be accepted.
          error NFTMarketReserveAuction_Bid_Must_Be_At_Least_Min_Amount(uint256 minAmount);
          /// @param reservePrice The current reserve price.
          error NFTMarketReserveAuction_Cannot_Bid_Lower_Than_Reserve_Price(uint256 reservePrice);
          /// @param endTime The timestamp at which the auction had ended.
          error NFTMarketReserveAuction_Cannot_Bid_On_Ended_Auction(uint256 endTime);
          error NFTMarketReserveAuction_Cannot_Bid_On_Nonexistent_Auction();
          error NFTMarketReserveAuction_Cannot_Finalize_Already_Settled_Auction();
          /// @param endTime The timestamp at which the auction will end.
          error NFTMarketReserveAuction_Cannot_Finalize_Auction_In_Progress(uint256 endTime);
          error NFTMarketReserveAuction_Cannot_Rebid_Over_Outstanding_Bid();
          error NFTMarketReserveAuction_Cannot_Update_Auction_In_Progress();
          /// @param maxDuration The maximum configuration for a duration of the auction, in seconds.
          error NFTMarketReserveAuction_Exceeds_Max_Duration(uint256 maxDuration);
          /// @param extensionDuration The extension duration, in seconds.
          error NFTMarketReserveAuction_Less_Than_Extension_Duration(uint256 extensionDuration);
          error NFTMarketReserveAuction_Must_Set_Non_Zero_Reserve_Price();
          /// @param seller The current owner of the NFT.
          error NFTMarketReserveAuction_Not_Matching_Seller(address seller);
          /// @param owner The current owner of the NFT.
          error NFTMarketReserveAuction_Only_Owner_Can_Update_Auction(address owner);
          error NFTMarketReserveAuction_Price_Already_Set();
          error NFTMarketReserveAuction_Too_Much_Value_Provided();
          /**
           * @title Allows the owner of an NFT to list it in auction.
           * @notice NFTs in auction are escrowed in the market contract.
           * @dev There is room to optimize the storage for auctions, significantly reducing gas costs.
           * This may be done in the future, but for now it will remain as is in order to ease upgrade compatibility.
           * @author batu-inal & HardlyDifficult
           */
          abstract contract NFTMarketReserveAuction is
            INFTMarketReserveAuction,
            Initializable,
            FoundationTreasuryNode,
            Context,
            FETHNode,
            MarketSharedCore,
            NFTMarketCore,
            ReentrancyGuardUpgradeable,
            SendValueWithFallbackWithdraw,
            MarketFees,
            NFTMarketExhibition,
            NFTMarketAuction
          {
            using TimeLibrary for uint256;
            /// @notice The auction configuration for a specific NFT.
            struct ReserveAuction {
              /// @notice The address of the NFT contract.
              address nftContract;
              /// @notice The id of the NFT.
              uint256 tokenId;
              /// @notice The owner of the NFT which listed it in auction.
              address payable seller;
              /// @notice The duration for this auction.
              uint256 duration;
              /// @notice The extension window for this auction.
              uint256 extensionDuration;
              /// @notice The time at which this auction will not accept any new bids.
              /// @dev This is `0` until the first bid is placed.
              uint256 endTime;
              /// @notice The current highest bidder in this auction.
              /// @dev This is `address(0)` until the first bid is placed.
              address payable bidder;
              /// @notice The latest price of the NFT in this auction.
              /// @dev This is set to the reserve price, and then to the highest bid once the auction has started.
              uint256 amount;
            }
            /// @notice Stores the auction configuration for a specific NFT.
            /// @dev This allows us to modify the storage struct without changing external APIs.
            struct ReserveAuctionStorage {
              /// @notice The address of the NFT contract.
              address nftContract;
              /// @notice The id of the NFT.
              uint256 tokenId;
              /// @notice The owner of the NFT which listed it in auction.
              address payable seller;
              /// @notice First slot (of 12B) used for the bidReferrerAddress.
              /// The bidReferrerAddress is the address used to pay the referrer on finalize.
              /// @dev This approach is used in order to pack storage, saving gas.
              uint96 bidReferrerAddressSlot0;
              /// @dev This field is no longer used.
              uint256 __gap_was_duration;
              /// @dev This field is no longer used.
              uint256 __gap_was_extensionDuration;
              /// @notice The time at which this auction will not accept any new bids.
              /// @dev This is `0` until the first bid is placed.
              uint256 endTime;
              /// @notice The current highest bidder in this auction.
              /// @dev This is `address(0)` until the first bid is placed.
              address payable bidder;
              /// @notice Second slot (of 8B) used for the bidReferrerAddress.
              uint64 bidReferrerAddressSlot1;
              /// @notice The latest price of the NFT in this auction.
              /// @dev This is set to the reserve price, and then to the highest bid once the auction has started.
              uint256 amount;
            }
            /// @notice The auction configuration for a specific auction id.
            mapping(address => mapping(uint256 => uint256)) private nftContractToTokenIdToAuctionId;
            /// @notice The auction id for a specific NFT.
            /// @dev This is deleted when an auction is finalized or canceled.
            mapping(uint256 => ReserveAuctionStorage) private auctionIdToAuction;
            /**
             * @dev Removing old unused variables in an upgrade safe way. Was:
             * uint256 private __gap_was_minPercentIncrementInBasisPoints;
             * uint256 private __gap_was_maxBidIncrementRequirement;
             * uint256 private __gap_was_duration;
             * uint256 private __gap_was_extensionDuration;
             * uint256 private __gap_was_goLiveDate;
             */
            uint256[5] private __gap_was_config;
            /// @notice How long an auction lasts for once the first bid has been received.
            uint256 private immutable DURATION;
            /// @notice The window for auction extensions, any bid placed in the final 15 minutes
            /// of an auction will reset the time remaining to 15 minutes.
            uint256 private constant EXTENSION_DURATION = 15 minutes;
            /// @notice Caps the max duration that may be configured so that overflows will not occur.
            uint256 private constant MAX_MAX_DURATION = 1_000 days;
            /**
             * @notice Emitted when a bid is placed.
             * @param auctionId The id of the auction this bid was for.
             * @param bidder The address of the bidder.
             * @param amount The amount of the bid.
             * @param endTime The new end time of the auction (which may have been set or extended by this bid).
             */
            event ReserveAuctionBidPlaced(uint256 indexed auctionId, address indexed bidder, uint256 amount, uint256 endTime);
            /**
             * @notice Emitted when an auction is canceled.
             * @dev This is only possible if the auction has not received any bids.
             * @param auctionId The id of the auction that was canceled.
             */
            event ReserveAuctionCanceled(uint256 indexed auctionId);
            /**
             * @notice Emitted when an NFT is listed for auction.
             * @param seller The address of the seller.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @param duration The duration of the auction (always 24-hours).
             * @param extensionDuration The duration of the auction extension window (always 15-minutes).
             * @param reservePrice The reserve price to kick off the auction.
             * @param auctionId The id of the auction that was created.
             */
            event ReserveAuctionCreated(
              address indexed seller,
              address indexed nftContract,
              uint256 indexed tokenId,
              uint256 duration,
              uint256 extensionDuration,
              uint256 reservePrice,
              uint256 auctionId
            );
            /**
             * @notice Emitted when an auction that has already ended is finalized,
             * indicating that the NFT has been transferred and revenue from the sale distributed.
             * @dev The amount of the highest bid / final sale price for this auction
             * is `totalFees` + `creatorRev` + `sellerRev`.
             * @param auctionId The id of the auction that was finalized.
             * @param seller The address of the seller.
             * @param bidder The address of the highest bidder that won the NFT.
             * @param totalFees The amount of ETH that was sent to Foundation & referrals for this sale.
             * @param creatorRev The amount of ETH that was sent to the creator for this sale.
             * @param sellerRev The amount of ETH that was sent to the owner for this sale.
             */
            event ReserveAuctionFinalized(
              uint256 indexed auctionId,
              address indexed seller,
              address indexed bidder,
              uint256 totalFees,
              uint256 creatorRev,
              uint256 sellerRev
            );
            /**
             * @notice Emitted when an auction is invalidated due to other market activity.
             * @dev This occurs when the NFT is sold another way, such as with `buy` or `acceptOffer`.
             * @param auctionId The id of the auction that was invalidated.
             */
            event ReserveAuctionInvalidated(uint256 indexed auctionId);
            /**
             * @notice Emitted when the auction's reserve price is changed.
             * @dev This is only possible if the auction has not received any bids.
             * @param auctionId The id of the auction that was updated.
             * @param reservePrice The new reserve price for the auction.
             */
            event ReserveAuctionUpdated(uint256 indexed auctionId, uint256 reservePrice);
            /// @notice Confirms that the reserve price is not zero.
            modifier onlyValidAuctionConfig(uint256 reservePrice) {
              if (reservePrice == 0) {
                revert NFTMarketReserveAuction_Must_Set_Non_Zero_Reserve_Price();
              }
              _;
            }
            /**
             * @notice Configures the duration for auctions.
             * @param duration The duration for auctions, in seconds.
             */
            constructor(uint256 duration) {
              if (duration > MAX_MAX_DURATION) {
                // This ensures that math in this file will not overflow due to a huge duration.
                revert NFTMarketReserveAuction_Exceeds_Max_Duration(MAX_MAX_DURATION);
              }
              if (duration < EXTENSION_DURATION) {
                // The auction duration configuration must be greater than the extension window of 15 minutes
                revert NFTMarketReserveAuction_Less_Than_Extension_Duration(EXTENSION_DURATION);
              }
              DURATION = duration;
            }
            /**
             * @notice If an auction has been created but has not yet received bids, it may be canceled by the seller.
             * @dev The NFT is transferred back to the owner unless there is still a buy price set.
             * @param auctionId The id of the auction to cancel.
             */
            function cancelReserveAuction(uint256 auctionId) external nonReentrant {
              ReserveAuctionStorage memory auction = auctionIdToAuction[auctionId];
              if (auction.seller != _msgSender()) {
                revert NFTMarketReserveAuction_Only_Owner_Can_Update_Auction(auction.seller);
              }
              if (auction.endTime != 0) {
                revert NFTMarketReserveAuction_Cannot_Update_Auction_In_Progress();
              }
              // Remove the auction.
              delete nftContractToTokenIdToAuctionId[auction.nftContract][auction.tokenId];
              delete auctionIdToAuction[auctionId];
              _removeNftFromExhibition(auction.nftContract, auction.tokenId);
              // Transfer the NFT unless it still has a buy price set.
              _transferFromEscrowIfAvailable(auction.nftContract, auction.tokenId, auction.seller);
              emit ReserveAuctionCanceled(auctionId);
            }
            /**
             * @notice [DEPRECATED] use `createReserveAuctionV2` instead.
             * Creates an auction for the given NFT.
             * The NFT is held in escrow until the auction is finalized or canceled.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @param reservePrice The initial reserve price for the auction.
             */
            function createReserveAuction(address nftContract, uint256 tokenId, uint256 reservePrice) external {
              createReserveAuctionV2({ nftContract: nftContract, tokenId: tokenId, reservePrice: reservePrice, exhibitionId: 0 });
            }
            /**
             * @notice Creates an auction for the given NFT.
             * The NFT is held in escrow until the auction is finalized or canceled.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @param reservePrice The initial reserve price for the auction.
             * @param exhibitionId The exhibition to list with, or 0 if n/a.
             * @return auctionId The id of the auction that was created.
             */
            function createReserveAuctionV2(
              address nftContract,
              uint256 tokenId,
              uint256 reservePrice,
              uint256 exhibitionId
            ) public nonReentrant onlyValidAuctionConfig(reservePrice) returns (uint256 auctionId) {
              auctionId = _getNextAndIncrementAuctionId();
              // If the `msg.sender` is not the owner of the NFT, transferring into escrow should fail.
              _transferToEscrow(nftContract, tokenId);
              // This check must be after _transferToEscrow in case auto-settle was required
              if (nftContractToTokenIdToAuctionId[nftContract][tokenId] != 0) {
                revert NFTMarketReserveAuction_Already_Listed(nftContractToTokenIdToAuctionId[nftContract][tokenId]);
              }
              // Store the auction details
              address payable sender = payable(_msgSender());
              nftContractToTokenIdToAuctionId[nftContract][tokenId] = auctionId;
              ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId];
              auction.nftContract = nftContract;
              auction.tokenId = tokenId;
              auction.seller = sender;
              auction.amount = reservePrice;
              _addNftToExhibition(nftContract, tokenId, exhibitionId);
              emit ReserveAuctionCreated({
                seller: sender,
                nftContract: nftContract,
                tokenId: tokenId,
                duration: DURATION,
                extensionDuration: EXTENSION_DURATION,
                reservePrice: reservePrice,
                auctionId: auctionId
              });
            }
            /**
             * @notice Once the countdown has expired for an auction, anyone can settle the auction.
             * This will send the NFT to the highest bidder and distribute revenue for this sale.
             * @param auctionId The id of the auction to settle.
             */
            function finalizeReserveAuction(uint256 auctionId) external nonReentrant {
              if (auctionIdToAuction[auctionId].endTime == 0) {
                revert NFTMarketReserveAuction_Cannot_Finalize_Already_Settled_Auction();
              }
              _finalizeReserveAuction({ auctionId: auctionId, keepInEscrow: false });
            }
            /**
             * @notice [DEPRECATED] use `placeBidV2` instead.
             * Place a bid in an auction.
             * A bidder may place a bid which is at least the value defined by `getMinBidAmount`.
             * If this is the first bid on the auction, the countdown will begin.
             * If there is already an outstanding bid, the previous bidder will be refunded at this time
             * and if the bid is placed in the final moments of the auction, the countdown may be extended.
             * @param auctionId The id of the auction to bid on.
             */
            function placeBid(uint256 auctionId) external payable {
              placeBidV2({ auctionId: auctionId, amount: msg.value, referrer: payable(0) });
            }
            /**
             * @notice Place a bid in an auction.
             * A bidder may place a bid which is at least the amount defined by `getMinBidAmount`.
             * If this is the first bid on the auction, the countdown will begin.
             * If there is already an outstanding bid, the previous bidder will be refunded at this time
             * and if the bid is placed in the final moments of the auction, the countdown may be extended.
             * @dev `amount` - `msg.value` is withdrawn from the bidder's FETH balance.
             * @param auctionId The id of the auction to bid on.
             * @param amount The amount to bid, if this is more than `msg.value` funds will be withdrawn from your FETH balance.
             * @param referrer The address of the referrer of this bid, or 0 if n/a.
             */
            function placeBidV2(uint256 auctionId, uint256 amount, address payable referrer) public payable nonReentrant {
              ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId];
              if (auction.amount == 0) {
                // No auction found
                revert NFTMarketReserveAuction_Cannot_Bid_On_Nonexistent_Auction();
              } else if (amount < msg.value) {
                // The amount is specified by the bidder, so if too much ETH is sent then something went wrong.
                revert NFTMarketReserveAuction_Too_Much_Value_Provided();
              }
              uint256 endTime = auction.endTime;
              address payable sender = payable(_msgSender());
              // Store the bid referral
              if (referrer != address(0) || endTime != 0) {
                auction.bidReferrerAddressSlot0 = uint96(uint160(address(referrer)) >> 64);
                auction.bidReferrerAddressSlot1 = uint64(uint160(address(referrer)));
              }
              if (endTime == 0) {
                // This is the first bid, kicking off the auction.
                if (amount < auction.amount) {
                  // The bid must be >= the reserve price.
                  revert NFTMarketReserveAuction_Cannot_Bid_Lower_Than_Reserve_Price(auction.amount);
                }
                // Notify other market tools that an auction for this NFT has been kicked off.
                // The only state change before this call is potentially withdrawing funds from FETH.
                _beforeAuctionStarted(auction.nftContract, auction.tokenId);
                // Store the bid details.
                auction.amount = amount;
                auction.bidder = sender;
                // On the first bid, set the endTime to now + duration.
                unchecked {
                  // Duration is always set to 24hrs so the below can't overflow.
                  endTime = block.timestamp + DURATION;
                }
                auction.endTime = endTime;
              } else {
                if (endTime.hasExpired()) {
                  // The auction has already ended.
                  revert NFTMarketReserveAuction_Cannot_Bid_On_Ended_Auction(endTime);
                } else if (auction.bidder == sender) {
                  // We currently do not allow a bidder to increase their bid unless another user has outbid them first.
                  revert NFTMarketReserveAuction_Cannot_Rebid_Over_Outstanding_Bid();
                } else {
                  uint256 minIncrement = _getMinIncrement(auction.amount);
                  if (amount < minIncrement) {
                    // If this bid outbids another, it must be at least 10% greater than the last bid.
                    revert NFTMarketReserveAuction_Bid_Must_Be_At_Least_Min_Amount(minIncrement);
                  }
                }
                // Cache and update bidder state
                uint256 originalAmount = auction.amount;
                address payable originalBidder = auction.bidder;
                auction.amount = amount;
                auction.bidder = sender;
                unchecked {
                  // When a bid outbids another, check to see if a time extension should apply.
                  // We confirmed that the auction has not ended, so endTime is always >= the current timestamp.
                  // Current time plus extension duration (always 15 mins) cannot overflow.
                  uint256 endTimeWithExtension = block.timestamp + EXTENSION_DURATION;
                  if (endTime < endTimeWithExtension) {
                    endTime = endTimeWithExtension;
                    auction.endTime = endTime;
                  }
                }
                // Refund the previous bidder
                _sendValueWithFallbackWithdraw({
                  user: originalBidder,
                  amount: originalAmount,
                  gasLimit: SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT
                });
              }
              _tryUseFETHBalance({ totalAmount: amount, shouldRefundSurplus: false });
              emit ReserveAuctionBidPlaced({ auctionId: auctionId, bidder: sender, amount: amount, endTime: endTime });
            }
            /**
             * @notice If an auction has been created but has not yet received bids, the reservePrice may be
             * changed by the seller.
             * @param auctionId The id of the auction to change.
             * @param reservePrice The new reserve price for this auction.
             */
            function updateReserveAuction(uint256 auctionId, uint256 reservePrice) external onlyValidAuctionConfig(reservePrice) {
              ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId];
              if (auction.seller != _msgSender()) {
                revert NFTMarketReserveAuction_Only_Owner_Can_Update_Auction(auction.seller);
              } else if (auction.endTime != 0) {
                revert NFTMarketReserveAuction_Cannot_Update_Auction_In_Progress();
              } else if (auction.amount == reservePrice) {
                revert NFTMarketReserveAuction_Price_Already_Set();
              }
              // Update the current reserve price.
              auction.amount = reservePrice;
              emit ReserveAuctionUpdated(auctionId, reservePrice);
            }
            /**
             * @notice Settle an auction that has already ended.
             * This will send the NFT to the highest bidder and distribute revenue for this sale.
             * @param keepInEscrow If true, the NFT will be kept in escrow to save gas by avoiding
             * redundant transfers if the NFT should remain in escrow, such as when the new owner
             * sets a buy price or lists it in a new auction.
             */
            function _finalizeReserveAuction(uint256 auctionId, bool keepInEscrow) private {
              ReserveAuctionStorage memory auction = auctionIdToAuction[auctionId];
              if (!auction.endTime.hasExpired()) {
                revert NFTMarketReserveAuction_Cannot_Finalize_Auction_In_Progress(auction.endTime);
              }
              (
                address payable sellerReferrerPaymentAddress,
                uint16 sellerReferrerTakeRateInBasisPoints
              ) = _getExhibitionForPayment(auction.nftContract, auction.tokenId);
              // Remove the auction.
              delete nftContractToTokenIdToAuctionId[auction.nftContract][auction.tokenId];
              delete auctionIdToAuction[auctionId];
              if (!keepInEscrow) {
                // The seller was authorized when the auction was originally created
                super._transferFromEscrow({
                  nftContract: auction.nftContract,
                  tokenId: auction.tokenId,
                  recipient: auction.bidder,
                  authorizeSeller: address(0)
                });
              }
              // Distribute revenue for this sale.
              (uint256 totalFees, uint256 creatorRev, uint256 sellerRev) = _distributeFunds(
                auction.nftContract,
                auction.tokenId,
                auction.seller,
                auction.amount,
                payable(address((uint160(auction.bidReferrerAddressSlot0) << 64) | uint160(auction.bidReferrerAddressSlot1))),
                sellerReferrerPaymentAddress,
                sellerReferrerTakeRateInBasisPoints
              );
              emit ReserveAuctionFinalized(auctionId, auction.seller, auction.bidder, totalFees, creatorRev, sellerRev);
            }
            /**
             * @inheritdoc NFTMarketCore
             * @dev If an auction is found:
             *  - If the auction is over, it will settle the auction and confirm the new seller won the auction.
             *  - If the auction has not received a bid, it will invalidate the auction.
             *  - If the auction is in progress, this will revert.
             */
            function _transferFromEscrow(
              address nftContract,
              uint256 tokenId,
              address recipient,
              address authorizeSeller
            ) internal virtual override {
              uint256 auctionId = nftContractToTokenIdToAuctionId[nftContract][tokenId];
              if (auctionId != 0) {
                ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId];
                if (auction.endTime == 0) {
                  // The auction has not received any bids yet so it may be invalided.
                  if (authorizeSeller != address(0) && auction.seller != authorizeSeller) {
                    // The account trying to transfer the NFT is not the current owner.
                    revert NFTMarketReserveAuction_Not_Matching_Seller(auction.seller);
                  }
                  // Remove the auction.
                  delete nftContractToTokenIdToAuctionId[nftContract][tokenId];
                  delete auctionIdToAuction[auctionId];
                  _removeNftFromExhibition(nftContract, tokenId);
                  emit ReserveAuctionInvalidated(auctionId);
                } else {
                  // If the auction has ended, the highest bidder will be the new owner
                  // and if the auction is in progress, this will revert.
                  // `authorizeSeller != address(0)` does not apply here since an unsettled auction must go
                  // through this path to know who the authorized seller should be.
                  if (auction.bidder != authorizeSeller) {
                    revert NFTMarketReserveAuction_Not_Matching_Seller(auction.bidder);
                  }
                  // Finalization will revert if the auction has not yet ended.
                  _finalizeReserveAuction({ auctionId: auctionId, keepInEscrow: true });
                }
                // The seller authorization has been confirmed.
                authorizeSeller = address(0);
              }
              super._transferFromEscrow(nftContract, tokenId, recipient, authorizeSeller);
            }
            /**
             * @inheritdoc NFTMarketCore
             * @dev Checks if there is an auction for this NFT before allowing the transfer to continue.
             */
            function _transferFromEscrowIfAvailable(
              address nftContract,
              uint256 tokenId,
              address recipient
            ) internal virtual override {
              if (nftContractToTokenIdToAuctionId[nftContract][tokenId] == 0) {
                // No auction was found
                super._transferFromEscrowIfAvailable(nftContract, tokenId, recipient);
              }
            }
            /**
             * @inheritdoc NFTMarketCore
             */
            function _transferToEscrow(address nftContract, uint256 tokenId) internal virtual override {
              uint256 auctionId = nftContractToTokenIdToAuctionId[nftContract][tokenId];
              if (auctionId == 0) {
                // NFT is not in auction
                super._transferToEscrow(nftContract, tokenId);
                return;
              }
              // Using storage saves gas since most of the data is not needed
              ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId];
              address sender = _msgSender();
              if (auction.endTime == 0) {
                // Reserve price set, confirm the seller is a match
                if (auction.seller != sender) {
                  revert NFTMarketReserveAuction_Not_Matching_Seller(auction.seller);
                }
              } else {
                // Auction in progress, confirm the highest bidder is a match
                if (auction.bidder != sender) {
                  revert NFTMarketReserveAuction_Not_Matching_Seller(auction.bidder);
                }
                // Finalize auction but leave NFT in escrow, reverts if the auction has not ended
                _finalizeReserveAuction({ auctionId: auctionId, keepInEscrow: true });
              }
            }
            /**
             * @notice Returns the minimum amount a bidder must spend to participate in an auction.
             * Bids must be greater than or equal to this value or they will revert.
             * @param auctionId The id of the auction to check.
             * @return minimum The minimum amount for a bid to be accepted.
             */
            function getMinBidAmount(uint256 auctionId) external view returns (uint256 minimum) {
              ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId];
              if (auction.endTime == 0) {
                return auction.amount;
              }
              return _getMinIncrement(auction.amount);
            }
            /**
             * @notice Returns auction details for a given auctionId.
             * @param auctionId The id of the auction to lookup.
             */
            function getReserveAuction(uint256 auctionId) external view returns (ReserveAuction memory auction) {
              ReserveAuctionStorage storage auctionStorage = auctionIdToAuction[auctionId];
              auction = ReserveAuction(
                auctionStorage.nftContract,
                auctionStorage.tokenId,
                auctionStorage.seller,
                DURATION,
                EXTENSION_DURATION,
                auctionStorage.endTime,
                auctionStorage.bidder,
                auctionStorage.amount
              );
            }
            /**
             * @notice Returns the auctionId for a given NFT, or 0 if no auction is found.
             * @dev If an auction is canceled, it will not be returned. However the auction may be over and pending finalization.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @return auctionId The id of the auction, or 0 if no auction is found.
             */
            function getReserveAuctionIdFor(address nftContract, uint256 tokenId) external view returns (uint256 auctionId) {
              auctionId = nftContractToTokenIdToAuctionId[nftContract][tokenId];
            }
            /**
             * @notice Returns the referrer for the current highest bid in the auction, or address(0).
             */
            function getReserveAuctionBidReferrer(uint256 auctionId) external view returns (address payable referrer) {
              ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId];
              referrer = payable(
                address((uint160(auction.bidReferrerAddressSlot0) << 64) | uint160(auction.bidReferrerAddressSlot1))
              );
            }
            /**
             * @inheritdoc MarketSharedCore
             * @dev Returns the seller that has the given NFT in escrow for an auction,
             * or bubbles the call up for other considerations.
             */
            function _getSellerOf(
              address nftContract,
              uint256 tokenId
            ) internal view virtual override(MarketSharedCore, NFTMarketCore) returns (address payable seller) {
              seller = auctionIdToAuction[nftContractToTokenIdToAuctionId[nftContract][tokenId]].seller;
              if (seller == address(0)) {
                seller = super._getSellerOf(nftContract, tokenId);
              }
            }
            /**
             * @inheritdoc NFTMarketCore
             */
            function _isInActiveAuction(address nftContract, uint256 tokenId) internal view override returns (bool) {
              uint256 auctionId = nftContractToTokenIdToAuctionId[nftContract][tokenId];
              return auctionId != 0 && !auctionIdToAuction[auctionId].endTime.hasExpired();
            }
            /**
             * @notice This empty reserved space is put in place to allow future versions to add new
             * variables without shifting down storage in the inheritance chain.
             * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
             */
            uint256[1_000] private __gap;
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          /// Constant values shared across mixins.
          /**
           * @dev 100% in basis points.
           */
          uint256 constant BASIS_POINTS = 10_000;
          /**
           * @dev The default admin role defined by OZ ACL modules.
           */
          bytes32 constant DEFAULT_ADMIN_ROLE = 0x00;
          ////////////////////////////////////////////////////////////////
          // Royalties & Take Rates
          ////////////////////////////////////////////////////////////////
          /**
           * @dev The max take rate an exhibition can have.
           */
          uint256 constant MAX_EXHIBITION_TAKE_RATE = 5_000;
          /**
           * @dev Cap the number of royalty recipients.
           * A cap is required to ensure gas costs are not too high when a sale is settled.
           */
          uint256 constant MAX_ROYALTY_RECIPIENTS = 5;
          /**
           * @dev Default royalty cut paid out on secondary sales.
           * Set to 10% of the secondary sale.
           */
          uint96 constant ROYALTY_IN_BASIS_POINTS = 1_000;
          /**
           * @dev 10%, expressed as a denominator for more efficient calculations.
           */
          uint256 constant ROYALTY_RATIO = BASIS_POINTS / ROYALTY_IN_BASIS_POINTS;
          ////////////////////////////////////////////////////////////////
          // Gas Limits
          ////////////////////////////////////////////////////////////////
          /**
           * @dev The gas limit used when making external read-only calls.
           * This helps to ensure that external calls does not prevent the market from executing.
           */
          uint256 constant READ_ONLY_GAS_LIMIT = 40_000;
          /**
           * @dev The gas limit to send ETH to multiple recipients, enough for a 5-way split.
           */
          uint256 constant SEND_VALUE_GAS_LIMIT_MULTIPLE_RECIPIENTS = 210_000;
          /**
           * @dev The gas limit to send ETH to a single recipient, enough for a contract with a simple receiver.
           */
          uint256 constant SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT = 20_000;
          ////////////////////////////////////////////////////////////////
          // Collection Type Names
          ////////////////////////////////////////////////////////////////
          /**
           * @dev The NFT collection type.
           */
          string constant NFT_COLLECTION_TYPE = "NFT Collection";
          /**
           * @dev The NFT drop collection type.
           */
          string constant NFT_DROP_COLLECTION_TYPE = "NFT Drop Collection";
          /**
           * @dev The NFT edition collection type.
           */
          string constant NFT_TIMED_EDITION_COLLECTION_TYPE = "NFT Timed Edition Collection";
          ////////////////////////////////////////////////////////////////
          // Business Logic
          ////////////////////////////////////////////////////////////////
          /**
           * @dev Limits scheduled start/end times to be less than 2 years in the future.
           */
          uint256 constant MAX_SCHEDULED_TIME_IN_THE_FUTURE = 365 days * 2;
          /**
           * @dev The minimum increase of 10% required when making an offer or placing a bid.
           */
          uint256 constant MIN_PERCENT_INCREMENT_DENOMINATOR = BASIS_POINTS / 1_000;
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts/utils/Context.sol";
          import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
          import "../../interfaces/internal/IFethMarket.sol";
          error FETHNode_FETH_Address_Is_Not_A_Contract();
          error FETHNode_Only_FETH_Can_Transfer_ETH();
          /**
           * @title A mixin for interacting with the FETH contract.
           * @author batu-inal & HardlyDifficult
           */
          abstract contract FETHNode is Context {
            using AddressUpgradeable for address;
            using AddressUpgradeable for address payable;
            /// @notice The FETH ERC-20 token for managing escrow and lockup.
            IFethMarket internal immutable feth;
            constructor(address _feth) {
              if (!_feth.isContract()) {
                revert FETHNode_FETH_Address_Is_Not_A_Contract();
              }
              feth = IFethMarket(_feth);
            }
            /**
             * @notice Only used by FETH. Any direct transfer from users will revert.
             */
            receive() external payable {
              if (msg.sender != address(feth)) {
                revert FETHNode_Only_FETH_Can_Transfer_ETH();
              }
            }
            /**
             * @notice Withdraw the msg.sender's available FETH balance if they requested more than the msg.value provided.
             * @dev This may revert if the msg.sender is non-receivable.
             * This helper should not be used anywhere that may lead to locked assets.
             * @param totalAmount The total amount of ETH required (including the msg.value).
             * @param shouldRefundSurplus If true, refund msg.value - totalAmount to the msg.sender.
             */
            function _tryUseFETHBalance(uint256 totalAmount, bool shouldRefundSurplus) internal {
              if (totalAmount > msg.value) {
                // Withdraw additional ETH required from the user's available FETH balance.
                unchecked {
                  // The if above ensures delta will not underflow.
                  // Withdraw ETH from the user's account in the FETH token contract,
                  // making the funds available in this contract as ETH.
                  feth.marketWithdrawFrom(_msgSender(), totalAmount - msg.value);
                }
              } else if (shouldRefundSurplus && totalAmount < msg.value) {
                // Return any surplus ETH to the user.
                unchecked {
                  // The if above ensures this will not underflow
                  payable(_msgSender()).sendValue(msg.value - totalAmount);
                }
              }
            }
            /**
             * @notice Gets the FETH contract used to escrow offer funds.
             * @return fethAddress The FETH contract address.
             */
            function getFethAddress() external view returns (address fethAddress) {
              fethAddress = address(feth);
            }
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
          import "../../interfaces/internal/roles/IAdminRole.sol";
          import "../../interfaces/internal/roles/IOperatorRole.sol";
          error FoundationTreasuryNode_Address_Is_Not_A_Contract();
          error FoundationTreasuryNode_Caller_Not_Admin();
          error FoundationTreasuryNode_Caller_Not_Operator();
          /**
           * @title A mixin that stores a reference to the Foundation treasury contract.
           * @notice The treasury collects fees and defines admin/operator roles.
           * @author batu-inal & HardlyDifficult
           */
          abstract contract FoundationTreasuryNode {
            using AddressUpgradeable for address payable;
            /// @dev This value was replaced with an immutable version.
            address payable private __gap_was_treasury;
            /// @notice The address of the treasury contract.
            address payable private immutable treasury;
            /// @notice Requires the caller is a Foundation admin.
            modifier onlyFoundationAdmin() {
              if (!IAdminRole(treasury).isAdmin(msg.sender)) {
                revert FoundationTreasuryNode_Caller_Not_Admin();
              }
              _;
            }
            /// @notice Requires the caller is a Foundation operator.
            modifier onlyFoundationOperator() {
              if (!IOperatorRole(treasury).isOperator(msg.sender)) {
                revert FoundationTreasuryNode_Caller_Not_Operator();
              }
              _;
            }
            /**
             * @notice Set immutable variables for the implementation contract.
             * @dev Assigns the treasury contract address.
             */
            constructor(address payable _treasury) {
              if (!_treasury.isContract()) {
                revert FoundationTreasuryNode_Address_Is_Not_A_Contract();
              }
              treasury = _treasury;
            }
            /**
             * @notice Gets the Foundation treasury contract.
             * @dev This call is used in the royalty registry contract.
             * @return treasuryAddress The address of the Foundation treasury contract.
             */
            function getFoundationTreasury() public view returns (address payable treasuryAddress) {
              treasuryAddress = treasury;
            }
            /**
             * @notice This empty reserved space is put in place to allow future versions to add new
             * variables without shifting down storage in the inheritance chain.
             * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
             */
            uint256[2_000] private __gap;
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts/utils/Context.sol";
          import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
          import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
          import "@manifoldxyz/royalty-registry-solidity/contracts/IRoyaltyRegistry.sol";
          import "../../interfaces/internal/INFTCollectionType.sol";
          import "../../interfaces/standards/royalties/IGetFees.sol";
          import "../../interfaces/standards/royalties/IGetRoyalties.sol";
          import "../../interfaces/standards/royalties/IOwnable.sol";
          import "../../interfaces/standards/royalties/IRoyaltyInfo.sol";
          import "../../interfaces/standards/royalties/ITokenCreator.sol";
          import "../../libraries/ArrayLibrary.sol";
          import "./Constants.sol";
          import "./FoundationTreasuryNode.sol";
          import "./SendValueWithFallbackWithdraw.sol";
          import "./MarketSharedCore.sol";
          error NFTMarketFees_Royalty_Registry_Is_Not_A_Contract();
          error NFTMarketFees_Invalid_Protocol_Fee();
          /**
           * @title A mixin to distribute funds when an NFT is sold.
           * @author batu-inal & HardlyDifficult
           */
          abstract contract MarketFees is FoundationTreasuryNode, Context, MarketSharedCore, SendValueWithFallbackWithdraw {
            using AddressUpgradeable for address;
            using ArrayLibrary for address payable[];
            using ArrayLibrary for uint256[];
            using ERC165Checker for address;
            /**
             * @dev Removing old unused variables in an upgrade safe way. Was:
             * uint256 private _primaryFoundationFeeBasisPoints;
             * uint256 private _secondaryFoundationFeeBasisPoints;
             * uint256 private _secondaryCreatorFeeBasisPoints;
             * mapping(address => mapping(uint256 => bool)) private _nftContractToTokenIdToFirstSaleCompleted;
             */
            uint256[4] private __gap_was_fees;
            /// @notice The royalties sent to creator recipients on secondary sales.
            uint256 private constant CREATOR_ROYALTY_DENOMINATOR = BASIS_POINTS / 1_000; // 10%
            /// @notice The fee collected by Foundation for sales facilitated by this market contract.
            uint256 private immutable DEFAULT_PROTOCOL_FEE_IN_BASIS_POINTS;
            /// @notice The fee collected by the buy referrer for sales facilitated by this market contract.
            ///         This fee is calculated from the total protocol fee.
            uint256 private constant BUY_REFERRER_FEE_DENOMINATOR = BASIS_POINTS / 100; // 1%
            /// @notice The address of the royalty registry which may be used to define royalty overrides for some collections.
            IRoyaltyRegistry private immutable royaltyRegistry;
            /// @notice The address of this contract's implementation.
            /// @dev This is used when making stateless external calls to this contract,
            /// saving gas over hopping through the proxy which is only necessary when accessing state.
            MarketFees private immutable implementationAddress;
            /// @notice True for the Drop market which only performs primary sales. False if primary & secondary are supported.
            bool private immutable assumePrimarySale;
            /**
             * @notice Emitted when an NFT sold with a referrer.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @param buyReferrer The account which received the buy referral incentive.
             * @param buyReferrerFee The portion of the protocol fee collected by the buy referrer.
             * @param buyReferrerSellerFee The portion of the owner revenue collected by the buy referrer (not implemented).
             */
            event BuyReferralPaid(
              address indexed nftContract,
              uint256 indexed tokenId,
              address buyReferrer,
              uint256 buyReferrerFee,
              uint256 buyReferrerSellerFee
            );
            /**
             * @notice Emitted when an NFT is sold when associated with a sell referrer.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @param sellerReferrer The account which received the sell referral incentive.
             * @param sellerReferrerFee The portion of the seller revenue collected by the sell referrer.
             */
            event SellerReferralPaid(
              address indexed nftContract,
              uint256 indexed tokenId,
              address sellerReferrer,
              uint256 sellerReferrerFee
            );
            /**
             * @notice Configures the registry allowing for royalty overrides to be defined.
             * @param _royaltyRegistry The registry to use for royalty overrides.
             * @param _assumePrimarySale True for the Drop market which only performs primary sales.
             * False if primary & secondary are supported.
             */
            constructor(uint16 protocolFeeInBasisPoints, address _royaltyRegistry, bool _assumePrimarySale) {
              if (
                protocolFeeInBasisPoints < BASIS_POINTS / BUY_REFERRER_FEE_DENOMINATOR ||
                protocolFeeInBasisPoints + BASIS_POINTS / CREATOR_ROYALTY_DENOMINATOR >= BASIS_POINTS - MAX_EXHIBITION_TAKE_RATE
              ) {
                /* If the protocol fee is invalid, revert:
                 * Protocol fee must be greater than the buy referrer fee since referrer fees are deducted from the protocol fee.
                 * The protocol fee must leave room for the creator royalties and the max exhibition take rate.
                 */
                revert NFTMarketFees_Invalid_Protocol_Fee();
              }
              DEFAULT_PROTOCOL_FEE_IN_BASIS_POINTS = protocolFeeInBasisPoints;
              if (!_royaltyRegistry.isContract()) {
                // Not using a 165 check since mainnet and goerli are not using the same versions of the registry.
                revert NFTMarketFees_Royalty_Registry_Is_Not_A_Contract();
              }
              royaltyRegistry = IRoyaltyRegistry(_royaltyRegistry);
              assumePrimarySale = _assumePrimarySale;
              // In the constructor, `this` refers to the implementation address. Everywhere else it'll be the proxy.
              implementationAddress = this;
            }
            /**
             * @notice Distributes funds to foundation, creator recipients, and NFT owner after a sale.
             */
            function _distributeFunds(
              address nftContract,
              uint256 tokenId,
              address payable seller,
              uint256 price,
              address payable buyReferrer,
              address payable sellerReferrerPaymentAddress,
              uint16 sellerReferrerTakeRateInBasisPoints
            ) internal returns (uint256 totalFees, uint256 creatorRev, uint256 sellerRev) {
              if (price == 0) {
                // When the sale price is 0, there are no revenue to distribute.
                return (0, 0, 0);
              }
              address payable[] memory creatorRecipients;
              uint256[] memory creatorShares;
              uint256 buyReferrerFee;
              uint256 sellerReferrerFee;
              (totalFees, creatorRecipients, creatorShares, sellerRev, buyReferrerFee, sellerReferrerFee) = _getFees(
                nftContract,
                tokenId,
                seller,
                price,
                buyReferrer,
                sellerReferrerTakeRateInBasisPoints
              );
              // Pay the creator(s)
              // If just a single recipient was defined, use a larger gas limit in order to support in-contract split logic.
              uint256 creatorGasLimit = creatorRecipients.length == 1
                ? SEND_VALUE_GAS_LIMIT_MULTIPLE_RECIPIENTS
                : SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT;
              unchecked {
                for (uint256 i = 0; i < creatorRecipients.length; ++i) {
                  _sendValueWithFallbackWithdraw(creatorRecipients[i], creatorShares[i], creatorGasLimit);
                  // Sum the total creator rev from shares
                  // creatorShares is in ETH so creatorRev will not overflow here.
                  creatorRev += creatorShares[i];
                }
              }
              // Pay the seller
              _sendValueWithFallbackWithdraw(seller, sellerRev, SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT);
              // Pay the protocol fee
              _sendValueWithFallbackWithdraw(getFoundationTreasury(), totalFees, SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT);
              // Pay the buy referrer fee
              if (buyReferrerFee != 0) {
                _sendValueWithFallbackWithdraw(buyReferrer, buyReferrerFee, SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT);
                emit BuyReferralPaid({
                  nftContract: nftContract,
                  tokenId: tokenId,
                  buyReferrer: buyReferrer,
                  buyReferrerFee: buyReferrerFee,
                  buyReferrerSellerFee: 0
                });
                unchecked {
                  // Add the referrer fee back into the total fees so that all 3 return fields sum to the total price for events
                  totalFees += buyReferrerFee;
                }
              }
              if (sellerReferrerPaymentAddress != address(0)) {
                if (sellerReferrerFee != 0) {
                  // Add the seller referrer fee back to revenue so that all 3 return fields sum to the total price for events.
                  unchecked {
                    if (sellerRev == 0) {
                      // When sellerRev is 0, this is a primary sale and all revenue is attributed to the "creator".
                      creatorRev += sellerReferrerFee;
                    } else {
                      sellerRev += sellerReferrerFee;
                    }
                  }
                  _sendValueWithFallbackWithdraw(
                    sellerReferrerPaymentAddress,
                    sellerReferrerFee,
                    SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT
                  );
                }
                emit SellerReferralPaid(nftContract, tokenId, sellerReferrerPaymentAddress, sellerReferrerFee);
              }
            }
            /**
             * @notice Returns how funds will be distributed for a sale at the given price point.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @param price The sale price to calculate the fees for.
             * @return totalFees How much will be sent to the Foundation treasury and/or referrals.
             * @return creatorRev How much will be sent across all the `creatorRecipients` defined.
             * @return creatorRecipients The addresses of the recipients to receive a portion of the creator fee.
             * @return creatorShares The percentage of the creator fee to be distributed to each `creatorRecipient`.
             * If there is only one `creatorRecipient`, this may be an empty array.
             * Otherwise `creatorShares.length` == `creatorRecipients.length`.
             * @return sellerRev How much will be sent to the owner/seller of the NFT.
             * If the NFT is being sold by the creator, this may be 0 and the full revenue will appear as `creatorRev`.
             * @return seller The address of the owner of the NFT.
             * If `sellerRev` is 0, this may be `address(0)`.
             */
            function getFeesAndRecipients(
              address nftContract,
              uint256 tokenId,
              uint256 price
            )
              external
              view
              returns (
                uint256 totalFees,
                uint256 creatorRev,
                address payable[] memory creatorRecipients,
                uint256[] memory creatorShares,
                uint256 sellerRev,
                address payable seller
              )
            {
              seller = _getSellerOrOwnerOf(nftContract, tokenId);
              (totalFees, creatorRecipients, creatorShares, sellerRev, , ) = _getFees({
                nftContract: nftContract,
                tokenId: tokenId,
                seller: seller,
                price: price,
                // Notice: Setting this value is a breaking change for the FNDMiddleware contract.
                // Will be wired in an upcoming release to communicate the buy referral information.
                buyReferrer: payable(0),
                sellerReferrerTakeRateInBasisPoints: 0
              });
              // Sum the total creator rev from shares
              unchecked {
                for (uint256 i = 0; i < creatorShares.length; ++i) {
                  creatorRev += creatorShares[i];
                }
              }
            }
            /**
             * @notice Returns the address of the registry allowing for royalty configuration overrides.
             * @dev See https://royaltyregistry.xyz/
             * @return registry The address of the royalty registry contract.
             */
            function getRoyaltyRegistry() external view returns (address registry) {
              registry = address(royaltyRegistry);
            }
            /**
             * @notice **For internal use only.**
             * @dev This function is external to allow using try/catch but is not intended for external use.
             * This checks the token creator.
             */
            function internalGetTokenCreator(
              address nftContract,
              uint256 tokenId
            ) external view returns (address payable creator) {
              creator = ITokenCreator(nftContract).tokenCreator{ gas: READ_ONLY_GAS_LIMIT }(tokenId);
            }
            /**
             * @notice **For internal use only.**
             * @dev This function is external to allow using try/catch but is not intended for external use.
             * If ERC2981 royalties (or getRoyalties) are defined by the NFT contract, allow this standard to define immutable
             * royalties that cannot be later changed via the royalty registry.
             */
            function internalGetImmutableRoyalties(
              address nftContract,
              uint256 tokenId
            ) external view returns (address payable[] memory recipients, uint256[] memory splitPerRecipientInBasisPoints) {
              // 1st priority: ERC-2981
              if (nftContract.supportsERC165InterfaceUnchecked(type(IRoyaltyInfo).interfaceId)) {
                try IRoyaltyInfo(nftContract).royaltyInfo{ gas: READ_ONLY_GAS_LIMIT }(tokenId, BASIS_POINTS) returns (
                  address receiver,
                  uint256 royaltyAmount
                ) {
                  // Manifold contracts return (address(this), 0) when royalties are not defined
                  // - so ignore results when the amount is 0
                  if (royaltyAmount > 0) {
                    recipients = new address payable[](1);
                    recipients[0] = payable(receiver);
                    splitPerRecipientInBasisPoints = new uint256[](1);
                    // The split amount is assumed to be 100% when only 1 recipient is returned
                    return (recipients, splitPerRecipientInBasisPoints);
                  }
                } catch {
                  // Fall through
                }
              }
              // 2nd priority: getRoyalties
              if (nftContract.supportsERC165InterfaceUnchecked(type(IGetRoyalties).interfaceId)) {
                try IGetRoyalties(nftContract).getRoyalties{ gas: READ_ONLY_GAS_LIMIT }(tokenId) returns (
                  address payable[] memory _recipients,
                  uint256[] memory recipientBasisPoints
                ) {
                  if (_recipients.length != 0 && _recipients.length == recipientBasisPoints.length) {
                    return (_recipients, recipientBasisPoints);
                  }
                } catch {
                  // Fall through
                }
              }
            }
            /**
             * @notice **For internal use only.**
             * @dev This function is external to allow using try/catch but is not intended for external use.
             * This checks for royalties defined in the royalty registry or via a non-standard royalty API.
             */
            function internalGetMutableRoyalties(
              address nftContract,
              uint256 tokenId,
              address payable creator
            ) external view returns (address payable[] memory recipients, uint256[] memory splitPerRecipientInBasisPoints) {
              /* Overrides must support ERC-165 when registered, except for overrides defined by the registry owner.
                 If that results in an override w/o 165 we may need to upgrade the market to support or ignore that override. */
              // The registry requires overrides are not 0 and contracts when set.
              // If no override is set, the nftContract address is returned.
              try royaltyRegistry.getRoyaltyLookupAddress{ gas: READ_ONLY_GAS_LIMIT }(nftContract) returns (
                address overrideContract
              ) {
                if (overrideContract != nftContract) {
                  nftContract = overrideContract;
                  // The functions above are repeated here if an override is set.
                  // 3rd priority: ERC-2981 override
                  if (nftContract.supportsERC165InterfaceUnchecked(type(IRoyaltyInfo).interfaceId)) {
                    try IRoyaltyInfo(nftContract).royaltyInfo{ gas: READ_ONLY_GAS_LIMIT }(tokenId, BASIS_POINTS) returns (
                      address receiver,
                      uint256 royaltyAmount
                    ) {
                      // Manifold contracts return (address(this), 0) when royalties are not defined
                      // - so ignore results when the amount is 0
                      if (royaltyAmount != 0) {
                        recipients = new address payable[](1);
                        recipients[0] = payable(receiver);
                        splitPerRecipientInBasisPoints = new uint256[](1);
                        // The split amount is assumed to be 100% when only 1 recipient is returned
                        return (recipients, splitPerRecipientInBasisPoints);
                      }
                    } catch {
                      // Fall through
                    }
                  }
                  // 4th priority: getRoyalties override
                  if (recipients.length == 0 && nftContract.supportsERC165InterfaceUnchecked(type(IGetRoyalties).interfaceId)) {
                    try IGetRoyalties(nftContract).getRoyalties{ gas: READ_ONLY_GAS_LIMIT }(tokenId) returns (
                      address payable[] memory _recipients,
                      uint256[] memory recipientBasisPoints
                    ) {
                      if (_recipients.length != 0 && _recipients.length == recipientBasisPoints.length) {
                        return (_recipients, recipientBasisPoints);
                      }
                    } catch {
                      // Fall through
                    }
                  }
                }
              } catch {
                // Ignore out of gas errors and continue using the nftContract address
              }
              // 5th priority: getFee* from contract or override
              if (nftContract.supportsERC165InterfaceUnchecked(type(IGetFees).interfaceId)) {
                try IGetFees(nftContract).getFeeRecipients{ gas: READ_ONLY_GAS_LIMIT }(tokenId) returns (
                  address payable[] memory _recipients
                ) {
                  if (_recipients.length != 0) {
                    try IGetFees(nftContract).getFeeBps{ gas: READ_ONLY_GAS_LIMIT }(tokenId) returns (
                      uint256[] memory recipientBasisPoints
                    ) {
                      if (_recipients.length == recipientBasisPoints.length) {
                        return (_recipients, recipientBasisPoints);
                      }
                    } catch {
                      // Fall through
                    }
                  }
                } catch {
                  // Fall through
                }
              }
              // 6th priority: tokenCreator w/ or w/o requiring 165 from contract or override
              if (creator != address(0)) {
                // Only pay the tokenCreator if there wasn't another royalty defined
                recipients = new address payable[](1);
                recipients[0] = creator;
                splitPerRecipientInBasisPoints = new uint256[](1);
                // The split amount is assumed to be 100% when only 1 recipient is returned
                return (recipients, splitPerRecipientInBasisPoints);
              }
              // 7th priority: owner from contract or override
              try IOwnable(nftContract).owner{ gas: READ_ONLY_GAS_LIMIT }() returns (address owner) {
                if (owner != address(0)) {
                  // Only pay the owner if there wasn't another royalty defined
                  recipients = new address payable[](1);
                  recipients[0] = payable(owner);
                  splitPerRecipientInBasisPoints = new uint256[](1);
                  // The split amount is assumed to be 100% when only 1 recipient is returned
                  return (recipients, splitPerRecipientInBasisPoints);
                }
              } catch {
                // Fall through
              }
              // If no valid payment address or creator is found, return 0 recipients
            }
            /**
             * @notice Calculates how funds should be distributed for the given sale details.
             * @dev When the NFT is being sold by the `tokenCreator`, all the seller revenue will
             * be split with the royalty recipients defined for that NFT.
             */
            function _getFees(
              address nftContract,
              uint256 tokenId,
              address payable seller,
              uint256 price,
              address payable buyReferrer,
              uint16 sellerReferrerTakeRateInBasisPoints
            )
              private
              view
              returns (
                uint256 totalFees,
                address payable[] memory creatorRecipients,
                uint256[] memory creatorShares,
                uint256 sellerRev,
                uint256 buyReferrerFee,
                uint256 sellerReferrerFee
              )
            {
              // Calculate the protocol fee
              totalFees = (price * _getProtocolFee(nftContract)) / BASIS_POINTS;
              address payable creator;
              try implementationAddress.internalGetTokenCreator(nftContract, tokenId) returns (address payable _creator) {
                creator = _creator;
              } catch {
                // Fall through
              }
              try implementationAddress.internalGetImmutableRoyalties(nftContract, tokenId) returns (
                address payable[] memory _recipients,
                uint256[] memory _splitPerRecipientInBasisPoints
              ) {
                (creatorRecipients, creatorShares) = (_recipients, _splitPerRecipientInBasisPoints);
              } catch {
                // Fall through
              }
              if (creatorRecipients.length == 0) {
                // Check mutable royalties only if we didn't find results from the immutable API
                try implementationAddress.internalGetMutableRoyalties(nftContract, tokenId, creator) returns (
                  address payable[] memory _recipients,
                  uint256[] memory _splitPerRecipientInBasisPoints
                ) {
                  (creatorRecipients, creatorShares) = (_recipients, _splitPerRecipientInBasisPoints);
                } catch {
                  // Fall through
                }
              }
              if (creatorRecipients.length != 0 || assumePrimarySale) {
                uint256 creatorRev;
                if (assumePrimarySale) {
                  // All revenue should go to the creator recipients
                  unchecked {
                    // totalFees is always < price.
                    creatorRev = price - totalFees;
                  }
                  if (creatorRecipients.length == 0) {
                    // If no creators were found via the royalty APIs, then set that recipient to the seller's address
                    creatorRecipients = new address payable[](1);
                    creatorRecipients[0] = seller;
                    creatorShares = new uint256[](1);
                    // The split amount is assumed to be 100% when only 1 recipient is returned
                  }
                } else if (seller == creator || (creatorRecipients.length != 0 && seller == creatorRecipients[0])) {
                  // When sold by the creator, all revenue is split if applicable.
                  unchecked {
                    // totalFees is always < price.
                    creatorRev = price - totalFees;
                  }
                } else {
                  // Rounding favors the owner first, then creator, and foundation last.
                  unchecked {
                    // Safe math is not required when dividing by a non-zero constant.
                    creatorRev = price / CREATOR_ROYALTY_DENOMINATOR;
                  }
                  sellerRev = price - totalFees - creatorRev;
                }
                // Cap the max number of recipients supported
                creatorRecipients.capLength(MAX_ROYALTY_RECIPIENTS);
                creatorShares.capLength(MAX_ROYALTY_RECIPIENTS);
                // Calculate the seller referrer fee when some revenue is awarded to the creator
                if (sellerReferrerTakeRateInBasisPoints != 0) {
                  sellerReferrerFee = (price * sellerReferrerTakeRateInBasisPoints) / BASIS_POINTS;
                  // Subtract the seller referrer fee from the seller revenue so we do not double pay.
                  if (sellerRev == 0) {
                    // If the seller revenue is 0, this is a primary sale where all seller revenue is attributed to the "creator".
                    creatorRev -= sellerReferrerFee;
                  } else {
                    sellerRev -= sellerReferrerFee;
                  }
                }
                // Sum the total shares defined
                uint256 totalShares;
                if (creatorRecipients.length > 1) {
                  unchecked {
                    for (uint256 i = 0; i < creatorRecipients.length; ++i) {
                      if (creatorRecipients[i] == seller) {
                        // If the seller is any of the recipients defined, assume a primary sale
                        creatorRev += sellerRev;
                        sellerRev = 0;
                      }
                      if (totalShares != type(uint256).max) {
                        if (creatorShares[i] > BASIS_POINTS) {
                          // If the numbers are >100% we ignore the fee recipients and pay just the first instead
                          totalShares = type(uint256).max;
                          // Continue the loop in order to detect a potential primary sale condition
                        } else {
                          totalShares += creatorShares[i];
                        }
                      }
                    }
                  }
                  if (totalShares == 0 || totalShares == type(uint256).max) {
                    // If no shares were defined or shares were out of bounds, pay only the first recipient
                    creatorRecipients.capLength(1);
                    creatorShares.capLength(1);
                  }
                }
                // Send payouts to each additional recipient if more than 1 was defined
                uint256 totalRoyaltiesDistributed;
                for (uint256 i = 1; i < creatorRecipients.length; ) {
                  uint256 royalty = (creatorRev * creatorShares[i]) / totalShares;
                  totalRoyaltiesDistributed += royalty;
                  creatorShares[i] = royalty;
                  unchecked {
                    ++i;
                  }
                }
                // Send the remainder to the 1st creator, rounding in their favor
                creatorShares[0] = creatorRev - totalRoyaltiesDistributed;
              } else {
                // No royalty recipients found.
                unchecked {
                  // totalFees is always < price.
                  sellerRev = price - totalFees;
                }
                // Calculate the seller referrer fee when there is no creator royalty
                if (sellerReferrerTakeRateInBasisPoints != 0) {
                  sellerReferrerFee = (price * sellerReferrerTakeRateInBasisPoints) / BASIS_POINTS;
                  sellerRev -= sellerReferrerFee;
                }
              }
              if (buyReferrer != address(0) && buyReferrer != _msgSender() && buyReferrer != seller && buyReferrer != creator) {
                unchecked {
                  buyReferrerFee = price / BUY_REFERRER_FEE_DENOMINATOR;
                  // buyReferrerFee is always <= totalFees
                  totalFees -= buyReferrerFee;
                }
              }
            }
            /**
             * @notice Calculates the protocol fee for the given NFT contract.
             * @dev This returns the contract's default fee but may be overridden to change fees based on the collection type.
             */
            function _getProtocolFee(address /* nftContract */) internal view virtual returns (uint256 protocolFeeInBasisPoints) {
              protocolFeeInBasisPoints = DEFAULT_PROTOCOL_FEE_IN_BASIS_POINTS;
            }
            /**
             * @notice This empty reserved space is put in place to allow future versions to add new
             * variables without shifting down storage in the inheritance chain.
             * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
             */
            uint256[500] private __gap;
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "./FETHNode.sol";
          /**
           * @title A place for common modifiers and functions used by various market mixins, if any.
           * @dev This also leaves a gap which can be used to add a new mixin to the top of the inheritance tree.
           * @author batu-inal & HardlyDifficult
           */
          abstract contract MarketSharedCore is FETHNode {
            /**
             * @notice Checks who the seller for an NFT is if listed in this market.
             * @param nftContract The address of the NFT contract.
             * @param tokenId The id of the NFT.
             * @return seller The seller which listed this NFT for sale, or address(0) if not listed.
             */
            function getSellerOf(address nftContract, uint256 tokenId) external view returns (address payable seller) {
              seller = _getSellerOf(nftContract, tokenId);
            }
            /**
             * @notice Checks who the seller for an NFT is if listed in this market.
             */
            function _getSellerOf(address nftContract, uint256 tokenId) internal view virtual returns (address payable seller);
            /**
             * @notice Checks who the seller for an NFT is if listed in this market or returns the current owner.
             */
            function _getSellerOrOwnerOf(
              address nftContract,
              uint256 tokenId
            ) internal view virtual returns (address payable sellerOrOwner);
            /**
             * @notice This empty reserved space is put in place to allow future versions to add new
             * variables without shifting down storage in the inheritance chain.
             * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
             */
            uint256[500] private __gap;
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts/utils/Context.sol";
          import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
          error RouterContext_Not_A_Contract();
          /**
           * @title Enables a trusted router contract to override the usual msg.sender address.
           * @author HardlyDifficult
           */
          abstract contract RouterContext is Context {
            using AddressUpgradeable for address;
            address private immutable approvedRouter;
            constructor(address router) {
              if (!router.isContract()) {
                revert RouterContext_Not_A_Contract();
              }
              approvedRouter = router;
            }
            /**
             * @notice Returns the router contract which is able to override the msg.sender address.
             * @return router The address of the trusted router.
             */
            function getApprovedRouterAddress() external view returns (address router) {
              router = approvedRouter;
            }
            /**
             * @notice Returns the sender of the transaction.
             * @dev If the msg.sender is the trusted router contract, then the last 20 bytes of the calldata is the authorized
             * sender.
             */
            function _msgSender() internal view virtual override returns (address sender) {
              sender = super._msgSender();
              if (sender == approvedRouter) {
                assembly {
                  // The router appends the msg.sender to the end of the calldata
                  // source: https://github.com/opengsn/gsn/blob/v3.0.0-beta.3/packages/contracts/src/ERC2771Recipient.sol#L48
                  sender := shr(96, calldataload(sub(calldatasize(), 20)))
                }
              }
            }
          }
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
          import "./FETHNode.sol";
          /**
           * @title A mixin for sending ETH with a fallback withdraw mechanism.
           * @notice Attempt to send ETH and if the transfer fails or runs out of gas, store the balance
           * in the FETH token contract for future withdrawal instead.
           * @dev This mixin was recently switched to escrow funds in FETH.
           * Once we have confirmed all pending balances have been withdrawn, we can remove the escrow tracking here.
           * @author batu-inal & HardlyDifficult
           */
          abstract contract SendValueWithFallbackWithdraw is FETHNode {
            using AddressUpgradeable for address payable;
            /// @dev Removing old unused variables in an upgrade safe way.
            uint256 private __gap_was_pendingWithdrawals;
            /**
             * @notice Emitted when escrowed funds are withdrawn to FETH.
             * @param user The account which has withdrawn ETH.
             * @param amount The amount of ETH which has been withdrawn.
             */
            event WithdrawalToFETH(address indexed user, uint256 amount);
            /**
             * @notice Attempt to send a user or contract ETH.
             * If it fails store the amount owned for later withdrawal in FETH.
             * @dev This may fail when sending ETH to a contract that is non-receivable or exceeds the gas limit specified.
             */
            function _sendValueWithFallbackWithdraw(address payable user, uint256 amount, uint256 gasLimit) internal {
              if (amount == 0) {
                return;
              }
              // Cap the gas to prevent consuming all available gas to block a tx from completing successfully
              // solhint-disable-next-line avoid-low-level-calls
              (bool success, ) = user.call{ value: amount, gas: gasLimit }("");
              if (!success) {
                // Store the funds that failed to send for the user in the FETH token
                feth.depositFor{ value: amount }(user);
                emit WithdrawalToFETH(user, amount);
              }
            }
            /**
             * @notice This empty reserved space is put in place to allow future versions to add new
             * variables without shifting down storage in the inheritance chain.
             * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
             */
            uint256[999] private __gap;
          }
          /*
            ・
             * ★
                ・ 。
                   ・ ゚☆ 。
                * ★ ゚・。 *  。
                      * ☆ 。・゚*.。
                   ゚ *.。☆。★ ・
          ​
                                `                     .-:::::-.`              `-::---...```
                               `-:`               .:+ssssoooo++//:.`       .-/+shhhhhhhhhhhhhyyyssooo:
                              .--::.            .+ossso+/////++/:://-`   .////+shhhhhhhhhhhhhhhhhhhhhy
                            `-----::.         `/+////+++///+++/:--:/+/-  -////+shhhhhhhhhhhhhhhhhhhhhy
                           `------:::-`      `//-.``.-/+ooosso+:-.-/oso- -////+shhhhhhhhhhhhhhhhhhhhhy
                          .--------:::-`     :+:.`  .-/osyyyyyyso++syhyo.-////+shhhhhhhhhhhhhhhhhhhhhy
                        `-----------:::-.    +o+:-.-:/oyhhhhhhdhhhhhdddy:-////+shhhhhhhhhhhhhhhhhhhhhy
                       .------------::::--  `oys+/::/+shhhhhhhdddddddddy/-////+shhhhhhhhhhhhhhhhhhhhhy
                      .--------------:::::-` +ys+////+yhhhhhhhddddddddhy:-////+yhhhhhhhhhhhhhhhhhhhhhy
                    `----------------::::::-`.ss+/:::+oyhhhhhhhhhhhhhhho`-////+shhhhhhhhhhhhhhhhhhhhhy
                   .------------------:::::::.-so//::/+osyyyhhhhhhhhhys` -////+shhhhhhhhhhhhhhhhhhhhhy
                 `.-------------------::/:::::..+o+////+oosssyyyyyyys+`  .////+shhhhhhhhhhhhhhhhhhhhhy
                 .--------------------::/:::.`   -+o++++++oooosssss/.     `-//+shhhhhhhhhhhhhhhhhhhhyo
               .-------   ``````.......--`        `-/+ooooosso+/-`          `./++++///:::--...``hhhhyo
                                                        `````
             * 
                ・ 。
              ・  ゚☆ 。
                * ★ ゚・。 *  。
                      * ☆ 。・゚*.。
                   ゚ *.。☆。★ ・
              *  ゚。·*・。 ゚*
               ☆゚・。°*. ゚
            ・ ゚*。・゚★。
            ・ *゚。   *
           ・゚*。★・
           ☆∴。 *
          ・ 。
          */
          // SPDX-License-Identifier: MIT OR Apache-2.0
          pragma solidity ^0.8.12;
          import "@openzeppelin/contracts/utils/Context.sol";
          import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
          import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
          import "./mixins/shared/Constants.sol";
          import "./mixins/shared/FETHNode.sol";
          import "./mixins/shared/FoundationTreasuryNode.sol";
          import "./mixins/shared/MarketFees.sol";
          import "./mixins/shared/MarketSharedCore.sol";
          import "./mixins/shared/RouterContext.sol";
          import "./mixins/shared/SendValueWithFallbackWithdraw.sol";
          import "./mixins/nftMarket/NFTMarketAuction.sol";
          import "./mixins/nftMarket/NFTMarketBuyPrice.sol";
          import "./mixins/nftMarket/NFTMarketCore.sol";
          import "./mixins/nftMarket/NFTMarketOffer.sol";
          import "./mixins/nftMarket/NFTMarketPrivateSaleGap.sol";
          import "./mixins/nftMarket/NFTMarketReserveAuction.sol";
          import "./mixins/nftMarket/NFTMarketExhibition.sol";
          /**
           * @title A market for NFTs on Foundation.
           * @notice The Foundation marketplace is a contract which allows traders to buy and sell NFTs.
           * It supports buying and selling via auctions, private sales, buy price, and offers.
           * @dev All sales in the Foundation market will pay the creator 10% royalties on secondary sales. This is not specific
           * to NFTs minted on Foundation, it should work for any NFT. If royalty information was not defined when the NFT was
           * originally deployed, it may be added using the [Royalty Registry](https://royaltyregistry.xyz/) which will be
           * respected by our market contract.
           * @author batu-inal & HardlyDifficult
           */
          contract NFTMarket is
            Initializable,
            FoundationTreasuryNode,
            Context,
            RouterContext,
            FETHNode,
            MarketSharedCore,
            NFTMarketCore,
            ReentrancyGuardUpgradeable,
            SendValueWithFallbackWithdraw,
            MarketFees,
            NFTMarketExhibition,
            NFTMarketAuction,
            NFTMarketReserveAuction,
            NFTMarketPrivateSaleGap,
            NFTMarketBuyPrice,
            NFTMarketOffer
          {
            /**
             * @notice Set immutable variables for the implementation contract.
             * @dev Using immutable instead of constants allows us to use different values on testnet.
             * @param treasury The Foundation Treasury contract address.
             * @param feth The FETH ERC-20 token contract address.
             * @param royaltyRegistry The Royalty Registry contract address.
             * @param duration The duration of the auction in seconds.
             */
            constructor(
              address payable treasury,
              address feth,
              address royaltyRegistry,
              uint256 duration,
              address router
            )
              FoundationTreasuryNode(treasury)
              FETHNode(feth)
              MarketFees(
                /* protocolFeeInBasisPoints: */
                500,
                royaltyRegistry,
                /* assumePrimarySale: */
                false
              )
              NFTMarketReserveAuction(duration)
              RouterContext(router)
            {
              _disableInitializers();
            }
            /**
             * @notice Called once to configure the contract after the initial proxy deployment.
             * @dev This farms the initialize call out to inherited contracts as needed to initialize mutable variables.
             */
            function initialize() external initializer {
              NFTMarketAuction._initializeNFTMarketAuction();
            }
            /**
             * @inheritdoc NFTMarketCore
             */
            function _beforeAuctionStarted(
              address nftContract,
              uint256 tokenId
            ) internal override(NFTMarketCore, NFTMarketBuyPrice, NFTMarketOffer) {
              // This is a no-op function required to avoid compile errors.
              super._beforeAuctionStarted(nftContract, tokenId);
            }
            /**
             * @inheritdoc NFTMarketCore
             */
            function _transferFromEscrow(
              address nftContract,
              uint256 tokenId,
              address recipient,
              address authorizeSeller
            ) internal override(NFTMarketCore, NFTMarketReserveAuction, NFTMarketBuyPrice) {
              // This is a no-op function required to avoid compile errors.
              super._transferFromEscrow(nftContract, tokenId, recipient, authorizeSeller);
            }
            /**
             * @inheritdoc NFTMarketCore
             */
            function _transferFromEscrowIfAvailable(
              address nftContract,
              uint256 tokenId,
              address recipient
            ) internal override(NFTMarketCore, NFTMarketReserveAuction, NFTMarketBuyPrice) {
              // This is a no-op function required to avoid compile errors.
              super._transferFromEscrowIfAvailable(nftContract, tokenId, recipient);
            }
            /**
             * @inheritdoc NFTMarketCore
             */
            function _transferToEscrow(
              address nftContract,
              uint256 tokenId
            ) internal override(NFTMarketCore, NFTMarketReserveAuction, NFTMarketBuyPrice) {
              // This is a no-op function required to avoid compile errors.
              super._transferToEscrow(nftContract, tokenId);
            }
            /**
             * @inheritdoc MarketSharedCore
             */
            function _getSellerOf(
              address nftContract,
              uint256 tokenId
            )
              internal
              view
              override(MarketSharedCore, NFTMarketCore, NFTMarketReserveAuction, NFTMarketBuyPrice)
              returns (address payable seller)
            {
              // This is a no-op function required to avoid compile errors.
              seller = super._getSellerOf(nftContract, tokenId);
            }
            /**
             * @inheritdoc RouterContext
             */
            function _msgSender() internal view override(Context, RouterContext) returns (address sender) {
              sender = super._msgSender();
            }
          }