ETH Price: $2,418.39 (-0.77%)

Transaction Decoder

Block:
17887588 at Aug-10-2023 10:52:23 PM +UTC
Transaction Fee:
0.00206285976340133 ETH $4.99
Gas Used:
115,510 Gas / 17.858711483 Gwei

Emitted Events:

388 NFTCollection.Approval( owner=AdminUpgradeabilityProxy, approved=0x00000000...000000000, tokenId=42 )
389 NFTCollection.Transfer( from=AdminUpgradeabilityProxy, to=[Sender] 0x65039d4327e54dd8e3c5388ce8e8a741e3b019f2, tokenId=42 )
390 AdminUpgradeabilityProxy.0x14b9c40404d5b41deb481f9a40b8aeb2bf4b47679b38cf757075a66ed510f7f1( 0x14b9c40404d5b41deb481f9a40b8aeb2bf4b47679b38cf757075a66ed510f7f1, 0x0000000000000000000000000000000000000000000000000000000000052615 )

Account State Difference:

  Address   Before After State Difference Code
0x65039d43...1e3b019f2
2.366383680544872481 Eth
Nonce: 268
2.364320820781471151 Eth
Nonce: 269
0.00206285976340133
(beaverbuild)
19.512230425790510353 Eth19.512241976790510353 Eth0.000011551
0x9C170e8e...E9b24a28D
0xcDA72070...3623d0B6f
(Foundation: Market)

Execution Trace

TransparentUpgradeableProxy.03af8f42( )
  • NFTMarketRouter.upsertListing( nftContract=0x9C170e8e616fdBf61934946a282656eE9b24a28D, tokenId=42, exhibitionId=0, reservePrice=0, auctionDuration=0, shouldSetBuyPrice=False, buyPrice=0 )
    • AdminUpgradeabilityProxy.2ab2b52b( )
      • NFTMarket.getReserveAuctionIdFor( nftContract=0x9C170e8e616fdBf61934946a282656eE9b24a28D, tokenId=42 ) => ( auctionId=337429 )
      • AdminUpgradeabilityProxy.9e79b41f( )
        • NFTMarket.getReserveAuction( auctionId=337429 ) => ( auction=[{name:nftContract, type:address, order:1, indexed:false, value:0x9C170e8e616fdBf61934946a282656eE9b24a28D, valueString:0x9C170e8e616fdBf61934946a282656eE9b24a28D}, {name:tokenId, type:uint256, order:2, indexed:false, value:42, valueString:42}, {name:seller, type:address, order:3, indexed:false, value:0x65039d4327e54Dd8e3C5388CE8e8A741e3b019f2, valueString:0x65039d4327e54Dd8e3C5388CE8e8A741e3b019f2}, {name:duration, type:uint256, order:4, indexed:false, value:86400, valueString:86400}, {name:extensionDuration, type:uint256, order:5, indexed:false, value:900, valueString:900}, {name:endTime, type:uint256, order:6, indexed:false, value:0, valueString:0}, {name:bidder, type:address, order:7, indexed:false, value:0x0000000000000000000000000000000000000000, valueString:0x0000000000000000000000000000000000000000}, {name:amount, type:uint256, order:8, indexed:false, value:100000000000000000, valueString:100000000000000000}] )
        • AdminUpgradeabilityProxy.4635256e( )
          • NFTMarket.getBuyPrice( nftContract=0x9C170e8e616fdBf61934946a282656eE9b24a28D, tokenId=42 ) => ( seller=0x0000000000000000000000000000000000000000, price=115792089237316195423570985008687907853269984665640564039457584007913129639935 )
          • AdminUpgradeabilityProxy.21506fff( )
            • NFTMarket.cancelReserveAuction( auctionId=337429 )
              • NFTCollection.transferFrom( from=0xcDA72070E455bb31C7690a170224Ce43623d0B6f, to=0x65039d4327e54Dd8e3C5388CE8e8A741e3b019f2, tokenId=42 )
                • NFTCollection.transferFrom( from=0xcDA72070E455bb31C7690a170224Ce43623d0B6f, to=0x65039d4327e54Dd8e3C5388CE8e8A741e3b019f2, tokenId=42 )
                  upsertListing[NFTMarketRouterList (ln:1305)]
                  File 1 of 6: TransparentUpgradeableProxy
                  // SPDX-License-Identifier: MIT
                  pragma solidity ^0.8.0;
                  import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
                  import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
                  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 "./IBeacon.sol";
                  import "../Proxy.sol";
                  import "../ERC1967/ERC1967Upgrade.sol";
                  /**
                   * @dev This contract implements a proxy that gets the implementation address for each call from a {UpgradeableBeacon}.
                   *
                   * The beacon address is stored in storage slot `uint256(keccak256('eip1967.proxy.beacon')) - 1`, so that it doesn't
                   * conflict with the storage layout of the implementation behind the proxy.
                   *
                   * _Available since v3.4._
                   */
                  contract BeaconProxy is Proxy, ERC1967Upgrade {
                      /**
                       * @dev Initializes the proxy with `beacon`.
                       *
                       * If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon. This
                       * will typically be an encoded function call, and allows initializating the storage of the proxy like a Solidity
                       * constructor.
                       *
                       * Requirements:
                       *
                       * - `beacon` must be a contract with the interface {IBeacon}.
                       */
                      constructor(address beacon, bytes memory data) payable {
                          assert(_BEACON_SLOT == bytes32(uint256(keccak256("eip1967.proxy.beacon")) - 1));
                          _upgradeBeaconToAndCall(beacon, data, false);
                      }
                      /**
                       * @dev Returns the current beacon address.
                       */
                      function _beacon() internal view virtual returns (address) {
                          return _getBeacon();
                      }
                      /**
                       * @dev Returns the current implementation address of the associated beacon.
                       */
                      function _implementation() internal view virtual override returns (address) {
                          return IBeacon(_getBeacon()).implementation();
                      }
                      /**
                       * @dev Changes the proxy to use a new beacon. Deprecated: see {_upgradeBeaconToAndCall}.
                       *
                       * If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon.
                       *
                       * Requirements:
                       *
                       * - `beacon` must be a contract.
                       * - The implementation returned by `beacon` must be a contract.
                       */
                      function _setBeacon(address beacon, bytes memory data) internal virtual {
                          _upgradeBeaconToAndCall(beacon, data, false);
                      }
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity ^0.8.0;
                  import "./IBeacon.sol";
                  import "../../access/Ownable.sol";
                  import "../../utils/Address.sol";
                  /**
                   * @dev This contract is used in conjunction with one or more instances of {BeaconProxy} to determine their
                   * implementation contract, which is where they will delegate all function calls.
                   *
                   * An owner is able to change the implementation the beacon points to, thus upgrading the proxies that use this beacon.
                   */
                  contract UpgradeableBeacon is IBeacon, Ownable {
                      address private _implementation;
                      /**
                       * @dev Emitted when the implementation returned by the beacon is changed.
                       */
                      event Upgraded(address indexed implementation);
                      /**
                       * @dev Sets the address of the initial implementation, and the deployer account as the owner who can upgrade the
                       * beacon.
                       */
                      constructor(address implementation_) {
                          _setImplementation(implementation_);
                      }
                      /**
                       * @dev Returns the current implementation address.
                       */
                      function implementation() public view virtual override returns (address) {
                          return _implementation;
                      }
                      /**
                       * @dev Upgrades the beacon to a new implementation.
                       *
                       * Emits an {Upgraded} event.
                       *
                       * Requirements:
                       *
                       * - msg.sender must be the owner of the contract.
                       * - `newImplementation` must be a contract.
                       */
                      function upgradeTo(address newImplementation) public virtual onlyOwner {
                          _setImplementation(newImplementation);
                          emit Upgraded(newImplementation);
                      }
                      /**
                       * @dev Sets the implementation contract address for this beacon
                       *
                       * Requirements:
                       *
                       * - `newImplementation` must be a contract.
                       */
                      function _setImplementation(address newImplementation) private {
                          require(Address.isContract(newImplementation), "UpgradeableBeacon: implementation is not a contract");
                          _implementation = newImplementation;
                      }
                  }
                  // 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 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 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 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;
                      }
                  }
                  

                  File 2 of 6: NFTCollection
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.12;
                  import "./interfaces/internal/INFTCollectionInitializer.sol";
                  import "./interfaces/standards/royalties/IGetRoyalties.sol";
                  import "./interfaces/standards/royalties/ITokenCreator.sol";
                  import "./interfaces/standards/royalties/IGetFees.sol";
                  import "./interfaces/standards/royalties/IRoyaltyInfo.sol";
                  import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
                  import "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol";
                  import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
                  import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
                  import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
                  import "./libraries/AddressLibrary.sol";
                  import "./mixins/collections/SequentialMintCollection.sol";
                  import "./mixins/collections/CollectionRoyalties.sol";
                  import "./mixins/shared/ContractFactory.sol";
                  /**
                   * @title A collection of NFTs by a single creator.
                   * @notice All NFTs from this contract are minted by the same creator.
                   * A 10% royalty to the creator is included which may be split with collaborators on a per-NFT basis.
                   * @author batu-inal & HardlyDifficult
                   */
                  contract NFTCollection is
                    INFTCollectionInitializer,
                    IGetRoyalties,
                    IGetFees,
                    IRoyaltyInfo,
                    ITokenCreator,
                    ContractFactory,
                    Initializable,
                    ERC165Upgradeable,
                    ERC721Upgradeable,
                    ERC721BurnableUpgradeable,
                    SequentialMintCollection,
                    CollectionRoyalties
                  {
                    using AddressLibrary for address;
                    using AddressUpgradeable for address;
                    /**
                     * @notice The baseURI to use for the tokenURI, if undefined then `ipfs://` is used.
                     */
                    string private baseURI_;
                    /**
                     * @notice Stores hashes minted to prevent duplicates.
                     * @dev 0 means not yet minted, set to 1 when minted.
                     * For why using uint is better than using bool here:
                     * github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.3/contracts/security/ReentrancyGuard.sol#L23-L27
                     */
                    mapping(string => uint256) private cidToMinted;
                    /**
                     * @dev Stores an optional alternate address to receive creator revenue and royalty payments.
                     * The target address may be a contract which could split or escrow payments.
                     */
                    mapping(uint256 => address payable) private tokenIdToCreatorPaymentAddress;
                    /**
                     * @dev Stores a CID for each NFT.
                     */
                    mapping(uint256 => string) private _tokenCIDs;
                    /**
                     * @notice Emitted when the owner changes the base URI to be used for NFTs in this collection.
                     * @param baseURI The new base URI to use.
                     */
                    event BaseURIUpdated(string baseURI);
                    /**
                     * @notice Emitted when a new NFT is minted.
                     * @param creator The address of the collection owner at this time this NFT was minted.
                     * @param tokenId The tokenId of the newly minted NFT.
                     * @param indexedTokenCID The CID of the newly minted NFT, indexed to enable watching for mint events by the tokenCID.
                     * @param tokenCID The actual CID of the newly minted NFT.
                     */
                    event Minted(address indexed creator, uint256 indexed tokenId, string indexed indexedTokenCID, string tokenCID);
                    /**
                     * @notice Emitted when the payment address for creator royalties is set.
                     * @param fromPaymentAddress The original address used for royalty payments.
                     * @param toPaymentAddress The new address used for royalty payments.
                     * @param tokenId The NFT which had the royalty payment address updated.
                     */
                    event TokenCreatorPaymentAddressSet(
                      address indexed fromPaymentAddress,
                      address indexed toPaymentAddress,
                      uint256 indexed tokenId
                    );
                    /**
                     * @notice Initialize the template's immutable variables.
                     * @param _contractFactory The factory which will be used to create collection contracts.
                     */
                    constructor(address _contractFactory)
                      ContractFactory(_contractFactory) // solhint-disable-next-line no-empty-blocks
                    {}
                    /**
                     * @notice Called by the contract factory on creation.
                     * @param _creator The creator of this collection.
                     * @param _name The collection's `name`.
                     * @param _symbol The collection's `symbol`.
                     */
                    function initialize(
                      address payable _creator,
                      string calldata _name,
                      string calldata _symbol
                    ) external initializer onlyContractFactory {
                      __ERC721_init(_name, _symbol);
                      _initializeSequentialMintCollection(_creator, 0);
                    }
                    /**
                     * @notice Allows the creator to burn a specific token if they currently own the NFT.
                     * @param tokenId The ID of the NFT to burn.
                     * @dev The function here asserts `onlyOwner` while the super confirms ownership.
                     */
                    function burn(uint256 tokenId) public override onlyOwner {
                      super.burn(tokenId);
                    }
                    /**
                     * @notice Mint an NFT defined by its metadata path.
                     * @dev This is only callable by the collection creator/owner.
                     * @param tokenCID The CID for the metadata json of the NFT to mint.
                     * @return tokenId The tokenId of the newly minted NFT.
                     */
                    function mint(string calldata tokenCID) external returns (uint256 tokenId) {
                      tokenId = _mint(tokenCID);
                    }
                    /**
                     * @notice Mint an NFT defined by its metadata path and approves the provided operator address.
                     * @dev This is only callable by the collection creator/owner.
                     * It can be used the first time they mint to save having to issue a separate approval
                     * transaction before listing the NFT for sale.
                     * @param tokenCID The CID for the metadata json of the NFT to mint.
                     * @param operator The address to set as an approved operator for the creator's account.
                     * @return tokenId The tokenId of the newly minted NFT.
                     */
                    function mintAndApprove(string calldata tokenCID, address operator) external returns (uint256 tokenId) {
                      tokenId = _mint(tokenCID);
                      setApprovalForAll(operator, true);
                    }
                    /**
                     * @notice Mint an NFT defined by its metadata path and have creator revenue/royalties sent to an alternate address.
                     * @dev This is only callable by the collection creator/owner.
                     * @param tokenCID The CID for the metadata json of the NFT to mint.
                     * @param tokenCreatorPaymentAddress The royalty recipient address to use for this NFT.
                     * @return tokenId The tokenId of the newly minted NFT.
                     */
                    function mintWithCreatorPaymentAddress(string calldata tokenCID, address payable tokenCreatorPaymentAddress)
                      public
                      returns (uint256 tokenId)
                    {
                      require(tokenCreatorPaymentAddress != address(0), "NFTCollection: tokenCreatorPaymentAddress is required");
                      tokenId = _mint(tokenCID);
                      tokenIdToCreatorPaymentAddress[tokenId] = tokenCreatorPaymentAddress;
                      emit TokenCreatorPaymentAddressSet(address(0), tokenCreatorPaymentAddress, tokenId);
                    }
                    /**
                     * @notice Mint an NFT defined by its metadata path and approves the provided operator address.
                     * @dev This is only callable by the collection creator/owner.
                     * It can be used the first time they mint to save having to issue a separate approval
                     * transaction before listing the NFT for sale.
                     * @param tokenCID The CID for the metadata json of the NFT to mint.
                     * @param tokenCreatorPaymentAddress The royalty recipient address to use for this NFT.
                     * @param operator The address to set as an approved operator for the creator's account.
                     * @return tokenId The tokenId of the newly minted NFT.
                     */
                    function mintWithCreatorPaymentAddressAndApprove(
                      string calldata tokenCID,
                      address payable tokenCreatorPaymentAddress,
                      address operator
                    ) external returns (uint256 tokenId) {
                      tokenId = mintWithCreatorPaymentAddress(tokenCID, tokenCreatorPaymentAddress);
                      setApprovalForAll(operator, true);
                    }
                    /**
                     * @notice Mint an NFT defined by its metadata path and have creator revenue/royalties sent to an alternate address
                     * which is defined by a contract call, typically a proxy contract address representing the payment terms.
                     * @dev This is only callable by the collection creator/owner.
                     * @param tokenCID The CID for the metadata json of the NFT to mint.
                     * @param paymentAddressFactory The contract to call which will return the address to use for payments.
                     * @param paymentAddressCall The call details to send to the factory provided.
                     * @return tokenId The tokenId of the newly minted NFT.
                     */
                    function mintWithCreatorPaymentFactory(
                      string calldata tokenCID,
                      address paymentAddressFactory,
                      bytes calldata paymentAddressCall
                    ) public returns (uint256 tokenId) {
                      address payable tokenCreatorPaymentAddress = paymentAddressFactory.callAndReturnContractAddress(paymentAddressCall);
                      tokenId = mintWithCreatorPaymentAddress(tokenCID, tokenCreatorPaymentAddress);
                    }
                    /**
                     * @notice Mint an NFT defined by its metadata path and have creator revenue/royalties sent to an alternate address
                     * which is defined by a contract call, typically a proxy contract address representing the payment terms.
                     * @dev This is only callable by the collection creator/owner.
                     * It can be used the first time they mint to save having to issue a separate approval
                     * transaction before listing the NFT for sale.
                     * @param tokenCID The CID for the metadata json of the NFT to mint.
                     * @param paymentAddressFactory The contract to call which will return the address to use for payments.
                     * @param paymentAddressCall The call details to send to the factory provided.
                     * @param operator The address to set as an approved operator for the creator's account.
                     * @return tokenId The tokenId of the newly minted NFT.
                     */
                    function mintWithCreatorPaymentFactoryAndApprove(
                      string calldata tokenCID,
                      address paymentAddressFactory,
                      bytes calldata paymentAddressCall,
                      address operator
                    ) external returns (uint256 tokenId) {
                      tokenId = mintWithCreatorPaymentFactory(tokenCID, paymentAddressFactory, paymentAddressCall);
                      setApprovalForAll(operator, true);
                    }
                    /**
                     * @notice Allows the collection creator 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 onlyOwner {
                      _selfDestruct();
                    }
                    /**
                     * @notice Allows the owner to assign a baseURI to use for the tokenURI instead of the default `ipfs://`.
                     * @param baseURIOverride The new base URI to use for all NFTs in this collection.
                     */
                    function updateBaseURI(string calldata baseURIOverride) external onlyOwner {
                      baseURI_ = baseURIOverride;
                      emit BaseURIUpdated(baseURIOverride);
                    }
                    /**
                     * @notice Allows the owner to set a max tokenID.
                     * This provides a guarantee to collectors about the limit of this collection contract, if applicable.
                     * @dev Once this value has been set, it may be decreased but can never be increased.
                     * This max may be more than the final `totalSupply` if 1 or more tokens were burned.
                     * @param _maxTokenId The max tokenId to set, all NFTs must have a tokenId less than or equal to this value.
                     */
                    function updateMaxTokenId(uint32 _maxTokenId) external onlyOwner {
                      _updateMaxTokenId(_maxTokenId);
                    }
                    function _burn(uint256 tokenId) internal override(ERC721Upgradeable, SequentialMintCollection) {
                      delete cidToMinted[_tokenCIDs[tokenId]];
                      delete tokenIdToCreatorPaymentAddress[tokenId];
                      delete _tokenCIDs[tokenId];
                      super._burn(tokenId);
                    }
                    function _mint(string calldata tokenCID) private onlyOwner returns (uint256 tokenId) {
                      require(bytes(tokenCID).length != 0, "NFTCollection: tokenCID is required");
                      require(cidToMinted[tokenCID] == 0, "NFTCollection: NFT was already minted");
                      // Number of tokens cannot realistically overflow 32 bits.
                      tokenId = ++latestTokenId;
                      require(maxTokenId == 0 || tokenId <= maxTokenId, "NFTCollection: Max token count has already been minted");
                      cidToMinted[tokenCID] = 1;
                      _tokenCIDs[tokenId] = tokenCID;
                      _safeMint(msg.sender, tokenId);
                      emit Minted(msg.sender, tokenId, tokenCID, tokenCID);
                    }
                    /**
                     * @notice The base URI used for all NFTs in this collection.
                     * @dev The `tokenCID` is appended to this to obtain an NFT's `tokenURI`.
                     *      e.g. The URI for a token with the `tokenCID`: "foo" and `baseURI`: "ipfs://" is "ipfs://foo".
                     * @return uri The base URI used by this collection.
                     */
                    function baseURI() external view returns (string memory uri) {
                      uri = _baseURI();
                    }
                    /**
                     * @notice Checks if the creator has already minted a given NFT using this collection contract.
                     * @param tokenCID The CID to check for.
                     * @return hasBeenMinted True if the creator has already minted an NFT with this CID.
                     */
                    function getHasMintedCID(string calldata tokenCID) external view returns (bool hasBeenMinted) {
                      hasBeenMinted = cidToMinted[tokenCID] != 0;
                    }
                    /**
                     * @inheritdoc CollectionRoyalties
                     */
                    function getTokenCreatorPaymentAddress(uint256 tokenId)
                      public
                      view
                      override
                      returns (address payable creatorPaymentAddress)
                    {
                      creatorPaymentAddress = tokenIdToCreatorPaymentAddress[tokenId];
                      if (creatorPaymentAddress == address(0)) {
                        creatorPaymentAddress = owner;
                      }
                    }
                    /**
                     * @inheritdoc IERC165Upgradeable
                     */
                    function supportsInterface(bytes4 interfaceId)
                      public
                      view
                      override(ERC165Upgradeable, ERC721Upgradeable, CollectionRoyalties)
                      returns (bool interfaceSupported)
                    {
                      // This is a no-op function required to avoid compile errors.
                      interfaceSupported = super.supportsInterface(interfaceId);
                    }
                    /**
                     * @inheritdoc IERC721MetadataUpgradeable
                     */
                    function tokenURI(uint256 tokenId) public view override returns (string memory uri) {
                      require(_exists(tokenId), "NFTCollection: URI query for nonexistent token");
                      uri = string.concat(_baseURI(), _tokenCIDs[tokenId]);
                    }
                    function _baseURI() internal view override returns (string memory uri) {
                      uri = baseURI_;
                      if (bytes(uri).length == 0) {
                        uri = "ipfs://";
                      }
                    }
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.12;
                  import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
                  struct CallWithoutValue {
                    address target;
                    bytes callData;
                  }
                  /**
                   * @title A library for address helpers not already covered by the OZ library.
                   * @author batu-inal & HardlyDifficult
                   */
                  library AddressLibrary {
                    using AddressUpgradeable for address;
                    using AddressUpgradeable for address payable;
                    /**
                     * @notice Calls an external contract with arbitrary data and parse the return value into an address.
                     * @param externalContract The address of the contract to call.
                     * @param callData The data to send to the contract.
                     * @return contractAddress The address of the contract returned by the call.
                     */
                    function callAndReturnContractAddress(address externalContract, bytes calldata callData)
                      internal
                      returns (address payable contractAddress)
                    {
                      bytes memory returnData = externalContract.functionCall(callData);
                      contractAddress = abi.decode(returnData, (address));
                      require(contractAddress.isContract(), "InternalProxyCall: did not return a contract");
                    }
                    function callAndReturnContractAddress(CallWithoutValue calldata call)
                      internal
                      returns (address payable contractAddress)
                    {
                      contractAddress = callAndReturnContractAddress(call.target, call.callData);
                    }
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.12;
                  /**
                   * @author batu-inal & HardlyDifficult
                   */
                  interface INFTCollectionInitializer {
                    function initialize(
                      address payable _creator,
                      string memory _name,
                      string memory _symbol
                    ) external;
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.12;
                  import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
                  import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
                  import "../../interfaces/standards/royalties/ITokenCreator.sol";
                  /**
                   * @title Extends the OZ ERC721 implementation for collections which mint sequential token IDs.
                   * @author batu-inal & HardlyDifficult
                   */
                  abstract contract SequentialMintCollection is ITokenCreator, Initializable, 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 The max tokenId which can be minted.
                     * @dev This max may be less than the final `totalSupply` if 1 or more tokens were burned.
                     * @return The max tokenId which can be minted.
                     */
                    uint32 public maxTokenId;
                    /**
                     * @notice Tracks how many tokens have been burned.
                     * @dev This number is used to calculate the total supply efficiently.
                     */
                    uint32 private burnCounter;
                    /****** End of storage ******/
                    /**
                     * @notice Emitted when the max tokenId supported by this collection is updated.
                     * @param maxTokenId The new max tokenId. All NFTs in this collection will have a tokenId less than
                     * or equal to this value.
                     */
                    event MaxTokenIdUpdated(uint256 indexed maxTokenId);
                    /**
                     * @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() {
                      require(msg.sender == owner, "SequentialMintCollection: Caller is not owner");
                      _;
                    }
                    function _initializeSequentialMintCollection(address payable _creator, uint32 _maxTokenId) internal onlyInitializing {
                      owner = _creator;
                      maxTokenId = _maxTokenId;
                    }
                    /**
                     * @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 {
                      require(totalSupply() == 0, "SequentialMintCollection: Any NFTs minted must be burned first");
                      emit SelfDestruct(msg.sender);
                      selfdestruct(payable(msg.sender));
                    }
                    /**
                     * @notice Allows the owner to set a max tokenID.
                     * This provides a guarantee to collectors about the limit of this collection contract, if applicable.
                     * @dev Once this value has been set, it may be decreased but can never be increased.
                     * @param _maxTokenId The max tokenId to set, all NFTs must have a tokenId less than or equal to this value.
                     */
                    function _updateMaxTokenId(uint32 _maxTokenId) internal {
                      require(_maxTokenId != 0, "SequentialMintCollection: Max token ID may not be cleared");
                      require(maxTokenId == 0 || _maxTokenId < maxTokenId, "SequentialMintCollection: Max token ID may not increase");
                      require(latestTokenId <= _maxTokenId, "SequentialMintCollection: Max token ID must be >= last mint");
                      maxTokenId = _maxTokenId;
                      emit MaxTokenIdUpdated(_maxTokenId);
                    }
                    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 "@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 "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
                  /**
                   * @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() {
                      require(msg.sender == contractFactory, "ContractFactory: Caller is not the factory");
                      _;
                    }
                    /**
                     * @notice Initialize the template's immutable variables.
                     * @param _contractFactory The factory which will be used to create these contracts.
                     */
                    constructor(address _contractFactory) {
                      require(_contractFactory.isContract(), "ContractFactory: Factory is not a contract");
                      contractFactory = _contractFactory;
                    }
                  }
                  // 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;
                  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;
                  /**
                   * @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;
                  /**
                   * @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
                  // OpenZeppelin Contracts (last updated v4.7.0) (proxy/utils/Initializable.sol)
                  pragma solidity ^0.8.2;
                  import "../../utils/AddressUpgradeable.sol";
                  /**
                   * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
                   * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
                   * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
                   * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
                   *
                   * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
                   * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
                   * case an upgrade adds a module that needs to be initialized.
                   *
                   * For example:
                   *
                   * [.hljs-theme-light.nopadding]
                   * ```
                   * contract MyToken is ERC20Upgradeable {
                   *     function initialize() initializer public {
                   *         __ERC20_init("MyToken", "MTK");
                   *     }
                   * }
                   * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
                   *     function initializeV2() reinitializer(2) public {
                   *         __ERC20Permit_init("MyToken");
                   *     }
                   * }
                   * ```
                   *
                   * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
                   * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
                   *
                   * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
                   * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
                   *
                   * [CAUTION]
                   * ====
                   * Avoid leaving a contract uninitialized.
                   *
                   * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
                   * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
                   * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
                   *
                   * [.hljs-theme-light.nopadding]
                   * ```
                   * /// @custom:oz-upgrades-unsafe-allow constructor
                   * constructor() {
                   *     _disableInitializers();
                   * }
                   * ```
                   * ====
                   */
                  abstract contract Initializable {
                      /**
                       * @dev Indicates that the contract has been initialized.
                       * @custom:oz-retyped-from bool
                       */
                      uint8 private _initialized;
                      /**
                       * @dev Indicates that the contract is in the process of being initialized.
                       */
                      bool private _initializing;
                      /**
                       * @dev Triggered when the contract has been initialized or reinitialized.
                       */
                      event Initialized(uint8 version);
                      /**
                       * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
                       * `onlyInitializing` functions can be used to initialize parent contracts. Equivalent to `reinitializer(1)`.
                       */
                      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.
                       *
                       * `initializer` is equivalent to `reinitializer(1)`, so 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.
                       *
                       * 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.
                       */
                      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.
                       */
                      function _disableInitializers() internal virtual {
                          require(!_initializing, "Initializable: contract is initializing");
                          if (_initialized < type(uint8).max) {
                              _initialized = type(uint8).max;
                              emit Initialized(type(uint8).max);
                          }
                      }
                  }
                  // 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 (last updated v4.7.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 = _owners[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 nor approved for all"
                          );
                          _approve(to, tokenId);
                      }
                      /**
                       * @dev See {IERC721-getApproved}.
                       */
                      function getApproved(uint256 tokenId) public view virtual override returns (address) {
                          _requireMinted(tokenId);
                          return _tokenApprovals[tokenId];
                      }
                      /**
                       * @dev See {IERC721-setApprovalForAll}.
                       */
                      function setApprovalForAll(address operator, bool approved) public virtual override {
                          _setApprovalForAll(_msgSender(), operator, approved);
                      }
                      /**
                       * @dev See {IERC721-isApprovedForAll}.
                       */
                      function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
                          return _operatorApprovals[owner][operator];
                      }
                      /**
                       * @dev See {IERC721-transferFrom}.
                       */
                      function transferFrom(
                          address from,
                          address to,
                          uint256 tokenId
                      ) public virtual override {
                          //solhint-disable-next-line max-line-length
                          require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner nor approved");
                          _transfer(from, to, tokenId);
                      }
                      /**
                       * @dev See {IERC721-safeTransferFrom}.
                       */
                      function safeTransferFrom(
                          address from,
                          address to,
                          uint256 tokenId
                      ) public virtual override {
                          safeTransferFrom(from, to, tokenId, "");
                      }
                      /**
                       * @dev See {IERC721-safeTransferFrom}.
                       */
                      function safeTransferFrom(
                          address from,
                          address to,
                          uint256 tokenId,
                          bytes memory data
                      ) public virtual override {
                          require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner nor approved");
                          _safeTransfer(from, to, tokenId, data);
                      }
                      /**
                       * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
                       * are aware of the ERC721 protocol to prevent tokens from being forever locked.
                       *
                       * `data` is additional data, it has no specified format and it is sent in call to `to`.
                       *
                       * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
                       * implement alternative mechanisms to perform token transfer, such as signature-based.
                       *
                       * Requirements:
                       *
                       * - `from` cannot be the zero address.
                       * - `to` cannot be the zero address.
                       * - `tokenId` token must exist and be owned by `from`.
                       * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
                       *
                       * Emits a {Transfer} event.
                       */
                      function _safeTransfer(
                          address from,
                          address to,
                          uint256 tokenId,
                          bytes memory data
                      ) internal virtual {
                          _transfer(from, to, tokenId);
                          require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer");
                      }
                      /**
                       * @dev Returns whether `tokenId` exists.
                       *
                       * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
                       *
                       * Tokens start existing when they are minted (`_mint`),
                       * and stop existing when they are burned (`_burn`).
                       */
                      function _exists(uint256 tokenId) internal view virtual returns (bool) {
                          return _owners[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);
                          _balances[to] += 1;
                          _owners[tokenId] = to;
                          emit Transfer(address(0), to, tokenId);
                          _afterTokenTransfer(address(0), to, tokenId);
                      }
                      /**
                       * @dev Destroys `tokenId`.
                       * The approval is cleared when the token is burned.
                       *
                       * Requirements:
                       *
                       * - `tokenId` must exist.
                       *
                       * Emits a {Transfer} event.
                       */
                      function _burn(uint256 tokenId) internal virtual {
                          address owner = ERC721Upgradeable.ownerOf(tokenId);
                          _beforeTokenTransfer(owner, address(0), tokenId);
                          // Clear approvals
                          _approve(address(0), tokenId);
                          _balances[owner] -= 1;
                          delete _owners[tokenId];
                          emit Transfer(owner, address(0), tokenId);
                          _afterTokenTransfer(owner, address(0), tokenId);
                      }
                      /**
                       * @dev Transfers `tokenId` from `from` to `to`.
                       *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
                       *
                       * Requirements:
                       *
                       * - `to` cannot be the zero address.
                       * - `tokenId` token must be owned by `from`.
                       *
                       * Emits a {Transfer} event.
                       */
                      function _transfer(
                          address from,
                          address to,
                          uint256 tokenId
                      ) internal virtual {
                          require(ERC721Upgradeable.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
                          require(to != address(0), "ERC721: transfer to the zero address");
                          _beforeTokenTransfer(from, to, tokenId);
                          // Clear approvals from the previous owner
                          _approve(address(0), tokenId);
                          _balances[from] -= 1;
                          _balances[to] += 1;
                          _owners[tokenId] = to;
                          emit Transfer(from, to, tokenId);
                          _afterTokenTransfer(from, to, tokenId);
                      }
                      /**
                       * @dev Approve `to` to operate on `tokenId`
                       *
                       * Emits an {Approval} event.
                       */
                      function _approve(address to, uint256 tokenId) internal virtual {
                          _tokenApprovals[tokenId] = to;
                          emit Approval(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.
                       *
                       * Calling conditions:
                       *
                       * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
                       * transferred to `to`.
                       * - When `from` is zero, `tokenId` will be minted for `to`.
                       * - When `to` is zero, ``from``'s `tokenId` will be burned.
                       * - `from` and `to` are never both zero.
                       *
                       * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
                       */
                      function _beforeTokenTransfer(
                          address from,
                          address to,
                          uint256 tokenId
                      ) internal virtual {}
                      /**
                       * @dev Hook that is called after any transfer of tokens. This includes
                       * minting and burning.
                       *
                       * Calling conditions:
                       *
                       * - when `from` and `to` are both non-zero.
                       * - `from` and `to` are never both zero.
                       *
                       * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
                       */
                      function _afterTokenTransfer(
                          address from,
                          address to,
                          uint256 tokenId
                      ) internal virtual {}
                      /**
                       * @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.7.0) (utils/Address.sol)
                  pragma solidity ^0.8.1;
                  /**
                   * @dev Collection of functions related to the address type
                   */
                  library AddressUpgradeable {
                      /**
                       * @dev Returns true if `account` is a contract.
                       *
                       * [IMPORTANT]
                       * ====
                       * It is unsafe to assume that an address for which this function returns
                       * false is an externally-owned account (EOA) and not a contract.
                       *
                       * Among others, `isContract` will return false for the following
                       * types of addresses:
                       *
                       *  - an externally-owned account
                       *  - a contract in construction
                       *  - an address where a contract will be created
                       *  - an address where a contract lived, but was destroyed
                       * ====
                       *
                       * [IMPORTANT]
                       * ====
                       * You shouldn't rely on `isContract` to protect against flash loan attacks!
                       *
                       * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
                       * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
                       * constructor.
                       * ====
                       */
                      function isContract(address account) internal view returns (bool) {
                          // This method relies on extcodesize/address.code.length, which returns 0
                          // for contracts in construction, since the code is only stored at the end
                          // of the constructor execution.
                          return account.code.length > 0;
                      }
                      /**
                       * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                       * `recipient`, forwarding all available gas and reverting on errors.
                       *
                       * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                       * of certain opcodes, possibly making contracts go over the 2300 gas limit
                       * imposed by `transfer`, making them unable to receive funds via
                       * `transfer`. {sendValue} removes this limitation.
                       *
                       * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                       *
                       * IMPORTANT: because control is transferred to `recipient`, care must be
                       * taken to not create reentrancy vulnerabilities. Consider using
                       * {ReentrancyGuard} or the
                       * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                       */
                      function sendValue(address payable recipient, uint256 amount) internal {
                          require(address(this).balance >= amount, "Address: insufficient balance");
                          (bool success, ) = recipient.call{value: amount}("");
                          require(success, "Address: unable to send value, recipient may have reverted");
                      }
                      /**
                       * @dev Performs a Solidity function call using a low level `call`. A
                       * plain `call` is an unsafe replacement for a function call: use this
                       * function instead.
                       *
                       * If `target` reverts with a revert reason, it is bubbled up by this
                       * function (like regular Solidity function calls).
                       *
                       * Returns the raw returned data. To convert to the expected return value,
                       * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
                       *
                       * Requirements:
                       *
                       * - `target` must be a contract.
                       * - calling `target` with `data` must not revert.
                       *
                       * _Available since v3.1._
                       */
                      function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                          return functionCall(target, data, "Address: low-level call failed");
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
                       * `errorMessage` as a fallback revert reason when `target` reverts.
                       *
                       * _Available since v3.1._
                       */
                      function functionCall(
                          address target,
                          bytes memory data,
                          string memory errorMessage
                      ) internal returns (bytes memory) {
                          return functionCallWithValue(target, data, 0, errorMessage);
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but also transferring `value` wei to `target`.
                       *
                       * Requirements:
                       *
                       * - the calling contract must have an ETH balance of at least `value`.
                       * - the called Solidity function must be `payable`.
                       *
                       * _Available since v3.1._
                       */
                      function functionCallWithValue(
                          address target,
                          bytes memory data,
                          uint256 value
                      ) internal returns (bytes memory) {
                          return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
                      }
                      /**
                       * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
                       * with `errorMessage` as a fallback revert reason when `target` reverts.
                       *
                       * _Available since v3.1._
                       */
                      function functionCallWithValue(
                          address target,
                          bytes memory data,
                          uint256 value,
                          string memory errorMessage
                      ) internal returns (bytes memory) {
                          require(address(this).balance >= value, "Address: insufficient balance for call");
                          require(isContract(target), "Address: call to non-contract");
                          (bool success, bytes memory returndata) = target.call{value: value}(data);
                          return verifyCallResult(success, returndata, errorMessage);
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but performing a static call.
                       *
                       * _Available since v3.3._
                       */
                      function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                          return functionStaticCall(target, data, "Address: low-level static call failed");
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                       * but performing a static call.
                       *
                       * _Available since v3.3._
                       */
                      function functionStaticCall(
                          address target,
                          bytes memory data,
                          string memory errorMessage
                      ) internal view returns (bytes memory) {
                          require(isContract(target), "Address: static call to non-contract");
                          (bool success, bytes memory returndata) = target.staticcall(data);
                          return verifyCallResult(success, returndata, errorMessage);
                      }
                      /**
                       * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
                       * revert reason using the provided one.
                       *
                       * _Available since v4.3._
                       */
                      function verifyCallResult(
                          bool success,
                          bytes memory returndata,
                          string memory errorMessage
                      ) internal pure returns (bytes memory) {
                          if (success) {
                              return returndata;
                          } else {
                              // Look for revert reason and bubble it up if present
                              if (returndata.length > 0) {
                                  // The easiest way to bubble the revert reason is using memory via assembly
                                  /// @solidity memory-safe-assembly
                                  assembly {
                                      let returndata_size := mload(returndata)
                                      revert(add(32, returndata), returndata_size)
                                  }
                              } else {
                                  revert(errorMessage);
                              }
                          }
                      }
                  }
                  // SPDX-License-Identifier: MIT
                  // OpenZeppelin Contracts (last updated v4.7.0) (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 nor 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 (utils/Context.sol)
                  pragma solidity ^0.8.0;
                  import "../proxy/utils/Initializable.sol";
                  /**
                   * @dev Provides information about the current execution context, including the
                   * sender of the transaction and its data. While these are generally available
                   * via msg.sender and msg.data, they should not be accessed in such a direct
                   * manner, since when dealing with meta-transactions the account sending and
                   * paying for execution may not be the actual sender (as far as an application
                   * is concerned).
                   *
                   * This contract is only required for intermediate, library-like contracts.
                   */
                  abstract contract ContextUpgradeable is Initializable {
                      function __Context_init() internal onlyInitializing {
                      }
                      function __Context_init_unchained() internal onlyInitializing {
                      }
                      function _msgSender() internal view virtual returns (address) {
                          return msg.sender;
                      }
                      function _msgData() internal view virtual returns (bytes calldata) {
                          return msg.data;
                      }
                      /**
                       * @dev This empty reserved space is put in place to allow future versions to add new
                       * variables without shifting down storage in the inheritance chain.
                       * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
                       */
                      uint256[50] private __gap;
                  }
                  // SPDX-License-Identifier: MIT
                  // OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)
                  pragma solidity ^0.8.0;
                  /**
                   * @dev String operations.
                   */
                  library StringsUpgradeable {
                      bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
                      uint8 private constant _ADDRESS_LENGTH = 20;
                      /**
                       * @dev Converts a `uint256` to its ASCII `string` decimal representation.
                       */
                      function toString(uint256 value) internal pure returns (string memory) {
                          // Inspired by OraclizeAPI's implementation - MIT licence
                          // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
                          if (value == 0) {
                              return "0";
                          }
                          uint256 temp = value;
                          uint256 digits;
                          while (temp != 0) {
                              digits++;
                              temp /= 10;
                          }
                          bytes memory buffer = new bytes(digits);
                          while (value != 0) {
                              digits -= 1;
                              buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
                              value /= 10;
                          }
                          return string(buffer);
                      }
                      /**
                       * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
                       */
                      function toHexString(uint256 value) internal pure returns (string memory) {
                          if (value == 0) {
                              return "0x00";
                          }
                          uint256 temp = value;
                          uint256 length = 0;
                          while (temp != 0) {
                              length++;
                              temp >>= 8;
                          }
                          return toHexString(value, length);
                      }
                      /**
                       * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
                       */
                      function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                          bytes memory buffer = new bytes(2 * length + 2);
                          buffer[0] = "0";
                          buffer[1] = "x";
                          for (uint256 i = 2 * length + 1; i > 1; --i) {
                              buffer[i] = _HEX_SYMBOLS[value & 0xf];
                              value >>= 4;
                          }
                          require(value == 0, "Strings: hex length insufficient");
                          return string(buffer);
                      }
                      /**
                       * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
                       */
                      function toHexString(address addr) internal pure returns (string memory) {
                          return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
                      }
                  }
                  // SPDX-License-Identifier: MIT
                  // OpenZeppelin Contracts (last updated v4.7.0) (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: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
                       *
                       * Requirements:
                       *
                       * - `from` cannot be the zero address.
                       * - `to` cannot be the zero address.
                       * - `tokenId` token must be owned by `from`.
                       * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
                       *
                       * Emits a {Transfer} event.
                       */
                      function transferFrom(
                          address from,
                          address to,
                          uint256 tokenId
                      ) external;
                      /**
                       * @dev Gives permission to `to` to transfer `tokenId` token to another account.
                       * The approval is cleared when the token is transferred.
                       *
                       * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
                       *
                       * Requirements:
                       *
                       * - The caller must own the token or be an approved operator.
                       * - `tokenId` must exist.
                       *
                       * Emits an {Approval} event.
                       */
                      function approve(address to, uint256 tokenId) external;
                      /**
                       * @dev Approve or remove `operator` as an operator for the caller.
                       * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
                       *
                       * Requirements:
                       *
                       * - The `operator` cannot be the caller.
                       *
                       * Emits an {ApprovalForAll} event.
                       */
                      function setApprovalForAll(address operator, bool _approved) external;
                      /**
                       * @dev Returns the account approved for `tokenId` token.
                       *
                       * Requirements:
                       *
                       * - `tokenId` must exist.
                       */
                      function getApproved(uint256 tokenId) external view returns (address operator);
                      /**
                       * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
                       *
                       * See {setApprovalForAll}
                       */
                      function isApprovedForAll(address owner, address operator) external view returns (bool);
                  }
                  // SPDX-License-Identifier: MIT
                  // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)
                  pragma solidity ^0.8.0;
                  /**
                   * @title ERC721 token receiver interface
                   * @dev Interface for any contract that wants to support safeTransfers
                   * from ERC721 asset contracts.
                   */
                  interface 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 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 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 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;
                  /**
                   * @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 The minimum increase of 10% required when making an offer or placing a bid.
                   */
                  uint256 constant MIN_PERCENT_INCREMENT_DENOMINATOR = BASIS_POINTS / 1_000;
                  /**
                   * @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 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;
                  /**
                   * @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;
                  // SPDX-License-Identifier: UNLICENSED
                  pragma solidity >=0.6.0;
                  import "../contracts/NFTCollection.sol";
                  contract $NFTCollection is NFTCollection {
                      bytes32 public __hh_exposed_bytecode_marker = "hardhat-exposed";
                      constructor(address _contractFactory) NFTCollection(_contractFactory) {}
                      function $_burn(uint256 tokenId) external {
                          return super._burn(tokenId);
                      }
                      function $_baseURI() external view returns (string memory) {
                          return super._baseURI();
                      }
                      function $_initializeSequentialMintCollection(address payable _creator,uint32 _maxTokenId) external {
                          return super._initializeSequentialMintCollection(_creator,_maxTokenId);
                      }
                      function $_selfDestruct() external {
                          return super._selfDestruct();
                      }
                      function $_updateMaxTokenId(uint32 _maxTokenId) external {
                          return super._updateMaxTokenId(_maxTokenId);
                      }
                      function $__ERC721Burnable_init() external {
                          return super.__ERC721Burnable_init();
                      }
                      function $__ERC721Burnable_init_unchained() external {
                          return super.__ERC721Burnable_init_unchained();
                      }
                      function $__ERC721_init(string calldata name_,string calldata symbol_) external {
                          return super.__ERC721_init(name_,symbol_);
                      }
                      function $__ERC721_init_unchained(string calldata name_,string calldata symbol_) external {
                          return super.__ERC721_init_unchained(name_,symbol_);
                      }
                      function $_safeTransfer(address from,address to,uint256 tokenId,bytes calldata data) external {
                          return super._safeTransfer(from,to,tokenId,data);
                      }
                      function $_exists(uint256 tokenId) external view returns (bool) {
                          return super._exists(tokenId);
                      }
                      function $_isApprovedOrOwner(address spender,uint256 tokenId) external view returns (bool) {
                          return super._isApprovedOrOwner(spender,tokenId);
                      }
                      function $_safeMint(address to,uint256 tokenId) external {
                          return super._safeMint(to,tokenId);
                      }
                      function $_safeMint(address to,uint256 tokenId,bytes calldata data) external {
                          return super._safeMint(to,tokenId,data);
                      }
                      function $_mint(address to,uint256 tokenId) external {
                          return super._mint(to,tokenId);
                      }
                      function $_transfer(address from,address to,uint256 tokenId) external {
                          return super._transfer(from,to,tokenId);
                      }
                      function $_approve(address to,uint256 tokenId) external {
                          return super._approve(to,tokenId);
                      }
                      function $_setApprovalForAll(address owner,address operator,bool approved) external {
                          return super._setApprovalForAll(owner,operator,approved);
                      }
                      function $_requireMinted(uint256 tokenId) external view {
                          return super._requireMinted(tokenId);
                      }
                      function $_beforeTokenTransfer(address from,address to,uint256 tokenId) external {
                          return super._beforeTokenTransfer(from,to,tokenId);
                      }
                      function $_afterTokenTransfer(address from,address to,uint256 tokenId) external {
                          return super._afterTokenTransfer(from,to,tokenId);
                      }
                      function $__ERC165_init() external {
                          return super.__ERC165_init();
                      }
                      function $__ERC165_init_unchained() external {
                          return super.__ERC165_init_unchained();
                      }
                      function $__Context_init() external {
                          return super.__Context_init();
                      }
                      function $__Context_init_unchained() external {
                          return super.__Context_init_unchained();
                      }
                      function $_msgSender() external view returns (address) {
                          return super._msgSender();
                      }
                      function $_msgData() external view returns (bytes memory) {
                          return super._msgData();
                      }
                      function $_disableInitializers() external {
                          return super._disableInitializers();
                      }
                      receive() external payable {}
                  }
                  // SPDX-License-Identifier: UNLICENSED
                  pragma solidity >=0.6.0;
                  import "../../../contracts/interfaces/internal/INFTCollectionInitializer.sol";
                  abstract contract $INFTCollectionInitializer is INFTCollectionInitializer {
                      bytes32 public __hh_exposed_bytecode_marker = "hardhat-exposed";
                      constructor() {}
                      receive() external payable {}
                  }
                  // SPDX-License-Identifier: UNLICENSED
                  pragma solidity >=0.6.0;
                  import "../../../../contracts/interfaces/standards/royalties/IGetFees.sol";
                  abstract contract $IGetFees is IGetFees {
                      bytes32 public __hh_exposed_bytecode_marker = "hardhat-exposed";
                      constructor() {}
                      receive() external payable {}
                  }
                  // SPDX-License-Identifier: UNLICENSED
                  pragma solidity >=0.6.0;
                  import "../../../../contracts/interfaces/standards/royalties/IGetRoyalties.sol";
                  abstract contract $IGetRoyalties is IGetRoyalties {
                      bytes32 public __hh_exposed_bytecode_marker = "hardhat-exposed";
                      constructor() {}
                      receive() external payable {}
                  }
                  // SPDX-License-Identifier: UNLICENSED
                  pragma solidity >=0.6.0;
                  import "../../../../contracts/interfaces/standards/royalties/IRoyaltyInfo.sol";
                  abstract contract $IRoyaltyInfo is IRoyaltyInfo {
                      bytes32 public __hh_exposed_bytecode_marker = "hardhat-exposed";
                      constructor() {}
                      receive() external payable {}
                  }
                  // SPDX-License-Identifier: UNLICENSED
                  pragma solidity >=0.6.0;
                  import "../../../../contracts/interfaces/standards/royalties/ITokenCreator.sol";
                  abstract contract $ITokenCreator is ITokenCreator {
                      bytes32 public __hh_exposed_bytecode_marker = "hardhat-exposed";
                      constructor() {}
                      receive() external payable {}
                  }
                  // SPDX-License-Identifier: UNLICENSED
                  pragma solidity >=0.6.0;
                  import "../../contracts/libraries/AddressLibrary.sol";
                  contract $AddressLibrary {
                      bytes32 public __hh_exposed_bytecode_marker = "hardhat-exposed";
                      event $callAndReturnContractAddress_address_bytes_Returned(address payable arg0);
                      event $callAndReturnContractAddress_CallWithoutValue_Returned(address payable arg0);
                      constructor() {}
                      function $callAndReturnContractAddress(address externalContract,bytes calldata callData) external payable returns (address payable) {
                          (address payable ret0) = AddressLibrary.callAndReturnContractAddress(externalContract,callData);
                          emit $callAndReturnContractAddress_address_bytes_Returned(ret0);
                          return (ret0);
                      }
                      function $callAndReturnContractAddress(CallWithoutValue calldata call) external payable returns (address payable) {
                          (address payable ret0) = AddressLibrary.callAndReturnContractAddress(call);
                          emit $callAndReturnContractAddress_CallWithoutValue_Returned(ret0);
                          return (ret0);
                      }
                      receive() external payable {}
                  }
                  // SPDX-License-Identifier: UNLICENSED
                  pragma solidity >=0.6.0;
                  import "../../../contracts/mixins/collections/CollectionRoyalties.sol";
                  abstract contract $CollectionRoyalties is CollectionRoyalties {
                      bytes32 public __hh_exposed_bytecode_marker = "hardhat-exposed";
                      constructor() {}
                      function $__ERC165_init() external {
                          return super.__ERC165_init();
                      }
                      function $__ERC165_init_unchained() external {
                          return super.__ERC165_init_unchained();
                      }
                      function $_disableInitializers() external {
                          return super._disableInitializers();
                      }
                      receive() external payable {}
                  }
                  // SPDX-License-Identifier: UNLICENSED
                  pragma solidity >=0.6.0;
                  import "../../../contracts/mixins/collections/SequentialMintCollection.sol";
                  contract $SequentialMintCollection is SequentialMintCollection {
                      bytes32 public __hh_exposed_bytecode_marker = "hardhat-exposed";
                      constructor() {}
                      function $_initializeSequentialMintCollection(address payable _creator,uint32 _maxTokenId) external {
                          return super._initializeSequentialMintCollection(_creator,_maxTokenId);
                      }
                      function $_selfDestruct() external {
                          return super._selfDestruct();
                      }
                      function $_updateMaxTokenId(uint32 _maxTokenId) external {
                          return super._updateMaxTokenId(_maxTokenId);
                      }
                      function $_burn(uint256 tokenId) external {
                          return super._burn(tokenId);
                      }
                      function $__ERC721Burnable_init() external {
                          return super.__ERC721Burnable_init();
                      }
                      function $__ERC721Burnable_init_unchained() external {
                          return super.__ERC721Burnable_init_unchained();
                      }
                      function $__ERC721_init(string calldata name_,string calldata symbol_) external {
                          return super.__ERC721_init(name_,symbol_);
                      }
                      function $__ERC721_init_unchained(string calldata name_,string calldata symbol_) external {
                          return super.__ERC721_init_unchained(name_,symbol_);
                      }
                      function $_baseURI() external view returns (string memory) {
                          return super._baseURI();
                      }
                      function $_safeTransfer(address from,address to,uint256 tokenId,bytes calldata data) external {
                          return super._safeTransfer(from,to,tokenId,data);
                      }
                      function $_exists(uint256 tokenId) external view returns (bool) {
                          return super._exists(tokenId);
                      }
                      function $_isApprovedOrOwner(address spender,uint256 tokenId) external view returns (bool) {
                          return super._isApprovedOrOwner(spender,tokenId);
                      }
                      function $_safeMint(address to,uint256 tokenId) external {
                          return super._safeMint(to,tokenId);
                      }
                      function $_safeMint(address to,uint256 tokenId,bytes calldata data) external {
                          return super._safeMint(to,tokenId,data);
                      }
                      function $_mint(address to,uint256 tokenId) external {
                          return super._mint(to,tokenId);
                      }
                      function $_transfer(address from,address to,uint256 tokenId) external {
                          return super._transfer(from,to,tokenId);
                      }
                      function $_approve(address to,uint256 tokenId) external {
                          return super._approve(to,tokenId);
                      }
                      function $_setApprovalForAll(address owner,address operator,bool approved) external {
                          return super._setApprovalForAll(owner,operator,approved);
                      }
                      function $_requireMinted(uint256 tokenId) external view {
                          return super._requireMinted(tokenId);
                      }
                      function $_beforeTokenTransfer(address from,address to,uint256 tokenId) external {
                          return super._beforeTokenTransfer(from,to,tokenId);
                      }
                      function $_afterTokenTransfer(address from,address to,uint256 tokenId) external {
                          return super._afterTokenTransfer(from,to,tokenId);
                      }
                      function $__ERC165_init() external {
                          return super.__ERC165_init();
                      }
                      function $__ERC165_init_unchained() external {
                          return super.__ERC165_init_unchained();
                      }
                      function $__Context_init() external {
                          return super.__Context_init();
                      }
                      function $__Context_init_unchained() external {
                          return super.__Context_init_unchained();
                      }
                      function $_msgSender() external view returns (address) {
                          return super._msgSender();
                      }
                      function $_msgData() external view returns (bytes memory) {
                          return super._msgData();
                      }
                      function $_disableInitializers() external {
                          return super._disableInitializers();
                      }
                      receive() external payable {}
                  }
                  // SPDX-License-Identifier: UNLICENSED
                  pragma solidity >=0.6.0;
                  import "../../../contracts/mixins/shared/Constants.sol";
                  // SPDX-License-Identifier: UNLICENSED
                  pragma solidity >=0.6.0;
                  import "../../../contracts/mixins/shared/ContractFactory.sol";
                  contract $ContractFactory is ContractFactory {
                      bytes32 public __hh_exposed_bytecode_marker = "hardhat-exposed";
                      constructor(address _contractFactory) ContractFactory(_contractFactory) {}
                      receive() external payable {}
                  }
                  

                  File 3 of 6: 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 4 of 6: NFTMarketRouter
                  // SPDX-License-Identifier: MIT
                  // OpenZeppelin Contracts (last updated v4.9.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
                       *
                       * Furthermore, `isContract` will also return true if the target contract within
                       * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
                       * which only has an effect at the end of a transaction.
                       * ====
                       *
                       * [IMPORTANT]
                       * ====
                       * You shouldn't rely on `isContract` to protect against flash loan attacks!
                       *
                       * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
                       * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
                       * constructor.
                       * ====
                       */
                      function isContract(address account) internal view returns (bool) {
                          // This method relies on extcodesize/address.code.length, which returns 0
                          // for contracts in construction, since the code is only stored at the end
                          // of the constructor execution.
                          return account.code.length > 0;
                      }
                      /**
                       * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                       * `recipient`, forwarding all available gas and reverting on errors.
                       *
                       * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                       * of certain opcodes, possibly making contracts go over the 2300 gas limit
                       * imposed by `transfer`, making them unable to receive funds via
                       * `transfer`. {sendValue} removes this limitation.
                       *
                       * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                       *
                       * IMPORTANT: because control is transferred to `recipient`, care must be
                       * taken to not create reentrancy vulnerabilities. Consider using
                       * {ReentrancyGuard} or the
                       * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                       */
                      function sendValue(address payable recipient, uint256 amount) internal {
                          require(address(this).balance >= amount, "Address: insufficient balance");
                          (bool success, ) = recipient.call{value: amount}("");
                          require(success, "Address: unable to send value, recipient may have reverted");
                      }
                      /**
                       * @dev Performs a Solidity function call using a low level `call`. A
                       * plain `call` is an unsafe replacement for a function call: use this
                       * function instead.
                       *
                       * If `target` reverts with a revert reason, it is bubbled up by this
                       * function (like regular Solidity function calls).
                       *
                       * Returns the raw returned data. To convert to the expected return value,
                       * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
                       *
                       * Requirements:
                       *
                       * - `target` must be a contract.
                       * - calling `target` with `data` must not revert.
                       *
                       * _Available since v3.1._
                       */
                      function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                          return functionCallWithValue(target, data, 0, "Address: low-level call failed");
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
                       * `errorMessage` as a fallback revert reason when `target` reverts.
                       *
                       * _Available since v3.1._
                       */
                      function functionCall(
                          address target,
                          bytes memory data,
                          string memory errorMessage
                      ) internal returns (bytes memory) {
                          return functionCallWithValue(target, data, 0, errorMessage);
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but also transferring `value` wei to `target`.
                       *
                       * Requirements:
                       *
                       * - the calling contract must have an ETH balance of at least `value`.
                       * - the called Solidity function must be `payable`.
                       *
                       * _Available since v3.1._
                       */
                      function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
                          return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
                      }
                      /**
                       * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
                       * with `errorMessage` as a fallback revert reason when `target` reverts.
                       *
                       * _Available since v3.1._
                       */
                      function functionCallWithValue(
                          address target,
                          bytes memory data,
                          uint256 value,
                          string memory errorMessage
                      ) internal returns (bytes memory) {
                          require(address(this).balance >= value, "Address: insufficient balance for call");
                          (bool success, bytes memory returndata) = target.call{value: value}(data);
                          return verifyCallResultFromTarget(target, success, returndata, errorMessage);
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but performing a static call.
                       *
                       * _Available since v3.3._
                       */
                      function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                          return functionStaticCall(target, data, "Address: low-level static call failed");
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                       * but performing a static call.
                       *
                       * _Available since v3.3._
                       */
                      function functionStaticCall(
                          address target,
                          bytes memory data,
                          string memory errorMessage
                      ) internal view returns (bytes memory) {
                          (bool success, bytes memory returndata) = target.staticcall(data);
                          return verifyCallResultFromTarget(target, success, returndata, errorMessage);
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but performing a delegate call.
                       *
                       * _Available since v3.4._
                       */
                      function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                          return functionDelegateCall(target, data, "Address: low-level delegate call failed");
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                       * but performing a delegate call.
                       *
                       * _Available since v3.4._
                       */
                      function functionDelegateCall(
                          address target,
                          bytes memory data,
                          string memory errorMessage
                      ) internal returns (bytes memory) {
                          (bool success, bytes memory returndata) = target.delegatecall(data);
                          return verifyCallResultFromTarget(target, success, returndata, errorMessage);
                      }
                      /**
                       * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
                       * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
                       *
                       * _Available since v4.8._
                       */
                      function verifyCallResultFromTarget(
                          address target,
                          bool success,
                          bytes memory returndata,
                          string memory errorMessage
                      ) internal view returns (bytes memory) {
                          if (success) {
                              if (returndata.length == 0) {
                                  // only check isContract if the call was successful and the return data is empty
                                  // otherwise we already know that it was a contract
                                  require(isContract(target), "Address: call to non-contract");
                              }
                              return returndata;
                          } else {
                              _revert(returndata, errorMessage);
                          }
                      }
                      /**
                       * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
                       * revert reason or using the provided one.
                       *
                       * _Available since v4.3._
                       */
                      function verifyCallResult(
                          bool success,
                          bytes memory returndata,
                          string memory errorMessage
                      ) internal pure returns (bytes memory) {
                          if (success) {
                              return returndata;
                          } else {
                              _revert(returndata, errorMessage);
                          }
                      }
                      function _revert(bytes memory returndata, string memory errorMessage) private pure {
                          // Look for revert reason and bubble it up if present
                          if (returndata.length > 0) {
                              // The easiest way to bubble the revert reason is using memory via assembly
                              /// @solidity memory-safe-assembly
                              assembly {
                                  let returndata_size := mload(returndata)
                                  revert(add(32, returndata), returndata_size)
                              }
                          } else {
                              revert(errorMessage);
                          }
                      }
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  import "./routes/INFTMarketReserveAuction.sol";
                  /**
                   * @title Interface with NFTMarket getters which are used by the router.
                   * @author HardlyDifficult
                   */
                  interface INFTMarketGetters {
                    function getBuyPrice(address nftContract, uint256 tokenId) external returns (address seller, uint256 price);
                    function getExhibitionIdForNft(address nftContract, uint256 tokenId) external returns (uint256 exhibitionId);
                    function getReserveAuction(
                      uint256 auctionId
                    ) external view returns (INFTMarketReserveAuction.ReserveAuction memory auction);
                    function getReserveAuctionIdFor(address nftContract, uint256 tokenId) external returns (uint256 auctionId);
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  import "../../../libraries/AddressLibrary.sol";
                  /**
                   * @title Interface for routing calls to the NFT Collection Factory to create drop collections.
                   * @author reggieag
                   */
                  interface INFTCollectionFactoryDrops {
                    function createNFTDropCollection(
                      string calldata name,
                      string calldata symbol,
                      string calldata baseURI,
                      bool isRevealed,
                      uint32 maxTokenId,
                      address approvedMinter,
                      uint96 nonce
                    ) external returns (address collection);
                    function createNFTDropCollectionWithPaymentFactory(
                      string calldata name,
                      string calldata symbol,
                      string calldata baseURI,
                      bool isRevealed,
                      uint32 maxTokenId,
                      address approvedMinter,
                      uint96 nonce,
                      CallWithoutValue calldata paymentAddressFactoryCall
                    ) external returns (address collection);
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  import "../../../libraries/AddressLibrary.sol";
                  /**
                   * @title Interface for routing calls to the NFT Collection Factory to create limited edition collections.
                   * @author gosseti
                   */
                  interface INFTCollectionFactoryLimitedEditions {
                    function createNFTLimitedEditionCollection(
                      string calldata name,
                      string calldata symbol,
                      string calldata baseURI,
                      uint32 maxTokenId,
                      address approvedMinter,
                      uint96 nonce
                    ) external returns (address collection);
                    function createNFTLimitedEditionCollectionWithPaymentFactory(
                      string calldata name,
                      string calldata symbol,
                      string calldata baseURI,
                      uint32 maxTokenId,
                      address approvedMinter,
                      uint96 nonce,
                      CallWithoutValue calldata paymentAddressFactoryCall
                    ) external returns (address collection);
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  import "../../../libraries/AddressLibrary.sol";
                  /**
                   * @title Interface for routing calls to the NFT Collection Factory to create timed edition collections.
                   * @author HardlyDifficult
                   */
                  interface INFTCollectionFactoryTimedEditions {
                    function createNFTTimedEditionCollection(
                      string calldata name,
                      string calldata symbol,
                      string calldata tokenURI,
                      uint256 mintEndTime,
                      address approvedMinter,
                      uint96 nonce
                    ) external returns (address collection);
                    function createNFTTimedEditionCollectionWithPaymentFactory(
                      string calldata name,
                      string calldata symbol,
                      string calldata tokenURI,
                      uint256 mintEndTime,
                      address approvedMinter,
                      uint96 nonce,
                      CallWithoutValue calldata paymentAddressFactoryCall
                    ) external returns (address collection);
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  /**
                   * @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;
                    function createFixedPriceSaleWithEarlyAccessAllowlistV2(
                      address nftContract,
                      uint256 exhibitionId,
                      uint256 price,
                      uint256 limitPerAccount,
                      uint256 generalAvailabilityStartTime,
                      uint256 earlyAccessStartTime,
                      bytes32 merkleRoot,
                      string calldata merkleTreeUri,
                      uint256 txDeadlineTime
                    ) external;
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  /**
                   * @title Interface for routing calls to the NFT Market to set buy now prices.
                   * @author HardlyDifficult
                   */
                  interface INFTMarketBuyNow {
                    function cancelBuyPrice(address nftContract, uint256 tokenId) external;
                    function setBuyPrice(address nftContract, uint256 tokenId, uint256 price) external;
                    function setBuyPriceV2(address nftContract, uint256 tokenId, uint256 exhibitionId, uint256 price) external;
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  /**
                   * @title Interface for routing calls to the NFT Market Exhibition
                   * @author philbirt
                   */
                  interface INFTMarketExhibitionForRouter {
                    function updateExhibitionNft(address nftContract, uint256 tokenId, uint256 price) external;
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  /**
                   * @title Interface for routing calls to the NFT Market to create reserve auctions.
                   * @author HardlyDifficult & reggieag
                   */
                  interface INFTMarketReserveAuction {
                    /// @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;
                    }
                    function cancelReserveAuction(uint256 auctionId) external;
                    function createReserveAuctionV3(
                      address nftContract,
                      uint256 tokenId,
                      uint256 exhibitionId,
                      uint256 reservePrice,
                      uint256 duration
                    ) external returns (uint256 auctionId);
                    function updateReserveAuctionV2(uint256 auctionId, uint256 exhibitionId, uint256 reservePrice) external;
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
                  struct CallWithoutValue {
                    address target;
                    bytes callData;
                  }
                  error AddressLibrary_Proxy_Call_Did_Not_Return_A_Contract(address addressReturned);
                  /**
                   * @title A library for address helpers not already covered by the OZ library.
                   * @author batu-inal & HardlyDifficult
                   */
                  library AddressLibrary {
                    using AddressUpgradeable for address;
                    using AddressUpgradeable for address payable;
                    /**
                     * @notice Calls an external contract with arbitrary data and parse the return value into an address.
                     * @param externalContract The address of the contract to call.
                     * @param callData The data to send to the contract.
                     * @return contractAddress The address of the contract returned by the call.
                     */
                    function callAndReturnContractAddress(
                      address externalContract,
                      bytes calldata callData
                    ) internal returns (address payable contractAddress) {
                      bytes memory returnData = externalContract.functionCall(callData);
                      contractAddress = abi.decode(returnData, (address));
                      if (!contractAddress.isContract()) {
                        revert AddressLibrary_Proxy_Call_Did_Not_Return_A_Contract(contractAddress);
                      }
                    }
                    function callAndReturnContractAddress(
                      CallWithoutValue calldata call
                    ) internal returns (address payable contractAddress) {
                      contractAddress = callAndReturnContractAddress(call.target, call.callData);
                    }
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  error RouteCallLibrary_Call_Failed_Without_Revert_Reason();
                  /**
                   * @title A library for calling external contracts with an address appended to the calldata.
                   * @author HardlyDifficult
                   */
                  library RouteCallLibrary {
                    /**
                     * @notice Routes a call to the specified contract, appending the msg.sender to the end of the calldata.
                     * If the call reverts, this will revert the transaction and the original reason is bubbled up.
                     * @param to The contract address to call.
                     * @param callData The call data to use when calling the contract, without the msg.sender.
                     */
                    function routeCallFromMsgSender(address to, bytes memory callData) internal returns (bytes memory returnData) {
                      // Forward the call, with the packed msg.sender appended, to the specified contract.
                      bool success;
                      // solhint-disable-next-line avoid-low-level-calls
                      (success, returnData) = to.call(abi.encodePacked(callData, msg.sender));
                      // If the call failed, bubble up the revert reason.
                      if (!success) {
                        _revert(returnData);
                      }
                    }
                    /**
                     * @notice Bubbles up the original revert reason of a low-level call failure where possible.
                     * @dev Copied from OZ's `Address.sol` library, with a minor modification to the final revert scenario.
                     * This should only be used when a low-level call fails.
                     */
                    function _revert(bytes memory returnData) 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 RouteCallLibrary_Call_Failed_Without_Revert_Reason();
                      }
                    }
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  /**
                   * @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.18;
                  import "../../../interfaces/internal/routes/INFTCollectionFactoryDrops.sol";
                  import "../../../interfaces/internal/routes/INFTCollectionFactoryTimedEditions.sol";
                  import "../../../interfaces/internal/routes/INFTDropMarketFixedPriceSale.sol";
                  import "../../../interfaces/internal/routes/INFTCollectionFactoryLimitedEditions.sol";
                  import "../../../libraries/AddressLibrary.sol";
                  import "../../../libraries/RouteCallLibrary.sol";
                  import "../NFTMarketRouterCore.sol";
                  /// @notice Parameters used to create a drop collection.
                  struct DropCollectionCreationParams {
                    /// @notice The collection's `name`.
                    string name;
                    /// @notice The collection's `symbol`.
                    string symbol;
                    /// @notice The base URI for the collection.
                    string baseURI;
                    /// @notice Whether the collection is revealed or not.
                    bool isRevealed;
                    /// @notice The max `tokenID` for this collection.
                    uint32 maxTokenId;
                    /// @notice The nonce used by the creator to create this collection.
                    uint96 nonce;
                  }
                  /// @notice Parameters used to create a limited edition collection.
                  struct LimitedEditionCollectionCreationParams {
                    /// @notice The collection's `name`.
                    string name;
                    /// @notice The collection's `symbol`.
                    string symbol;
                    /// @notice The token URI for the collection.
                    string tokenURI;
                    /// @notice The max `tokenID` for this collection.
                    uint32 maxTokenId;
                    /// @notice The nonce used by the creator to create this collection.
                    uint96 nonce;
                  }
                  /// @notice Parameters used to create a timed edition collection.
                  struct TimedEditionCollectionCreationParams {
                    /// @notice The collection's `name`.
                    string name;
                    /// @notice The collection's `symbol`.
                    string symbol;
                    /// @notice The token URI for the collection.
                    string tokenURI;
                    /// @notice The nonce used by the creator to create this collection.
                    uint96 nonce;
                  }
                  /**
                   * @title Wraps external calls to the NFTCollectionFactory contract.
                   * @dev Each call uses standard APIs and params, along with the msg.sender appended to the calldata. They will decode
                   * return values as appropriate. If any of these calls fail, the tx will revert with the original reason.
                   * @author HardlyDifficult & reggieag
                   */
                  abstract contract NFTCollectionFactoryRouterAPIs is NFTMarketRouterCore {
                    using RouteCallLibrary for address;
                    function _createNFTDropCollection(
                      DropCollectionCreationParams calldata collectionParams
                    ) internal returns (address collection) {
                      bytes memory returnData = nftCollectionFactory.routeCallFromMsgSender(
                        abi.encodeWithSelector(
                          INFTCollectionFactoryDrops.createNFTDropCollection.selector,
                          collectionParams.name,
                          collectionParams.symbol,
                          collectionParams.baseURI,
                          collectionParams.isRevealed,
                          collectionParams.maxTokenId,
                          nftDropMarket,
                          collectionParams.nonce
                        )
                      );
                      collection = abi.decode(returnData, (address));
                    }
                    function _createNFTDropCollectionWithPaymentFactory(
                      DropCollectionCreationParams calldata collectionParams,
                      CallWithoutValue calldata paymentAddressFactoryCall
                    ) internal returns (address collection) {
                      bytes memory returnData = nftCollectionFactory.routeCallFromMsgSender(
                        abi.encodeWithSelector(
                          INFTCollectionFactoryDrops.createNFTDropCollectionWithPaymentFactory.selector,
                          collectionParams.name,
                          collectionParams.symbol,
                          collectionParams.baseURI,
                          collectionParams.isRevealed,
                          collectionParams.maxTokenId,
                          nftDropMarket,
                          collectionParams.nonce,
                          paymentAddressFactoryCall
                        )
                      );
                      collection = abi.decode(returnData, (address));
                    }
                    function _createNFTTimedEditionCollection(
                      string calldata name,
                      string calldata symbol,
                      string calldata tokenURI,
                      uint256 mintEndTime,
                      address approvedMinter,
                      uint96 nonce
                    ) internal returns (address collection) {
                      bytes memory returnData = nftCollectionFactory.routeCallFromMsgSender(
                        abi.encodeWithSelector(
                          INFTCollectionFactoryTimedEditions.createNFTTimedEditionCollection.selector,
                          name,
                          symbol,
                          tokenURI,
                          mintEndTime,
                          approvedMinter,
                          nonce
                        )
                      );
                      collection = abi.decode(returnData, (address));
                    }
                    function _createNFTTimedEditionCollectionWithPaymentFactory(
                      string calldata name,
                      string calldata symbol,
                      string calldata tokenURI,
                      uint256 mintEndTime,
                      address approvedMinter,
                      uint96 nonce,
                      CallWithoutValue calldata paymentAddressFactoryCall
                    ) internal returns (address collection) {
                      bytes memory returnData = nftCollectionFactory.routeCallFromMsgSender(
                        abi.encodeWithSelector(
                          INFTCollectionFactoryTimedEditions.createNFTTimedEditionCollectionWithPaymentFactory.selector,
                          name,
                          symbol,
                          tokenURI,
                          mintEndTime,
                          approvedMinter,
                          nonce,
                          paymentAddressFactoryCall
                        )
                      );
                      collection = abi.decode(returnData, (address));
                    }
                    function _createNFTLimitedEditionCollection(
                      LimitedEditionCollectionCreationParams calldata collectionParams
                    ) internal returns (address collection) {
                      bytes memory returnData = nftCollectionFactory.routeCallFromMsgSender(
                        abi.encodeWithSelector(
                          INFTCollectionFactoryLimitedEditions.createNFTLimitedEditionCollection.selector,
                          collectionParams.name,
                          collectionParams.symbol,
                          collectionParams.tokenURI,
                          collectionParams.maxTokenId,
                          nftDropMarket,
                          collectionParams.nonce
                        )
                      );
                      collection = abi.decode(returnData, (address));
                    }
                    function _createNFTLimitedEditionCollectionWithPaymentFactory(
                      LimitedEditionCollectionCreationParams calldata collectionParams,
                      CallWithoutValue calldata paymentAddressFactoryCall
                    ) internal returns (address collection) {
                      bytes memory returnData = nftCollectionFactory.routeCallFromMsgSender(
                        abi.encodeWithSelector(
                          INFTCollectionFactoryLimitedEditions.createNFTLimitedEditionCollectionWithPaymentFactory.selector,
                          collectionParams.name,
                          collectionParams.symbol,
                          collectionParams.tokenURI,
                          collectionParams.maxTokenId,
                          nftDropMarket,
                          collectionParams.nonce,
                          paymentAddressFactoryCall
                        )
                      );
                      collection = abi.decode(returnData, (address));
                    }
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  import "../../../interfaces/internal/routes/INFTDropMarketFixedPriceSale.sol";
                  import "../../../libraries/RouteCallLibrary.sol";
                  import "../NFTMarketRouterCore.sol";
                  /// @notice Parameters used to create a fixed price sale.
                  struct FixedPriceSaleParams {
                    /// @notice The exhibition to associate this fix priced sale to.
                    /// Set this to 0 to exist outside of an exhibition.
                    uint256 exhibitionId;
                    /// @notice The fixed price per NFT in the collection.
                    uint256 price;
                    /// @notice The max number of NFTs an account may mint in this sale.
                    uint256 limitPerAccount;
                    /// @notice The start time of the general availability period, in seconds since the Unix epoch.
                    /// @dev When set to 0, general availability is set to the block timestamp the transaction is mined.
                    uint256 generalAvailabilityStartTime;
                  }
                  /// @notice Parameters used to create a fixed price sale.
                  struct FixedPriceSaleParamsV2 {
                    /// @notice The exhibition to associate this fix priced sale to.
                    /// Set this to 0 to exist outside of an exhibition.
                    uint256 exhibitionId;
                    /// @notice The fixed price per NFT in the collection.
                    uint256 price;
                    /// @notice The max number of NFTs an account may mint in this sale.
                    uint256 limitPerAccount;
                    /// @notice The start time of the general availability period, in seconds since the Unix epoch.
                    /// @dev When set to 0, general availability is set to the block timestamp the transaction is mined.
                    uint256 generalAvailabilityStartTime;
                    /// @notice The sale duration in seconds.
                    uint256 saleDuration;
                  }
                  /**
                   * @title Wraps external calls to the NFTDropMarket contract.
                   * @dev Each call uses standard APIs and params, along with the msg.sender appended to the calldata. They will decode
                   * return values as appropriate. If any of these calls fail, the tx will revert with the original reason.
                   * @author HardlyDifficult & reggieag
                   */
                  abstract contract NFTDropMarketRouterAPIs is NFTMarketRouterCore {
                    using RouteCallLibrary for address;
                    function _createFixedPriceSaleV3(
                      address nftContract,
                      FixedPriceSaleParams memory fixedPriceSaleParams,
                      uint256 txDeadlineTime
                    ) internal {
                      nftDropMarket.routeCallFromMsgSender(
                        abi.encodeWithSelector(
                          INFTDropMarketFixedPriceSale.createFixedPriceSaleV3.selector,
                          nftContract,
                          fixedPriceSaleParams.exhibitionId,
                          fixedPriceSaleParams.price,
                          fixedPriceSaleParams.limitPerAccount,
                          fixedPriceSaleParams.generalAvailabilityStartTime,
                          txDeadlineTime
                        )
                      );
                    }
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  import "../../../interfaces/internal/routes/INFTMarketBuyNow.sol";
                  import "../../../interfaces/internal/routes/INFTMarketExhibition.sol";
                  import "../../../interfaces/internal/routes/INFTMarketReserveAuction.sol";
                  import "../../../libraries/RouteCallLibrary.sol";
                  import "../NFTMarketRouterCore.sol";
                  /**
                   * @title Wraps external calls to the NFTMarket contract.
                   * @dev Each call uses standard APIs and params, along with the msg.sender appended to the calldata. They will decode
                   * return values as appropriate. If any of these calls fail, the tx will revert with the original reason.
                   * @author HardlyDifficult, reggieag
                   */
                  abstract contract NFTMarketRouterAPIs is NFTMarketRouterCore {
                    using RouteCallLibrary for address;
                    function _cancelReserveAuction(uint256 auctionId) internal {
                      nftMarket.routeCallFromMsgSender(
                        abi.encodeWithSelector(INFTMarketReserveAuction.cancelReserveAuction.selector, auctionId)
                      );
                    }
                    function _cancelBuyPrice(address nftContract, uint256 tokenId) internal {
                      nftMarket.routeCallFromMsgSender(
                        abi.encodeWithSelector(INFTMarketBuyNow.cancelBuyPrice.selector, nftContract, tokenId)
                      );
                    }
                    function _createReserveAuctionV3(
                      address nftContract,
                      uint256 tokenId,
                      uint256 exhibitionId,
                      uint256 reservePrice,
                      uint256 duration
                    ) internal returns (uint auctionId) {
                      bytes memory returnData = nftMarket.routeCallFromMsgSender(
                        abi.encodeWithSelector(
                          INFTMarketReserveAuction.createReserveAuctionV3.selector,
                          nftContract,
                          tokenId,
                          exhibitionId,
                          reservePrice,
                          duration
                        )
                      );
                      auctionId = abi.decode(returnData, (uint256));
                    }
                    function _setBuyPrice(address nftContract, uint256 tokenId, uint256 price) internal {
                      nftMarket.routeCallFromMsgSender(
                        abi.encodeWithSelector(INFTMarketBuyNow.setBuyPrice.selector, nftContract, tokenId, price)
                      );
                    }
                    function _setBuyPriceV2(address nftContract, uint256 tokenId, uint256 exhibitionId, uint256 price) internal {
                      nftMarket.routeCallFromMsgSender(
                        abi.encodeWithSelector(INFTMarketBuyNow.setBuyPriceV2.selector, nftContract, tokenId, exhibitionId, price)
                      );
                    }
                    function _updateNftExhibition(address nftContract, uint256 tokenId, uint256 exhibitionId) internal {
                      nftMarket.routeCallFromMsgSender(
                        abi.encodeWithSelector(
                          INFTMarketExhibitionForRouter.updateExhibitionNft.selector,
                          nftContract,
                          tokenId,
                          exhibitionId
                        )
                      );
                    }
                    function _updateReserveAuctionV2(uint256 auctionId, uint256 exhibitionId, uint256 reservePrice) internal {
                      nftMarket.routeCallFromMsgSender(
                        abi.encodeWithSelector(
                          INFTMarketReserveAuction.updateReserveAuctionV2.selector,
                          auctionId,
                          exhibitionId,
                          reservePrice
                        )
                      );
                    }
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  import "../../libraries/AddressLibrary.sol";
                  import "../shared/TxDeadline.sol";
                  import "./apis/NFTCollectionFactoryRouterAPIs.sol";
                  import "./apis/NFTDropMarketRouterAPIs.sol";
                  /**
                   * @title Offers value-added functions for creating limited edition collections using the NFTCollectionFactory contract
                   * and creating sales using the NFTDropMarket contract.
                   * An example of a value-added function is the ability to create a collection and sale in a single transaction.
                   * @author gosseti
                   */
                  abstract contract NFTCreateAndListLimitedEditionCollection is
                    TxDeadline,
                    NFTCollectionFactoryRouterAPIs,
                    NFTDropMarketRouterAPIs
                  {
                    /**
                     * @notice Create a new limited edition collection contract and fixed price sale.
                     * @param collectionParams The parameters for the limited edition collection creation.
                     * @param fixedPriceSaleParams The parameters for the fixed price sale creation.
                     * @param txDeadlineTime The deadline timestamp for the transaction to be mined, in seconds since Unix epoch.
                     * @return collection The address of the newly created collection contract.
                     * @dev The collection will include the `nftDropMarket` as an approved minter.
                     */
                    function createLimitedEditionCollectionAndFixedPriceSale(
                      LimitedEditionCollectionCreationParams calldata collectionParams,
                      FixedPriceSaleParams calldata fixedPriceSaleParams,
                      uint256 txDeadlineTime
                    ) external txDeadlineNotExpired(txDeadlineTime) returns (address collection) {
                      collection = _createNFTLimitedEditionCollection({ collectionParams: collectionParams });
                      _createFixedPriceSaleV3({
                        nftContract: collection,
                        fixedPriceSaleParams: fixedPriceSaleParams,
                        // The deadline provided has already been validated above.
                        txDeadlineTime: 0
                      });
                    }
                    /**
                     * @notice Create a new limited edition collection contract with a payment factory and fixed price sale.
                     * @param collectionParams The parameters for the limited edition collection creation.
                     * @param paymentAddressFactoryCall The contract call which will return the address to use for payments.
                     * @param fixedPriceSaleParams The parameters for the fixed price sale creation.
                     * @param txDeadlineTime The deadline timestamp for the transaction to be mined, in seconds since Unix epoch.
                     * @return collection The address of the newly created collection contract.
                     * @dev The collection will include the `nftDropMarket` as an approved minter.
                     */
                    function createLimitedEditionCollectionAndFixedPriceSaleWithPaymentFactory(
                      LimitedEditionCollectionCreationParams calldata collectionParams,
                      CallWithoutValue calldata paymentAddressFactoryCall,
                      FixedPriceSaleParams calldata fixedPriceSaleParams,
                      uint256 txDeadlineTime
                    ) external txDeadlineNotExpired(txDeadlineTime) returns (address collection) {
                      collection = _createNFTLimitedEditionCollectionWithPaymentFactory({
                        collectionParams: collectionParams,
                        paymentAddressFactoryCall: paymentAddressFactoryCall
                      });
                      _createFixedPriceSaleV3({
                        nftContract: collection,
                        fixedPriceSaleParams: fixedPriceSaleParams,
                        // The deadline provided has already been validated above.
                        txDeadlineTime: 0
                      });
                    }
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  import "../../libraries/AddressLibrary.sol";
                  import "../shared/TxDeadline.sol";
                  import "./apis/NFTCollectionFactoryRouterAPIs.sol";
                  import "./apis/NFTDropMarketRouterAPIs.sol";
                  error NFTCreateAndListTimedEditionCollection_Sale_Duration_Cannot_Be_Zero();
                  /**
                   * @title Offers value-added functions for creating edition collections using the NFTCollectionFactory contract
                   * and creating sales using the NFTDropMarket contract.
                   * An example of a value-added function is the ability to create a collection and sale in a single transaction.
                   * @author reggieag & HardlyDifficult & gosseti
                   */
                  abstract contract NFTCreateAndListTimedEditionCollection is
                    TxDeadline,
                    NFTCollectionFactoryRouterAPIs,
                    NFTDropMarketRouterAPIs
                  {
                    /**
                     * @notice How long the minting period is open, after the general availability start time.
                     */
                    uint256 private constant DEFAULT_MINT_END_TIME_DURATION = 1 days;
                    /**
                     * @notice [DEPRECATED] use `createTimedEditionCollectionAndFixedPriceSaleV2` instead.
                     * The sale will last for 24 hours starting at `fixedPriceSaleParams.generalAvailabilityStartTime`.
                     * @param collectionParams The parameters for the edition collection creation.
                     * @param fixedPriceSaleParams  The parameters for the sale creation.
                     * @param txDeadlineTime The deadline timestamp for the transaction to be mined, in seconds since Unix epoch.
                     * @return collection The address of the newly created collection contract.
                     * @dev The collection will include the `nftDropMarket` as an approved minter.
                     */
                    function createTimedEditionCollectionAndFixedPriceSale(
                      TimedEditionCollectionCreationParams calldata collectionParams,
                      FixedPriceSaleParams calldata fixedPriceSaleParams,
                      uint256 txDeadlineTime
                    ) external returns (address collection) {
                      FixedPriceSaleParamsV2 memory mappedFixedPriceSaleParams = FixedPriceSaleParamsV2({
                        exhibitionId: fixedPriceSaleParams.exhibitionId,
                        price: fixedPriceSaleParams.price,
                        limitPerAccount: fixedPriceSaleParams.limitPerAccount,
                        generalAvailabilityStartTime: fixedPriceSaleParams.generalAvailabilityStartTime,
                        saleDuration: DEFAULT_MINT_END_TIME_DURATION
                      });
                      collection = _createTimedEditionCollectionAndFixedPriceSale(
                        collectionParams,
                        mappedFixedPriceSaleParams,
                        txDeadlineTime
                      );
                    }
                    /**
                     * @notice Create a new edition collection contract and timed sale.
                     * @param collectionParams The parameters for the edition collection creation.
                     * @param fixedPriceSaleParams  The parameters for the sale creation.
                     * @param txDeadlineTime The deadline timestamp for the transaction to be mined, in seconds since Unix epoch.
                     * @return collection The address of the newly created collection contract.
                     * @dev The collection will include the `nftDropMarket` as an approved minter.
                     */
                    function createTimedEditionCollectionAndFixedPriceSaleV2(
                      TimedEditionCollectionCreationParams calldata collectionParams,
                      FixedPriceSaleParamsV2 calldata fixedPriceSaleParams,
                      uint256 txDeadlineTime
                    ) external returns (address collection) {
                      collection = _createTimedEditionCollectionAndFixedPriceSale(collectionParams, fixedPriceSaleParams, txDeadlineTime);
                    }
                    /**
                     * @notice Create a new edition collection contract and timed sale.
                     * @param collectionParams The parameters for the edition collection creation.
                     * @param fixedPriceSaleParams  The parameters for the sale creation.
                     * @param txDeadlineTime The deadline timestamp for the transaction to be mined, in seconds since Unix epoch.
                     * @return collection The address of the newly created collection contract.
                     * @dev The collection will include the `nftDropMarket` as an approved minter.
                     */
                    function _createTimedEditionCollectionAndFixedPriceSale(
                      TimedEditionCollectionCreationParams calldata collectionParams,
                      FixedPriceSaleParamsV2 memory fixedPriceSaleParams,
                      uint256 txDeadlineTime
                    ) private txDeadlineNotExpired(txDeadlineTime) returns (address collection) {
                      if (fixedPriceSaleParams.saleDuration == 0) {
                        revert NFTCreateAndListTimedEditionCollection_Sale_Duration_Cannot_Be_Zero();
                      }
                      uint256 generalAvailabilityStartTime = fixedPriceSaleParams.generalAvailabilityStartTime;
                      if (generalAvailabilityStartTime == 0) {
                        generalAvailabilityStartTime = block.timestamp;
                      }
                      collection = _createNFTTimedEditionCollection({
                        name: collectionParams.name,
                        symbol: collectionParams.symbol,
                        tokenURI: collectionParams.tokenURI,
                        mintEndTime: generalAvailabilityStartTime + fixedPriceSaleParams.saleDuration,
                        approvedMinter: nftDropMarket,
                        nonce: collectionParams.nonce
                      });
                      FixedPriceSaleParams memory mappedFixedPriceSaleParams = FixedPriceSaleParams({
                        exhibitionId: fixedPriceSaleParams.exhibitionId,
                        price: fixedPriceSaleParams.price,
                        limitPerAccount: fixedPriceSaleParams.limitPerAccount,
                        generalAvailabilityStartTime: fixedPriceSaleParams.generalAvailabilityStartTime
                      });
                      _createFixedPriceSaleV3({
                        nftContract: collection,
                        fixedPriceSaleParams: mappedFixedPriceSaleParams,
                        // The deadline provided has already been validated above.
                        txDeadlineTime: 0
                      });
                    }
                    /**
                     * @notice [DEPRECATED] use `createTimedEditionCollectionAndFixedPriceSaleWithPaymentFactoryV2` instead.
                     * The sale will last for 24 hours starting at `fixedPriceSaleParams.generalAvailabilityStartTime`.
                     * @param collectionParams The parameters for the edition collection creation.
                     * @param paymentAddressFactoryCall The contract call which will return the address to use for payments.
                     * @param fixedPriceSaleParams  The parameters for the sale creation.
                     * @param txDeadlineTime The deadline timestamp for the transaction to be mined, in seconds since Unix epoch.
                     * @return collection The address of the newly created collection contract.
                     * @dev The collection will include the `nftDropMarket` as an approved minter.
                     */
                    function createTimedEditionCollectionAndFixedPriceSaleWithPaymentFactory(
                      TimedEditionCollectionCreationParams calldata collectionParams,
                      CallWithoutValue calldata paymentAddressFactoryCall,
                      FixedPriceSaleParams calldata fixedPriceSaleParams,
                      uint256 txDeadlineTime
                    ) external returns (address collection) {
                      FixedPriceSaleParamsV2 memory mappedFixedPriceSaleParams = FixedPriceSaleParamsV2({
                        exhibitionId: fixedPriceSaleParams.exhibitionId,
                        price: fixedPriceSaleParams.price,
                        limitPerAccount: fixedPriceSaleParams.limitPerAccount,
                        generalAvailabilityStartTime: fixedPriceSaleParams.generalAvailabilityStartTime,
                        saleDuration: DEFAULT_MINT_END_TIME_DURATION
                      });
                      collection = _createTimedEditionCollectionAndFixedPriceSaleWithPaymentFactory(
                        collectionParams,
                        paymentAddressFactoryCall,
                        mappedFixedPriceSaleParams,
                        txDeadlineTime
                      );
                    }
                    /**
                     * @notice Create a new edition collection contract and timed sale with a payment factory.
                     * @param collectionParams The parameters for the edition collection creation.
                     * @param paymentAddressFactoryCall The contract call which will return the address to use for payments.
                     * @param fixedPriceSaleParams  The parameters for the sale creation.
                     * @param txDeadlineTime The deadline timestamp for the transaction to be mined, in seconds since Unix epoch.
                     * @return collection The address of the newly created collection contract.
                     * @dev The collection will include the `nftDropMarket` as an approved minter.
                     */
                    function createTimedEditionCollectionAndFixedPriceSaleWithPaymentFactoryV2(
                      TimedEditionCollectionCreationParams calldata collectionParams,
                      CallWithoutValue calldata paymentAddressFactoryCall,
                      FixedPriceSaleParamsV2 calldata fixedPriceSaleParams,
                      uint256 txDeadlineTime
                    ) external returns (address collection) {
                      collection = _createTimedEditionCollectionAndFixedPriceSaleWithPaymentFactory(
                        collectionParams,
                        paymentAddressFactoryCall,
                        fixedPriceSaleParams,
                        txDeadlineTime
                      );
                    }
                    /**
                     * @notice Create a new edition collection contract and timed sale with a payment factory.
                     * @param collectionParams The parameters for the edition collection creation.
                     * @param paymentAddressFactoryCall The contract call which will return the address to use for payments.
                     * @param fixedPriceSaleParams  The parameters for the sale creation.
                     * @param txDeadlineTime The deadline timestamp for the transaction to be mined, in seconds since Unix epoch.
                     * @return collection The address of the newly created collection contract.
                     * @dev The collection will include the `nftDropMarket` as an approved minter.
                     */
                    function _createTimedEditionCollectionAndFixedPriceSaleWithPaymentFactory(
                      TimedEditionCollectionCreationParams calldata collectionParams,
                      CallWithoutValue calldata paymentAddressFactoryCall,
                      FixedPriceSaleParamsV2 memory fixedPriceSaleParams,
                      uint256 txDeadlineTime
                    ) private txDeadlineNotExpired(txDeadlineTime) returns (address collection) {
                      if (fixedPriceSaleParams.saleDuration == 0) {
                        revert NFTCreateAndListTimedEditionCollection_Sale_Duration_Cannot_Be_Zero();
                      }
                      uint256 generalAvailabilityStartTime = fixedPriceSaleParams.generalAvailabilityStartTime;
                      if (generalAvailabilityStartTime == 0) {
                        generalAvailabilityStartTime = block.timestamp;
                      }
                      collection = _createNFTTimedEditionCollectionWithPaymentFactory({
                        name: collectionParams.name,
                        symbol: collectionParams.symbol,
                        tokenURI: collectionParams.tokenURI,
                        mintEndTime: generalAvailabilityStartTime + fixedPriceSaleParams.saleDuration,
                        approvedMinter: nftDropMarket,
                        nonce: collectionParams.nonce,
                        paymentAddressFactoryCall: paymentAddressFactoryCall
                      });
                      FixedPriceSaleParams memory mappedFixedPriceSaleParams = FixedPriceSaleParams({
                        exhibitionId: fixedPriceSaleParams.exhibitionId,
                        price: fixedPriceSaleParams.price,
                        limitPerAccount: fixedPriceSaleParams.limitPerAccount,
                        generalAvailabilityStartTime: fixedPriceSaleParams.generalAvailabilityStartTime
                      });
                      _createFixedPriceSaleV3({
                        nftContract: collection,
                        fixedPriceSaleParams: mappedFixedPriceSaleParams,
                        // The deadline provided has already been validated above.
                        txDeadlineTime: 0
                      });
                    }
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
                  error NFTMarketRouterCore_NFT_Collection_Factory_Is_Not_A_Contract();
                  error NFTMarketRouterCore_NFT_Drop_Market_Is_Not_A_Contract();
                  error NFTMarketRouterCore_NFT_Market_Is_Not_A_Contract();
                  /**
                   * @title Shared logic for NFT Market Router mixins.
                   * @author HardlyDifficult
                   */
                  abstract contract NFTMarketRouterCore {
                    using AddressUpgradeable for address;
                    /**
                     * @notice The address of the NFTMarket contract to which requests will be routed.
                     */
                    address internal immutable nftMarket;
                    /**
                     * @notice The address of the NFTDropMarket contract to which requests will be routed.
                     */
                    address internal immutable nftDropMarket;
                    /**
                     * @notice The address of the NFTCollectionFactory contract to which requests will be routed.
                     */
                    address internal immutable nftCollectionFactory;
                    /**
                     * @notice Initialize the template's immutable variables.
                     * @param _nftMarket The address of the NFTMarket contract to which requests will be routed.
                     * @param _nftDropMarket The address of the NFTDropMarket contract to which requests will be routed.
                     * @param _nftCollectionFactory The address of the NFTCollectionFactory contract to which requests will be routed.
                     */
                    constructor(address _nftMarket, address _nftDropMarket, address _nftCollectionFactory) {
                      if (!_nftCollectionFactory.isContract()) {
                        revert NFTMarketRouterCore_NFT_Collection_Factory_Is_Not_A_Contract();
                      }
                      if (!_nftMarket.isContract()) {
                        revert NFTMarketRouterCore_NFT_Market_Is_Not_A_Contract();
                      }
                      if (!_nftDropMarket.isContract()) {
                        revert NFTMarketRouterCore_NFT_Drop_Market_Is_Not_A_Contract();
                      }
                      nftCollectionFactory = _nftCollectionFactory;
                      nftDropMarket = _nftDropMarket;
                      nftMarket = _nftMarket;
                    }
                    /**
                     * @notice The address of the NFTMarket contract to which requests will be routed.
                     * @return market The address of the NFTMarket contract.
                     */
                    function getNftMarketAddress() external view returns (address market) {
                      market = nftMarket;
                    }
                    /**
                     * @notice The address of the NFTDropMarket contract to which requests will be routed.
                     * @return market The address of the NFTDropMarket contract.
                     */
                    function getNftDropMarketAddress() external view returns (address market) {
                      market = nftDropMarket;
                    }
                    /**
                     * @notice The address of the NFTCollectionFactory contract to which requests will be routed.
                     * @return collectionFactory The address of the NFTCollectionFactory contract.
                     */
                    function getNftCollectionFactory() external view returns (address collectionFactory) {
                      collectionFactory = nftCollectionFactory;
                    }
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  import "../../interfaces/internal/INFTMarketGetters.sol";
                  import "./apis/NFTMarketRouterAPIs.sol";
                  error NFTMarketRouterList_Buy_Price_Set_But_Should_Set_Buy_Price_Is_False();
                  error NFTMarketRouterList_Duration_Set_Without_Reserve_Price();
                  error NFTMarketRouterList_Exhibition_Id_Set_Without_Prices();
                  error NFTMarketRouterList_Exhibition_Id_Set_Without_Reserve_Price();
                  error NFTMarketRouterList_Token_Ids_Not_Set();
                  error NFTMarketRouterList_Must_Set_Reserve_Or_Buy_Price();
                  error NFTMarketRouterList_Must_Set_Reserve_Buy_Price_Or_Exhibition();
                  /**
                   * @title Offers value-added functions for listing NFTs in the NFTMarket contract.
                   * @author batu-inal & HardlyDifficult & reggieag
                   */
                  abstract contract NFTMarketRouterList is NFTMarketRouterAPIs {
                    /**
                     * @notice [DEPRECATED] use `batchListFromCollectionV2` instead.
                     * Batch create reserve auction and/or set a buy price for many NFTs and escrow in the market contract.
                     * A reserve auction price and/or a buy price must be set.
                     * @param nftContract The address of the NFT contract.
                     * @param tokenIds The ids of NFTs from the collection to set prices for.
                     * @param exhibitionId The id of the exhibition the auctions are to be listed with.
                     * Set this to 0 if n/a. Only applies to creating auctions.
                     * @param reservePrice The initial reserve price for the auctions created.
                     * Set the reservePrice to 0 to skip creating auctions.
                     * @param shouldSetBuyPrice True if buy prices should be set for these NFTs.
                     * Set this to false to skip setting buy prices. 0 is a valid buy price enabling a giveaway.
                     * @param buyPrice The price at which someone could buy these NFTs.
                     * @return firstAuctionIdOfSequence 0 if reservePrice is 0, otherwise this is the id of the first auction listed.
                     * The other auctions in the batch are listed sequentially from `first id` to `first id + count`.
                     * @dev Notes:
                     *   a) Approval should be granted for the NFTMarket contract before using this function.
                     *   b) If any NFT is already listed for auction then the entire batch call will revert.
                     */
                    function batchListFromCollection(
                      address nftContract,
                      uint256[] calldata tokenIds,
                      uint256 exhibitionId,
                      uint256 reservePrice,
                      bool shouldSetBuyPrice,
                      uint256 buyPrice
                    ) external returns (uint256 firstAuctionIdOfSequence) {
                      firstAuctionIdOfSequence = batchListFromCollectionV2(
                        nftContract,
                        tokenIds,
                        exhibitionId,
                        reservePrice,
                        0, // use default duration
                        shouldSetBuyPrice,
                        buyPrice
                      );
                    }
                    /**
                     * @notice Batch create reserve auction and/or set a buy price for many NFTs and escrow in the market contract.
                     * A reserve auction price and/or a buy price must be set.
                     * @param nftContract The address of the NFT contract.
                     * @param tokenIds The ids of NFTs from the collection to set prices for.
                     * @param exhibitionId The id of the exhibition the auctions are to be listed with.
                     * Set this to 0 if n/a. Only applies to creating auctions.
                     * @param reservePrice The initial reserve price for the auctions created.
                     * Set the reservePrice to 0 to skip creating auctions.
                     * @param auctionDuration The duration of the auctions created.
                     * Set this to 0 to use the default duration of 24 hours.
                     * @param shouldSetBuyPrice True if buy prices should be set for these NFTs.
                     * Set this to false to skip setting buy prices. 0 is a valid buy price enabling a giveaway.
                     * @param buyPrice The price at which someone could buy these NFTs.
                     * @return firstAuctionIdOfSequence 0 if reservePrice is 0, otherwise this is the id of the first auction listed.
                     * The other auctions in the batch are listed sequentially from `first id` to `first id + count`.
                     * @dev Notes:
                     *   a) Approval should be granted for the NFTMarket contract before using this function.
                     *   b) If any NFT is already listed for auction then the entire batch call will revert.
                     */
                    function batchListFromCollectionV2(
                      address nftContract,
                      uint256[] calldata tokenIds,
                      uint256 exhibitionId,
                      uint256 reservePrice,
                      uint256 auctionDuration,
                      bool shouldSetBuyPrice,
                      uint256 buyPrice
                    ) public returns (uint256 firstAuctionIdOfSequence) {
                      // Validate input.
                      if (tokenIds.length == 0) {
                        revert NFTMarketRouterList_Token_Ids_Not_Set();
                      }
                      if (!shouldSetBuyPrice && buyPrice != 0) {
                        revert NFTMarketRouterList_Buy_Price_Set_But_Should_Set_Buy_Price_Is_False();
                      }
                      // List NFTs for sale.
                      if (reservePrice != 0) {
                        // Create auctions.
                        // Process the first NFT in order to capture that auction ID as the return value.
                        firstAuctionIdOfSequence = _createReserveAuctionV3(
                          nftContract,
                          tokenIds[0],
                          exhibitionId,
                          reservePrice,
                          auctionDuration
                        );
                        if (shouldSetBuyPrice) {
                          // And set buy prices.
                          _setBuyPrice(nftContract, tokenIds[0], buyPrice);
                        }
                        for (uint256 i = 1; i < tokenIds.length; ) {
                          _createReserveAuctionV3(nftContract, tokenIds[i], exhibitionId, reservePrice, auctionDuration);
                          if (shouldSetBuyPrice) {
                            _setBuyPrice(nftContract, tokenIds[i], buyPrice);
                          }
                          unchecked {
                            ++i;
                          }
                        }
                      } else {
                        // Set buy prices only (no auctions).
                        if (exhibitionId != 0) {
                          // Exhibitions are only for auctions ATM.
                          revert NFTMarketRouterList_Exhibition_Id_Set_Without_Reserve_Price();
                        }
                        if (auctionDuration != 0) {
                          // Duration is only for auctions.
                          revert NFTMarketRouterList_Duration_Set_Without_Reserve_Price();
                        }
                        if (!shouldSetBuyPrice) {
                          revert NFTMarketRouterList_Must_Set_Reserve_Or_Buy_Price();
                        }
                        for (uint256 i = 0; i < tokenIds.length; ) {
                          _setBuyPrice(nftContract, tokenIds[i], buyPrice);
                          unchecked {
                            ++i;
                          }
                        }
                      }
                    }
                    /**
                     * @notice Create reserve auction, set a buy price, and/or set exhibition for an NFT and escrow in the market
                     * contract. This will check the current state of the auction, buy now, and exhibition, and update the values if they
                     * have changed.
                     * @param nftContract The address of the NFT contract.
                     * @param tokenId The id of the NFT from the collection to set prices for.
                     * @param exhibitionId The id of the exhibition to associate the listings to.
                     * Set this to 0 if n/a.
                     * @param reservePrice The initial reserve price for the auction created.
                     * Set the reservePrice to 0 to skip creating auction.
                     * @param auctionDuration The duration of the auction created.
                     * Set this to 0 to use the default duration of 24 hours.
                     * @param shouldSetBuyPrice True if buy prices should be set for this NFT.
                     * Set this to false to skip setting buy prices, or if there is an existing buy price it will cancel it.
                     * 0 is a valid buy price enabling a giveaway.
                     * @param buyPrice The price at which someone could buy this NFT.
                     * @dev Notes:
                     *   a) Approval should be granted for the NFTMarket contract before using this function.
                     */
                    function upsertListing(
                      address nftContract,
                      uint256 tokenId,
                      uint256 exhibitionId,
                      uint256 reservePrice,
                      uint256 auctionDuration,
                      bool shouldSetBuyPrice,
                      uint256 buyPrice
                    ) public {
                      if (!shouldSetBuyPrice && buyPrice != 0) {
                        revert NFTMarketRouterList_Buy_Price_Set_But_Should_Set_Buy_Price_Is_False();
                      }
                      // Attempting to set an exhibition without any listings.
                      if (!shouldSetBuyPrice && reservePrice == 0 && exhibitionId != 0) {
                        revert NFTMarketRouterList_Exhibition_Id_Set_Without_Prices();
                      }
                      // Attempting to cancel auction listing and update auction duration.
                      if (reservePrice == 0 && auctionDuration != 0) {
                        revert NFTMarketRouterList_Duration_Set_Without_Reserve_Price();
                      }
                      uint256 auctionId = INFTMarketGetters(nftMarket).getReserveAuctionIdFor(nftContract, tokenId);
                      uint256 currentReservePrice;
                      if (auctionId != 0) {
                        currentReservePrice = INFTMarketGetters(nftMarket).getReserveAuction(auctionId).amount;
                      }
                      (address buyPriceSeller, uint256 currentBuyPrice) = INFTMarketGetters(nftMarket).getBuyPrice(nftContract, tokenId);
                      // Attempting to set no listings for the NFT when none exist.
                      if ((currentReservePrice == 0 && reservePrice == 0) && (buyPriceSeller == address(0) && !shouldSetBuyPrice)) {
                        revert NFTMarketRouterList_Must_Set_Reserve_Or_Buy_Price();
                      }
                      // Attempting to keep the prices the same as they currently are.
                      if (
                        currentReservePrice == reservePrice &&
                        ((!shouldSetBuyPrice && buyPriceSeller == address(0)) || (currentBuyPrice == buyPrice))
                      ) {
                        uint256 currentExhibitionId = INFTMarketGetters(nftMarket).getExhibitionIdForNft(nftContract, tokenId);
                        // Attempting to keep the exhibition the same as it is.
                        if (currentExhibitionId == exhibitionId) {
                          revert NFTMarketRouterList_Must_Set_Reserve_Buy_Price_Or_Exhibition();
                        }
                        _updateNftExhibition(nftContract, tokenId, exhibitionId);
                      } else {
                        // Attempting to update at least one price.
                        // Attempting to set a reserve price.
                        if (reservePrice != 0) {
                          // There is an already existing auction.
                          if (currentReservePrice != 0) {
                            // The current reserve price does not match the new reserve price,
                            // so update the already existing auction.
                            if (currentReservePrice != reservePrice) {
                              _updateReserveAuctionV2(auctionId, exhibitionId, reservePrice);
                            }
                          } else {
                            // There is no auction currently in place, so create one.
                            _createReserveAuctionV3(nftContract, tokenId, exhibitionId, reservePrice, auctionDuration);
                          }
                        }
                        // Attempting to set a buy now price
                        if (shouldSetBuyPrice) {
                          // Only set a new buy now price if it does not match the current price.
                          if (currentBuyPrice != buyPrice) {
                            _setBuyPriceV2(nftContract, tokenId, exhibitionId, buyPrice);
                          }
                        }
                        // Cancel reserve or buy prices
                        // We do this at the end of the function so that we keep the item in escrow until all prices are set
                        // Attempting to set no reserve price
                        // If there is an existing auction, cancel it.
                        if (reservePrice == 0 && currentReservePrice != 0) {
                          _cancelReserveAuction(auctionId);
                        }
                        // Attempting to set no buy now price
                        // If there is an existing buy now, cancel it.
                        if (!shouldSetBuyPrice && buyPriceSeller != address(0)) {
                          _cancelBuyPrice(nftContract, tokenId);
                        }
                      }
                    }
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  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.18;
                  import "./mixins/shared/TxDeadline.sol";
                  import "./mixins/nftMarketRouter/NFTMarketRouterCore.sol";
                  import "./mixins/nftMarketRouter/NFTMarketRouterList.sol";
                  import "./mixins/nftMarketRouter/NFTCreateAndListTimedEditionCollection.sol";
                  import "./mixins/nftMarketRouter/NFTCreateAndListLimitedEditionCollection.sol";
                  import "./mixins/nftMarketRouter/apis/NFTMarketRouterAPIs.sol";
                  import "./mixins/nftMarketRouter/apis/NFTDropMarketRouterAPIs.sol";
                  import "./mixins/nftMarketRouter/apis/NFTCollectionFactoryRouterAPIs.sol";
                  /**
                   * @title A contract which offers value-added APIs and routes requests to the NFTMarket's existing API.
                   * @dev Features in this contract can be created with a clear separation of concerns from the NFTMarket contract.
                   * It also provides the contract size space required for targeted APIs and to experiment with new features.
                   * @author batu-inal & HardlyDifficult & reggieag
                   */
                  contract NFTMarketRouter is
                    TxDeadline,
                    NFTMarketRouterCore,
                    NFTMarketRouterAPIs,
                    NFTCollectionFactoryRouterAPIs,
                    NFTDropMarketRouterAPIs,
                    NFTMarketRouterList,
                    NFTCreateAndListTimedEditionCollection,
                    NFTCreateAndListLimitedEditionCollection
                  {
                    /**
                     * @notice Initialize the template's immutable variables.
                     * @param _nftMarket The address of the NFTMarket contract to which requests will be routed.
                     * @param _nftDropMarket The address of the NFTDropMarket contract to which requests will be routed.
                     * @param _nftCollectionFactory The address of the NFTCollectionFactory contract to which requests will be routed.
                     */
                    constructor(
                      address _nftMarket,
                      address _nftDropMarket,
                      address _nftCollectionFactory
                    ) NFTMarketRouterCore(_nftMarket, _nftDropMarket, _nftCollectionFactory) {}
                  }
                  

                  File 5 of 6: NFTMarket
                  // SPDX-License-Identifier: MIT
                  // OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)
                  pragma solidity ^0.8.2;
                  import "../../utils/AddressUpgradeable.sol";
                  /**
                   * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
                   * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
                   * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
                   * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
                   *
                   * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
                   * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
                   * case an upgrade adds a module that needs to be initialized.
                   *
                   * For example:
                   *
                   * [.hljs-theme-light.nopadding]
                   * ```solidity
                   * 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.9.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 Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
                       * `nonReentrant` function in the call stack.
                       */
                      function _reentrancyGuardEntered() internal view returns (bool) {
                          return _status == _ENTERED;
                      }
                      /**
                       * @dev This empty reserved space is put in place to allow future versions to add new
                       * variables without shifting down storage in the inheritance chain.
                       * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
                       */
                      uint256[49] private __gap;
                  }
                  // SPDX-License-Identifier: MIT
                  // OpenZeppelin Contracts (last updated v4.9.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
                       *
                       * Furthermore, `isContract` will also return true if the target contract within
                       * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
                       * which only has an effect at the end of a transaction.
                       * ====
                       *
                       * [IMPORTANT]
                       * ====
                       * You shouldn't rely on `isContract` to protect against flash loan attacks!
                       *
                       * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
                       * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
                       * constructor.
                       * ====
                       */
                      function isContract(address account) internal view returns (bool) {
                          // This method relies on extcodesize/address.code.length, which returns 0
                          // for contracts in construction, since the code is only stored at the end
                          // of the constructor execution.
                          return account.code.length > 0;
                      }
                      /**
                       * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                       * `recipient`, forwarding all available gas and reverting on errors.
                       *
                       * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                       * of certain opcodes, possibly making contracts go over the 2300 gas limit
                       * imposed by `transfer`, making them unable to receive funds via
                       * `transfer`. {sendValue} removes this limitation.
                       *
                       * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                       *
                       * IMPORTANT: because control is transferred to `recipient`, care must be
                       * taken to not create reentrancy vulnerabilities. Consider using
                       * {ReentrancyGuard} or the
                       * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                       */
                      function sendValue(address payable recipient, uint256 amount) internal {
                          require(address(this).balance >= amount, "Address: insufficient balance");
                          (bool success, ) = recipient.call{value: amount}("");
                          require(success, "Address: unable to send value, recipient may have reverted");
                      }
                      /**
                       * @dev Performs a Solidity function call using a low level `call`. A
                       * plain `call` is an unsafe replacement for a function call: use this
                       * function instead.
                       *
                       * If `target` reverts with a revert reason, it is bubbled up by this
                       * function (like regular Solidity function calls).
                       *
                       * Returns the raw returned data. To convert to the expected return value,
                       * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
                       *
                       * Requirements:
                       *
                       * - `target` must be a contract.
                       * - calling `target` with `data` must not revert.
                       *
                       * _Available since v3.1._
                       */
                      function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                          return functionCallWithValue(target, data, 0, "Address: low-level call failed");
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
                       * `errorMessage` as a fallback revert reason when `target` reverts.
                       *
                       * _Available since v3.1._
                       */
                      function functionCall(
                          address target,
                          bytes memory data,
                          string memory errorMessage
                      ) internal returns (bytes memory) {
                          return functionCallWithValue(target, data, 0, errorMessage);
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but also transferring `value` wei to `target`.
                       *
                       * Requirements:
                       *
                       * - the calling contract must have an ETH balance of at least `value`.
                       * - the called Solidity function must be `payable`.
                       *
                       * _Available since v3.1._
                       */
                      function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
                          return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
                      }
                      /**
                       * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
                       * with `errorMessage` as a fallback revert reason when `target` reverts.
                       *
                       * _Available since v3.1._
                       */
                      function functionCallWithValue(
                          address target,
                          bytes memory data,
                          uint256 value,
                          string memory errorMessage
                      ) internal returns (bytes memory) {
                          require(address(this).balance >= value, "Address: insufficient balance for call");
                          (bool success, bytes memory returndata) = target.call{value: value}(data);
                          return verifyCallResultFromTarget(target, success, returndata, errorMessage);
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but performing a static call.
                       *
                       * _Available since v3.3._
                       */
                      function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                          return functionStaticCall(target, data, "Address: low-level static call failed");
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                       * but performing a static call.
                       *
                       * _Available since v3.3._
                       */
                      function functionStaticCall(
                          address target,
                          bytes memory data,
                          string memory errorMessage
                      ) internal view returns (bytes memory) {
                          (bool success, bytes memory returndata) = target.staticcall(data);
                          return verifyCallResultFromTarget(target, success, returndata, errorMessage);
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but performing a delegate call.
                       *
                       * _Available since v3.4._
                       */
                      function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                          return functionDelegateCall(target, data, "Address: low-level delegate call failed");
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                       * but performing a delegate call.
                       *
                       * _Available since v3.4._
                       */
                      function functionDelegateCall(
                          address target,
                          bytes memory data,
                          string memory errorMessage
                      ) internal returns (bytes memory) {
                          (bool success, bytes memory returndata) = target.delegatecall(data);
                          return verifyCallResultFromTarget(target, success, returndata, errorMessage);
                      }
                      /**
                       * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
                       * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
                       *
                       * _Available since v4.8._
                       */
                      function verifyCallResultFromTarget(
                          address target,
                          bool success,
                          bytes memory returndata,
                          string memory errorMessage
                      ) internal view returns (bytes memory) {
                          if (success) {
                              if (returndata.length == 0) {
                                  // only check isContract if the call was successful and the return data is empty
                                  // otherwise we already know that it was a contract
                                  require(isContract(target), "Address: call to non-contract");
                              }
                              return returndata;
                          } else {
                              _revert(returndata, errorMessage);
                          }
                      }
                      /**
                       * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
                       * revert reason or using the provided one.
                       *
                       * _Available since v4.3._
                       */
                      function verifyCallResult(
                          bool success,
                          bytes memory returndata,
                          string memory errorMessage
                      ) internal pure returns (bytes memory) {
                          if (success) {
                              return returndata;
                          } else {
                              _revert(returndata, errorMessage);
                          }
                      }
                      function _revert(bytes memory returndata, string memory errorMessage) private pure {
                          // Look for revert reason and bubble it up if present
                          if (returndata.length > 0) {
                              // The easiest way to bubble the revert reason is using memory via assembly
                              /// @solidity memory-safe-assembly
                              assembly {
                                  let returndata_size := mload(returndata)
                                  revert(add(32, returndata), returndata_size)
                              }
                          } else {
                              revert(errorMessage);
                          }
                      }
                  }
                  // SPDX-License-Identifier: MIT
                  // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
                  pragma solidity ^0.8.0;
                  import "../proxy/utils/Initializable.sol";
                  /**
                   * @dev Provides information about the current execution context, including the
                   * sender of the transaction and its data. While these are generally available
                   * via msg.sender and msg.data, they should not be accessed in such a direct
                   * manner, since when dealing with meta-transactions the account sending and
                   * paying for execution may not be the actual sender (as far as an application
                   * is concerned).
                   *
                   * This contract is only required for intermediate, library-like contracts.
                   */
                  abstract contract ContextUpgradeable is Initializable {
                      function __Context_init() internal onlyInitializing {
                      }
                      function __Context_init_unchained() internal onlyInitializing {
                      }
                      function _msgSender() internal view virtual returns (address) {
                          return msg.sender;
                      }
                      function _msgData() internal view virtual returns (bytes calldata) {
                          return msg.data;
                      }
                      /**
                       * @dev This empty reserved space is put in place to allow future versions to add new
                       * variables without shifting down storage in the inheritance chain.
                       * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
                       */
                      uint256[50] private __gap;
                  }
                  // SPDX-License-Identifier: MIT
                  // OpenZeppelin Contracts (last updated v4.9.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/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.18;
                  /**
                   * @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.18;
                  import "../../mixins/shared/MarketStructs.sol";
                  interface IMarketUtils {
                    function getTransactionBreakdown(
                      MarketTransactionOptions calldata options
                    )
                      external
                      view
                      returns (
                        uint256 protocolFeeAmount,
                        address payable[] memory creatorRecipients,
                        uint256[] memory creatorShares,
                        uint256 sellerRev,
                        uint256 buyReferrerFee,
                        uint256 sellerReferrerFee
                      );
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  /**
                   * @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.18;
                  /**
                   * @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.18;
                  import "./routes/INFTMarketReserveAuction.sol";
                  /**
                   * @title Interface with NFTMarket getters which are used by the router.
                   * @author HardlyDifficult
                   */
                  interface INFTMarketGetters {
                    function getBuyPrice(address nftContract, uint256 tokenId) external returns (address seller, uint256 price);
                    function getExhibitionIdForNft(address nftContract, uint256 tokenId) external returns (uint256 exhibitionId);
                    function getReserveAuction(
                      uint256 auctionId
                    ) external view returns (INFTMarketReserveAuction.ReserveAuction memory auction);
                    function getReserveAuctionIdFor(address nftContract, uint256 tokenId) external returns (uint256 auctionId);
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  /**
                   * @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.18;
                  /**
                   * @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.18;
                  /**
                   * @title Interface for routing calls to the NFT Market to set buy now prices.
                   * @author HardlyDifficult
                   */
                  interface INFTMarketBuyNow {
                    function cancelBuyPrice(address nftContract, uint256 tokenId) external;
                    function setBuyPrice(address nftContract, uint256 tokenId, uint256 price) external;
                    function setBuyPriceV2(address nftContract, uint256 tokenId, uint256 exhibitionId, uint256 price) external;
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  /**
                   * @title Interface for routing calls to the NFT Market Exhibition
                   * @author philbirt
                   */
                  interface INFTMarketExhibitionForRouter {
                    function updateExhibitionNft(address nftContract, uint256 tokenId, uint256 price) external;
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  /**
                   * @title Interface for routing calls to the NFT Market to create reserve auctions.
                   * @author HardlyDifficult & reggieag
                   */
                  interface INFTMarketReserveAuction {
                    /// @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;
                    }
                    function cancelReserveAuction(uint256 auctionId) external;
                    function createReserveAuctionV3(
                      address nftContract,
                      uint256 tokenId,
                      uint256 exhibitionId,
                      uint256 reservePrice,
                      uint256 duration
                    ) external returns (uint256 auctionId);
                    function updateReserveAuctionV2(uint256 auctionId, uint256 exhibitionId, uint256 reservePrice) external;
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  /**
                   * @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.18;
                  /**
                   * @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.18;
                  import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
                  import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
                  import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
                  import "../../interfaces/internal/INFTMarketGetters.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_Only_Owner_Can_Update_Exhibition_Nft(address seller);
                  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
                    INFTMarketGetters,
                    INFTMarketBuyNow,
                    FoundationTreasuryNode,
                    ContextUpgradeable,
                    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 [DEPRECATED] use `setBuyPriceV2` instead.
                     * 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 {
                      _setBuyPrice(nftContract, tokenId, price);
                    }
                    /**
                     * @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 exhibitionId The exhibition to list with, or 0 if n/a.
                     * @param price The price at which someone could buy this NFT.
                     */
                    function setBuyPriceV2(address nftContract, uint256 tokenId, uint256 exhibitionId, uint256 price) external {
                      _setBuyPrice(nftContract, tokenId, price);
                      _updateExhibitionNft(nftContract, tokenId, exhibitionId);
                    }
                    function _setBuyPrice(address nftContract, uint256 tokenId, uint256 price) private 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);
                    }
                    function _authorizeExhibitionUpdate(
                      address nftContract,
                      uint256 tokenId
                    ) internal virtual override returns (bool canUpdateExhibitionNft) {
                      address seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller;
                      if (seller != address(0)) {
                        if (seller != _msgSender()) {
                          revert NFTMarketBuyPrice_Only_Owner_Can_Update_Exhibition_Nft(seller);
                        }
                        canUpdateExhibitionNft = true;
                      } else {
                        canUpdateExhibitionNft = super._authorizeExhibitionUpdate(nftContract, tokenId);
                      }
                    }
                    /**
                     * @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(NFTMarketCore, NFTMarketExhibition) {
                      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 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.18;
                  import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
                  import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
                  import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.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 ContextUpgradeable, 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 Confirms the current Exhibition NFT relationship can be updated.
                     * @dev Verifies that the NFT is listed, not in active auction, and the sender is the owner.
                     */
                    function _authorizeExhibitionUpdate(
                      address /*nftContract*/,
                      uint256 /*tokenId*/
                    ) internal virtual returns (bool canUpdateExhibitionNft) {
                      // False by default, may be set to true by a market tool mixin if the NFT is listed.
                    }
                    /**
                     * @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 _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.18;
                  import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
                  import "../../interfaces/internal/INFTMarketExhibition.sol";
                  import "../../interfaces/internal/routes/INFTMarketExhibition.sol";
                  import "../../interfaces/internal/INFTMarketGetters.sol";
                  import "../shared/Constants.sol";
                  import "./NFTMarketCore.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_Can_Not_Remove_Not_Associated_With_Exhibition();
                  error NFTMarketExhibition_Can_Not_Update_Exhibition_NFT();
                  error NFTMarketExhibition_Curator_Automatically_Allowed();
                  error NFTMarketExhibition_Exhibition_Does_Not_Exist();
                  error NFTMarketExhibition_Exhibition_NFT_Already_Set();
                  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
                    INFTMarketGetters,
                    INFTMarketExhibition,
                    INFTMarketExhibitionForRouter,
                    ContextUpgradeable,
                    NFTMarketCore
                  {
                    /**
                     * @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();
                      }
                      _;
                    }
                    ////////////////////////////////////////////////////////////////
                    // Exhibition Management
                    ////////////////////////////////////////////////////////////////
                    /**
                     * @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);
                    }
                    /**
                     * @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;
                    }
                    ////////////////////////////////////////////////////////////////
                    // Allowlist
                    ////////////////////////////////////////////////////////////////
                    /**
                     * @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 Updates an NFTs exhibition relationship, if at least one market mechanism exists
                     * @param nftContract The address of the NFT contract.
                     * @param tokenId The id of the NFT.
                     * @param exhibitionId The exhibition ID.
                     */
                    function updateExhibitionNft(address nftContract, uint256 tokenId, uint256 exhibitionId) external {
                      uint256 currentExhibitionId = $nftContractToTokenIdToExhibitionId[nftContract][tokenId];
                      if (currentExhibitionId == exhibitionId) {
                        if (exhibitionId == 0) {
                          // A more targeted error message for this scenario.
                          revert NFTMarketExhibition_Can_Not_Remove_Not_Associated_With_Exhibition();
                        }
                        revert NFTMarketExhibition_Exhibition_NFT_Already_Set();
                      }
                      if (!_authorizeExhibitionUpdate(nftContract, tokenId)) {
                        revert NFTMarketExhibition_Can_Not_Update_Exhibition_NFT();
                      }
                      _updateExhibitionNft(nftContract, tokenId, 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 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;
                      }
                    }
                    ////////////////////////////////////////////////////////////////
                    // Inventory
                    ////////////////////////////////////////////////////////////////
                    /**
                     * @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 If we call this with exhibitionId = 0 and there is already an exhibition associated, this will emit an
                     * NftRemovedFromExhibition event. If we are associating to a new exhibition, we will also emit an
                     * NftRemovedFromExhibition event. If we are attempting to associate to the currently associated exhibition, we will
                     * not emit an NftAddedToExhibition event.
                     */
                    function _updateExhibitionNft(address nftContract, uint256 tokenId, uint256 exhibitionId) internal {
                      uint256 currentExhibitionId = $nftContractToTokenIdToExhibitionId[nftContract][tokenId];
                      // We do not need to update the exhibition NFT relationship, so return early
                      if (currentExhibitionId == exhibitionId) {
                        return;
                      }
                      if (exhibitionId == 0) {
                        _removeNftFromExhibition(nftContract, tokenId);
                      } else {
                        address curator = $idToExhibition[exhibitionId].curator;
                        if (curator == address(0)) {
                          revert NFTMarketExhibition_Exhibition_Does_Not_Exist();
                        }
                        address sender = _msgSender();
                        if (!$exhibitionIdToSellerToIsAllowed[exhibitionId][sender] && curator != sender) {
                          revert NFTMarketExhibition_Seller_Not_Allowed_In_Exhibition();
                        }
                        $nftContractToTokenIdToExhibitionId[nftContract][tokenId] = exhibitionId;
                        // If there is an exhibition already set, emit a removal
                        if (currentExhibitionId != 0) {
                          emit NftRemovedFromExhibition(nftContract, tokenId, currentExhibitionId);
                        }
                        emit NftAddedToExhibition(nftContract, tokenId, exhibitionId);
                      }
                    }
                    /**
                     * @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);
                      }
                    }
                    function _transferFromEscrowIfAvailable(
                      address nftContract,
                      uint256 tokenId,
                      address recipient
                    ) internal virtual override {
                      _removeNftFromExhibition(nftContract, tokenId);
                      super._transferFromEscrowIfAvailable(nftContract, tokenId, recipient);
                    }
                    /**
                     * @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];
                    }
                    ////////////////////////////////////////////////////////////////
                    // Payments
                    ////////////////////////////////////////////////////////////////
                    /**
                     * @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 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 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.18;
                  import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.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
                    FoundationTreasuryNode,
                    ContextUpgradeable,
                    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);
                      if (referrer == address(feth)) {
                        // FETH cannot be paid as a referrer, clear the value instead.
                        referrer = payable(0);
                      }
                      // 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.
                      address owner = IERC721(nftContract).ownerOf(tokenId);
                      if (owner == address(this)) {
                        // The NFT is currently in escrow (e.g. it has a buy price set)
                        // This should revert if `msg.sender` is not the owner of this NFT or if the NFT is in active auction.
                        _transferFromEscrow(nftContract, tokenId, offer.buyer, sender);
                      } else {
                        // NFT should be in the seller's wallet. If attempted by the wrong sender or if the market is not approved this
                        // will revert.
                        IERC721(nftContract).transferFrom(sender, offer.buyer, tokenId);
                      }
                      // 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);
                    }
                    function _transferFromEscrowIfAvailable(
                      address nftContract,
                      uint256 tokenId,
                      address recipient
                    ) internal virtual override(NFTMarketCore, NFTMarketExhibition) {
                      super._transferFromEscrowIfAvailable(nftContract, tokenId, recipient);
                    }
                    /**
                     * @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.18;
                  /**
                   * @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.18;
                  import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
                  import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
                  import "../../interfaces/internal/routes/INFTMarketReserveAuction.sol";
                  import "../../interfaces/internal/INFTMarketGetters.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_Exhibition_Nft_Auction_In_Progress();
                  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);
                  error NFTMarketReserveAuction_Only_Owner_Can_Update_Exhibition_Nft(address owner);
                  /// @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 & reggieag
                   */
                  abstract contract NFTMarketReserveAuction is
                    INFTMarketGetters,
                    INFTMarketReserveAuction,
                    FoundationTreasuryNode,
                    ContextUpgradeable,
                    FETHNode,
                    MarketSharedCore,
                    NFTMarketCore,
                    ReentrancyGuardUpgradeable,
                    SendValueWithFallbackWithdraw,
                    MarketFees,
                    NFTMarketExhibition,
                    NFTMarketAuction
                  {
                    using TimeLibrary for uint256;
                    /// @notice Stores the auction configuration for a specific NFT.
                    /// @dev This allows us to modify the storage struct without changing external APIs.
                    struct ReserveAuctionStorage {
                      // Slot 0
                      /// @notice The address of the NFT contract.
                      address nftContract;
                      // (96-bits free space)
                      // Slot 1
                      /// @notice The id of the NFT.
                      uint256 tokenId;
                      // (slot full)
                      // Slot 2
                      /// @notice The owner of the NFT which listed it in auction.
                      address payable seller;
                      /// @notice First slot (12 bytes) 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;
                      // (slot full)
                      // Slot 3
                      /// @dev This field is no longer used but was previously assigned to.
                      uint256 __gap_was_duration;
                      // (slot full)
                      // Slot 4
                      /// @dev This field is no longer used but was previous assigned to.
                      uint256 __gap_was_extensionDuration;
                      // (slot full)
                      // Slot 5
                      /// @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;
                      // (slot full)
                      // Slot 6
                      /// @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 (8 bytes) used for the bidReferrerAddress.
                      uint64 bidReferrerAddressSlot1;
                      /// @dev Auction duration length in seconds.
                      uint32 duration;
                      // (slot full)
                      // Slot 7
                      /// @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;
                      // (slot full)
                    }
                    /// @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 Default for how long an auction lasts for once the first bid has been received.
                    uint256 private immutable DEFAULT_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 for an auction.
                    uint256 private constant MAX_DURATION = 7 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_DURATION) {
                        // This ensures that math in this file will not overflow due to a huge duration.
                        revert NFTMarketReserveAuction_Exceeds_Max_Duration(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);
                      }
                      DEFAULT_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];
                      // Transfer the NFT unless it still has a buy price set.
                      _transferFromEscrowIfAvailable(auction.nftContract, auction.tokenId, auction.seller);
                      emit ReserveAuctionCanceled(auctionId);
                    }
                    /**
                     * @notice [DEPRECATED] use `createReserveAuctionV3` 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 {
                      _createReserveAuction({
                        nftContract: nftContract,
                        tokenId: tokenId,
                        reservePrice: reservePrice,
                        duration: 0 // use default duration
                      });
                    }
                    /**
                     * @notice [DEPRECATED] use `createReserveAuctionV3` 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.
                     * @param exhibitionId The exhibition to list with, or 0 if we don't want to associate to an exhibition.
                     * Setting to 0 will also remove any previously existing association to an exhibition.
                     * @return auctionId The id of the auction that was created.
                     */
                    function createReserveAuctionV2(
                      address nftContract,
                      uint256 tokenId,
                      uint256 reservePrice,
                      uint256 exhibitionId
                    ) external returns (uint256 auctionId) {
                      auctionId = createReserveAuctionV3({
                        nftContract: nftContract,
                        tokenId: tokenId,
                        exhibitionId: exhibitionId,
                        reservePrice: reservePrice,
                        duration: 0 // use default duration
                      });
                    }
                    /**
                     * @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 exhibitionId The exhibition to list with, or 0 if we don't want to associate to an exhibition.
                     * Setting to 0 will also remove any previously existing association to an exhibition.
                     * @param reservePrice The initial reserve price for the auction.
                     * @param duration The length of the auction, in seconds.
                     * @return auctionId The id of the auction that was created.
                     */
                    function createReserveAuctionV3(
                      address nftContract,
                      uint256 tokenId,
                      uint256 exhibitionId,
                      uint256 reservePrice,
                      uint256 duration
                    ) public returns (uint256 auctionId) {
                      auctionId = _createReserveAuction(nftContract, tokenId, reservePrice, duration);
                      _updateExhibitionNft(nftContract, tokenId, exhibitionId);
                    }
                    function _createReserveAuction(
                      address nftContract,
                      uint256 tokenId,
                      uint256 reservePrice,
                      uint256 duration
                    ) private nonReentrant onlyValidAuctionConfig(reservePrice) returns (uint256 auctionId) {
                      if (duration == 0) {
                        duration = DEFAULT_DURATION;
                      } else {
                        if (duration > MAX_DURATION) {
                          // This ensures that math in this file will not overflow due to a huge duration.
                          revert NFTMarketReserveAuction_Exceeds_Max_Duration(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);
                        }
                      }
                      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;
                      if (duration != DEFAULT_DURATION) {
                        // If duration is DEFAULT_DURATION, we don't need to write to storage.
                        // Safe cast is not required since duration is capped by MAX_DURATION.
                        auction.duration = uint32(duration);
                      }
                      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(feth)) {
                        // FETH cannot be paid as a referrer, clear the value instead.
                        referrer = payable(0);
                      }
                      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.
                        uint256 duration = auction.duration;
                        if (duration == 0) {
                          duration = DEFAULT_DURATION;
                        }
                        unchecked {
                          // Duration can't be more than MAX_DURATION (7 days), 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 [DEPRECATED] use `updateReserveAuctionV2` instead.
                     * 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 {
                      _updateReserveAuction(auctionId, reservePrice);
                    }
                    /**
                     * @notice If an auction has been created but has not yet received bids, the reservePrice
                     * and exhibitionId may be changed by the seller.
                     * @param auctionId The id of the auction to change.
                     * @param exhibitionId The exhibition to list with, or 0 if n/a.
                     * @param reservePrice The new reserve price for this auction.
                     */
                    function updateReserveAuctionV2(uint256 auctionId, uint256 exhibitionId, uint256 reservePrice) external {
                      (address nftContract, uint256 tokenId) = _updateReserveAuction(auctionId, reservePrice);
                      _updateExhibitionNft(nftContract, tokenId, exhibitionId);
                    }
                    function _updateReserveAuction(
                      uint256 auctionId,
                      uint256 reservePrice
                    ) private onlyValidAuctionConfig(reservePrice) returns (address nftContract, uint256 tokenId) {
                      ReserveAuctionStorage storage 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();
                      }
                      if (auction.amount == reservePrice) {
                        revert NFTMarketReserveAuction_Price_Already_Set();
                      }
                      // Update the current reserve price.
                      auction.amount = reservePrice;
                      nftContract = auction.nftContract;
                      tokenId = auction.tokenId;
                      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);
                    }
                    function _authorizeExhibitionUpdate(
                      address nftContract,
                      uint256 tokenId
                    ) internal virtual override returns (bool canUpdateExhibitionNft) {
                      uint256 auctionId = nftContractToTokenIdToAuctionId[nftContract][tokenId];
                      if (auctionId != 0) {
                        ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId];
                        if (auction.seller != _msgSender()) {
                          revert NFTMarketReserveAuction_Only_Owner_Can_Update_Exhibition_Nft(auction.seller);
                        }
                        if (auction.endTime != 0) {
                          revert NFTMarketReserveAuction_Cannot_Update_Exhibition_Nft_Auction_In_Progress();
                        }
                        canUpdateExhibitionNft = true;
                      } else {
                        canUpdateExhibitionNft = super._authorizeExhibitionUpdate(nftContract, tokenId);
                      }
                    }
                    /**
                     * @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(NFTMarketCore, NFTMarketExhibition) {
                      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];
                      uint256 duration = auctionStorage.duration;
                      if (duration == 0) {
                        duration = DEFAULT_DURATION;
                      }
                      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 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.18;
                  /// 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 Reward paid to referrers when a sale is made.
                   * Set to 1% of the sale amount. This 1% is deducted from the protocol fee.
                   */
                  uint96 constant BUY_REFERRER_IN_BASIS_POINTS = 100;
                  /**
                   * @dev 10%, expressed as a denominator for more efficient calculations.
                   */
                  uint256 constant ROYALTY_RATIO = BASIS_POINTS / ROYALTY_IN_BASIS_POINTS;
                  /**
                   * @dev 1%, expressed as a denominator for more efficient calculations.
                   */
                  uint256 constant BUY_REFERRER_RATIO = BASIS_POINTS / BUY_REFERRER_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 timed edition collection type.
                   */
                  string constant NFT_TIMED_EDITION_COLLECTION_TYPE = "NFT Timed Edition Collection";
                  /**
                   * @dev The NFT limited edition collection type.
                   */
                  string constant NFT_LIMITED_EDITION_COLLECTION_TYPE = "NFT Limited 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;
                  /**
                   * @dev Protocol fee for edition mints in basis points.
                   */
                  uint256 constant EDITION_PROTOCOL_FEE_IN_BASIS_POINTS = 500;
                  /**
                   * @dev Hash of the edition type names.
                   * This is precalculated in order to save gas on use.
                   * `keccak256(abi.encodePacked(NFT_TIMED_EDITION_COLLECTION_TYPE))`
                   */
                  bytes32 constant timedEditionTypeHash = 0xee2afa3f960e108aca17013728aafa363a0f4485661d9b6f41c6b4ddb55008ee;
                  bytes32 constant limitedEditionTypeHash = 0x7df1f68d01ab1a6ee0448a4c3fbda832177331ff72c471b12b0051c96742eef5;
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.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 ContextUpgradeable {
                    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.18;
                  import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
                  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 is Initializable {
                    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.18;
                  import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
                  import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
                  import "../../interfaces/internal/INFTCollectionType.sol";
                  import "../../interfaces/internal/IMarketUtils.sol";
                  import "./Constants.sol";
                  import "./FoundationTreasuryNode.sol";
                  import "./MarketSharedCore.sol";
                  import "./MarketStructs.sol";
                  import "./SendValueWithFallbackWithdraw.sol";
                  error NFTMarketFees_Market_Utils_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,
                    ContextUpgradeable,
                    MarketSharedCore,
                    SendValueWithFallbackWithdraw
                  {
                    using AddressUpgradeable 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 True for the Drop market which only performs primary sales. False if primary & secondary are supported.
                    bool private immutable assumePrimarySale;
                    /// @notice The fee collected by Foundation for sales facilitated by this market contract.
                    uint256 private immutable defaultProtocolFeeInBasisPoints;
                    /// @notice Reference to our MarketUtils contract.
                    IMarketUtils private immutable marketUtils;
                    /**
                     * @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 Sets the immutable variables for this contract.
                     * @param _defaultProtocolFeeInBasisPoints The default protocol fee to use for this market.
                     * @param marketUtilsAddress The address to use for our MarketUtils contract.
                     * @param _assumePrimarySale True for the Drop market which only performs primary sales.
                     * False if primary & secondary are supported.
                     */
                    constructor(uint16 _defaultProtocolFeeInBasisPoints, address marketUtilsAddress, bool _assumePrimarySale) {
                      if (
                        _defaultProtocolFeeInBasisPoints < BASIS_POINTS / BUY_REFERRER_RATIO ||
                        _defaultProtocolFeeInBasisPoints + BASIS_POINTS / ROYALTY_RATIO >= 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();
                      }
                      if (!marketUtilsAddress.isContract()) {
                        revert NFTMarketFees_Market_Utils_Is_Not_A_Contract();
                      }
                      assumePrimarySale = _assumePrimarySale;
                      defaultProtocolFeeInBasisPoints = _defaultProtocolFeeInBasisPoints;
                      marketUtils = IMarketUtils(marketUtilsAddress);
                    }
                    /**
                     * @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 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
                    )
                      public
                      view
                      returns (
                        uint256 protocolFeeAmount,
                        address payable[] memory creatorRecipients,
                        uint256[] memory creatorShares,
                        uint256 sellerRev,
                        uint256 buyReferrerFee,
                        uint256 sellerReferrerFee
                      )
                    {
                      MarketTransactionOptions memory options = MarketTransactionOptions({
                        // Market info
                        marketTakeRateInBasisPoints: _getProtocolFee(nftContract),
                        assumePrimarySale: assumePrimarySale,
                        // Sale info
                        nftContract: nftContract,
                        tokenId: tokenId,
                        price: price,
                        seller: seller,
                        // Referrals
                        buyReferrer: buyReferrer,
                        sellerReferrerTakeRateInBasisPoints: sellerReferrerTakeRateInBasisPoints,
                        // Transaction info
                        sender: _msgSender()
                      });
                      (protocolFeeAmount, creatorRecipients, creatorShares, sellerRev, buyReferrerFee, sellerReferrerFee) = marketUtils
                        .getTransactionBreakdown(options);
                    }
                    /**
                     * @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)`.
                     * @dev Currently in use by the FNDMiddleware `getFees` call (now deprecated).
                     */
                    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 MarketUtils contract.
                     */
                    function getMarketUtilsAddress() external view returns (address marketUtilsAddress) {
                      marketUtilsAddress = address(marketUtils);
                    }
                    /**
                     * @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 = defaultProtocolFeeInBasisPoints;
                    }
                    /**
                     * @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 mixins uses 504 slots in total.
                     */
                    uint256[500] private __gap;
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  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) {
                      // Returns address(0) by default.
                    }
                    /**
                     * @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[450] private __gap;
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  /// @notice Details about a marketplace sale.
                  struct MarketTransactionOptions {
                    ////////////////////////////////////////////////////////////////
                    // Market config
                    ////////////////////////////////////////////////////////////////
                    /// @notice Percentage of the transaction to go the the market, expressed in basis points.
                    uint256 marketTakeRateInBasisPoints;
                    /// @notice set to true when the token is being sold by it's creator
                    bool assumePrimarySale;
                    ////////////////////////////////////////////////////////////////
                    // Sale info
                    ////////////////////////////////////////////////////////////////
                    /// @notice The contract address of the nft
                    address nftContract;
                    /// @notice The token id of the nft.
                    uint256 tokenId;
                    /// @notice price at which the token is being sold
                    uint256 price;
                    /// @notice address of the account that is selling the token
                    address payable seller;
                    ////////////////////////////////////////////////////////////////
                    // Referrals
                    ////////////////////////////////////////////////////////////////
                    /// @notice Address of the account that referred the buyer.
                    address payable buyReferrer;
                    /// @notice Percentage of the transaction to go the the account which referred the seller, expressed in basis points.
                    uint16 sellerReferrerTakeRateInBasisPoints;
                    ////////////////////////////////////////////////////////////////
                    // Transaction info
                    ////////////////////////////////////////////////////////////////
                    /// @notice The msg.sender which executed the purchase transaction.
                    address sender;
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.18;
                  import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.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 ContextUpgradeable {
                    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.18;
                  import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
                  import "./FETHNode.sol";
                  import "./FoundationTreasuryNode.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 FoundationTreasuryNode, 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;
                      }
                      if (user == address(feth)) {
                        // FETH may revert on ETH transfers and will reject `depositFor` calls to itself, so redirect funds to the
                        // treasury contract instead.
                        user = getFoundationTreasury();
                      }
                      // 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.18;
                  import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.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,
                    ContextUpgradeable,
                    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 duration The duration of the auction in seconds.
                     * @param router The trusted router contract address.
                     * @param marketUtils The MarketUtils contract address.
                     */
                    constructor(
                      address payable treasury,
                      address feth,
                      uint256 duration,
                      address router,
                      address marketUtils
                    )
                      FoundationTreasuryNode(treasury)
                      FETHNode(feth)
                      MarketFees(
                        /* protocolFeeInBasisPoints: */
                        500,
                        marketUtils,
                        /* 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, NFTMarketExhibition, NFTMarketReserveAuction, NFTMarketBuyPrice, NFTMarketOffer) {
                      // 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 NFTMarketCore
                     */
                    function _authorizeExhibitionUpdate(
                      address nftContract,
                      uint256 tokenId
                    )
                      internal
                      virtual
                      override(NFTMarketCore, NFTMarketReserveAuction, NFTMarketBuyPrice)
                      returns (bool canUpdateExhibitionNft)
                    {
                      canUpdateExhibitionNft = super._authorizeExhibitionUpdate(nftContract, tokenId);
                    }
                    /**
                     * @inheritdoc MarketSharedCore
                     */
                    function _getSellerOf(
                      address nftContract,
                      uint256 tokenId
                    )
                      internal
                      view
                      override(MarketSharedCore, 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(ContextUpgradeable, RouterContext) returns (address sender) {
                      sender = super._msgSender();
                    }
                  }
                  

                  File 6 of 6: NFTCollection
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.12;
                  import "./interfaces/internal/INFTCollectionInitializer.sol";
                  import "./interfaces/standards/royalties/IGetRoyalties.sol";
                  import "./interfaces/standards/royalties/ITokenCreator.sol";
                  import "./interfaces/standards/royalties/IGetFees.sol";
                  import "./interfaces/standards/royalties/IRoyaltyInfo.sol";
                  import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
                  import "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol";
                  import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
                  import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
                  import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
                  import "./libraries/AddressLibrary.sol";
                  import "./mixins/collections/SequentialMintCollection.sol";
                  import "./mixins/collections/CollectionRoyalties.sol";
                  import "./mixins/shared/ContractFactory.sol";
                  /**
                   * @title A collection of NFTs by a single creator.
                   * @notice All NFTs from this contract are minted by the same creator.
                   * A 10% royalty to the creator is included which may be split with collaborators on a per-NFT basis.
                   * @author batu-inal & HardlyDifficult
                   */
                  contract NFTCollection is
                    INFTCollectionInitializer,
                    IGetRoyalties,
                    IGetFees,
                    IRoyaltyInfo,
                    ITokenCreator,
                    ContractFactory,
                    Initializable,
                    ERC165Upgradeable,
                    ERC721Upgradeable,
                    ERC721BurnableUpgradeable,
                    SequentialMintCollection,
                    CollectionRoyalties
                  {
                    using AddressLibrary for address;
                    using AddressUpgradeable for address;
                    /**
                     * @notice The baseURI to use for the tokenURI, if undefined then `ipfs://` is used.
                     */
                    string private baseURI_;
                    /**
                     * @notice Stores hashes minted to prevent duplicates.
                     * @dev 0 means not yet minted, set to 1 when minted.
                     * For why using uint is better than using bool here:
                     * github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.3/contracts/security/ReentrancyGuard.sol#L23-L27
                     */
                    mapping(string => uint256) private cidToMinted;
                    /**
                     * @dev Stores an optional alternate address to receive creator revenue and royalty payments.
                     * The target address may be a contract which could split or escrow payments.
                     */
                    mapping(uint256 => address payable) private tokenIdToCreatorPaymentAddress;
                    /**
                     * @dev Stores a CID for each NFT.
                     */
                    mapping(uint256 => string) private _tokenCIDs;
                    /**
                     * @notice Emitted when the owner changes the base URI to be used for NFTs in this collection.
                     * @param baseURI The new base URI to use.
                     */
                    event BaseURIUpdated(string baseURI);
                    /**
                     * @notice Emitted when a new NFT is minted.
                     * @param creator The address of the collection owner at this time this NFT was minted.
                     * @param tokenId The tokenId of the newly minted NFT.
                     * @param indexedTokenCID The CID of the newly minted NFT, indexed to enable watching for mint events by the tokenCID.
                     * @param tokenCID The actual CID of the newly minted NFT.
                     */
                    event Minted(address indexed creator, uint256 indexed tokenId, string indexed indexedTokenCID, string tokenCID);
                    /**
                     * @notice Emitted when the payment address for creator royalties is set.
                     * @param fromPaymentAddress The original address used for royalty payments.
                     * @param toPaymentAddress The new address used for royalty payments.
                     * @param tokenId The NFT which had the royalty payment address updated.
                     */
                    event TokenCreatorPaymentAddressSet(
                      address indexed fromPaymentAddress,
                      address indexed toPaymentAddress,
                      uint256 indexed tokenId
                    );
                    /**
                     * @notice Initialize the template's immutable variables.
                     * @param _contractFactory The factory which will be used to create collection contracts.
                     */
                    constructor(address _contractFactory)
                      ContractFactory(_contractFactory) // solhint-disable-next-line no-empty-blocks
                    {}
                    /**
                     * @notice Called by the contract factory on creation.
                     * @param _creator The creator of this collection.
                     * @param _name The collection's `name`.
                     * @param _symbol The collection's `symbol`.
                     */
                    function initialize(
                      address payable _creator,
                      string calldata _name,
                      string calldata _symbol
                    ) external initializer onlyContractFactory {
                      __ERC721_init(_name, _symbol);
                      _initializeSequentialMintCollection(_creator, 0);
                    }
                    /**
                     * @notice Allows the creator to burn a specific token if they currently own the NFT.
                     * @param tokenId The ID of the NFT to burn.
                     * @dev The function here asserts `onlyOwner` while the super confirms ownership.
                     */
                    function burn(uint256 tokenId) public override onlyOwner {
                      super.burn(tokenId);
                    }
                    /**
                     * @notice Mint an NFT defined by its metadata path.
                     * @dev This is only callable by the collection creator/owner.
                     * @param tokenCID The CID for the metadata json of the NFT to mint.
                     * @return tokenId The tokenId of the newly minted NFT.
                     */
                    function mint(string calldata tokenCID) external returns (uint256 tokenId) {
                      tokenId = _mint(tokenCID);
                    }
                    /**
                     * @notice Mint an NFT defined by its metadata path and approves the provided operator address.
                     * @dev This is only callable by the collection creator/owner.
                     * It can be used the first time they mint to save having to issue a separate approval
                     * transaction before listing the NFT for sale.
                     * @param tokenCID The CID for the metadata json of the NFT to mint.
                     * @param operator The address to set as an approved operator for the creator's account.
                     * @return tokenId The tokenId of the newly minted NFT.
                     */
                    function mintAndApprove(string calldata tokenCID, address operator) external returns (uint256 tokenId) {
                      tokenId = _mint(tokenCID);
                      setApprovalForAll(operator, true);
                    }
                    /**
                     * @notice Mint an NFT defined by its metadata path and have creator revenue/royalties sent to an alternate address.
                     * @dev This is only callable by the collection creator/owner.
                     * @param tokenCID The CID for the metadata json of the NFT to mint.
                     * @param tokenCreatorPaymentAddress The royalty recipient address to use for this NFT.
                     * @return tokenId The tokenId of the newly minted NFT.
                     */
                    function mintWithCreatorPaymentAddress(string calldata tokenCID, address payable tokenCreatorPaymentAddress)
                      public
                      returns (uint256 tokenId)
                    {
                      require(tokenCreatorPaymentAddress != address(0), "NFTCollection: tokenCreatorPaymentAddress is required");
                      tokenId = _mint(tokenCID);
                      tokenIdToCreatorPaymentAddress[tokenId] = tokenCreatorPaymentAddress;
                      emit TokenCreatorPaymentAddressSet(address(0), tokenCreatorPaymentAddress, tokenId);
                    }
                    /**
                     * @notice Mint an NFT defined by its metadata path and approves the provided operator address.
                     * @dev This is only callable by the collection creator/owner.
                     * It can be used the first time they mint to save having to issue a separate approval
                     * transaction before listing the NFT for sale.
                     * @param tokenCID The CID for the metadata json of the NFT to mint.
                     * @param tokenCreatorPaymentAddress The royalty recipient address to use for this NFT.
                     * @param operator The address to set as an approved operator for the creator's account.
                     * @return tokenId The tokenId of the newly minted NFT.
                     */
                    function mintWithCreatorPaymentAddressAndApprove(
                      string calldata tokenCID,
                      address payable tokenCreatorPaymentAddress,
                      address operator
                    ) external returns (uint256 tokenId) {
                      tokenId = mintWithCreatorPaymentAddress(tokenCID, tokenCreatorPaymentAddress);
                      setApprovalForAll(operator, true);
                    }
                    /**
                     * @notice Mint an NFT defined by its metadata path and have creator revenue/royalties sent to an alternate address
                     * which is defined by a contract call, typically a proxy contract address representing the payment terms.
                     * @dev This is only callable by the collection creator/owner.
                     * @param tokenCID The CID for the metadata json of the NFT to mint.
                     * @param paymentAddressFactory The contract to call which will return the address to use for payments.
                     * @param paymentAddressCall The call details to send to the factory provided.
                     * @return tokenId The tokenId of the newly minted NFT.
                     */
                    function mintWithCreatorPaymentFactory(
                      string calldata tokenCID,
                      address paymentAddressFactory,
                      bytes calldata paymentAddressCall
                    ) public returns (uint256 tokenId) {
                      address payable tokenCreatorPaymentAddress = paymentAddressFactory.callAndReturnContractAddress(paymentAddressCall);
                      tokenId = mintWithCreatorPaymentAddress(tokenCID, tokenCreatorPaymentAddress);
                    }
                    /**
                     * @notice Mint an NFT defined by its metadata path and have creator revenue/royalties sent to an alternate address
                     * which is defined by a contract call, typically a proxy contract address representing the payment terms.
                     * @dev This is only callable by the collection creator/owner.
                     * It can be used the first time they mint to save having to issue a separate approval
                     * transaction before listing the NFT for sale.
                     * @param tokenCID The CID for the metadata json of the NFT to mint.
                     * @param paymentAddressFactory The contract to call which will return the address to use for payments.
                     * @param paymentAddressCall The call details to send to the factory provided.
                     * @param operator The address to set as an approved operator for the creator's account.
                     * @return tokenId The tokenId of the newly minted NFT.
                     */
                    function mintWithCreatorPaymentFactoryAndApprove(
                      string calldata tokenCID,
                      address paymentAddressFactory,
                      bytes calldata paymentAddressCall,
                      address operator
                    ) external returns (uint256 tokenId) {
                      tokenId = mintWithCreatorPaymentFactory(tokenCID, paymentAddressFactory, paymentAddressCall);
                      setApprovalForAll(operator, true);
                    }
                    /**
                     * @notice Allows the collection creator 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 onlyOwner {
                      _selfDestruct();
                    }
                    /**
                     * @notice Allows the owner to assign a baseURI to use for the tokenURI instead of the default `ipfs://`.
                     * @param baseURIOverride The new base URI to use for all NFTs in this collection.
                     */
                    function updateBaseURI(string calldata baseURIOverride) external onlyOwner {
                      baseURI_ = baseURIOverride;
                      emit BaseURIUpdated(baseURIOverride);
                    }
                    /**
                     * @notice Allows the owner to set a max tokenID.
                     * This provides a guarantee to collectors about the limit of this collection contract, if applicable.
                     * @dev Once this value has been set, it may be decreased but can never be increased.
                     * This max may be more than the final `totalSupply` if 1 or more tokens were burned.
                     * @param _maxTokenId The max tokenId to set, all NFTs must have a tokenId less than or equal to this value.
                     */
                    function updateMaxTokenId(uint32 _maxTokenId) external onlyOwner {
                      _updateMaxTokenId(_maxTokenId);
                    }
                    function _burn(uint256 tokenId) internal override(ERC721Upgradeable, SequentialMintCollection) {
                      delete cidToMinted[_tokenCIDs[tokenId]];
                      delete tokenIdToCreatorPaymentAddress[tokenId];
                      delete _tokenCIDs[tokenId];
                      super._burn(tokenId);
                    }
                    function _mint(string calldata tokenCID) private onlyOwner returns (uint256 tokenId) {
                      require(bytes(tokenCID).length != 0, "NFTCollection: tokenCID is required");
                      require(cidToMinted[tokenCID] == 0, "NFTCollection: NFT was already minted");
                      // Number of tokens cannot realistically overflow 32 bits.
                      tokenId = ++latestTokenId;
                      require(maxTokenId == 0 || tokenId <= maxTokenId, "NFTCollection: Max token count has already been minted");
                      cidToMinted[tokenCID] = 1;
                      _tokenCIDs[tokenId] = tokenCID;
                      _safeMint(msg.sender, tokenId);
                      emit Minted(msg.sender, tokenId, tokenCID, tokenCID);
                    }
                    /**
                     * @notice The base URI used for all NFTs in this collection.
                     * @dev The `tokenCID` is appended to this to obtain an NFT's `tokenURI`.
                     *      e.g. The URI for a token with the `tokenCID`: "foo" and `baseURI`: "ipfs://" is "ipfs://foo".
                     * @return uri The base URI used by this collection.
                     */
                    function baseURI() external view returns (string memory uri) {
                      uri = _baseURI();
                    }
                    /**
                     * @notice Checks if the creator has already minted a given NFT using this collection contract.
                     * @param tokenCID The CID to check for.
                     * @return hasBeenMinted True if the creator has already minted an NFT with this CID.
                     */
                    function getHasMintedCID(string calldata tokenCID) external view returns (bool hasBeenMinted) {
                      hasBeenMinted = cidToMinted[tokenCID] != 0;
                    }
                    /**
                     * @inheritdoc CollectionRoyalties
                     */
                    function getTokenCreatorPaymentAddress(uint256 tokenId)
                      public
                      view
                      override
                      returns (address payable creatorPaymentAddress)
                    {
                      creatorPaymentAddress = tokenIdToCreatorPaymentAddress[tokenId];
                      if (creatorPaymentAddress == address(0)) {
                        creatorPaymentAddress = owner;
                      }
                    }
                    /**
                     * @inheritdoc IERC165Upgradeable
                     */
                    function supportsInterface(bytes4 interfaceId)
                      public
                      view
                      override(ERC165Upgradeable, ERC721Upgradeable, CollectionRoyalties)
                      returns (bool interfaceSupported)
                    {
                      // This is a no-op function required to avoid compile errors.
                      interfaceSupported = super.supportsInterface(interfaceId);
                    }
                    /**
                     * @inheritdoc IERC721MetadataUpgradeable
                     */
                    function tokenURI(uint256 tokenId) public view override returns (string memory uri) {
                      require(_exists(tokenId), "NFTCollection: URI query for nonexistent token");
                      uri = string.concat(_baseURI(), _tokenCIDs[tokenId]);
                    }
                    function _baseURI() internal view override returns (string memory uri) {
                      uri = baseURI_;
                      if (bytes(uri).length == 0) {
                        uri = "ipfs://";
                      }
                    }
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.12;
                  import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
                  struct CallWithoutValue {
                    address target;
                    bytes callData;
                  }
                  /**
                   * @title A library for address helpers not already covered by the OZ library.
                   * @author batu-inal & HardlyDifficult
                   */
                  library AddressLibrary {
                    using AddressUpgradeable for address;
                    using AddressUpgradeable for address payable;
                    /**
                     * @notice Calls an external contract with arbitrary data and parse the return value into an address.
                     * @param externalContract The address of the contract to call.
                     * @param callData The data to send to the contract.
                     * @return contractAddress The address of the contract returned by the call.
                     */
                    function callAndReturnContractAddress(address externalContract, bytes calldata callData)
                      internal
                      returns (address payable contractAddress)
                    {
                      bytes memory returnData = externalContract.functionCall(callData);
                      contractAddress = abi.decode(returnData, (address));
                      require(contractAddress.isContract(), "InternalProxyCall: did not return a contract");
                    }
                    function callAndReturnContractAddress(CallWithoutValue calldata call)
                      internal
                      returns (address payable contractAddress)
                    {
                      contractAddress = callAndReturnContractAddress(call.target, call.callData);
                    }
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.12;
                  /**
                   * @author batu-inal & HardlyDifficult
                   */
                  interface INFTCollectionInitializer {
                    function initialize(
                      address payable _creator,
                      string memory _name,
                      string memory _symbol
                    ) external;
                  }
                  // SPDX-License-Identifier: MIT OR Apache-2.0
                  pragma solidity ^0.8.12;
                  import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
                  import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
                  import "../../interfaces/standards/royalties/ITokenCreator.sol";
                  /**
                   * @title Extends the OZ ERC721 implementation for collections which mint sequential token IDs.
                   * @author batu-inal & HardlyDifficult
                   */
                  abstract contract SequentialMintCollection is ITokenCreator, Initializable, 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 The max tokenId which can be minted.
                     * @dev This max may be less than the final `totalSupply` if 1 or more tokens were burned.
                     * @return The max tokenId which can be minted.
                     */
                    uint32 public maxTokenId;
                    /**
                     * @notice Tracks how many tokens have been burned.
                     * @dev This number is used to calculate the total supply efficiently.
                     */
                    uint32 private burnCounter;
                    /****** End of storage ******/
                    /**
                     * @notice Emitted when the max tokenId supported by this collection is updated.
                     * @param maxTokenId The new max tokenId. All NFTs in this collection will have a tokenId less than
                     * or equal to this value.
                     */
                    event MaxTokenIdUpdated(uint256 indexed maxTokenId);
                    /**
                     * @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() {
                      require(msg.sender == owner, "SequentialMintCollection: Caller is not owner");
                      _;
                    }
                    function _initializeSequentialMintCollection(address payable _creator, uint32 _maxTokenId) internal onlyInitializing {
                      owner = _creator;
                      maxTokenId = _maxTokenId;
                    }
                    /**
                     * @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 {
                      require(totalSupply() == 0, "SequentialMintCollection: Any NFTs minted must be burned first");
                      emit SelfDestruct(msg.sender);
                      selfdestruct(payable(msg.sender));
                    }
                    /**
                     * @notice Allows the owner to set a max tokenID.
                     * This provides a guarantee to collectors about the limit of this collection contract, if applicable.
                     * @dev Once this value has been set, it may be decreased but can never be increased.
                     * @param _maxTokenId The max tokenId to set, all NFTs must have a tokenId less than or equal to this value.
                     */
                    function _updateMaxTokenId(uint32 _maxTokenId) internal {
                      require(_maxTokenId != 0, "SequentialMintCollection: Max token ID may not be cleared");
                      require(maxTokenId == 0 || _maxTokenId < maxTokenId, "SequentialMintCollection: Max token ID may not increase");
                      require(latestTokenId <= _maxTokenId, "SequentialMintCollection: Max token ID must be >= last mint");
                      maxTokenId = _maxTokenId;
                      emit MaxTokenIdUpdated(_maxTokenId);
                    }
                    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 "@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 "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
                  /**
                   * @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() {
                      require(msg.sender == contractFactory, "ContractFactory: Caller is not the factory");
                      _;
                    }
                    /**
                     * @notice Initialize the template's immutable variables.
                     * @param _contractFactory The factory which will be used to create these contracts.
                     */
                    constructor(address _contractFactory) {
                      require(_contractFactory.isContract(), "ContractFactory: Factory is not a contract");
                      contractFactory = _contractFactory;
                    }
                  }
                  // 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;
                  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;
                  /**
                   * @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;
                  /**
                   * @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
                  // OpenZeppelin Contracts (last updated v4.7.0) (proxy/utils/Initializable.sol)
                  pragma solidity ^0.8.2;
                  import "../../utils/AddressUpgradeable.sol";
                  /**
                   * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
                   * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
                   * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
                   * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
                   *
                   * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
                   * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
                   * case an upgrade adds a module that needs to be initialized.
                   *
                   * For example:
                   *
                   * [.hljs-theme-light.nopadding]
                   * ```
                   * contract MyToken is ERC20Upgradeable {
                   *     function initialize() initializer public {
                   *         __ERC20_init("MyToken", "MTK");
                   *     }
                   * }
                   * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
                   *     function initializeV2() reinitializer(2) public {
                   *         __ERC20Permit_init("MyToken");
                   *     }
                   * }
                   * ```
                   *
                   * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
                   * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
                   *
                   * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
                   * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
                   *
                   * [CAUTION]
                   * ====
                   * Avoid leaving a contract uninitialized.
                   *
                   * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
                   * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
                   * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
                   *
                   * [.hljs-theme-light.nopadding]
                   * ```
                   * /// @custom:oz-upgrades-unsafe-allow constructor
                   * constructor() {
                   *     _disableInitializers();
                   * }
                   * ```
                   * ====
                   */
                  abstract contract Initializable {
                      /**
                       * @dev Indicates that the contract has been initialized.
                       * @custom:oz-retyped-from bool
                       */
                      uint8 private _initialized;
                      /**
                       * @dev Indicates that the contract is in the process of being initialized.
                       */
                      bool private _initializing;
                      /**
                       * @dev Triggered when the contract has been initialized or reinitialized.
                       */
                      event Initialized(uint8 version);
                      /**
                       * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
                       * `onlyInitializing` functions can be used to initialize parent contracts. Equivalent to `reinitializer(1)`.
                       */
                      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.
                       *
                       * `initializer` is equivalent to `reinitializer(1)`, so 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.
                       *
                       * 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.
                       */
                      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.
                       */
                      function _disableInitializers() internal virtual {
                          require(!_initializing, "Initializable: contract is initializing");
                          if (_initialized < type(uint8).max) {
                              _initialized = type(uint8).max;
                              emit Initialized(type(uint8).max);
                          }
                      }
                  }
                  // 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 (last updated v4.7.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 = _owners[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 nor approved for all"
                          );
                          _approve(to, tokenId);
                      }
                      /**
                       * @dev See {IERC721-getApproved}.
                       */
                      function getApproved(uint256 tokenId) public view virtual override returns (address) {
                          _requireMinted(tokenId);
                          return _tokenApprovals[tokenId];
                      }
                      /**
                       * @dev See {IERC721-setApprovalForAll}.
                       */
                      function setApprovalForAll(address operator, bool approved) public virtual override {
                          _setApprovalForAll(_msgSender(), operator, approved);
                      }
                      /**
                       * @dev See {IERC721-isApprovedForAll}.
                       */
                      function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
                          return _operatorApprovals[owner][operator];
                      }
                      /**
                       * @dev See {IERC721-transferFrom}.
                       */
                      function transferFrom(
                          address from,
                          address to,
                          uint256 tokenId
                      ) public virtual override {
                          //solhint-disable-next-line max-line-length
                          require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner nor approved");
                          _transfer(from, to, tokenId);
                      }
                      /**
                       * @dev See {IERC721-safeTransferFrom}.
                       */
                      function safeTransferFrom(
                          address from,
                          address to,
                          uint256 tokenId
                      ) public virtual override {
                          safeTransferFrom(from, to, tokenId, "");
                      }
                      /**
                       * @dev See {IERC721-safeTransferFrom}.
                       */
                      function safeTransferFrom(
                          address from,
                          address to,
                          uint256 tokenId,
                          bytes memory data
                      ) public virtual override {
                          require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner nor approved");
                          _safeTransfer(from, to, tokenId, data);
                      }
                      /**
                       * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
                       * are aware of the ERC721 protocol to prevent tokens from being forever locked.
                       *
                       * `data` is additional data, it has no specified format and it is sent in call to `to`.
                       *
                       * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
                       * implement alternative mechanisms to perform token transfer, such as signature-based.
                       *
                       * Requirements:
                       *
                       * - `from` cannot be the zero address.
                       * - `to` cannot be the zero address.
                       * - `tokenId` token must exist and be owned by `from`.
                       * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
                       *
                       * Emits a {Transfer} event.
                       */
                      function _safeTransfer(
                          address from,
                          address to,
                          uint256 tokenId,
                          bytes memory data
                      ) internal virtual {
                          _transfer(from, to, tokenId);
                          require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer");
                      }
                      /**
                       * @dev Returns whether `tokenId` exists.
                       *
                       * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
                       *
                       * Tokens start existing when they are minted (`_mint`),
                       * and stop existing when they are burned (`_burn`).
                       */
                      function _exists(uint256 tokenId) internal view virtual returns (bool) {
                          return _owners[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);
                          _balances[to] += 1;
                          _owners[tokenId] = to;
                          emit Transfer(address(0), to, tokenId);
                          _afterTokenTransfer(address(0), to, tokenId);
                      }
                      /**
                       * @dev Destroys `tokenId`.
                       * The approval is cleared when the token is burned.
                       *
                       * Requirements:
                       *
                       * - `tokenId` must exist.
                       *
                       * Emits a {Transfer} event.
                       */
                      function _burn(uint256 tokenId) internal virtual {
                          address owner = ERC721Upgradeable.ownerOf(tokenId);
                          _beforeTokenTransfer(owner, address(0), tokenId);
                          // Clear approvals
                          _approve(address(0), tokenId);
                          _balances[owner] -= 1;
                          delete _owners[tokenId];
                          emit Transfer(owner, address(0), tokenId);
                          _afterTokenTransfer(owner, address(0), tokenId);
                      }
                      /**
                       * @dev Transfers `tokenId` from `from` to `to`.
                       *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
                       *
                       * Requirements:
                       *
                       * - `to` cannot be the zero address.
                       * - `tokenId` token must be owned by `from`.
                       *
                       * Emits a {Transfer} event.
                       */
                      function _transfer(
                          address from,
                          address to,
                          uint256 tokenId
                      ) internal virtual {
                          require(ERC721Upgradeable.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
                          require(to != address(0), "ERC721: transfer to the zero address");
                          _beforeTokenTransfer(from, to, tokenId);
                          // Clear approvals from the previous owner
                          _approve(address(0), tokenId);
                          _balances[from] -= 1;
                          _balances[to] += 1;
                          _owners[tokenId] = to;
                          emit Transfer(from, to, tokenId);
                          _afterTokenTransfer(from, to, tokenId);
                      }
                      /**
                       * @dev Approve `to` to operate on `tokenId`
                       *
                       * Emits an {Approval} event.
                       */
                      function _approve(address to, uint256 tokenId) internal virtual {
                          _tokenApprovals[tokenId] = to;
                          emit Approval(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.
                       *
                       * Calling conditions:
                       *
                       * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
                       * transferred to `to`.
                       * - When `from` is zero, `tokenId` will be minted for `to`.
                       * - When `to` is zero, ``from``'s `tokenId` will be burned.
                       * - `from` and `to` are never both zero.
                       *
                       * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
                       */
                      function _beforeTokenTransfer(
                          address from,
                          address to,
                          uint256 tokenId
                      ) internal virtual {}
                      /**
                       * @dev Hook that is called after any transfer of tokens. This includes
                       * minting and burning.
                       *
                       * Calling conditions:
                       *
                       * - when `from` and `to` are both non-zero.
                       * - `from` and `to` are never both zero.
                       *
                       * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
                       */
                      function _afterTokenTransfer(
                          address from,
                          address to,
                          uint256 tokenId
                      ) internal virtual {}
                      /**
                       * @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.7.0) (utils/Address.sol)
                  pragma solidity ^0.8.1;
                  /**
                   * @dev Collection of functions related to the address type
                   */
                  library AddressUpgradeable {
                      /**
                       * @dev Returns true if `account` is a contract.
                       *
                       * [IMPORTANT]
                       * ====
                       * It is unsafe to assume that an address for which this function returns
                       * false is an externally-owned account (EOA) and not a contract.
                       *
                       * Among others, `isContract` will return false for the following
                       * types of addresses:
                       *
                       *  - an externally-owned account
                       *  - a contract in construction
                       *  - an address where a contract will be created
                       *  - an address where a contract lived, but was destroyed
                       * ====
                       *
                       * [IMPORTANT]
                       * ====
                       * You shouldn't rely on `isContract` to protect against flash loan attacks!
                       *
                       * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
                       * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
                       * constructor.
                       * ====
                       */
                      function isContract(address account) internal view returns (bool) {
                          // This method relies on extcodesize/address.code.length, which returns 0
                          // for contracts in construction, since the code is only stored at the end
                          // of the constructor execution.
                          return account.code.length > 0;
                      }
                      /**
                       * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                       * `recipient`, forwarding all available gas and reverting on errors.
                       *
                       * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                       * of certain opcodes, possibly making contracts go over the 2300 gas limit
                       * imposed by `transfer`, making them unable to receive funds via
                       * `transfer`. {sendValue} removes this limitation.
                       *
                       * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                       *
                       * IMPORTANT: because control is transferred to `recipient`, care must be
                       * taken to not create reentrancy vulnerabilities. Consider using
                       * {ReentrancyGuard} or the
                       * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                       */
                      function sendValue(address payable recipient, uint256 amount) internal {
                          require(address(this).balance >= amount, "Address: insufficient balance");
                          (bool success, ) = recipient.call{value: amount}("");
                          require(success, "Address: unable to send value, recipient may have reverted");
                      }
                      /**
                       * @dev Performs a Solidity function call using a low level `call`. A
                       * plain `call` is an unsafe replacement for a function call: use this
                       * function instead.
                       *
                       * If `target` reverts with a revert reason, it is bubbled up by this
                       * function (like regular Solidity function calls).
                       *
                       * Returns the raw returned data. To convert to the expected return value,
                       * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
                       *
                       * Requirements:
                       *
                       * - `target` must be a contract.
                       * - calling `target` with `data` must not revert.
                       *
                       * _Available since v3.1._
                       */
                      function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                          return functionCall(target, data, "Address: low-level call failed");
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
                       * `errorMessage` as a fallback revert reason when `target` reverts.
                       *
                       * _Available since v3.1._
                       */
                      function functionCall(
                          address target,
                          bytes memory data,
                          string memory errorMessage
                      ) internal returns (bytes memory) {
                          return functionCallWithValue(target, data, 0, errorMessage);
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but also transferring `value` wei to `target`.
                       *
                       * Requirements:
                       *
                       * - the calling contract must have an ETH balance of at least `value`.
                       * - the called Solidity function must be `payable`.
                       *
                       * _Available since v3.1._
                       */
                      function functionCallWithValue(
                          address target,
                          bytes memory data,
                          uint256 value
                      ) internal returns (bytes memory) {
                          return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
                      }
                      /**
                       * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
                       * with `errorMessage` as a fallback revert reason when `target` reverts.
                       *
                       * _Available since v3.1._
                       */
                      function functionCallWithValue(
                          address target,
                          bytes memory data,
                          uint256 value,
                          string memory errorMessage
                      ) internal returns (bytes memory) {
                          require(address(this).balance >= value, "Address: insufficient balance for call");
                          require(isContract(target), "Address: call to non-contract");
                          (bool success, bytes memory returndata) = target.call{value: value}(data);
                          return verifyCallResult(success, returndata, errorMessage);
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but performing a static call.
                       *
                       * _Available since v3.3._
                       */
                      function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                          return functionStaticCall(target, data, "Address: low-level static call failed");
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                       * but performing a static call.
                       *
                       * _Available since v3.3._
                       */
                      function functionStaticCall(
                          address target,
                          bytes memory data,
                          string memory errorMessage
                      ) internal view returns (bytes memory) {
                          require(isContract(target), "Address: static call to non-contract");
                          (bool success, bytes memory returndata) = target.staticcall(data);
                          return verifyCallResult(success, returndata, errorMessage);
                      }
                      /**
                       * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
                       * revert reason using the provided one.
                       *
                       * _Available since v4.3._
                       */
                      function verifyCallResult(
                          bool success,
                          bytes memory returndata,
                          string memory errorMessage
                      ) internal pure returns (bytes memory) {
                          if (success) {
                              return returndata;
                          } else {
                              // Look for revert reason and bubble it up if present
                              if (returndata.length > 0) {
                                  // The easiest way to bubble the revert reason is using memory via assembly
                                  /// @solidity memory-safe-assembly
                                  assembly {
                                      let returndata_size := mload(returndata)
                                      revert(add(32, returndata), returndata_size)
                                  }
                              } else {
                                  revert(errorMessage);
                              }
                          }
                      }
                  }
                  // SPDX-License-Identifier: MIT
                  // OpenZeppelin Contracts (last updated v4.7.0) (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 nor 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 (utils/Context.sol)
                  pragma solidity ^0.8.0;
                  import "../proxy/utils/Initializable.sol";
                  /**
                   * @dev Provides information about the current execution context, including the
                   * sender of the transaction and its data. While these are generally available
                   * via msg.sender and msg.data, they should not be accessed in such a direct
                   * manner, since when dealing with meta-transactions the account sending and
                   * paying for execution may not be the actual sender (as far as an application
                   * is concerned).
                   *
                   * This contract is only required for intermediate, library-like contracts.
                   */
                  abstract contract ContextUpgradeable is Initializable {
                      function __Context_init() internal onlyInitializing {
                      }
                      function __Context_init_unchained() internal onlyInitializing {
                      }
                      function _msgSender() internal view virtual returns (address) {
                          return msg.sender;
                      }
                      function _msgData() internal view virtual returns (bytes calldata) {
                          return msg.data;
                      }
                      /**
                       * @dev This empty reserved space is put in place to allow future versions to add new
                       * variables without shifting down storage in the inheritance chain.
                       * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
                       */
                      uint256[50] private __gap;
                  }
                  // SPDX-License-Identifier: MIT
                  // OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)
                  pragma solidity ^0.8.0;
                  /**
                   * @dev String operations.
                   */
                  library StringsUpgradeable {
                      bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
                      uint8 private constant _ADDRESS_LENGTH = 20;
                      /**
                       * @dev Converts a `uint256` to its ASCII `string` decimal representation.
                       */
                      function toString(uint256 value) internal pure returns (string memory) {
                          // Inspired by OraclizeAPI's implementation - MIT licence
                          // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
                          if (value == 0) {
                              return "0";
                          }
                          uint256 temp = value;
                          uint256 digits;
                          while (temp != 0) {
                              digits++;
                              temp /= 10;
                          }
                          bytes memory buffer = new bytes(digits);
                          while (value != 0) {
                              digits -= 1;
                              buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
                              value /= 10;
                          }
                          return string(buffer);
                      }
                      /**
                       * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
                       */
                      function toHexString(uint256 value) internal pure returns (string memory) {
                          if (value == 0) {
                              return "0x00";
                          }
                          uint256 temp = value;
                          uint256 length = 0;
                          while (temp != 0) {
                              length++;
                              temp >>= 8;
                          }
                          return toHexString(value, length);
                      }
                      /**
                       * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
                       */
                      function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                          bytes memory buffer = new bytes(2 * length + 2);
                          buffer[0] = "0";
                          buffer[1] = "x";
                          for (uint256 i = 2 * length + 1; i > 1; --i) {
                              buffer[i] = _HEX_SYMBOLS[value & 0xf];
                              value >>= 4;
                          }
                          require(value == 0, "Strings: hex length insufficient");
                          return string(buffer);
                      }
                      /**
                       * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
                       */
                      function toHexString(address addr) internal pure returns (string memory) {
                          return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
                      }
                  }
                  // SPDX-License-Identifier: MIT
                  // OpenZeppelin Contracts (last updated v4.7.0) (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: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
                       *
                       * Requirements:
                       *
                       * - `from` cannot be the zero address.
                       * - `to` cannot be the zero address.
                       * - `tokenId` token must be owned by `from`.
                       * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
                       *
                       * Emits a {Transfer} event.
                       */
                      function transferFrom(
                          address from,
                          address to,
                          uint256 tokenId
                      ) external;
                      /**
                       * @dev Gives permission to `to` to transfer `tokenId` token to another account.
                       * The approval is cleared when the token is transferred.
                       *
                       * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
                       *
                       * Requirements:
                       *
                       * - The caller must own the token or be an approved operator.
                       * - `tokenId` must exist.
                       *
                       * Emits an {Approval} event.
                       */
                      function approve(address to, uint256 tokenId) external;
                      /**
                       * @dev Approve or remove `operator` as an operator for the caller.
                       * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
                       *
                       * Requirements:
                       *
                       * - The `operator` cannot be the caller.
                       *
                       * Emits an {ApprovalForAll} event.
                       */
                      function setApprovalForAll(address operator, bool _approved) external;
                      /**
                       * @dev Returns the account approved for `tokenId` token.
                       *
                       * Requirements:
                       *
                       * - `tokenId` must exist.
                       */
                      function getApproved(uint256 tokenId) external view returns (address operator);
                      /**
                       * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
                       *
                       * See {setApprovalForAll}
                       */
                      function isApprovedForAll(address owner, address operator) external view returns (bool);
                  }
                  // SPDX-License-Identifier: MIT
                  // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)
                  pragma solidity ^0.8.0;
                  /**
                   * @title ERC721 token receiver interface
                   * @dev Interface for any contract that wants to support safeTransfers
                   * from ERC721 asset contracts.
                   */
                  interface 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 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 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 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;
                  /**
                   * @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 The minimum increase of 10% required when making an offer or placing a bid.
                   */
                  uint256 constant MIN_PERCENT_INCREMENT_DENOMINATOR = BASIS_POINTS / 1_000;
                  /**
                   * @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 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;
                  /**
                   * @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;
                  // SPDX-License-Identifier: UNLICENSED
                  pragma solidity >=0.6.0;
                  import "../contracts/NFTCollection.sol";
                  contract $NFTCollection is NFTCollection {
                      bytes32 public __hh_exposed_bytecode_marker = "hardhat-exposed";
                      constructor(address _contractFactory) NFTCollection(_contractFactory) {}
                      function $_burn(uint256 tokenId) external {
                          return super._burn(tokenId);
                      }
                      function $_baseURI() external view returns (string memory) {
                          return super._baseURI();
                      }
                      function $_initializeSequentialMintCollection(address payable _creator,uint32 _maxTokenId) external {
                          return super._initializeSequentialMintCollection(_creator,_maxTokenId);
                      }
                      function $_selfDestruct() external {
                          return super._selfDestruct();
                      }
                      function $_updateMaxTokenId(uint32 _maxTokenId) external {
                          return super._updateMaxTokenId(_maxTokenId);
                      }
                      function $__ERC721Burnable_init() external {
                          return super.__ERC721Burnable_init();
                      }
                      function $__ERC721Burnable_init_unchained() external {
                          return super.__ERC721Burnable_init_unchained();
                      }
                      function $__ERC721_init(string calldata name_,string calldata symbol_) external {
                          return super.__ERC721_init(name_,symbol_);
                      }
                      function $__ERC721_init_unchained(string calldata name_,string calldata symbol_) external {
                          return super.__ERC721_init_unchained(name_,symbol_);
                      }
                      function $_safeTransfer(address from,address to,uint256 tokenId,bytes calldata data) external {
                          return super._safeTransfer(from,to,tokenId,data);
                      }
                      function $_exists(uint256 tokenId) external view returns (bool) {
                          return super._exists(tokenId);
                      }
                      function $_isApprovedOrOwner(address spender,uint256 tokenId) external view returns (bool) {
                          return super._isApprovedOrOwner(spender,tokenId);
                      }
                      function $_safeMint(address to,uint256 tokenId) external {
                          return super._safeMint(to,tokenId);
                      }
                      function $_safeMint(address to,uint256 tokenId,bytes calldata data) external {
                          return super._safeMint(to,tokenId,data);
                      }
                      function $_mint(address to,uint256 tokenId) external {
                          return super._mint(to,tokenId);
                      }
                      function $_transfer(address from,address to,uint256 tokenId) external {
                          return super._transfer(from,to,tokenId);
                      }
                      function $_approve(address to,uint256 tokenId) external {
                          return super._approve(to,tokenId);
                      }
                      function $_setApprovalForAll(address owner,address operator,bool approved) external {
                          return super._setApprovalForAll(owner,operator,approved);
                      }
                      function $_requireMinted(uint256 tokenId) external view {
                          return super._requireMinted(tokenId);
                      }
                      function $_beforeTokenTransfer(address from,address to,uint256 tokenId) external {
                          return super._beforeTokenTransfer(from,to,tokenId);
                      }
                      function $_afterTokenTransfer(address from,address to,uint256 tokenId) external {
                          return super._afterTokenTransfer(from,to,tokenId);
                      }
                      function $__ERC165_init() external {
                          return super.__ERC165_init();
                      }
                      function $__ERC165_init_unchained() external {
                          return super.__ERC165_init_unchained();
                      }
                      function $__Context_init() external {
                          return super.__Context_init();
                      }
                      function $__Context_init_unchained() external {
                          return super.__Context_init_unchained();
                      }
                      function $_msgSender() external view returns (address) {
                          return super._msgSender();
                      }
                      function $_msgData() external view returns (bytes memory) {
                          return super._msgData();
                      }
                      function $_disableInitializers() external {
                          return super._disableInitializers();
                      }
                      receive() external payable {}
                  }
                  // SPDX-License-Identifier: UNLICENSED
                  pragma solidity >=0.6.0;
                  import "../../../contracts/interfaces/internal/INFTCollectionInitializer.sol";
                  abstract contract $INFTCollectionInitializer is INFTCollectionInitializer {
                      bytes32 public __hh_exposed_bytecode_marker = "hardhat-exposed";
                      constructor() {}
                      receive() external payable {}
                  }
                  // SPDX-License-Identifier: UNLICENSED
                  pragma solidity >=0.6.0;
                  import "../../../../contracts/interfaces/standards/royalties/IGetFees.sol";
                  abstract contract $IGetFees is IGetFees {
                      bytes32 public __hh_exposed_bytecode_marker = "hardhat-exposed";
                      constructor() {}
                      receive() external payable {}
                  }
                  // SPDX-License-Identifier: UNLICENSED
                  pragma solidity >=0.6.0;
                  import "../../../../contracts/interfaces/standards/royalties/IGetRoyalties.sol";
                  abstract contract $IGetRoyalties is IGetRoyalties {
                      bytes32 public __hh_exposed_bytecode_marker = "hardhat-exposed";
                      constructor() {}
                      receive() external payable {}
                  }
                  // SPDX-License-Identifier: UNLICENSED
                  pragma solidity >=0.6.0;
                  import "../../../../contracts/interfaces/standards/royalties/IRoyaltyInfo.sol";
                  abstract contract $IRoyaltyInfo is IRoyaltyInfo {
                      bytes32 public __hh_exposed_bytecode_marker = "hardhat-exposed";
                      constructor() {}
                      receive() external payable {}
                  }
                  // SPDX-License-Identifier: UNLICENSED
                  pragma solidity >=0.6.0;
                  import "../../../../contracts/interfaces/standards/royalties/ITokenCreator.sol";
                  abstract contract $ITokenCreator is ITokenCreator {
                      bytes32 public __hh_exposed_bytecode_marker = "hardhat-exposed";
                      constructor() {}
                      receive() external payable {}
                  }
                  // SPDX-License-Identifier: UNLICENSED
                  pragma solidity >=0.6.0;
                  import "../../contracts/libraries/AddressLibrary.sol";
                  contract $AddressLibrary {
                      bytes32 public __hh_exposed_bytecode_marker = "hardhat-exposed";
                      event $callAndReturnContractAddress_address_bytes_Returned(address payable arg0);
                      event $callAndReturnContractAddress_CallWithoutValue_Returned(address payable arg0);
                      constructor() {}
                      function $callAndReturnContractAddress(address externalContract,bytes calldata callData) external payable returns (address payable) {
                          (address payable ret0) = AddressLibrary.callAndReturnContractAddress(externalContract,callData);
                          emit $callAndReturnContractAddress_address_bytes_Returned(ret0);
                          return (ret0);
                      }
                      function $callAndReturnContractAddress(CallWithoutValue calldata call) external payable returns (address payable) {
                          (address payable ret0) = AddressLibrary.callAndReturnContractAddress(call);
                          emit $callAndReturnContractAddress_CallWithoutValue_Returned(ret0);
                          return (ret0);
                      }
                      receive() external payable {}
                  }
                  // SPDX-License-Identifier: UNLICENSED
                  pragma solidity >=0.6.0;
                  import "../../../contracts/mixins/collections/CollectionRoyalties.sol";
                  abstract contract $CollectionRoyalties is CollectionRoyalties {
                      bytes32 public __hh_exposed_bytecode_marker = "hardhat-exposed";
                      constructor() {}
                      function $__ERC165_init() external {
                          return super.__ERC165_init();
                      }
                      function $__ERC165_init_unchained() external {
                          return super.__ERC165_init_unchained();
                      }
                      function $_disableInitializers() external {
                          return super._disableInitializers();
                      }
                      receive() external payable {}
                  }
                  // SPDX-License-Identifier: UNLICENSED
                  pragma solidity >=0.6.0;
                  import "../../../contracts/mixins/collections/SequentialMintCollection.sol";
                  contract $SequentialMintCollection is SequentialMintCollection {
                      bytes32 public __hh_exposed_bytecode_marker = "hardhat-exposed";
                      constructor() {}
                      function $_initializeSequentialMintCollection(address payable _creator,uint32 _maxTokenId) external {
                          return super._initializeSequentialMintCollection(_creator,_maxTokenId);
                      }
                      function $_selfDestruct() external {
                          return super._selfDestruct();
                      }
                      function $_updateMaxTokenId(uint32 _maxTokenId) external {
                          return super._updateMaxTokenId(_maxTokenId);
                      }
                      function $_burn(uint256 tokenId) external {
                          return super._burn(tokenId);
                      }
                      function $__ERC721Burnable_init() external {
                          return super.__ERC721Burnable_init();
                      }
                      function $__ERC721Burnable_init_unchained() external {
                          return super.__ERC721Burnable_init_unchained();
                      }
                      function $__ERC721_init(string calldata name_,string calldata symbol_) external {
                          return super.__ERC721_init(name_,symbol_);
                      }
                      function $__ERC721_init_unchained(string calldata name_,string calldata symbol_) external {
                          return super.__ERC721_init_unchained(name_,symbol_);
                      }
                      function $_baseURI() external view returns (string memory) {
                          return super._baseURI();
                      }
                      function $_safeTransfer(address from,address to,uint256 tokenId,bytes calldata data) external {
                          return super._safeTransfer(from,to,tokenId,data);
                      }
                      function $_exists(uint256 tokenId) external view returns (bool) {
                          return super._exists(tokenId);
                      }
                      function $_isApprovedOrOwner(address spender,uint256 tokenId) external view returns (bool) {
                          return super._isApprovedOrOwner(spender,tokenId);
                      }
                      function $_safeMint(address to,uint256 tokenId) external {
                          return super._safeMint(to,tokenId);
                      }
                      function $_safeMint(address to,uint256 tokenId,bytes calldata data) external {
                          return super._safeMint(to,tokenId,data);
                      }
                      function $_mint(address to,uint256 tokenId) external {
                          return super._mint(to,tokenId);
                      }
                      function $_transfer(address from,address to,uint256 tokenId) external {
                          return super._transfer(from,to,tokenId);
                      }
                      function $_approve(address to,uint256 tokenId) external {
                          return super._approve(to,tokenId);
                      }
                      function $_setApprovalForAll(address owner,address operator,bool approved) external {
                          return super._setApprovalForAll(owner,operator,approved);
                      }
                      function $_requireMinted(uint256 tokenId) external view {
                          return super._requireMinted(tokenId);
                      }
                      function $_beforeTokenTransfer(address from,address to,uint256 tokenId) external {
                          return super._beforeTokenTransfer(from,to,tokenId);
                      }
                      function $_afterTokenTransfer(address from,address to,uint256 tokenId) external {
                          return super._afterTokenTransfer(from,to,tokenId);
                      }
                      function $__ERC165_init() external {
                          return super.__ERC165_init();
                      }
                      function $__ERC165_init_unchained() external {
                          return super.__ERC165_init_unchained();
                      }
                      function $__Context_init() external {
                          return super.__Context_init();
                      }
                      function $__Context_init_unchained() external {
                          return super.__Context_init_unchained();
                      }
                      function $_msgSender() external view returns (address) {
                          return super._msgSender();
                      }
                      function $_msgData() external view returns (bytes memory) {
                          return super._msgData();
                      }
                      function $_disableInitializers() external {
                          return super._disableInitializers();
                      }
                      receive() external payable {}
                  }
                  // SPDX-License-Identifier: UNLICENSED
                  pragma solidity >=0.6.0;
                  import "../../../contracts/mixins/shared/Constants.sol";
                  // SPDX-License-Identifier: UNLICENSED
                  pragma solidity >=0.6.0;
                  import "../../../contracts/mixins/shared/ContractFactory.sol";
                  contract $ContractFactory is ContractFactory {
                      bytes32 public __hh_exposed_bytecode_marker = "hardhat-exposed";
                      constructor(address _contractFactory) ContractFactory(_contractFactory) {}
                      receive() external payable {}
                  }