ETH Price: $2,508.81 (-1.25%)

Contract

0x3B2eFf875C23DE8760E3DCe5DD33aACD95106e76
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To

There are no matching entries

Please try again later

Latest 1 internal transaction

Advanced mode:
Parent Transaction Hash Block From To
186925182023-12-01 15:41:11273 days ago1701445271  Contract Creation0 ETH
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
NFTMarket

Compiler Version
v0.8.23+commit.f704f362

Optimization Enabled:
Yes with 300 runs

Other Settings:
shanghai EvmVersion
File 1 of 42 : NFTMarket.sol
/*
  ・
   * ★
      ・ 。
         ・ ゚☆ 。
      * ★ ゚・。 *  。
            * ☆ 。・゚*.。
         ゚ *.。☆。★ ・
​
                      `                     .-:::::-.`              `-::---...```
                     `-:`               .:+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 { ContextUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";

import { FETHNode } from "./mixins/shared/FETHNode.sol";
import { FoundationTreasuryNodeV1 } from "./mixins/shared/FoundationTreasuryNodeV1.sol";
import { MarketFees } from "./mixins/shared/MarketFees.sol";
import { MarketSharedCore } from "./mixins/shared/MarketSharedCore.sol";
import { WorldsNftNode } from "./mixins/shared/WorldsNftNode.sol";
import { RouterContextSingle } from "./mixins/shared/RouterContextSingle.sol";
import { SendValueWithFallbackWithdraw } from "./mixins/shared/SendValueWithFallbackWithdraw.sol";
import { WorldsRouteAPIs } from "./mixins/shared/WorldsRouteAPIs.sol";

import { NFTMarketAuction } from "./mixins/nftMarket/NFTMarketAuction.sol";
import { NFTMarketBuyPrice } from "./mixins/nftMarket/NFTMarketBuyPrice.sol";
import { NFTMarketCore } from "./mixins/nftMarket/NFTMarketCore.sol";
import { NFTMarketExhibition } from "./mixins/nftMarket/NFTMarketExhibition.sol";
import { NFTMarketOffer } from "./mixins/nftMarket/NFTMarketOffer.sol";
import { NFTMarketPrivateSaleGap } from "./mixins/nftMarket/NFTMarketPrivateSaleGap.sol";
import { NFTMarketReserveAuction } from "./mixins/nftMarket/NFTMarketReserveAuction.sol";
import { NFTMarketScheduling } from "./mixins/nftMarket/NFTMarketScheduling.sol";
import { NFTMarketWorldsAPIs } from "./mixins/nftMarket/NFTMarketWorldsAPIs.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,
  FoundationTreasuryNodeV1,
  ContextUpgradeable,
  RouterContextSingle,
  FETHNode,
  MarketSharedCore,
  NFTMarketCore,
  ReentrancyGuardUpgradeable,
  SendValueWithFallbackWithdraw,
  MarketFees,
  WorldsNftNode,
  WorldsRouteAPIs,
  NFTMarketWorldsAPIs,
  NFTMarketExhibition,
  NFTMarketScheduling,
  NFTMarketAuction,
  NFTMarketReserveAuction,
  NFTMarketPrivateSaleGap,
  NFTMarketBuyPrice,
  NFTMarketOffer
{
  ////////////////////////////////////////////////////////////////
  // Setup
  ////////////////////////////////////////////////////////////////

  /**
   * @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.
   * @param worldsNft The Worlds NFT contract address.
   */
  constructor(
    address payable treasury,
    address feth,
    uint256 duration,
    address router,
    address marketUtils,
    address worldsNft
  )
    FoundationTreasuryNodeV1(treasury)
    FETHNode(feth)
    WorldsNftNode(worldsNft)
    MarketFees(
      /* protocolFeeInBasisPoints: */
      500,
      marketUtils,
      /* assumePrimarySale: */
      false
    )
    NFTMarketReserveAuction(duration)
    RouterContextSingle(router)
  {}

  ////////////////////////////////////////////////////////////////
  // Inheritance Requirements
  // (no-ops to avoid compile errors)
  ////////////////////////////////////////////////////////////////

  /**
   * @inheritdoc NFTMarketCore
   */
  function _beforeAuctionStarted(
    address nftContract,
    uint256 tokenId
  ) internal override(NFTMarketCore, NFTMarketScheduling, NFTMarketReserveAuction, NFTMarketBuyPrice, NFTMarketOffer) {
    super._beforeAuctionStarted(nftContract, tokenId);
  }

  /**
   * @inheritdoc MarketFees
   */
  function _distributeFunds(
    address nftContract,
    uint256 tokenId,
    address payable seller,
    uint256 price,
    address payable buyReferrer,
    address payable sellerReferrerPaymentAddress,
    uint16 sellerReferrerTakeRateInBasisPoints
  )
    internal
    virtual
    override(MarketFees, NFTMarketBuyPrice, NFTMarketReserveAuction, NFTMarketScheduling)
    returns (uint256 totalFees, uint256 creatorRev, uint256 sellerRev)
  {
    (totalFees, creatorRev, sellerRev) = super._distributeFunds(
      nftContract,
      tokenId,
      seller,
      price,
      buyReferrer,
      sellerReferrerPaymentAddress,
      sellerReferrerTakeRateInBasisPoints
    );
  }

  /**
   * @inheritdoc MarketSharedCore
   */
  function _getSellerOf(
    address nftContract,
    uint256 tokenId
  )
    internal
    view
    override(MarketSharedCore, NFTMarketReserveAuction, NFTMarketBuyPrice)
    returns (address payable seller)
  {
    seller = super._getSellerOf(nftContract, tokenId);
  }

  /**
   * @inheritdoc NFTMarketCore
   */
  function _isAuthorizedExhibitionOrScheduleUpdate(
    address nftContract,
    uint256 tokenId
  ) internal view override(NFTMarketCore, NFTMarketReserveAuction, NFTMarketBuyPrice) returns (bool canUpdateNft) {
    canUpdateNft = super._isAuthorizedExhibitionOrScheduleUpdate(nftContract, tokenId);
  }

  /**
   * @inheritdoc RouterContextSingle
   */
  function _msgSender() internal view override(ContextUpgradeable, RouterContextSingle) returns (address sender) {
    sender = super._msgSender();
  }

  /**
   * @inheritdoc NFTMarketCore
   */
  function _transferFromEscrow(
    address nftContract,
    uint256 tokenId,
    address recipient,
    address authorizeSeller
  ) internal override(NFTMarketCore, NFTMarketReserveAuction, NFTMarketBuyPrice) {
    super._transferFromEscrow(nftContract, tokenId, recipient, authorizeSeller);
  }

  /**
   * @inheritdoc NFTMarketCore
   */
  function _transferFromEscrowIfAvailable(
    address nftContract,
    uint256 tokenId,
    address originalSeller
  )
    internal
    override(
      NFTMarketCore,
      NFTMarketExhibition,
      NFTMarketScheduling,
      NFTMarketReserveAuction,
      NFTMarketBuyPrice,
      NFTMarketOffer
    )
  {
    super._transferFromEscrowIfAvailable(nftContract, tokenId, originalSeller);
  }

  /**
   * @inheritdoc NFTMarketCore
   */
  function _transferToEscrow(
    address nftContract,
    uint256 tokenId
  ) internal override(NFTMarketCore, NFTMarketReserveAuction, NFTMarketBuyPrice) {
    super._transferToEscrow(nftContract, tokenId);
  }
}

File 2 of 42 : Initializable.sol
// 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;
    }
}

File 3 of 42 : ReentrancyGuardUpgradeable.sol
// 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;
}

File 4 of 42 : AddressUpgradeable.sol
// 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);
        }
    }
}

File 5 of 42 : ContextUpgradeable.sol
// 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;
}

File 6 of 42 : IERC721.sol
// 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);
}

File 7 of 42 : IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

File 8 of 42 : IFethMarket.sol
// 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);
}

File 9 of 42 : IMarketUtils.sol
// 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
    );
}

File 10 of 42 : INFTCollectionType.sol
// 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);
}

File 11 of 42 : INFTMarketExhibition.sol
// 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 worldOrExhibitionId,
    address seller
  ) external view returns (bool allowedSeller);

  function getExhibition(
    uint256 worldOrExhibitionId
  ) external view returns (string memory name, address payable worldPaymentAddress, uint16 takeRateInBasisPoints);
}

File 12 of 42 : INFTMarketExhibitionMigration.sol
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;

interface INFTMarketExhibitionMigration {
  struct NFTListing {
    address nftContract;
    uint256 nftTokenId;
  }

  function worldsInitializeMigration() external returns (uint256 lastExhibitionIdCreated);

  function worldsMigrateExhibition(
    uint256 exhibitionId,
    address curator
  ) external returns (string memory name, uint16 takeRateInBasisPoints);

  function worldsMigrateExhibitionListings(
    uint256 exhibitionId,
    INFTMarketExhibitionMigration.NFTListing[] calldata nftListings
  ) external returns (address[] memory nftSellers);
}

File 13 of 42 : INFTMarketGetters.sol
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;

import "../../mixins/shared/MarketStructs.sol";

/**
 * @title Interface with NFTMarket getters which are used by the router.
 * @author HardlyDifficult
 */
interface INFTMarketGetters {
  function getBuyPrice(address nftContract, uint256 tokenId) external view returns (address seller, uint256 price);

  function getExhibitionIdForNft(
    address nftContract,
    uint256 tokenId
  ) external view returns (uint256 worldOrExhibitionId);

  function getSaleStartsAt(address nftContract, uint256 tokenId) external view returns (uint256 saleStartsAt);

  function getReserveAuction(uint256 auctionId) external view returns (ReserveAuction memory auction);

  function getReserveAuctionIdFor(address nftContract, uint256 tokenId) external view returns (uint256 auctionId);
}

File 14 of 42 : IWorldsNFTMarket.sol
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;

interface IWorldsNFTMarket {
  ////////////////////////////////////////////////////////////////
  // World management
  ////////////////////////////////////////////////////////////////

  function mint(
    uint16 defaultTakeRateInBasisPoints,
    address payable worldPaymentAddress,
    string calldata name
  ) external returns (uint256 worldId);

  function burn(uint256 worldId) external;

  function getWorldName(uint256 worldId) external view returns (string memory name);

  ////////////////////////////////////////////////////////////////
  // Allowlist
  ////////////////////////////////////////////////////////////////

  function addToAllowlistBySeller(uint256 worldId, address seller) external;

  function isSellerAllowed(uint256 worldId, address seller) external view returns (bool isAllowed);

  ////////////////////////////////////////////////////////////////
  // NFT specific
  ////////////////////////////////////////////////////////////////

  function addToWorldByNft(
    uint256 worldId,
    address nftContract,
    uint256 nftTokenId,
    uint16 takeRateInBasisPoints
  ) external;

  function getAssociationByNft(
    address nftContract,
    uint256 nftTokenId,
    address seller
  ) external view returns (uint256 worldId, uint16 takeRateInBasisPoints);

  function removeFromWorldByNft(address nftContract, uint256 nftTokenId) external;

  function soldInWorldByNft(
    address seller,
    address nftContract,
    uint256 nftTokenId,
    address buyer,
    uint256 salePrice
  ) external returns (uint256 worldId, address payable worldPaymentAddress, uint16 takeRateInBasisPoints);
}

File 15 of 42 : IWorldsSharedMarket.sol
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;

interface IWorldsSharedMarket {
  function getDefaultTakeRate(uint256 worldId) external view returns (uint16 defaultTakeRateInBasisPoints);

  function getPaymentAddress(uint256 worldId) external view returns (address payable worldPaymentAddress);
}

File 16 of 42 : IAdminRole.sol
// 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);
}

File 17 of 42 : IOperatorRole.sol
// 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);
}

File 18 of 42 : INFTMarketBuyNow.sol
// 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 setBuyPriceV2(address nftContract, uint256 tokenId, uint256 worldOrExhibitionId, uint256 price) external;
}

File 19 of 42 : INFTMarketExhibition.sol
// 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;
}

File 20 of 42 : INFTMarketReserveAuction.sol
// 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 {
  function cancelReserveAuction(uint256 auctionId) external;

  function createReserveAuctionV3(
    address nftContract,
    uint256 tokenId,
    uint256 worldOrExhibitionId,
    uint256 reservePrice,
    uint256 duration
  ) external returns (uint256 auctionId);

  function updateReserveAuctionV2(uint256 auctionId, uint256 worldOrExhibitionId, uint256 reservePrice) external;
}

File 21 of 42 : INFTMarketScheduling.sol
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;

interface INFTMarketScheduling {
  function setSaleStartsAt(address nftContract, uint256 tokenId, uint256 saleStartsAt) external;
}

File 22 of 42 : RouteCallLibrary.sol
// 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 from address to the end of the calldata.
   * If the call reverts, this will revert the transaction and the original reason is bubbled up.
   * @param from The address to use as the msg sender when calling the contract.
   * @param to The contract address to call.
   * @param callData The call data to use when calling the contract, without the sender appended.
   */
  function routeCallTo(address from, address to, bytes memory callData) internal returns (bytes memory returnData) {
    // Forward the call, with the packed from address appended, to the specified contract.
    bool success;
    (success, returnData) = tryRouteCallTo(from, to, callData);

    // If the call failed, bubble up the revert reason.
    if (!success) {
      revertWithError(returnData);
    }
  }

  /**
   * @notice Routes a call to the specified contract, appending the from address to the end of the calldata.
   * This will not revert even if the external call fails.
   * @param from The address to use as the msg sender when calling the contract.
   * @param to The contract address to call.
   * @param callData The call data to use when calling the contract, without the sender appended.
   * @dev Consumers should look for positive confirmation that if the transaction is not successful, the returned revert
   * reason is expected as an acceptable reason to ignore. Generically ignoring reverts will lead to out-of-gas errors
   * being ignored and result in unexpected behavior.
   */
  function tryRouteCallTo(
    address from,
    address to,
    bytes memory callData
  ) internal returns (bool success, bytes memory returnData) {
    // Forward the call, with the packed from address appended, to the specified contract.
    // solhint-disable-next-line avoid-low-level-calls
    (success, returnData) = to.call(abi.encodePacked(callData, from));
  }

  /**
   * @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 revertWithError(bytes memory returnData) internal 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();
    }
  }

  /**
   * @notice Extracts the appended sender address from the calldata.
   * @dev This uses the last 20 bytes of the calldata, with no guarantees that an address has indeed been appended.
   * If this is used for a call that was not routed with `routeCallTo`, the address returned will be incorrect (and
   * may be address(0)).
   */
  function extractAppendedSenderAddress() internal pure returns (address sender) {
    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)))
    }
  }
}

File 23 of 42 : TimeLibrary.sol
// 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;
  }
}

File 24 of 42 : NFTMarketAuction.sol
// 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 Returns id to assign to the next auction.
   */
  function _getNextAndIncrementAuctionId() internal returns (uint256) {
    // AuctionId cannot overflow 256 bits.
    unchecked {
      if (nextAuctionId == 0) {
        // Ensures that the first auctionId is 1.
        ++nextAuctionId;
      }

      // Returns the current nextAuctionId instead of ++nextAuctionId to ensure the sequence ID is preserved on mainnet.
      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;
}

File 25 of 42 : NFTMarketBuyPrice.sol
// 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/FoundationTreasuryNodeV1.sol";
import "../shared/FETHNode.sol";
import "../shared/MarketSharedCore.sol";
import "../shared/WorldsNftNode.sol";
import "../shared/SendValueWithFallbackWithdraw.sol";

import "./NFTMarketCore.sol";
import "./NFTMarketExhibition.sol";
import "./NFTMarketScheduling.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);
/// @param owner The current owner of this NFT.
error NFTMarketBuyPrice_Only_Owner_Can_Update_Nft(address owner);
/// @param owner The current owner of this NFT.
error NFTMarketBuyPrice_Only_Owner_Can_Update_Sale_Starts_At(address owner);
error NFTMarketBuyPrice_Price_Already_Set();
error NFTMarketBuyPrice_Price_Too_High();
/// @param seller The current owner of this NFT.
error NFTMarketBuyPrice_Seller_Mismatch(address seller);
error NFTMarketBuyPrice_Listing_Is_Not_Active(uint256 startTime);

/**
 * @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,
  FoundationTreasuryNodeV1,
  ContextUpgradeable,
  FETHNode,
  MarketSharedCore,
  NFTMarketCore,
  ReentrancyGuardUpgradeable,
  SendValueWithFallbackWithdraw,
  MarketFees,
  WorldsNftNode,
  NFTMarketExhibition,
  NFTMarketScheduling
{
  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 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) external 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, seller);

    emit BuyPriceCanceled(nftContract, tokenId);
  }

  /**
   * @notice Sets the buy price for an NFT and escrows it in the market contract.
   * A 0 price is acceptable and valid price you can set, enabling a giveaway to the first collector that calls `buy`.
   * @dev If there is an offer for this amount or higher, that will be accepted instead of setting a buy price.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @param price The price at which someone could buy this NFT.
   */
  function setBuyPrice(address nftContract, uint256 tokenId, uint256 price) public nonReentrant {
    // If there is a valid offer at this price or higher, accept that instead.
    if (_autoAcceptOffer(nftContract, tokenId, price)) {
      return;
    }

    if (price > type(uint96).max) {
      // This ensures that no data is lost when storing the price as `uint96`.
      revert NFTMarketBuyPrice_Price_Too_High();
    }

    BuyPrice storage buyPrice = nftContractToTokenIdToBuyPrice[nftContract][tokenId];
    address seller = buyPrice.seller;

    if (buyPrice.price == price && seller != address(0)) {
      revert NFTMarketBuyPrice_Price_Already_Set();
    }

    // Store the new price for this NFT.
    buyPrice.price = uint96(price);

    address payable sender = payable(_msgSender());

    if (seller == address(0)) {
      // Transfer the NFT into escrow, if it's already in escrow confirm the `msg.sender` is the owner.
      _transferToEscrow(nftContract, tokenId);

      // The price was not previously set for this NFT, store the seller.
      buyPrice.seller = sender;
    } else if (seller != sender) {
      // Buy price was previously set by a different user
      revert NFTMarketBuyPrice_Only_Owner_Can_Set_Price(seller);
    }

    emit BuyPriceSet(nftContract, tokenId, sender, price);
  }

  /**
   * @notice [DEPRECATED] Use `setBuyPrice` 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 worldOrExhibitionId The World NFT or 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 worldOrExhibitionId, uint256 price) external {
    setBuyPrice(nftContract, tokenId, price);

    _updateWorldOrExhibitionForNft(_msgSender(), nftContract, tokenId, worldOrExhibitionId);
  }

  function _isAuthorizedExhibitionOrScheduleUpdate(
    address nftContract,
    uint256 tokenId
  ) internal view virtual override returns (bool canUpdateNft) {
    address seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller;

    if (seller != address(0)) {
      if (seller != _msgSender()) {
        revert NFTMarketBuyPrice_Only_Owner_Can_Update_Nft(seller);
      }

      canUpdateNft = true;
    } else {
      canUpdateNft = super._isAuthorizedExhibitionOrScheduleUpdate(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(NFTMarketCore, NFTMarketScheduling) {
    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 {
    _validateSaleStartsAtHasBeenReached(nftContract, tokenId);
    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
    ) = _getWorldOrExhibitionForPayment(buyPrice.seller, nftContract, tokenId, sender, buyPrice.price);

    // 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 originalSeller
  ) internal virtual override(NFTMarketCore, NFTMarketExhibition, NFTMarketScheduling) {
    address seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller;

    // If a buy price has been set for this NFT then it should remain in escrow.
    if (seller == address(0)) {
      // Otherwise continue to attempt the transfer.
      super._transferFromEscrowIfAvailable(nftContract, tokenId, originalSeller);
    }
  }

  /**
   * @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);
    }
  }

  ////////////////////////////////////////////////////////////////
  // Inheritance Requirements
  // (no-ops to avoid compile errors)
  ////////////////////////////////////////////////////////////////

  /**
   * @inheritdoc MarketFees
   */
  function _distributeFunds(
    address nftContract,
    uint256 tokenId,
    address payable seller,
    uint256 price,
    address payable buyReferrer,
    address payable sellerReferrerPaymentAddress,
    uint16 sellerReferrerTakeRateInBasisPoints
  )
    internal
    virtual
    override(MarketFees, NFTMarketScheduling)
    returns (uint256 totalFees, uint256 creatorRev, uint256 sellerRev)
  {
    (totalFees, creatorRev, sellerRev) = super._distributeFunds(
      nftContract,
      tokenId,
      seller,
      price,
      buyReferrer,
      sellerReferrerPaymentAddress,
      sellerReferrerTakeRateInBasisPoints
    );
  }

  /**
   * @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;
}

File 26 of 42 : NFTMarketCore.sol
// 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();
error NFTMarketCore_Can_Not_Update_Unlisted_Nft();

/**
 * @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 Requires that an NFT is listed for sale, not in active auction, and the msg.sender is the seller which
   * listed the NFT.
   */
  function _authorizeExhibitionOrScheduleUpdate(address nftContract, uint256 tokenId) internal view {
    if (!_isAuthorizedExhibitionOrScheduleUpdate(nftContract, tokenId)) {
      revert NFTMarketCore_Can_Not_Update_Unlisted_Nft();
    }
  }

  /**
   * @notice Confirms permission to update the Exhibition or schedule for an NFT.
   * @return canUpdateNft True if the NFT is listed for sale and authorize checks did not revert.
   * @dev Verifies that the NFT is listed, not in active auction, and the sender is the owner.
   */
  function _isAuthorizedExhibitionOrScheduleUpdate(
    address /*nftContract*/,
    uint256 /*tokenId*/
  ) internal view virtual returns (bool canUpdateNft) {
    // 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 originalSeller
  ) internal virtual {
    _transferFromEscrow(nftContract, tokenId, originalSeller, 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;
}

File 27 of 42 : NFTMarketExhibition.sol
// SPDX-License-Identifier: MIT OR Apache-2.0

pragma solidity ^0.8.18;

import { ContextUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";

import { INFTMarketExhibition } from "../../interfaces/internal/INFTMarketExhibition.sol";
import { INFTMarketExhibitionForRouter } from "../../interfaces/internal/routes/INFTMarketExhibition.sol";
import { INFTMarketExhibitionMigration } from "../../interfaces/internal/INFTMarketExhibitionMigration.sol";
import { INFTMarketGetters } from "../../interfaces/internal/INFTMarketGetters.sol";
import { IWorldsNFTMarket } from "../../interfaces/internal/IWorldsNFTMarket.sol";
import { IWorldsSharedMarket } from "../../interfaces/internal/IWorldsSharedMarket.sol";

import { MAX_WORLD_TAKE_RATE } from "../shared/Constants.sol";
import { WorldsNftNode } from "../shared/WorldsNftNode.sol";

import { NFTMarketCore } from "./NFTMarketCore.sol";
import { NFTMarketWorldsAPIs } from "./NFTMarketWorldsAPIs.sol";

/// @param curator The curator for this exhibition.
error NFTMarketExhibition_Caller_Is_Not_Curator(address curator);
error NFTMarketExhibition_Caller_Is_Not_Worlds_Contract(address worlds);
error NFTMarketExhibition_Can_Not_Add_Dupe_Seller();
error NFTMarketExhibition_Can_Not_Remove_Not_Associated_With_Exhibition();
error NFTMarketExhibition_Curator_Automatically_Allowed();
error NFTMarketExhibition_Curator_Does_Not_Match(address curator);
error NFTMarketExhibition_Exhibition_Does_Not_Exist();
error NFTMarketExhibition_Exhibition_NFT_Already_Set();
error NFTMarketExhibition_NFT_Not_Associated_With_Exhibition(
  address nftContract,
  uint256 nftTokenId,
  uint256 currentExhibitionId
);
error NFTMarketExhibition_Seller_Not_Allowed_In_Exhibition();
error NFTMarketExhibition_Sellers_Required();
error NFTMarketExhibition_Take_Rate_Too_High();
error NFTMarketExhibition_World_Migration_Already_Completed();

/**
 * @title Enables a curation surface for sellers to exhibit their NFTs.
 * @author HardlyDifficult
 * @dev [DEPRECATED] This mixin is being deprecated in favor of the Worlds NFT contract.
 */
abstract contract NFTMarketExhibition is
  INFTMarketGetters,
  INFTMarketExhibition,
  INFTMarketExhibitionMigration,
  INFTMarketExhibitionForRouter,
  ContextUpgradeable,
  NFTMarketCore,
  WorldsNftNode,
  NFTMarketWorldsAPIs
{
  /// @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.
  /// @dev This value is frozen & ignored once the World migration has been completed.
  uint32 private $latestExhibitionId;

  /// @notice True once exhibition creation has migrated to use the new Worlds contract instead.
  bool private $worldMigrationCompleted;

  /// @notice Maps the exhibition ID to their details.
  /// @dev This will not be populated when a World NFT is created.
  mapping(uint256 exhibitionId => Exhibition exhibitionDetails) private $idToExhibition;

  /// @notice Maps an exhibition to the list of sellers allowed to list with it.
  /// @dev This will not be populated when a World NFT is used.
  mapping(uint256 exhibitionId => mapping(address seller => bool isAllowed)) private $exhibitionIdToSellerToIsAllowed;

  /// @notice Maps an NFT to the exhibition it was listed with.
  /// @dev This will not be populated when a World NFT is used.
  mapping(address nftContract => mapping(uint256 tokenId => uint256 exhibitionId))
    private $nftContractToTokenIdToExhibitionId;

  /**
   * @notice Emitted when a World NFT or exhibition is created.
   * @param worldOrExhibitionId The ID for this World NFT or exhibition.
   * @param curator The curator which created this World or exhibition.
   * @param name The name for this World or exhibition.
   * @param takeRateInBasisPoints The rate of the sale which goes to the curator.
   * @dev This will not be emitted when a user interacts directly with the Worlds contract.
   */
  event ExhibitionCreated(
    uint256 indexed worldOrExhibitionId,
    address indexed curator,
    string name,
    uint16 takeRateInBasisPoints
  );

  /**
   * @notice Emitted when a World NFT is burned or an exhibition is deleted.
   * @param worldOrExhibitionId The ID for the World NFT or exhibition which was deleted.
   * @dev This will not be emitted when a user interacts directly with the Worlds contract.
   */
  event ExhibitionDeleted(uint256 indexed worldOrExhibitionId);

  /**
   * @notice Emitted when an exhibition is migrated into a World NFT.
   * @param exhibitionId The ID of the exhibition which has been converted into an NFT ID.
   */
  event ExhibitionMigratedToWorlds(uint256 indexed exhibitionId);

  /**
   * @notice Emitted when an NFT is listed in a World or exhibition.
   * @param nftContract The contract address of the NFT.
   * @param tokenId The ID of the NFT.
   * @param worldOrExhibitionId The ID of the World or exhibition it was listed with.
   * @dev This will not be emitted when a user interacts directly with the Worlds contract.
   */
  event NftAddedToExhibition(address indexed nftContract, uint256 indexed tokenId, uint256 indexed worldOrExhibitionId);

  /**
   * @notice Emitted when an NFT is no longer associated with a World or exhibition for reasons other than a sale.
   * @param nftContract The contract address of the NFT.
   * @param tokenId The ID of the NFT.
   * @param worldOrExhibitionId The ID of the World or exhibition it was originally listed with.
   * @dev This will not be emitted when a user interacts directly with the Worlds contract.
   */
  event NftRemovedFromExhibition(
    address indexed nftContract,
    uint256 indexed tokenId,
    uint256 indexed worldOrExhibitionId
  );

  /**
   * @notice Emitted when sellers are granted access to list with a World or an exhibition.
   * @param worldOrExhibitionId The ID of the World or exhibition.
   * @param sellers The list of sellers granted access.
   * @dev This will not be emitted when a user interacts directly with the Worlds contract.
   */
  event SellersAddedToExhibition(uint256 indexed worldOrExhibitionId, address[] sellers);

  ////////////////////////////////////////////////////////////////
  // Modifiers
  ////////////////////////////////////////////////////////////////

  /// @notice Reverts if the msg.sender is not the Worlds contract.
  function _requireFromWorlds() private view {
    if (msg.sender != worlds) {
      revert NFTMarketExhibition_Caller_Is_Not_Worlds_Contract(worlds);
    }
  }

  /// @notice Reverts if the _msgSender is not the curator of the exhibition.
  /// @dev This helper should only be used for exhibitions (not World NFTs).
  function _requireExhibitionCurator(uint256 exhibitionId) private view {
    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);
    }
  }

  ////////////////////////////////////////////////////////////////
  // Exhibition Management
  ////////////////////////////////////////////////////////////////

  /**
   * @notice Creates a World NFT (once migration has been enabled) or an exhibition.
   * @param name The name for this World or exhibition.
   * @param takeRateInBasisPoints The rate of the sale which goes to the curator of this World or exhibition.
   * @param sellers The list of sellers allowed to list with this World or exhibition.
   * @return worldOrExhibitionId The ID of the World NFT or exhibition which was created.
   */
  function createExhibition(
    string calldata name,
    uint16 takeRateInBasisPoints,
    address[] calldata sellers
  ) external returns (uint256 worldOrExhibitionId) {
    address payable sender = payable(_msgSender());
    if ($worldMigrationCompleted) {
      // If the worlds migration has been enabled, always create a World NFT instead of an exhibition.
      worldOrExhibitionId = _mintWorld({
        defaultTakeRateInBasisPoints: takeRateInBasisPoints,
        worldPaymentAddress: sender,
        name: name
      });
    } else {
      // Before the worlds migration, we create og exhibitions.

      if (takeRateInBasisPoints > MAX_WORLD_TAKE_RATE) {
        revert NFTMarketExhibition_Take_Rate_Too_High();
      }

      worldOrExhibitionId = ++$latestExhibitionId;
      $idToExhibition[worldOrExhibitionId] = Exhibition({
        curator: sender,
        takeRateInBasisPoints: takeRateInBasisPoints,
        name: name
      });
    }

    // Both Worlds and exhibitions emit the same creation event for backwards compatibility for indexers.
    emit ExhibitionCreated({
      worldOrExhibitionId: worldOrExhibitionId,
      curator: sender,
      name: name,
      takeRateInBasisPoints: takeRateInBasisPoints
    });

    // After creating the World or exhibition, add seller permissions.
    if ($worldMigrationCompleted) {
      // If a World was created, explicitly add the curator to the allowlist since that permission is no longer assumed.
      _addToAllowlistBySeller(worldOrExhibitionId, sender);
    }
    addSellersToExhibition(worldOrExhibitionId, sellers);
  }

  /**
   * @notice Burns a World NFT or deletes an exhibition created by the msg.sender.
   * @param worldOrExhibitionId The ID of the World NFT or exhibition to delete.
   * @dev Once deleted, any NFTs listed with this World or exhibition will still be listed but will no longer be
   * associated with or share revenue with the World or exhibition.
   */
  function deleteExhibition(uint256 worldOrExhibitionId) external {
    // Attempt to burn a World NFT first.
    if (!_burnIfWorld(worldOrExhibitionId)) {
      // If the ID does not exist as a World NFT, check exhibitions instead.
      _requireExhibitionCurator(worldOrExhibitionId);

      delete $idToExhibition[worldOrExhibitionId];
    }

    // Both Worlds and exhibitions emit the same deleted event for backwards compatibility for indexers.
    emit ExhibitionDeleted(worldOrExhibitionId);
  }

  /**
   * @notice Returns World or exhibition details for a given ID.
   * @param worldOrExhibitionId The ID of the World NFT or exhibition to look up.
   * @return name The name of the World or exhibition.
   * @return worldPaymentAddress The curator's payment address for the World or exhibition.
   * @return takeRateInBasisPoints The rate of the sale which goes to the curator.
   * For Worlds, the take rate may differ per listing. This value represents the default value used.
   * @dev If the World or exhibition does not exist or has since been burned/deleted, the worldPaymentAddress will be
   * address(0).
   */
  function getExhibition(
    uint256 worldOrExhibitionId
  ) external view returns (string memory name, address payable worldPaymentAddress, uint16 takeRateInBasisPoints) {
    // Attempt to read as a World NFT first.
    name = IWorldsNFTMarket(worlds).getWorldName(worldOrExhibitionId);
    if (bytes(name).length != 0) {
      worldPaymentAddress = IWorldsSharedMarket(worlds).getPaymentAddress(worldOrExhibitionId);
      takeRateInBasisPoints = IWorldsSharedMarket(worlds).getDefaultTakeRate(worldOrExhibitionId);
    } else {
      // If the ID does not exist as a World NFT, check exhibitions instead.
      Exhibition memory exhibition = $idToExhibition[worldOrExhibitionId];
      name = exhibition.name;
      worldPaymentAddress = exhibition.curator;
      takeRateInBasisPoints = exhibition.takeRateInBasisPoints;
    }
  }

  ////////////////////////////////////////////////////////////////
  // Worlds Migration
  ////////////////////////////////////////////////////////////////

  /**
   * @notice DO NOT CALL DIRECTLY. This function is triggered by the Worlds NFT contract,
   * `initializeWorldsNftMarketExhibitionMigration`.
   * @return lastExhibitionIdCreated The last exhibition ID which was created before the migration was enabled, to be
   * used as the starting point for new NFTs minted in the Worlds contract.
   * @dev Once called, all new exhibitions will be created as World NFTs instead.
   */
  function worldsInitializeMigration() external returns (uint256 lastExhibitionIdCreated) {
    _requireFromWorlds();
    if ($worldMigrationCompleted) {
      revert NFTMarketExhibition_World_Migration_Already_Completed();
    }

    lastExhibitionIdCreated = $latestExhibitionId;
    $worldMigrationCompleted = true;
  }

  /**
   * @notice DO NOT CALL DIRECTLY. This function is triggered by the Worlds NFT contract, `migrateFromExhibition`.
   * @param exhibitionId The exhibition which is to be migrated into the Worlds NFT.
   * @param curator The curator of the exhibition.
   * @return name The name of the exhibition.
   * @return takeRateInBasisPoints The take rate for the exhibition.
   */
  function worldsMigrateExhibition(
    uint256 exhibitionId,
    address curator
  ) external returns (string memory name, uint16 takeRateInBasisPoints) {
    _requireFromWorlds();
    if ($idToExhibition[exhibitionId].curator != curator) {
      if ($idToExhibition[exhibitionId].curator == address(0)) {
        revert NFTMarketExhibition_Exhibition_Does_Not_Exist();
      }
      revert NFTMarketExhibition_Curator_Does_Not_Match($idToExhibition[exhibitionId].curator);
    }

    name = $idToExhibition[exhibitionId].name;
    takeRateInBasisPoints = $idToExhibition[exhibitionId].takeRateInBasisPoints;

    delete $idToExhibition[exhibitionId];

    emit ExhibitionMigratedToWorlds(exhibitionId);
  }

  /**
   * @notice DO NOT CALL DIRECTLY. This function is triggered by the Worlds NFT contract, `migrateFromExhibition` and
   * `resumeMigrateFromExhibition`.
   * @param exhibitionId The exhibition which is to be migrated into the Worlds NFT.
   * @param nftListings The NFT listings which already exist in the NFTMarket for this exhibition, and should be added
   * to the World's NFT inventory.
   * @return nftSellers The sellers which listed each of the `nftListings`.
   * @dev This function will delete local storage for each of the collection listings so that Worlds storage becomes a
   * single source of truth.
   */
  function worldsMigrateExhibitionListings(
    uint256 exhibitionId,
    INFTMarketExhibitionMigration.NFTListing[] calldata nftListings
  ) external returns (address[] memory nftSellers) {
    _requireFromWorlds();

    nftSellers = new address[](nftListings.length);
    for (uint256 i = 0; i < nftListings.length; ) {
      address nftContract = nftListings[i].nftContract;
      uint256 nftTokenId = nftListings[i].nftTokenId;

      // Validate the NFT is already associated with the exhibition.
      if ($nftContractToTokenIdToExhibitionId[nftContract][nftTokenId] != exhibitionId) {
        revert NFTMarketExhibition_NFT_Not_Associated_With_Exhibition(
          nftContract,
          nftTokenId,
          $nftContractToTokenIdToExhibitionId[nftContract][nftTokenId]
        );
      }

      // Return the seller which listed the NFT for sale, used by the inventory settings in Worlds.
      nftSellers[i] = _getSellerOf(nftContract, nftTokenId);

      // Delete local storage so Worlds becomes the single source of truth.
      delete $nftContractToTokenIdToExhibitionId[nftContract][nftTokenId];

      unchecked {
        ++i;
      }
    }
  }

  ////////////////////////////////////////////////////////////////
  // Allowlist
  ////////////////////////////////////////////////////////////////

  /**
   * @notice Adds sellers to a World or exhibition.
   * @param worldOrExhibitionId The World NFT or exhibition ID.
   * @param sellers The new list of sellers to be allowed to list with this World/exhibition.
   */
  function addSellersToExhibition(uint256 worldOrExhibitionId, address[] calldata sellers) public {
    if (sellers.length == 0) {
      revert NFTMarketExhibition_Sellers_Required();
    }

    // Try populating a World first
    if (_addToAllowlistBySellerIfWorld(worldOrExhibitionId, sellers[0])) {
      // If this is a World NFT, populate the remaining sellers provided.
      for (uint256 i = 1; i < sellers.length; ) {
        _addToAllowlistBySeller(worldOrExhibitionId, sellers[i]);

        unchecked {
          ++i;
        }
      }
    } else {
      // Populate exhibition allow list if the World doesn't exist
      _requireExhibitionCurator(worldOrExhibitionId);

      for (uint256 i = 0; i < sellers.length; ) {
        address seller = sellers[i];
        if ($exhibitionIdToSellerToIsAllowed[worldOrExhibitionId][seller]) {
          revert NFTMarketExhibition_Can_Not_Add_Dupe_Seller();
        }
        if (seller == _msgSender()) {
          revert NFTMarketExhibition_Curator_Automatically_Allowed();
        }

        $exhibitionIdToSellerToIsAllowed[worldOrExhibitionId][seller] = true;

        unchecked {
          ++i;
        }
      }
    }

    // Emit the original event regardless of if it's a World or an exhibition.
    emit SellersAddedToExhibition(worldOrExhibitionId, sellers);
  }

  /**
   * @notice Checks if a given seller is approved to list with a given World or exhibition.
   * @param worldOrExhibitionId The ID of the World NFT or exhibition to check.
   * @param seller The address of the seller to check.
   * @return allowedSeller True if the seller is approved to list with the World/exhibition.
   */
  function isAllowedSellerForExhibition(
    uint256 worldOrExhibitionId,
    address seller
  ) external view returns (bool allowedSeller) {
    allowedSeller = IWorldsNFTMarket(worlds).isSellerAllowed(worldOrExhibitionId, seller);

    if (!allowedSeller) {
      // If not allowed to list with the World, check exhibitions instead.

      address curator = $idToExhibition[worldOrExhibitionId].curator;
      if (curator != address(0)) {
        // If the exhibition exists, check the allowlist.

        allowedSeller = $exhibitionIdToSellerToIsAllowed[worldOrExhibitionId][seller] || seller == curator;
      }
    }
  }

  ////////////////////////////////////////////////////////////////
  // Inventory
  ////////////////////////////////////////////////////////////////

  /**
   * @notice Updates an NFTs World or exhibition relationship, if at least the NFT is currently available for sale and
   * in escrow.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @param worldOrExhibitionId The World NFT or exhibition ID, or 0 to remove any existing associations.
   * @dev The difference between this and _updateWorldOrExhibitionForNft is this will confirm permission and revert on a
   * no-op.
   */
  function updateExhibitionNft(address nftContract, uint256 tokenId, uint256 worldOrExhibitionId) external {
    _authorizeExhibitionOrScheduleUpdate(nftContract, tokenId);

    address seller = _getSellerOf(nftContract, tokenId);
    if (!_updateWorldOrExhibitionForNft(seller, nftContract, tokenId, worldOrExhibitionId)) {
      // The request was a no-op, revert with a relevant reason.
      if (worldOrExhibitionId == 0) {
        revert NFTMarketExhibition_Can_Not_Remove_Not_Associated_With_Exhibition();
      }
      revert NFTMarketExhibition_Exhibition_NFT_Already_Set();
    }
  }

  /**
   * @notice Assigns an NFT to a World NFT or an exhibition.
   * @param nftContract The contract address of the NFT.
   * @param tokenId The ID of the NFT.
   * @param worldOrExhibitionId The ID of the World or exhibition to list the NFT with, or 0 to remove any existing
   * relationship.
   * @dev If we call this with worldOrExhibitionId = 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 _updateWorldOrExhibitionForNft(
    address seller,
    address nftContract,
    uint256 tokenId,
    uint256 worldOrExhibitionId
  ) internal returns (bool changed) {
    (uint256 existingWorld, ) = IWorldsNFTMarket(worlds).getAssociationByNft(nftContract, tokenId, seller);

    if (worldOrExhibitionId == 0) {
      // Remove existing relationships.

      // Remove from Worlds.
      if (existingWorld != 0) {
        _removeFromWorldByNft(seller, nftContract, tokenId);
        emit NftRemovedFromExhibition(nftContract, tokenId, existingWorld);
        changed = true;
      }

      // Remove from exhibitions.
      changed = _deleteExhibitionRelationshipIfFound(nftContract, tokenId) || changed;
    } else {
      // Assign to a World or exhibition.

      if (existingWorld == worldOrExhibitionId) {
        // Already assigned to the desired World NFT relationship, remove any exhibition relationship found.
        return _deleteExhibitionRelationshipIfFound(nftContract, tokenId);
      }

      // Try adding to the World, potentially replacing a previous World NFT relationship.
      if (
        _addToWorldByNftIfWorld(
          worldOrExhibitionId,
          nftContract,
          tokenId,
          IWorldsSharedMarket(worlds).getDefaultTakeRate(worldOrExhibitionId)
        )
      ) {
        // The desired relationship is a World NFT, now assigned.

        if (existingWorld != 0) {
          // If the NFT was previously assigned to another World NFT, emit removal here for event parity.
          emit NftRemovedFromExhibition(nftContract, tokenId, existingWorld);
        }

        // Remove any exhibition relationship found since it now in a World.
        _deleteExhibitionRelationshipIfFound(nftContract, tokenId);

        emit NftAddedToExhibition(nftContract, tokenId, worldOrExhibitionId);
        changed = true;
      } else {
        // The desired relationship is an exhibition (or DNE).

        // When assigning to an exhibition, remove any existing World NFT relationship.
        if (existingWorld != 0) {
          _removeFromWorldByNft(seller, nftContract, tokenId);
          emit NftRemovedFromExhibition(nftContract, tokenId, existingWorld);
          changed = true;
        }

        uint256 currentExhibitionId = $nftContractToTokenIdToExhibitionId[nftContract][tokenId];
        if (currentExhibitionId == worldOrExhibitionId) {
          // We do not need to update the exhibition relationship.
          return changed;
        }

        address curator = $idToExhibition[worldOrExhibitionId].curator;
        if (curator == address(0)) {
          revert NFTMarketExhibition_Exhibition_Does_Not_Exist();
        }

        if (!$exhibitionIdToSellerToIsAllowed[worldOrExhibitionId][seller] && curator != seller) {
          revert NFTMarketExhibition_Seller_Not_Allowed_In_Exhibition();
        }

        $nftContractToTokenIdToExhibitionId[nftContract][tokenId] = worldOrExhibitionId;

        // If there was an exhibition already set, emit a removal first.
        if (currentExhibitionId != 0) {
          emit NftRemovedFromExhibition(nftContract, tokenId, currentExhibitionId);
        }

        emit NftAddedToExhibition(nftContract, tokenId, worldOrExhibitionId);
        changed = true;
      }
    }
  }

  /// @dev This helper should only be used for exhibitions (not World NFTs).
  function _deleteExhibitionRelationshipIfFound(address nftContract, uint256 tokenId) private returns (bool deleted) {
    uint256 currentExhibitionId = $nftContractToTokenIdToExhibitionId[nftContract][tokenId];
    if (currentExhibitionId != 0) {
      delete $nftContractToTokenIdToExhibitionId[nftContract][tokenId];
      emit NftRemovedFromExhibition(nftContract, tokenId, currentExhibitionId);
      deleted = true;
    }
  }

  /**
   * @dev This mixin appears before the market tools in inheritance order, so when this is called we have already
   * confirmed that the NFT is no longer listed and will indeed leave escrow.
   */
  function _transferFromEscrowIfAvailable(
    address nftContract,
    uint256 tokenId,
    address originalSeller
  ) internal virtual override {
    // Clear any World or exhibition relationship before the NFT leaves escrow.
    _updateWorldOrExhibitionForNft({
      seller: originalSeller,
      nftContract: nftContract,
      tokenId: tokenId,
      worldOrExhibitionId: 0
    });

    // Then transfer the NFT from escrow.
    super._transferFromEscrowIfAvailable(nftContract, tokenId, originalSeller);
  }

  /**
   * @notice Returns the World NFT or exhibition ID for a given NFT, if it was listed with one.
   * @param nftContract The contract address of the NFT.
   * @param tokenId The ID of the NFT.
   * @return worldOrExhibitionId The ID of the World or exhibition this NFT is assigned to, or 0 if it's not assigned to
   * one.
   */
  function getExhibitionIdForNft(
    address nftContract,
    uint256 tokenId
  ) external view returns (uint256 worldOrExhibitionId) {
    // Check for a World NFT association first.
    (worldOrExhibitionId, ) = IWorldsNFTMarket(worlds).getAssociationByNft(
      nftContract,
      tokenId,
      _getSellerOrOwnerOf(nftContract, tokenId)
    );

    if (worldOrExhibitionId == 0) {
      // If not in a World, check exhibitions instead
      worldOrExhibitionId = $nftContractToTokenIdToExhibitionId[nftContract][tokenId];
    }
  }

  ////////////////////////////////////////////////////////////////
  // Payments
  ////////////////////////////////////////////////////////////////

  /**
   * @notice Returns World or exhibition details if this NFT was assigned to one, and clears the assignment.
   * @return worldPaymentAddress 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 _getWorldOrExhibitionForPayment(
    address seller,
    address nftContract,
    uint256 tokenId,
    address buyer,
    uint256 salePrice
  ) internal returns (address payable worldPaymentAddress, uint16 takeRateInBasisPoints) {
    uint256 worldOrExhibitionId;
    (worldOrExhibitionId, worldPaymentAddress, takeRateInBasisPoints) = IWorldsNFTMarket(worlds).soldInWorldByNft(
      seller,
      nftContract,
      tokenId,
      buyer,
      salePrice
    );

    if (worldOrExhibitionId != 0) {
      // The NFT was associated with a World.

      // Clear the World association on sale
      _removeFromWorldByNft(seller, nftContract, tokenId);

      // When listed in both worlds and exhibitions, we pay the World but clean up both states.
      _deleteExhibitionRelationshipIfFound(nftContract, tokenId);
    } else {
      // If not in a World, check if it's associated with an exhibition.

      worldOrExhibitionId = $nftContractToTokenIdToExhibitionId[nftContract][tokenId];

      if (worldOrExhibitionId != 0) {
        // The NFT was associated with an exhibition.

        worldPaymentAddress = $idToExhibition[worldOrExhibitionId].curator;

        if (worldPaymentAddress == address(0)) {
          // If the worldPaymentAddress was not found as an exhibition, this may have been migrated to a World NFT but
          // did not migrate the complete inventory.
          worldPaymentAddress = IWorldsSharedMarket(worlds).getPaymentAddress(worldOrExhibitionId);
          takeRateInBasisPoints = IWorldsSharedMarket(worlds).getDefaultTakeRate(worldOrExhibitionId);
        } else {
          // Otherwise the exhibition is still stored locally, so we can populate the take rate from storage.
          takeRateInBasisPoints = $idToExhibition[worldOrExhibitionId].takeRateInBasisPoints;
        }

        delete $nftContractToTokenIdToExhibitionId[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
   * @dev This file uses a total of 250 slots.
   */
  uint256[246] private __gap;
}

File 28 of 42 : NFTMarketOffer.sol
// 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/FoundationTreasuryNodeV1.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
  FoundationTreasuryNodeV1,
  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
    ) = _getWorldOrExhibitionForPayment(sender, nftContract, tokenId, offer.buyer, offer.amount);

    // 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);
  }

  /**
   * @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)));
  }

  ////////////////////////////////////////////////////////////////
  // Inheritance Requirements
  // (no-ops to avoid compile errors)
  ////////////////////////////////////////////////////////////////

  function _transferFromEscrowIfAvailable(
    address nftContract,
    uint256 tokenId,
    address originalSeller
  ) internal virtual override(NFTMarketCore, NFTMarketExhibition) {
    super._transferFromEscrowIfAvailable(nftContract, tokenId, originalSeller);
  }

  /**
   * @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;
}

File 29 of 42 : NFTMarketPrivateSaleGap.sol
// 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;
}

File 30 of 42 : NFTMarketReserveAuction.sol
// 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/FoundationTreasuryNodeV1.sol";
import "../shared/FETHNode.sol";
import "../shared/MarketFees.sol";
import "../shared/MarketSharedCore.sol";
import "../shared/WorldsNftNode.sol";
import "../shared/SendValueWithFallbackWithdraw.sol";

import "./NFTMarketAuction.sol";
import "./NFTMarketCore.sol";
import "./NFTMarketExhibition.sol";
import "./NFTMarketScheduling.sol";

/// @param auctionId The already listed auctionId for this NFT.
error NFTMarketReserveAuction_Already_Listed(uint256 auctionId);
/// @param minAmount The minimum amount that must be bid in order for it to be accepted.
error NFTMarketReserveAuction_Bid_Must_Be_At_Least_Min_Amount(uint256 minAmount);
/// @param reservePrice The current reserve price.
error NFTMarketReserveAuction_Cannot_Bid_Lower_Than_Reserve_Price(uint256 reservePrice);
/// @param endTime The timestamp at which the auction had ended.
error NFTMarketReserveAuction_Cannot_Bid_On_Ended_Auction(uint256 endTime);
error NFTMarketReserveAuction_Cannot_Bid_On_Nonexistent_Auction();
error NFTMarketReserveAuction_Cannot_Finalize_Already_Settled_Auction();
/// @param endTime The timestamp at which the auction will end.
error NFTMarketReserveAuction_Cannot_Finalize_Auction_In_Progress(uint256 endTime);
error NFTMarketReserveAuction_Cannot_Rebid_Over_Outstanding_Bid();
error NFTMarketReserveAuction_Cannot_Update_Auction_In_Progress();
error NFTMarketReserveAuction_Cannot_Update_Nft_While_Auction_In_Progress();
error NFTMarketReserveAuction_Cannot_Update_Sale_Starts_At_Auction_In_Progress();
/// @param maxDuration The maximum configuration for a duration of the auction, in seconds.
error NFTMarketReserveAuction_Exceeds_Max_Duration(uint256 maxDuration);
/// @param extensionDuration The extension duration, in seconds.
error NFTMarketReserveAuction_Less_Than_Extension_Duration(uint256 extensionDuration);
error NFTMarketReserveAuction_Must_Set_Non_Zero_Reserve_Price();
/// @param seller The current owner of the NFT.
error NFTMarketReserveAuction_Not_Matching_Seller(address seller);
/// @param owner The current owner of the NFT.
error NFTMarketReserveAuction_Only_Owner_Can_Update_Auction(address owner);
/// @param owner The current owner of the NFT.
error NFTMarketReserveAuction_Only_Owner_Can_Update_Nft(address owner);
/// @param owner The current owner of the NFT.
error NFTMarketReserveAuction_Only_Owner_Can_Update_Sale_Starts_At(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,
  FoundationTreasuryNodeV1,
  ContextUpgradeable,
  FETHNode,
  MarketSharedCore,
  NFTMarketCore,
  ReentrancyGuardUpgradeable,
  SendValueWithFallbackWithdraw,
  MarketFees,
  WorldsNftNode,
  NFTMarketExhibition,
  NFTMarketScheduling,
  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 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];
    address sender = _msgSender();
    if (auction.seller != sender) {
      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, sender);

    emit ReserveAuctionCanceled(auctionId);
  }

  /**
   * @notice Creates an auction for the given NFT.
   * The NFT is held in escrow until the auction is finalized or canceled.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @param reservePrice The initial reserve price for the auction.
   * @param duration The length of the auction, in seconds.
   * @return auctionId The id of the auction that was created.
   */
  function createReserveAuction(
    address nftContract,
    uint256 tokenId,
    uint256 reservePrice,
    uint256 duration
  ) public nonReentrant returns (uint256 auctionId) {
    _validateAuctionConfig(reservePrice);
    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 [DEPRECATED] Use `createReserveAuction` 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 worldOrExhibitionId The World NFT or exhibition to list with, or 0 if we don't want to associate to a
   * World/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 worldOrExhibitionId,
    uint256 reservePrice,
    uint256 duration
  ) external returns (uint256 auctionId) {
    auctionId = createReserveAuction(nftContract, tokenId, reservePrice, duration);
    _updateWorldOrExhibitionForNft(_msgSender(), nftContract, tokenId, worldOrExhibitionId);
  }

  /**
   * @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 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) external 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 If an auction has been created but has not yet received bids, the reservePrice and worldOrExhibitionId 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) public {
    _validateAuctionConfig(reservePrice);
    ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId];
    address sender = _msgSender();
    if (auction.seller != sender) {
      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;

    emit ReserveAuctionUpdated(auctionId, reservePrice);
  }

  /**
   * @notice [DEPRECATED] Use `updateReserveAuction` instead.
   * If an auction has been created but has not yet received bids, the reservePrice and worldOrExhibitionId may
   * be changed by the seller.
   * @param auctionId The id of the auction to change.
   * @param worldOrExhibitionId The World NFT or exhibition ID to list with, or 0 to remove from any existing
   * relationship.
   * @param reservePrice The new reserve price for this auction.
   */
  function updateReserveAuctionV2(uint256 auctionId, uint256 worldOrExhibitionId, uint256 reservePrice) external {
    updateReserveAuction(auctionId, reservePrice);

    ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId];
    _updateWorldOrExhibitionForNft(_msgSender(), auction.nftContract, auction.tokenId, worldOrExhibitionId);
  }

  /**
   * @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
    ) = _getWorldOrExhibitionForPayment(
        auction.seller,
        auction.nftContract,
        auction.tokenId,
        auction.bidder,
        auction.amount
      );

    // 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 _isAuthorizedExhibitionOrScheduleUpdate(
    address nftContract,
    uint256 tokenId
  ) internal view virtual override returns (bool canUpdateNft) {
    uint256 auctionId = nftContractToTokenIdToAuctionId[nftContract][tokenId];

    if (auctionId != 0) {
      ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId];

      if (auction.seller != _msgSender()) {
        revert NFTMarketReserveAuction_Only_Owner_Can_Update_Nft(auction.seller);
      }
      if (auction.endTime != 0) {
        revert NFTMarketReserveAuction_Cannot_Update_Nft_While_Auction_In_Progress();
      }

      canUpdateNft = true;
    } else {
      canUpdateNft = super._isAuthorizedExhibitionOrScheduleUpdate(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);
        }
        address originalSeller = auction.seller;

        // Remove the auction.
        delete nftContractToTokenIdToAuctionId[nftContract][tokenId];
        delete auctionIdToAuction[auctionId];
        _updateWorldOrExhibitionForNft({
          seller: originalSeller,
          nftContract: nftContract,
          tokenId: tokenId,
          worldOrExhibitionId: 0
        });

        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 originalSeller
  ) internal virtual override(NFTMarketCore, NFTMarketExhibition, NFTMarketScheduling) {
    if (nftContractToTokenIdToAuctionId[nftContract][tokenId] == 0) {
      // No auction was found
      super._transferFromEscrowIfAvailable(nftContract, tokenId, originalSeller);
    }
  }

  /**
   * @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 Confirms that the reserve price is not zero.
  function _validateAuctionConfig(uint256 reservePrice) private pure {
    if (reservePrice == 0) {
      revert NFTMarketReserveAuction_Must_Set_Non_Zero_Reserve_Price();
    }
  }

  ////////////////////////////////////////////////////////////////
  // Inheritance Requirements
  // (no-ops to avoid compile errors)
  ////////////////////////////////////////////////////////////////

  /**
   * @inheritdoc NFTMarketCore
   */
  function _beforeAuctionStarted(
    address nftContract,
    uint256 tokenId
  ) internal virtual override(NFTMarketCore, NFTMarketScheduling) {
    super._beforeAuctionStarted(nftContract, tokenId);
  }

  /**
   * @inheritdoc MarketFees
   */
  function _distributeFunds(
    address nftContract,
    uint256 tokenId,
    address payable seller,
    uint256 price,
    address payable buyReferrer,
    address payable sellerReferrerPaymentAddress,
    uint16 sellerReferrerTakeRateInBasisPoints
  )
    internal
    virtual
    override(MarketFees, NFTMarketScheduling)
    returns (uint256 totalFees, uint256 creatorRev, uint256 sellerRev)
  {
    (totalFees, creatorRev, sellerRev) = super._distributeFunds(
      nftContract,
      tokenId,
      seller,
      price,
      buyReferrer,
      sellerReferrerPaymentAddress,
      sellerReferrerTakeRateInBasisPoints
    );
  }

  /**
   * @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;
}

File 31 of 42 : NFTMarketScheduling.sol
// SPDX-License-Identifier: MIT OR Apache-2.0

pragma solidity ^0.8.18;

import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";

import "../../libraries/TimeLibrary.sol";

import "../../interfaces/internal/routes/INFTMarketScheduling.sol";
import "../../interfaces/internal/INFTMarketGetters.sol";

import "../shared/Constants.sol";
import "../shared/MarketFees.sol";

import "./NFTMarketCore.sol";

// Errors when configuring a schedule
error NFTMarketScheduling_Sale_Starts_At_Already_Set();
error NFTMarketScheduling_Sale_Starts_At_Is_In_Past();
error NFTMarketScheduling_Sale_Starts_At_Too_Far_In_The_Future(uint256 maxStartsAt);

// Errors when validating a schedule
error NFTMarketScheduling_Sale_Starts_At_Is_In_Future();

/**
 * @title Allows listed NFTs to schedule a sale starts at time.
 * @dev This supports both Auctions and BuyNow.
 * @author HardlyDifficult & smhutch
 */
abstract contract NFTMarketScheduling is
  INFTMarketGetters,
  INFTMarketScheduling,
  ContextUpgradeable,
  NFTMarketCore,
  MarketFees
{
  using TimeLibrary for uint256;

  /// @notice Stores the saleStartsAt time for listed NFTs
  mapping(address => mapping(uint256 => uint256)) private $nftContractToTokenIdToSaleStartsAt;

  /**
   * @notice emitted when an a saleStartsAt time is changed for an NFT.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @param operator The address that triggered this change.
   * @param saleStartsAt The time at which the NFT will be available to buy or place bids on.
   *  When zero, this represents that the NFT is unscheduled.
   *  When above zero, this value represents the time in seconds since the Unix epoch.
   */
  event SetSaleStartsAt(
    address indexed nftContract,
    uint256 indexed tokenId,
    address indexed operator,
    uint256 saleStartsAt
  );

  ////////////////////////////////////////////////////////////////
  // Configuration
  ////////////////////////////////////////////////////////////////

  /**
   * @notice sets the saleStartsAt time for a listed NFT.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @param saleStartsAt The time at which the NFT will be available to buy or place bids on.
   *  When zero, the NFT has no saleStartsAt and can be purchased anytime.
   *  When above zero, this value represents the time in seconds since the Unix epoch.
   */
  function setSaleStartsAt(address nftContract, uint256 tokenId, uint256 saleStartsAt) external {
    // Check if it's already set first since this may be a common occurrence.
    if ($nftContractToTokenIdToSaleStartsAt[nftContract][tokenId] == saleStartsAt) {
      revert NFTMarketScheduling_Sale_Starts_At_Already_Set();
    }

    _authorizeExhibitionOrScheduleUpdate(nftContract, tokenId);
    if (saleStartsAt != 0) {
      if (saleStartsAt.hasExpired()) {
        revert NFTMarketScheduling_Sale_Starts_At_Is_In_Past();
      }

      if (saleStartsAt > block.timestamp + MAX_SCHEDULED_TIME_IN_THE_FUTURE) {
        // Prevent arbitrarily large values from accidentally being set.
        revert NFTMarketScheduling_Sale_Starts_At_Too_Far_In_The_Future(
          block.timestamp + MAX_SCHEDULED_TIME_IN_THE_FUTURE
        );
      }
    }

    $nftContractToTokenIdToSaleStartsAt[nftContract][tokenId] = saleStartsAt;

    emit SetSaleStartsAt({
      nftContract: nftContract,
      tokenId: tokenId,
      operator: _msgSender(),
      saleStartsAt: saleStartsAt
    });
  }

  /**
   * @notice Returns the saleStartsAt time for a listed NFT.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @return saleStartsAt The time at which the NFT will be available to buy or place bids on.
   * 0 if there is no schedule set and the NFT may be purchased anytime (or is not yet listed).
   */
  function getSaleStartsAt(address nftContract, uint256 tokenId) external view returns (uint256 saleStartsAt) {
    saleStartsAt = $nftContractToTokenIdToSaleStartsAt[nftContract][tokenId];
  }

  ////////////////////////////////////////////////////////////////
  // Validation
  ////////////////////////////////////////////////////////////////

  function _validateSaleStartsAtHasBeenReached(address nftContract, uint256 tokenId) internal view {
    if (!$nftContractToTokenIdToSaleStartsAt[nftContract][tokenId].hasBeenReached()) {
      revert NFTMarketScheduling_Sale_Starts_At_Is_In_Future();
    }
  }

  /**
   * @inheritdoc NFTMarketCore
   * @dev Validates the saleStartsAt time for the NFT when the first bid is placed.
   */
  function _beforeAuctionStarted(address nftContract, uint256 tokenId) internal virtual override {
    _validateSaleStartsAtHasBeenReached(nftContract, tokenId);
    super._beforeAuctionStarted(nftContract, tokenId);
  }

  ////////////////////////////////////////////////////////////////
  // Cleanup
  ////////////////////////////////////////////////////////////////

  function _clearScheduleIfSet(address nftContract, uint256 tokenId) private {
    if ($nftContractToTokenIdToSaleStartsAt[nftContract][tokenId] != 0) {
      // Clear the saleStartsAt time so that it does not apply to the next listing
      delete $nftContractToTokenIdToSaleStartsAt[nftContract][tokenId];
      emit SetSaleStartsAt({ nftContract: nftContract, tokenId: tokenId, operator: _msgSender(), saleStartsAt: 0 });
    }
  }

  /**
   * @dev When a sale occurs, clear the schedule if one was set.
   */
  function _distributeFunds(
    address nftContract,
    uint256 tokenId,
    address payable seller,
    uint256 price,
    address payable buyReferrer,
    address payable sellerReferrerPaymentAddress,
    uint16 sellerReferrerTakeRateInBasisPoints
  ) internal virtual override returns (uint256 totalFees, uint256 creatorRev, uint256 sellerRev) {
    _clearScheduleIfSet(nftContract, tokenId);

    (totalFees, creatorRev, sellerRev) = super._distributeFunds(
      nftContract,
      tokenId,
      seller,
      price,
      buyReferrer,
      sellerReferrerPaymentAddress,
      sellerReferrerTakeRateInBasisPoints
    );
  }

  /**
   * @dev Called when a listing is canceled. This mixin appears before the market tools in inheritance order, so when
   * this is called we have already confirmed that the NFT is no longer listed and will indeed leave escrow.
   */
  function _transferFromEscrowIfAvailable(
    address nftContract,
    uint256 tokenId,
    address originalSeller
  ) internal virtual override {
    _clearScheduleIfSet(nftContract, tokenId);

    super._transferFromEscrowIfAvailable(nftContract, tokenId, originalSeller);
  }

  /**
   * @notice This empty reserved space is put in place to allow future versions to add new variables without shifting
   * down storage in the inheritance chain. See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
   * @dev This mixin uses 250 slots in total.
   */
  uint256[249] private __gap;
}

File 32 of 42 : NFTMarketWorldsAPIs.sol
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;

import { IWorldsNFTMarket } from "../../interfaces/internal/IWorldsNFTMarket.sol";

import { RouteCallLibrary } from "../../libraries/RouteCallLibrary.sol";
import { WorldsRouteAPIs } from "../shared/WorldsRouteAPIs.sol";

/**
 * @title Routes calls to the Worlds contract, specifying to use the current _msgSender() as the caller.
 * @author HardlyDifficult
 */
abstract contract NFTMarketWorldsAPIs is WorldsRouteAPIs {
  using RouteCallLibrary for address;

  ////////////////////////////////////////////////////////////////
  // Management
  ////////////////////////////////////////////////////////////////

  function _mintWorld(
    uint16 defaultTakeRateInBasisPoints,
    address payable worldPaymentAddress,
    string memory name
  ) internal returns (uint256 worldId) {
    bytes memory returnData = _msgSender().routeCallTo(
      worlds,
      abi.encodeCall(IWorldsNFTMarket.mint, (defaultTakeRateInBasisPoints, worldPaymentAddress, name))
    );
    worldId = abi.decode(returnData, (uint256));
  }

  function _burnIfWorld(uint256 worldId) internal returns (bool isWorld) {
    isWorld = _callWorldsAndReturnFalseIfInvalidWorldId(abi.encodeCall(IWorldsNFTMarket.burn, (worldId)));
  }

  ////////////////////////////////////////////////////////////////
  // Allowlist
  ////////////////////////////////////////////////////////////////

  function _addToAllowlistBySellerIfWorld(uint256 worldId, address seller) internal returns (bool isWorld) {
    isWorld = _callWorldsAndReturnFalseIfInvalidWorldId(
      abi.encodeCall(IWorldsNFTMarket.addToAllowlistBySeller, (worldId, seller))
    );
  }

  function _addToAllowlistBySeller(uint256 worldId, address seller) internal {
    _msgSender().routeCallTo(worlds, abi.encodeCall(IWorldsNFTMarket.addToAllowlistBySeller, (worldId, seller)));
  }

  ////////////////////////////////////////////////////////////////
  // Inventory
  ////////////////////////////////////////////////////////////////

  function _addToWorldByNftIfWorld(
    uint256 worldId,
    address nftContract,
    uint256 nftTokenId,
    uint16 takeRateInBasisPoints
  ) internal returns (bool isWorld) {
    isWorld = _callWorldsAndReturnFalseIfInvalidWorldId(
      abi.encodeCall(IWorldsNFTMarket.addToWorldByNft, (worldId, nftContract, nftTokenId, takeRateInBasisPoints))
    );
  }

  /**
   * @param seller This is the seller which had originally listed the NFT, and may not be the current msg.sender.
   */
  function _removeFromWorldByNft(address seller, address nftContract, uint256 nftTokenId) internal {
    seller.routeCallTo(worlds, abi.encodeCall(IWorldsNFTMarket.removeFromWorldByNft, (nftContract, nftTokenId)));
  }
}

File 33 of 42 : Constants.sol
// 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_WORLD_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;

File 34 of 42 : FETHNode.sol
// 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);
  }
}

File 35 of 42 : FoundationTreasuryNodeV1.sol
// 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 Stores a reference to Foundation's treasury contract for other mixins to leverage.
 * @notice The treasury collects fees and defines admin/operator roles.
 * @author batu-inal & HardlyDifficult
 */
abstract contract FoundationTreasuryNodeV1 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
   * @dev This mixin uses a total of 2,001 slots.
   */
  uint256[2_000] private __gap;
}

File 36 of 42 : MarketFees.sol
// 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 "./FoundationTreasuryNodeV1.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
  FoundationTreasuryNodeV1,
  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_WORLD_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.
   * @dev `virtual` allows other mixins to be notified anytime a sale occurs.
   */
  function _distributeFunds(
    address nftContract,
    uint256 tokenId,
    address payable seller,
    uint256 price,
    address payable buyReferrer,
    address payable sellerReferrerPaymentAddress,
    uint16 sellerReferrerTakeRateInBasisPoints
  ) internal virtual 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;
}

File 37 of 42 : MarketSharedCore.sol
// 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;
}

File 38 of 42 : MarketStructs.sol
// 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;
}

/// @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;
}

File 39 of 42 : RouterContextSingle.sol
// 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 "../../libraries/RouteCallLibrary.sol";

error RouterContextSingle_Address_Is_Not_A_Contract();

/**
 * @title Enables a trusted contract to override the usual msg.sender address.
 * @author HardlyDifficult
 */
abstract contract RouterContextSingle is ContextUpgradeable {
  using AddressUpgradeable for address;

  address private immutable approvedRouter;

  constructor(address router) {
    if (!router.isContract()) {
      revert RouterContextSingle_Address_Is_Not_A_Contract();
    }

    approvedRouter = router;
  }

  /**
   * @notice Returns the 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 Gets the sender of the transaction to use, overriding the usual msg.sender if the caller is a trusted
   * router.
   * @dev If the msg.sender is a trusted router contract, then the last 20 bytes of the calldata represents the
   * authorized sender to use.
   * If this is used for a call that was not routed with `routeCallTo`, the address returned will be incorrect (and
   * may be address(0)).
   */
  function _msgSender() internal view virtual override returns (address sender) {
    sender = super._msgSender();
    if (sender == approvedRouter) {
      sender = RouteCallLibrary.extractAppendedSenderAddress();
    }
  }
}

File 40 of 42 : SendValueWithFallbackWithdraw.sol
// SPDX-License-Identifier: MIT OR Apache-2.0

pragma solidity ^0.8.18;

import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";

import "./FETHNode.sol";
import "./FoundationTreasuryNodeV1.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 FoundationTreasuryNodeV1, 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;
}

File 41 of 42 : WorldsNftNode.sol
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;

import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";

error WorldsNftNode_Worlds_NFT_Is_Not_A_Contract();

/**
 * @title Stores a reference to the Worlds NFT contract for contracts to leverage.
 * @author HardlyDifficult
 */
abstract contract WorldsNftNode {
  using AddressUpgradeable for address;

  address internal immutable worlds;

  constructor(address worldsNft) {
    if (!worldsNft.isContract()) {
      revert WorldsNftNode_Worlds_NFT_Is_Not_A_Contract();
    }

    worlds = worldsNft;
  }

  /**
   * @notice Returns the address of the Worlds NFT contract.
   */
  function getWorldsNftAddress() external view returns (address worldsNft) {
    worldsNft = worlds;
  }

  // This mixin uses 0 slots.
}

File 42 of 42 : WorldsRouteAPIs.sol
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;

import { ContextUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";

import { RouteCallLibrary } from "../../libraries/RouteCallLibrary.sol";

import { WorldsNftNode } from "../shared/WorldsNftNode.sol";

/**
 * @title Helper for routing calls to the Worlds contract, specifying to use the current _msgSender() as the caller.
 * Handles checking the revert reason in order to special case the "invalid token ID" error.
 * @author HardlyDifficult
 */
abstract contract WorldsRouteAPIs is ContextUpgradeable, WorldsNftNode {
  using RouteCallLibrary for address;

  /// @dev keccak256(abi.encodeWithSignature("Error(string)", "ERC721: invalid token ID"));
  bytes32 private constant invalidTokenIdErrorHash = 0x931c7fca1aa4d9db6fb474c5724471bc286fbb3d892ec4f390fa55323a23dc0c;

  /**
   * @notice Route a call to the Worlds contract, specifying the current _msgSender() as the caller.
   * Reverts if the call fails for any reason other than the token ID being invalid.
   * @param callData The data to send to the Worlds contract.
   * @return isWorld True if the call was successful. False if the call reverted with the "invalid token ID" error.
   */
  function _callWorldsAndReturnFalseIfInvalidWorldId(bytes memory callData) internal returns (bool isWorld) {
    bytes memory returnData;
    (isWorld, returnData) = _msgSender().tryRouteCallTo(worlds, callData);

    if (!isWorld && keccak256(returnData) != invalidTokenIdErrorHash) {
      // This is a World NFT, but another error occurred.
      RouteCallLibrary.revertWithError(returnData);
    }
  }
}

Settings
{
  "evmVersion": "shanghai",
  "optimizer": {
    "enabled": true,
    "runs": 300
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {}
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"address payable","name":"treasury","type":"address"},{"internalType":"address","name":"feth","type":"address"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"address","name":"router","type":"address"},{"internalType":"address","name":"marketUtils","type":"address"},{"internalType":"address","name":"worldsNft","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"FETHNode_FETH_Address_Is_Not_A_Contract","type":"error"},{"inputs":[],"name":"FETHNode_Only_FETH_Can_Transfer_ETH","type":"error"},{"inputs":[],"name":"FoundationTreasuryNode_Address_Is_Not_A_Contract","type":"error"},{"inputs":[{"internalType":"uint256","name":"buyPrice","type":"uint256"}],"name":"NFTMarketBuyPrice_Cannot_Buy_At_Lower_Price","type":"error"},{"inputs":[],"name":"NFTMarketBuyPrice_Cannot_Buy_Unset_Price","type":"error"},{"inputs":[],"name":"NFTMarketBuyPrice_Cannot_Cancel_Unset_Price","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"NFTMarketBuyPrice_Only_Owner_Can_Cancel_Price","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"NFTMarketBuyPrice_Only_Owner_Can_Set_Price","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"NFTMarketBuyPrice_Only_Owner_Can_Update_Nft","type":"error"},{"inputs":[],"name":"NFTMarketBuyPrice_Price_Already_Set","type":"error"},{"inputs":[],"name":"NFTMarketBuyPrice_Price_Too_High","type":"error"},{"inputs":[{"internalType":"address","name":"seller","type":"address"}],"name":"NFTMarketBuyPrice_Seller_Mismatch","type":"error"},{"inputs":[],"name":"NFTMarketCore_Can_Not_Update_Unlisted_Nft","type":"error"},{"inputs":[],"name":"NFTMarketCore_Seller_Not_Found","type":"error"},{"inputs":[{"internalType":"address","name":"curator","type":"address"}],"name":"NFTMarketExhibition_Caller_Is_Not_Curator","type":"error"},{"inputs":[{"internalType":"address","name":"worlds","type":"address"}],"name":"NFTMarketExhibition_Caller_Is_Not_Worlds_Contract","type":"error"},{"inputs":[],"name":"NFTMarketExhibition_Can_Not_Add_Dupe_Seller","type":"error"},{"inputs":[],"name":"NFTMarketExhibition_Can_Not_Remove_Not_Associated_With_Exhibition","type":"error"},{"inputs":[],"name":"NFTMarketExhibition_Curator_Automatically_Allowed","type":"error"},{"inputs":[{"internalType":"address","name":"curator","type":"address"}],"name":"NFTMarketExhibition_Curator_Does_Not_Match","type":"error"},{"inputs":[],"name":"NFTMarketExhibition_Exhibition_Does_Not_Exist","type":"error"},{"inputs":[],"name":"NFTMarketExhibition_Exhibition_NFT_Already_Set","type":"error"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"nftTokenId","type":"uint256"},{"internalType":"uint256","name":"currentExhibitionId","type":"uint256"}],"name":"NFTMarketExhibition_NFT_Not_Associated_With_Exhibition","type":"error"},{"inputs":[],"name":"NFTMarketExhibition_Seller_Not_Allowed_In_Exhibition","type":"error"},{"inputs":[],"name":"NFTMarketExhibition_Sellers_Required","type":"error"},{"inputs":[],"name":"NFTMarketExhibition_Take_Rate_Too_High","type":"error"},{"inputs":[],"name":"NFTMarketExhibition_World_Migration_Already_Completed","type":"error"},{"inputs":[],"name":"NFTMarketFees_Invalid_Protocol_Fee","type":"error"},{"inputs":[],"name":"NFTMarketFees_Market_Utils_Is_Not_A_Contract","type":"error"},{"inputs":[],"name":"NFTMarketOffer_Cannot_Be_Made_While_In_Auction","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentOfferAmount","type":"uint256"}],"name":"NFTMarketOffer_Offer_Below_Min_Amount","type":"error"},{"inputs":[{"internalType":"uint256","name":"expiry","type":"uint256"}],"name":"NFTMarketOffer_Offer_Expired","type":"error"},{"inputs":[{"internalType":"address","name":"currentOfferFrom","type":"address"}],"name":"NFTMarketOffer_Offer_From_Does_Not_Match","type":"error"},{"inputs":[{"internalType":"uint256","name":"minOfferAmount","type":"uint256"}],"name":"NFTMarketOffer_Offer_Must_Be_At_Least_Min_Amount","type":"error"},{"inputs":[{"internalType":"uint256","name":"auctionId","type":"uint256"}],"name":"NFTMarketReserveAuction_Already_Listed","type":"error"},{"inputs":[{"internalType":"uint256","name":"minAmount","type":"uint256"}],"name":"NFTMarketReserveAuction_Bid_Must_Be_At_Least_Min_Amount","type":"error"},{"inputs":[{"internalType":"uint256","name":"reservePrice","type":"uint256"}],"name":"NFTMarketReserveAuction_Cannot_Bid_Lower_Than_Reserve_Price","type":"error"},{"inputs":[{"internalType":"uint256","name":"endTime","type":"uint256"}],"name":"NFTMarketReserveAuction_Cannot_Bid_On_Ended_Auction","type":"error"},{"inputs":[],"name":"NFTMarketReserveAuction_Cannot_Bid_On_Nonexistent_Auction","type":"error"},{"inputs":[],"name":"NFTMarketReserveAuction_Cannot_Finalize_Already_Settled_Auction","type":"error"},{"inputs":[{"internalType":"uint256","name":"endTime","type":"uint256"}],"name":"NFTMarketReserveAuction_Cannot_Finalize_Auction_In_Progress","type":"error"},{"inputs":[],"name":"NFTMarketReserveAuction_Cannot_Rebid_Over_Outstanding_Bid","type":"error"},{"inputs":[],"name":"NFTMarketReserveAuction_Cannot_Update_Auction_In_Progress","type":"error"},{"inputs":[],"name":"NFTMarketReserveAuction_Cannot_Update_Nft_While_Auction_In_Progress","type":"error"},{"inputs":[{"internalType":"uint256","name":"maxDuration","type":"uint256"}],"name":"NFTMarketReserveAuction_Exceeds_Max_Duration","type":"error"},{"inputs":[{"internalType":"uint256","name":"extensionDuration","type":"uint256"}],"name":"NFTMarketReserveAuction_Less_Than_Extension_Duration","type":"error"},{"inputs":[],"name":"NFTMarketReserveAuction_Must_Set_Non_Zero_Reserve_Price","type":"error"},{"inputs":[{"internalType":"address","name":"seller","type":"address"}],"name":"NFTMarketReserveAuction_Not_Matching_Seller","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"NFTMarketReserveAuction_Only_Owner_Can_Update_Auction","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"NFTMarketReserveAuction_Only_Owner_Can_Update_Nft","type":"error"},{"inputs":[],"name":"NFTMarketReserveAuction_Price_Already_Set","type":"error"},{"inputs":[],"name":"NFTMarketReserveAuction_Too_Much_Value_Provided","type":"error"},{"inputs":[],"name":"NFTMarketScheduling_Sale_Starts_At_Already_Set","type":"error"},{"inputs":[],"name":"NFTMarketScheduling_Sale_Starts_At_Is_In_Future","type":"error"},{"inputs":[],"name":"NFTMarketScheduling_Sale_Starts_At_Is_In_Past","type":"error"},{"inputs":[{"internalType":"uint256","name":"maxStartsAt","type":"uint256"}],"name":"NFTMarketScheduling_Sale_Starts_At_Too_Far_In_The_Future","type":"error"},{"inputs":[],"name":"RouteCallLibrary_Call_Failed_Without_Revert_Reason","type":"error"},{"inputs":[],"name":"RouterContextSingle_Address_Is_Not_A_Contract","type":"error"},{"inputs":[],"name":"WorldsNftNode_Worlds_NFT_Is_Not_A_Contract","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"seller","type":"address"},{"indexed":false,"internalType":"address","name":"buyer","type":"address"},{"indexed":false,"internalType":"uint256","name":"totalFees","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"creatorRev","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sellerRev","type":"uint256"}],"name":"BuyPriceAccepted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"BuyPriceCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"BuyPriceInvalidated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"seller","type":"address"},{"indexed":false,"internalType":"uint256","name":"price","type":"uint256"}],"name":"BuyPriceSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"address","name":"buyReferrer","type":"address"},{"indexed":false,"internalType":"uint256","name":"buyReferrerFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"buyReferrerSellerFee","type":"uint256"}],"name":"BuyReferralPaid","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"worldOrExhibitionId","type":"uint256"},{"indexed":true,"internalType":"address","name":"curator","type":"address"},{"indexed":false,"internalType":"string","name":"name","type":"string"},{"indexed":false,"internalType":"uint16","name":"takeRateInBasisPoints","type":"uint16"}],"name":"ExhibitionCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"worldOrExhibitionId","type":"uint256"}],"name":"ExhibitionDeleted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"exhibitionId","type":"uint256"}],"name":"ExhibitionMigratedToWorlds","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"worldOrExhibitionId","type":"uint256"}],"name":"NftAddedToExhibition","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"worldOrExhibitionId","type":"uint256"}],"name":"NftRemovedFromExhibition","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"buyer","type":"address"},{"indexed":false,"internalType":"address","name":"seller","type":"address"},{"indexed":false,"internalType":"uint256","name":"totalFees","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"creatorRev","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sellerRev","type":"uint256"}],"name":"OfferAccepted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"OfferInvalidated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"buyer","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"expiration","type":"uint256"}],"name":"OfferMade","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"auctionId","type":"uint256"},{"indexed":true,"internalType":"address","name":"bidder","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"endTime","type":"uint256"}],"name":"ReserveAuctionBidPlaced","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"auctionId","type":"uint256"}],"name":"ReserveAuctionCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"seller","type":"address"},{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"duration","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"extensionDuration","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"reservePrice","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"auctionId","type":"uint256"}],"name":"ReserveAuctionCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"auctionId","type":"uint256"},{"indexed":true,"internalType":"address","name":"seller","type":"address"},{"indexed":true,"internalType":"address","name":"bidder","type":"address"},{"indexed":false,"internalType":"uint256","name":"totalFees","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"creatorRev","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sellerRev","type":"uint256"}],"name":"ReserveAuctionFinalized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"auctionId","type":"uint256"}],"name":"ReserveAuctionInvalidated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"auctionId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"reservePrice","type":"uint256"}],"name":"ReserveAuctionUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"address","name":"sellerReferrer","type":"address"},{"indexed":false,"internalType":"uint256","name":"sellerReferrerFee","type":"uint256"}],"name":"SellerReferralPaid","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"worldOrExhibitionId","type":"uint256"},{"indexed":false,"internalType":"address[]","name":"sellers","type":"address[]"}],"name":"SellersAddedToExhibition","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"uint256","name":"saleStartsAt","type":"uint256"}],"name":"SetSaleStartsAt","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"WithdrawalToFETH","type":"event"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"offerFrom","type":"address"},{"internalType":"uint256","name":"minAmount","type":"uint256"}],"name":"acceptOffer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"worldOrExhibitionId","type":"uint256"},{"internalType":"address[]","name":"sellers","type":"address[]"}],"name":"addSellersToExhibition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"maxPrice","type":"uint256"},{"internalType":"address payable","name":"referrer","type":"address"}],"name":"buyV2","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"cancelBuyPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"auctionId","type":"uint256"}],"name":"cancelReserveAuction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint16","name":"takeRateInBasisPoints","type":"uint16"},{"internalType":"address[]","name":"sellers","type":"address[]"}],"name":"createExhibition","outputs":[{"internalType":"uint256","name":"worldOrExhibitionId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"reservePrice","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"}],"name":"createReserveAuction","outputs":[{"internalType":"uint256","name":"auctionId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"worldOrExhibitionId","type":"uint256"},{"internalType":"uint256","name":"reservePrice","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"}],"name":"createReserveAuctionV3","outputs":[{"internalType":"uint256","name":"auctionId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"worldOrExhibitionId","type":"uint256"}],"name":"deleteExhibition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"auctionId","type":"uint256"}],"name":"finalizeReserveAuction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getApprovedRouterAddress","outputs":[{"internalType":"address","name":"router","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getBuyPrice","outputs":[{"internalType":"address","name":"seller","type":"address"},{"internalType":"uint256","name":"price","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"worldOrExhibitionId","type":"uint256"}],"name":"getExhibition","outputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"address payable","name":"worldPaymentAddress","type":"address"},{"internalType":"uint16","name":"takeRateInBasisPoints","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getExhibitionIdForNft","outputs":[{"internalType":"uint256","name":"worldOrExhibitionId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address payable","name":"seller","type":"address"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"address payable","name":"buyReferrer","type":"address"},{"internalType":"uint16","name":"sellerReferrerTakeRateInBasisPoints","type":"uint16"}],"name":"getFees","outputs":[{"internalType":"uint256","name":"protocolFeeAmount","type":"uint256"},{"internalType":"address payable[]","name":"creatorRecipients","type":"address[]"},{"internalType":"uint256[]","name":"creatorShares","type":"uint256[]"},{"internalType":"uint256","name":"sellerRev","type":"uint256"},{"internalType":"uint256","name":"buyReferrerFee","type":"uint256"},{"internalType":"uint256","name":"sellerReferrerFee","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"}],"name":"getFeesAndRecipients","outputs":[{"internalType":"uint256","name":"totalFees","type":"uint256"},{"internalType":"uint256","name":"creatorRev","type":"uint256"},{"internalType":"address payable[]","name":"creatorRecipients","type":"address[]"},{"internalType":"uint256[]","name":"creatorShares","type":"uint256[]"},{"internalType":"uint256","name":"sellerRev","type":"uint256"},{"internalType":"address payable","name":"seller","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getFethAddress","outputs":[{"internalType":"address","name":"fethAddress","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getFoundationTreasury","outputs":[{"internalType":"address payable","name":"treasuryAddress","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMarketUtilsAddress","outputs":[{"internalType":"address","name":"marketUtilsAddress","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"auctionId","type":"uint256"}],"name":"getMinBidAmount","outputs":[{"internalType":"uint256","name":"minimum","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getMinOfferAmount","outputs":[{"internalType":"uint256","name":"minimum","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getOffer","outputs":[{"internalType":"address","name":"buyer","type":"address"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getOfferReferrer","outputs":[{"internalType":"address payable","name":"referrer","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"auctionId","type":"uint256"}],"name":"getReserveAuction","outputs":[{"components":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address payable","name":"seller","type":"address"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"uint256","name":"extensionDuration","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"address payable","name":"bidder","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct ReserveAuction","name":"auction","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"auctionId","type":"uint256"}],"name":"getReserveAuctionBidReferrer","outputs":[{"internalType":"address payable","name":"referrer","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getReserveAuctionIdFor","outputs":[{"internalType":"uint256","name":"auctionId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getSaleStartsAt","outputs":[{"internalType":"uint256","name":"saleStartsAt","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getSellerOf","outputs":[{"internalType":"address payable","name":"seller","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getWorldsNftAddress","outputs":[{"internalType":"address","name":"worldsNft","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"worldOrExhibitionId","type":"uint256"},{"internalType":"address","name":"seller","type":"address"}],"name":"isAllowedSellerForExhibition","outputs":[{"internalType":"bool","name":"allowedSeller","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address payable","name":"referrer","type":"address"}],"name":"makeOfferV2","outputs":[{"internalType":"uint256","name":"expiration","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"auctionId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address payable","name":"referrer","type":"address"}],"name":"placeBidV2","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"}],"name":"setBuyPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"worldOrExhibitionId","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"}],"name":"setBuyPriceV2","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"saleStartsAt","type":"uint256"}],"name":"setSaleStartsAt","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"worldOrExhibitionId","type":"uint256"}],"name":"updateExhibitionNft","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"auctionId","type":"uint256"},{"internalType":"uint256","name":"reservePrice","type":"uint256"}],"name":"updateReserveAuction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"auctionId","type":"uint256"},{"internalType":"uint256","name":"worldOrExhibitionId","type":"uint256"},{"internalType":"uint256","name":"reservePrice","type":"uint256"}],"name":"updateReserveAuctionV2","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"worldsInitializeMigration","outputs":[{"internalType":"uint256","name":"lastExhibitionIdCreated","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"exhibitionId","type":"uint256"},{"internalType":"address","name":"curator","type":"address"}],"name":"worldsMigrateExhibition","outputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint16","name":"takeRateInBasisPoints","type":"uint16"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"exhibitionId","type":"uint256"},{"components":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"nftTokenId","type":"uint256"}],"internalType":"struct INFTMarketExhibitionMigration.NFTListing[]","name":"nftListings","type":"tuple[]"}],"name":"worldsMigrateExhibitionListings","outputs":[{"internalType":"address[]","name":"nftSellers","type":"address[]"}],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]

61018060405234801562000011575f80fd5b506040516200647338038062006473833981016040819052620000349162000252565b83816101f4845f89888c6001600160a01b0381163b620000675760405163028bba2560e61b815260040160405180910390fd5b6001600160a01b0390811660805281163b62000096576040516376ee64e560e11b815260040160405180910390fd5b6001600160a01b0390811660a05281163b620000c55760405163de58082760e01b815260040160405180910390fd5b6001600160a01b031660c052620000e06064612710620002ea565b620000ee90612710620002ea565b8361ffff1610806200013c57506200010b6113886127106200030a565b6200011b6103e8612710620002ea565b6200012990612710620002ea565b620001399061ffff861662000326565b10155b156200015b57604051630567777b60e41b815260040160405180910390fd5b6001600160a01b0382163b620001845760405163140d37eb60e21b815260040160405180910390fd5b151560e05261ffff91909116610100526001600160a01b039081166101205281163b620001c457604051631341312360e01b815260040160405180910390fd5b6001600160a01b03166101405262093a80811115620002005760405163ccd285bd60e01b815262093a8060048201526024015b60405180910390fd5b610384811015620002295760405163494c8c0760e11b81526103846004820152602401620001f7565b61016052506200033c945050505050565b6001600160a01b03811681146200024f575f80fd5b50565b5f805f805f8060c0878903121562000268575f80fd5b865162000275816200023a565b602088015190965062000288816200023a565b604088015160608901519196509450620002a2816200023a565b6080880151909350620002b5816200023a565b60a0880151909250620002c8816200023a565b809150509295509295509295565b634e487b7160e01b5f52601160045260245ffd5b5f826200030557634e487b7160e01b5f52601260045260245ffd5b500490565b81810381811115620003205762000320620002d6565b92915050565b80820180821115620003205762000320620002d6565b60805160a05160c05160e05161010051610120516101405161016051615ff46200047f5f395f818161250601528181612730015281816128980152612aea01525f818161035a01528181610f3201528181611038015281816110e50152818161116f015281816115f3015281816130ea015281816131d101528181613489015281816134c5015281816135880152818161363c01528181613fcb015281816140bf015281816141490152818161425e01526143f901525f818161030f01526123ca01525f61232001525f61234901525f81816102600152818161074c01528181611bbf01528181611ccd01528181611d96015281816129b301528181612e5601528181613d0501528181613dd901528181613e780152614b6401525f81816106180152613f4701525f818161096c01528181613d3e01526151610152615ff45ff3fe608060405260043610610250575f3560e01c8063614b151c11610143578063895633ba116100c2578063b01ef60811610087578063e2cf968b11610062578063e2cf968b14610920578063e5d1e7231461093f578063f7a2da231461095e575f80fd5b8063b01ef608146108db578063b3a4074e146108ee578063b6aff8c11461090d575f80fd5b8063895633ba1461073e5780639e64ba6c146107705780639e79b41f146107d8578063ac71045e14610866578063af1e1de3146108aa575f80fd5b8063798bac8d11610108578063798bac8d1461066f5780637b3a58841461068e5780637e043795146106ad5780638098531d146106ee57806387a4fdcb1461070d575f80fd5b8063614b151c146105d85780636512ed2d146105eb5780636a90a8271461060a57806370f0c5d91461063c5780637430e0c614610650575f80fd5b80633c58e54d116101cf5780634635256e116101945780634635256e1461051e57806347e357401461055c5780634eb123171461057b5780634fca06c61461059a578063574c8229146105b9575f80fd5b80633c58e54d146104595780633e9e8bf814610487578063401cf150146104b4578063442559a2146104e0578063445738d8146104ff575f80fd5b8063262907c511610215578063262907c51461037e57806329e0e1601461039d5780632ab2b52b146103bc5780632e06db961461040b5780632fbbb25a1461043a575f80fd5b806303ec16d7146102a457806321506fff146102c357806321561935146102e257806321dbd9aa14610301578063228b13181461034c575f80fd5b366102a057336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461029e5760405163551c9c2760e11b815260040160405180910390fd5b005b5f80fd5b3480156102af575f80fd5b5061029e6102be3660046152fc565b610990565b3480156102ce575f80fd5b5061029e6102dd36600461531c565b610a83565b3480156102ed575f80fd5b5061029e6102fc366004615347565b610c78565b34801561030c575f80fd5b507f00000000000000000000000000000000000000000000000000000000000000005b6040516001600160a01b0390911681526020015b60405180910390f35b348015610357575f80fd5b507f000000000000000000000000000000000000000000000000000000000000000061032f565b348015610389575f80fd5b5061032f610398366004615347565b610d8c565b3480156103a8575f80fd5b5061029e6103b7366004615371565b610e09565b3480156103c7575f80fd5b506103fd6103d6366004615347565b6001600160a01b039091165f90815261177660209081526040808320938352929052205490565b604051908152602001610343565b348015610416575f80fd5b5061042a6104253660046153b6565b610f0a565b6040519015158152602001610343565b348015610445575f80fd5b5061029e6104543660046153e4565b61100d565b348015610464575f80fd5b5061047861047336600461531c565b611032565b60405161034393929190615469565b348015610492575f80fd5b506104a66104a13660046153b6565b6112d3565b60405161034392919061549e565b3480156104bf575f80fd5b506104d36104ce3660046154c3565b611471565b604051610343919061553b565b3480156104eb575f80fd5b506103fd6104fa366004615347565b6115f0565b34801561050a575f80fd5b506103fd6105193660046155d7565b6116d5565b348015610529575f80fd5b5061053d610538366004615347565b6118cc565b604080516001600160a01b039093168352602083019190915201610343565b348015610567575f80fd5b506103fd61057636600461531c565b61193f565b348015610586575f80fd5b5061029e61059536600461567e565b611976565b3480156105a5575f80fd5b5061032f6105b4366004615347565b611ab0565b3480156105c4575f80fd5b5061029e6105d336600461567e565b611abb565b6103fd6105e63660046156b0565b611b1b565b3480156105f6575f80fd5b5061029e6106053660046156f7565b611e55565b348015610615575f80fd5b507f000000000000000000000000000000000000000000000000000000000000000061032f565b348015610647575f80fd5b506103fd612025565b34801561065b575f80fd5b5061029e61066a36600461531c565b61207b565b34801561067a575f80fd5b5061029e61068936600461567e565b6120c9565b348015610699575f80fd5b5061029e6106a836600461531c565b612275565b3480156106b8575f80fd5b506103fd6106c7366004615347565b6001600160a01b039091165f90815261129360209081526040808320938352929052205490565b3480156106f9575f80fd5b506103fd61070836600461573f565b6122e5565b348015610718575f80fd5b5061072c61072736600461577f565b612307565b6040516103439695949392919061585d565b348015610749575f80fd5b507f000000000000000000000000000000000000000000000000000000000000000061032f565b34801561077b575f80fd5b5061032f61078a36600461531c565b5f9081526117776020526040908190206006810154600290910154600160a01b9182900467ffffffffffffffff1691900490911b73ffffffffffffffffffffffff0000000000000000161790565b3480156107e3575f80fd5b506107f76107f236600461531c565b61247d565b60405161034391905f610100820190506001600160a01b0380845116835260208401516020840152806040850151166040840152606084015160608401526080840151608084015260a084015160a08401528060c08501511660c08401525060e083015160e083015292915050565b348015610871575f80fd5b50610885610880366004615347565b61258e565b604080516001600160a01b039094168452602084019290925290820152606001610343565b3480156108b5575f80fd5b506108c96108c436600461567e565b61260a565b604051610343969594939291906158a6565b61029e6108e93660046156b0565b612679565b3480156108f9575f80fd5b506103fd6109083660046153e4565b612715565b61029e61091b3660046158f9565b612945565b34801561092b575f80fd5b5061029e61093a36600461592f565b612c5d565b34801561094a575f80fd5b506103fd610959366004615347565b612c99565b348015610969575f80fd5b507f000000000000000000000000000000000000000000000000000000000000000061032f565b61099981612cf7565b5f82815261177760205260408120906109b0612d17565b60028301549091506001600160a01b038083169116146109f9576002820154604051632600954360e21b81526001600160a01b0390911660048201526024015b60405180910390fd5b600582015415610a1c57604051635aea7c4760e01b815260040160405180910390fd5b82826007015403610a4057604051634b669ac760e01b815260040160405180910390fd5b6007820183905560405183815284907f0c0f2662914f0cd1e952db2aa425901cb00e7c1f507687d22cb04e836d55d9c7906020015b60405180910390a250505050565b610a8b612d25565b5f8181526117776020908152604080832081516101608101835281546001600160a01b039081168252600183015494820194909452600282015480851693820193909352600160a01b928390046001600160601b0316606082015260038201546080820152600482015460a0820152600582015460c0820152600682015493841660e082015291830467ffffffffffffffff16610100830152600160e01b90920463ffffffff1661012082015260079091015461014082015290610b4d612d17565b9050806001600160a01b031682604001516001600160a01b031614610b96576040808301519051632600954360e21b81526001600160a01b0390911660048201526024016109f0565b60c082015115610bb957604051635aea7c4760e01b815260040160405180910390fd5b81516001600160a01b03165f9081526117766020908152604080832082860180518552908352818420849055868452611777909252822080546001600160a01b03191681556001810183905560028101839055600381018390556004810183905560058101839055600681018390556007019190915582519051610c3e919083612d80565b60405183907f14b9c40404d5b41deb481f9a40b8aeb2bf4b47679b38cf757075a66ed510f7f1905f90a25050610c756001610b8755565b50565b610c80612d25565b6001600160a01b038083165f908152611f4e6020908152604080832085845290915281205490911690610cb1612d17565b90506001600160a01b038216610cda5760405163604fc74160e11b815260040160405180910390fd5b806001600160a01b0316826001600160a01b031614610d1757604051637824da0d60e11b81526001600160a01b03831660048201526024016109f0565b6001600160a01b0384165f908152611f4e60209081526040808320868452909152812055610d46848484612d80565b60405183906001600160a01b038616907f70c7877531c04c7d9caa8a7eca127384f04e8a6ee58b63f778ce5401d8bcae41905f90a35050610d886001610b8755565b5050565b6001600160a01b0382165f908152612337602090815260408083208484529091528120805463ffffffff16421115610dc7575f915050610e03565b80546001820154600160801b90910460201b73ffffffffffffffffffffffffffffffff0000000016600160a01b90910463ffffffff16175b9150505b92915050565b610e11612d25565b6001600160a01b0384165f908152612337602090815260408083208684529091529020805463ffffffff16421115610e67578054604051638c9e57cf60e01b815263ffffffff90911660048201526024016109f0565b8054600160201b90046001600160601b0316821115610ead578054604051632423736160e01b8152600160201b9091046001600160601b031660048201526024016109f0565b60018101546001600160a01b03848116911614610eee57600181015460405163a7d95dc360e01b81526001600160a01b0390911660048201526024016109f0565b610ef88585612d8b565b50610f046001610b8755565b50505050565b6040516332ac730f60e01b8152600481018390526001600160a01b0382811660248301525f917f0000000000000000000000000000000000000000000000000000000000000000909116906332ac730f90604401602060405180830381865afa158015610f79573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f9d9190615958565b905080610e03575f83815261119a60205260409020546001600160a01b03168015611006575f84815261119b602090815260408083206001600160a01b038716845290915290205460ff1680610dff5750806001600160a01b0316836001600160a01b03161491505b5092915050565b6110188484836120c9565b61102b611023612d17565b8585856130b8565b5050505050565b60605f807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663b4a0f56f856040518263ffffffff1660e01b815260040161108491815260200190565b5f60405180830381865afa15801561109e573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526110c591908101906159bc565b925082515f146111e75760405163724ef50160e01b8152600481018590527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063724ef50190602401602060405180830381865afa158015611132573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111569190615a42565b604051638abf92c960e01b8152600481018690529092507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690638abf92c990602401602060405180830381865afa1580156111bc573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111e09190615a5d565b90506112cc565b5f84815261119a60209081526040808320815160608101835281546001600160a01b0381168252600160a01b900461ffff1693810193909352600181018054919284019161123490615a78565b80601f016020809104026020016040519081016040528092919081815260200182805461126090615a78565b80156112ab5780601f10611282576101008083540402835291602001916112ab565b820191905f5260205f20905b81548152906001019060200180831161128e57829003601f168201915b505050505081525050905080604001519350805f0151925080602001519150505b9193909250565b60605f6112de61347e565b5f84815261119a60205260409020546001600160a01b03848116911614611369575f84815261119a60205260409020546001600160a01b03166113345760405163031dea5760e21b815260040160405180910390fd5b5f84815261119a60205260409081902054905163131e7a7360e11b81526001600160a01b0390911660048201526024016109f0565b5f84815261119a60205260409020600101805461138590615a78565b80601f01602080910402602001604051908101604052809291908181526020018280546113b190615a78565b80156113fc5780601f106113d3576101008083540402835291602001916113fc565b820191905f5260205f20905b8154815290600101906020018083116113df57829003601f168201915b5050505f87815261119a6020526040812080546001600160b01b031981168255949650600160a01b90940461ffff169450915061143e905060018301826152b2565b505060405184907ff1e9dfc0a0390550f6542ae820ee8fb346d6893de8f624a3e07087f15776b585905f90a29250929050565b606061147b61347e565b8167ffffffffffffffff81111561149457611494615977565b6040519080825280602002602001820160405280156114bd578160200160208202803683370190505b5090505f5b828110156115e8575f8484838181106114dd576114dd615aaa565b6114f39260206040909202019081019150615abe565b90505f85858481811061150857611508615aaa565b6001600160a01b0385165f90815261119c60209081526040808320938102959095018101358083529290529290922054919250508714611590576001600160a01b0382165f81815261119c602090815260408083208584529091529081902054905163285df36960e21b815260048101929092526024820183905260448201526064016109f0565b61159a82826134f4565b8484815181106115ac576115ac615aaa565b6001600160a01b0392831660209182029290920181019190915292165f90815261119c83526040808220928252919092528120556001016114c2565b509392505050565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316638f40f378848461162c87876134ff565b60405160e085901b6001600160e01b03191681526001600160a01b039384166004820152602481019290925290911660448201526064016040805180830381865afa15801561167d573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116a19190615ad9565b5090505f819003610e0357506001600160a01b03919091165f90815261119c60209081526040808320938352929052205490565b5f806116df612d17565b61119954909150600160201b900460ff161561173c57611735858289898080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061358192505050565b9150611854565b6113888561ffff16111561176357604051632b7b866160e01b815260040160405180910390fd5b61119980545f906117799063ffffffff16615b10565b91906101000a81548163ffffffff021916908363ffffffff160217905563ffffffff1691506040518060600160405280826001600160a01b031681526020018661ffff16815260200188888080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92018290525093909452505084815261119a6020908152604091829020845181549286015161ffff16600160a01b026001600160b01b03199093166001600160a01b03909116179190911781559083015190915060018201906118509082615b76565b5050505b806001600160a01b0316827f9eee3ce0e6f7eeabd69ecf363898e9f490dbfda9ad953e1019a2c6aeceb4a7ef89898960405161189293929190615c32565b60405180910390a361119954600160201b900460ff16156118b7576118b7828261361d565b6118c2828585611e55565b5095945050505050565b6001600160a01b038083165f908152611f4e60209081526040808320858452909152812054909116908161190257505f19611938565b506001600160a01b0383165f908152611f4e60209081526040808320858452909152902054600160a01b90046001600160601b03165b9250929050565b5f8181526117776020526040812060058101548203611962576007015492915050565b61196f8160070154613691565b9392505050565b6001600160a01b0383165f908152611293602090815260408083208584529091529020548190036119ba5760405163e5c3f26360e01b815260040160405180910390fd5b6119c483836136b2565b8015611a30576119d381421190565b156119f15760405163dbaca9d160e01b815260040160405180910390fd5b6119ff6303c2670042615c6d565b811115611a3057611a146303c2670042615c6d565b60405163857c0df160e01b81526004016109f091815260200190565b6001600160a01b0383165f908152611293602090815260408083208584529091529020819055611a5e612d17565b6001600160a01b031682846001600160a01b03167fc8a67fb17ad40fabf835a5c96e0438644464257af6f325f44f1615aabc0e41ba84604051611aa391815260200190565b60405180910390a4505050565b5f61196f83836134f4565b611ac583836136b2565b5f611ad084846134f4565b9050611ade818585856130b8565b610f0457815f03611b0257604051631cc0fcd760e31b815260040160405180910390fd5b60405163f000942d60e01b815260040160405180910390fd5b5f611b278585856136d9565b15611b3357505f611e4d565b611b3d8585613744565b15611b5b576040516383a483f560e01b815260040160405180910390fd5b6001600160a01b0385165f90815261233760209081526040808320878452909152812090611b87612d17565b825490915063ffffffff16421115611c3357604051634ec58ed760e01b81526001600160a01b038281166004830152602482018790527f00000000000000000000000000000000000000000000000000000000000000001690634ec58ed790349060440160206040518083038185885af1158015611c07573d5f803e3d5ffd5b50505050506040513d601f19601f82011682018060405250810190611c2c9190615c80565b9250611d40565b81545f90611c5090600160201b90046001600160601b0316613691565b905080861015611c7657604051637205187360e11b8152600481018290526024016109f0565b60018301548354604051635fdec56160e01b81526001600160a01b03928316600482015263ffffffff82166024820152600160201b9091046001600160601b031660448201528382166064820152608481018890527f000000000000000000000000000000000000000000000000000000000000000090911690635fdec56190349060a40160206040518083038185885af1158015611d17573d5f803e3d5ffd5b50505050506040513d601f19601f82011682018060405250810190611d3c9190615c80565b9350505b6001820180546001600160a01b0319166001600160a01b0383811691909117909155825463ffffffff85166fffffffffffffffffffffffffffffffff1990911617600160201b6001600160601b038816021783557f0000000000000000000000000000000000000000000000000000000000000000811690851603611dc3575f93505b81546001600160801b03908116602086811c909216600160801b0217835560018301805463ffffffff60a01b1916600160a01b63ffffffff881602179055604080518781529182018590526001600160a01b03838116928992918b16917ece0a712e4e277ac7b34942865f0de7a5629dffe0539b70423ad5ff1ed6ab42910160405180910390a450505b949350505050565b5f819003611e75576040516274040b60e91b815260040160405180910390fd5b611ea58383835f818110611e8b57611e8b615aaa565b9050602002016020810190611ea09190615abe565b61378e565b15611ef35760015b81811015611eed57611ee584848484818110611ecb57611ecb615aaa565b9050602002016020810190611ee09190615abe565b61361d565b600101611ead565b50611fe6565b611efc836137df565b5f5b81811015611fe4575f838383818110611f1957611f19615aaa565b9050602002016020810190611f2e9190615abe565b5f86815261119b602090815260408083206001600160a01b038516845290915290205490915060ff1615611f755760405163199e223b60e21b815260040160405180910390fd5b611f7d612d17565b6001600160a01b0316816001600160a01b031603611fae576040516343e2197f60e01b815260040160405180910390fd5b5f85815261119b602090815260408083206001600160a01b03909416835292905220805460ff1916600190811790915501611efe565b505b827fd5a31bd2d34d303249ac7f54bfc7578390f90f5d39cb39813f67190fa36f5c178383604051612018929190615c97565b60405180910390a2505050565b5f61202e61347e565b61119954600160201b900460ff161561205a57604051635cb2b73360e01b815260040160405180910390fd5b50611199805464ff00000000198116600160201b1790915563ffffffff1690565b612083612d25565b5f818152611777602052604081206005015490036120b4576040516325b56c7d60e11b815260040160405180910390fd5b6120be815f613862565b610c756001610b8755565b6120d1612d25565b6120dc838383613abf565b612265576001600160601b03811115612108576040516335ec82cb60e01b815260040160405180910390fd5b6001600160a01b038381165f908152611f4e60209081526040808320868452909152902080549091811690600160a01b90046001600160601b03168314801561215957506001600160a01b03811615155b1561217757604051635b4a879b60e11b815260040160405180910390fd5b81546001600160a01b0316600160a01b6001600160601b038516021782555f61219e612d17565b90506001600160a01b0382166121d6576121b88686613b1f565b82546001600160a01b0319166001600160a01b038216178355612213565b806001600160a01b0316826001600160a01b031614612213576040516334bec8c760e11b81526001600160a01b03831660048201526024016109f0565b806001600160a01b031685876001600160a01b03167ffcc77ea8bdcce862f43b7fb00fe6b0eb90d6aeead27d3800d9257cf7a05f9d968760405161225991815260200190565b60405180910390a45050505b6122706001610b8755565b505050565b61227e81613b29565b6122b85761228b816137df565b5f81815261119a6020526040812080546001600160b01b0319168155906122b560018301826152b2565b50505b60405181907f2a9aeaf340ca0da469c1f7e3d513c0e6c9cd287016f29d257a4ef70e13dc441c905f90a250565b5f6122f286868585612715565b90506118c26122ff612d17565b8787876130b8565b5f6060805f805f806040518061012001604052806123427f000000000000000000000000000000000000000000000000000000000000000090565b81526020017f0000000000000000000000000000000000000000000000000000000000000000151581526020018e6001600160a01b031681526020018d81526020018b81526020018c6001600160a01b031681526020018a6001600160a01b031681526020018961ffff1681526020016123ba612d17565b6001600160a01b031681525090507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316630e13eac1826040518263ffffffff1660e01b81526004016124149190615ce4565b5f60405180830381865afa15801561242e573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526124559190810190615e0b565b809750819850829950839a50849b50859c505050505050505096509650965096509650969050565b6124d86040518061010001604052805f6001600160a01b031681526020015f81526020015f6001600160a01b031681526020015f81526020015f81526020015f81526020015f6001600160a01b031681526020015f81525090565b5f8281526117776020526040812060068101549091600160e01b90910463ffffffff169081900361252657507f00000000000000000000000000000000000000000000000000000000000000005b604080516101008101825283546001600160a01b03908116825260018501546020830152600285015481169282019290925260608101929092526103846080830152600583015460a083015260068301541660c082015260079091015460e082015292915050565b6001600160a01b0382165f90815261233760209081526040808320848452909152812080548291829163ffffffff164211156125d3575f805f93509350935050612603565b600181015490546001600160a01b03909116935063ffffffff81169250600160201b90046001600160601b031690505b9250925092565b5f806060805f8061261b89896134ff565b905061262b8989838a5f80612307565b50939950919650945092505f90505b835181101561266c5783818151811061265557612655615aaa565b60200260200101518601955080600101905061263a565b5093975093979195509350565b6001600160a01b0384165f908152611f4e6020908152604080832086845290915290208054600160a01b90046001600160601b03168310156126e25780546040516316b5016f60e01b8152600160a01b9091046001600160601b031660048201526024016109f0565b80546001600160a01b031661270a57604051633692386160e21b815260040160405180910390fd5b61102b858584613b6f565b5f61271e612d25565b61272783612cf7565b815f03612756577f000000000000000000000000000000000000000000000000000000000000000091506127a6565b62093a8082111561277f5760405163ccd285bd60e01b815262093a8060048201526024016109f0565b6103848210156127a65760405163494c8c0760e11b815261038460048201526024016109f0565b6127ae613cc7565b90506127ba8585613b1f565b6001600160a01b0385165f9081526117766020908152604080832087845290915290205415612825576001600160a01b0385165f9081526117766020908152604080832087845290915290819020549051637618a00360e01b815260048101919091526024016109f0565b5f61282e612d17565b6001600160a01b038781165f818152611776602090815260408083208b84528252808320889055878352611777909152902080546001600160a01b0319908116909217815560018101899055600281018054909216928416929092179055600781018690559091507f000000000000000000000000000000000000000000000000000000000000000084146128dd576006810180546001600160e01b0316600160e01b63ffffffff8716021790555b6040805185815261038460208201529081018690526060810184905286906001600160a01b03808a1691908516907f1062dd3b35f12b4064331244d00f40c1d4831965e4285654157a2409c6217cff9060800160405180910390a45050611e4d6001610b8755565b61294d612d25565b5f8381526117776020526040812060078101549091036129805760405163125197d160e01b815260040160405180910390fd5b348310156129a15760405163e2bbc1e360e01b815260040160405180910390fd5b60058101545f6129af612d17565b90507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316846001600160a01b0316036129ee575f93505b6001600160a01b038416151580612a0457508115155b15612a5e576002830180546001600160a01b0316604086901c6001600160601b0316600160a01b9081029190911790915560068401805467ffffffffffffffff60a01b191667ffffffffffffffff87169092029190911790555b815f03612b1a578260070154851015612a92578260070154604051630c79bdc760e21b81526004016109f091815260200190565b82546001840154612aac916001600160a01b031690613ced565b600783018590556006830180546001600160a01b0319166001600160a01b0383161790819055600160e01b900463ffffffff165f819003612b0a57507f00000000000000000000000000000000000000000000000000000000000000005b4201600584018190559150612bff565b612b2382421190565b15612b4457604051633feeb88d60e01b8152600481018390526024016109f0565b60068301546001600160a01b03808316911603612b7457604051631c280aed60e31b815260040160405180910390fd5b5f612b828460070154613691565b905080861015612ba85760405163cd698a1960e01b8152600481018290526024016109f0565b50600783018054600685018054928890556001600160a01b038481166001600160a01b0319851617909155909116426103840180851015612bee57600586018190559350835b50612bfc8183614e20613cf7565b50505b612c09855f613e6e565b60408051868152602081018490526001600160a01b0383169188917f26ea3ebbda62eb1baef13e1c237dddd956c87f80b2801f2616d806d52557b121910160405180910390a35050506122706001610b8755565b612c678382610990565b5f8381526117776020526040902061102b612c80612d17565b825460018401546001600160a01b0390911690866130b8565b6001600160a01b0382165f908152612337602090815260408083208484529091528120805463ffffffff164211612ced578054612ce590600160201b90046001600160601b0316613691565b915050610e03565b5060019392505050565b805f03610c7557604051631d4b87f360e11b815260040160405180910390fd5b5f612d20613f3c565b905090565b6002610b875403612d785760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c0060448201526064016109f0565b6002610b8755565b612270838383613f7d565b6001600160a01b038281165f90815261233760209081526040808320858452808352818420825160a081018452815463ffffffff8082168352600160201b82046001600160601b03908116848901908152600160801b9093046001600160801b031684880152600185018054808c1660608701908152600160a01b8204851660808801528d8c5297909952989094556001600160c01b031990961690965591518251955193516313723ecf60e21b8152908716600482015294909316602485015291166044830152917f00000000000000000000000000000000000000000000000000000000000000001690634dc8fb3c906064015f604051808303815f87803b158015612e97575f80fd5b505af1158015612ea9573d5f803e3d5ffd5b505050505f612eb6612d17565b90505f80612ed8838787876060015188602001516001600160601b0316613f88565b6040516331a9108f60e11b81526004810188905291935091505f906001600160a01b03881690636352211e90602401602060405180830381865afa158015612f22573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612f469190615a42565b9050306001600160a01b03821603612f6d57612f68878787606001518761420e565b612fd9565b60608501516040516323b872dd60e01b81526001600160a01b038681166004830152918216602482015260448101889052908816906323b872dd906064015f604051808303815f87803b158015612fc2575f80fd5b505af1158015612fd4573d5f803e3d5ffd5b505050505b5f805f61302f8a8a898b602001516001600160601b03166130288d604001518e6080015173ffffffffffffffffffffffffffffffff0000000060209290921b9190911663ffffffff9091161790565b8b8b61421a565b92509250925087606001516001600160a01b0316898b6001600160a01b03167f1cb8adb37d6d35e94cd0695ca39895b84371864713f5ca7eada52af9ff23744b8a8787876040516130a494939291906001600160a01b0394909416845260208401929092526040830152606082015260800190565b60405180910390a450505050505050505050565b6040516311e81e6f60e31b81526001600160a01b0384811660048301526024820184905285811660448301525f9182917f00000000000000000000000000000000000000000000000000000000000000001690638f40f378906064016040805180830381865afa15801561312e573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906131529190615ad9565b509050825f036131b05780156131975761316d86868661423f565b8084866001600160a01b03165f80516020615f9f83398151915260405160405180910390a4600191505b6131a185856142bc565b806131a95750815b9150613475565b8281036131c9576131c185856142bc565b915050611e4d565b6132618386867f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316638abf92c9886040518263ffffffff1660e01b815260040161321d91815260200190565b602060405180830381865afa158015613238573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061325c9190615a5d565b61432f565b156132de578015613292578084866001600160a01b03165f80516020615f9f83398151915260405160405180910390a45b61329c85856142bc565b508284866001600160a01b03167fb17e0c916df75a12480835f00b3927cb871bbe00bacf819f81a1d92f9ff7f38d60405160405180910390a460019150613475565b8015613319576132ef86868661423f565b8084866001600160a01b03165f80516020615f9f83398151915260405160405180910390a4600191505b6001600160a01b0385165f90815261119c6020908152604080832087845290915290205483810361334b575050611e4d565b5f84815261119a60205260409020546001600160a01b0316806133815760405163031dea5760e21b815260040160405180910390fd5b5f85815261119b602090815260408083206001600160a01b038c16845290915290205460ff161580156133c65750876001600160a01b0316816001600160a01b031614155b156133e457604051631ba4e8d560e21b815260040160405180910390fd5b6001600160a01b0387165f90815261119c6020908152604080832089845290915290208590558115613436578186886001600160a01b03165f80516020615f9f83398151915260405160405180910390a45b8486886001600160a01b03167fb17e0c916df75a12480835f00b3927cb871bbe00bacf819f81a1d92f9ff7f38d60405160405180910390a46001935050505b50949350505050565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146134f257604051637383019160e11b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001660048201526024016109f0565b565b5f61196f8383614391565b5f61350a83836134f4565b90506001600160a01b038116610e03576040516331a9108f60e11b8152600481018390526001600160a01b03841690636352211e90602401602060405180830381865afa15801561355d573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061196f9190615a42565b5f806135fe7f00000000000000000000000000000000000000000000000000000000000000008686866040516024016135bc93929190615eed565b60408051601f198184030181529190526020810180516001600160e01b031663d5adf48960e01b1790526135ee612d17565b6001600160a01b031691906143c6565b9050808060200190518101906136149190615c80565b95945050505050565b604051602481018390526001600160a01b0382166044820152612270907f00000000000000000000000000000000000000000000000000000000000000009060640160408051601f198184030181529190526020810180516001600160e01b031663081de21760e31b1790526135ee612d17565b5f600a82048082036136a85761196f836001615c6d565b61196f8382615c6d565b6136bc82826143e6565b610d88576040516338e4293b60e21b815260040160405180910390fd5b6001600160a01b038084165f908152611f4e60209081526040808320868452909152812080549192909116158061372057508054600160a01b90046001600160601b031683105b1561372e575f91505061196f565b61373985855f613b6f565b506001949350505050565b6001600160a01b0382165f908152611776602090815260408083208484529091528120548015801590610dff57505f90815261177760205260409020600501544211159392505050565b604051602481018390526001600160a01b03821660448201525f9061196f9060640160408051601f198184030181529190526020810180516001600160e01b031663081de21760e31b1790526143f1565b5f81815261119a60205260409020546001600160a01b03166137ff612d17565b6001600160a01b0316816001600160a01b031614610d88576001600160a01b03811661383e5760405163031dea5760e21b815260040160405180910390fd5b60405163b39cb29b60e01b81526001600160a01b03821660048201526024016109f0565b5f828152611777602090815260409182902082516101608101845281546001600160a01b039081168252600183015493820193909352600282015480841694820194909452600160a01b938490046001600160601b0316606082015260038201546080820152600482015460a0820152600582015460c08201819052600683015493841660e083015293830467ffffffffffffffff16610100820152600160e01b90920463ffffffff1661012083015260070154610140820152904211613944578060c001516040516301d00bfb60e51b81526004016109f091815260200190565b5f806139678360400151845f015185602001518660e00151876101400151613f88565b84516001600160a01b03165f908152611776602090815260408083208289015184528252808320839055898352611777909152812080546001600160a01b0319168155600181018290556002810182905560038101829055600481018290556005810182905560068101829055600701559092509050836139f9576139f9835f015184602001518560e001515f61447e565b5f805f613a46865f0151876020015188604001518961014001518a610100015167ffffffffffffffff1660408c606001516001600160601b03166001600160a01b0316901b178a8a61421a565b9250925092508560e001516001600160a01b031686604001516001600160a01b0316897f2edb0e99c6ac35be6731dab554c1d1fa1b7beb675090dbb09fb14e615aca1c4a868686604051613aad939291909283526020830191909152604082015260600190565b60405180910390a45050505050505050565b6001600160a01b0383165f908152612337602090815260408083208584529091528120805463ffffffff16421180613b0757508054600160201b90046001600160601b031683115b15613b15575f91505061196f565b6137398585612d8b565b610d88828261450f565b5f610e0382604051602401613b4091815260200190565b60408051601f198184030181529190526020810180516001600160e01b0316630852cd8d60e31b1790526143f1565b613b77612d25565b613b818383614588565b6001600160a01b038381165f908152611f4e60209081526040808320868452808352818420825180840190935280549586168352600160a01b9095046001600160601b0316828401528684529091529155613bdc84846145cc565b613bf481602001516001600160601b03166001613e6e565b5f613bfd612d17565b90505f80613c1e845f015188888688602001516001600160601b0316613f88565b91509150613c2e8787855f61420e565b5f805f613c508a8a895f01518a602001516001600160601b03168c8a8a61421a565b8951604080516001600160a01b038c8116825260208201879052918101859052606081018490529497509295509093508116918b918d16907fd28c0a7dd63bc853a4e36306655da9f8c0b29ff9d0605bb976ae420e46a999309060800160405180910390a4505050505050506122706001610b8755565b5f61138d545f03613cdd5761138d805460010190555b5061138d80546001810190915590565b610d888282614618565b815f03613d0357505050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316836001600160a01b031603613d60577f000000000000000000000000000000000000000000000000000000000000000092505b5f836001600160a01b03168383906040515f60405180830381858888f193505050503d805f8114613dac576040519150601f19603f3d011682016040523d82523d5f602084013e613db1565b606091505b5050905080610f045760405163aa67c91960e01b81526001600160a01b0385811660048301527f0000000000000000000000000000000000000000000000000000000000000000169063aa67c9199085906024015f604051808303818588803b158015613e1c575f80fd5b505af1158015613e2e573d5f803e3d5ffd5b5050505050836001600160a01b03167fa2201512569adb2d513531dfd69b66df50bd5cffb8c1bbe65a4611f9e1eadbd184604051610a7591815260200190565b34821115613f0e577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663452f2b8f613ead612d17565b6040516001600160e01b031960e084901b1681526001600160a01b03909116600482015234850360248201526044015b5f604051808303815f87803b158015613ef4575f80fd5b505af1158015613f06573d5f803e3d5ffd5b505050505050565b808015613f1a57503482105b15610d8857610d88823403613f2d612d17565b6001600160a01b03169061462c565b336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168103613f7a575060131936013560601c90565b90565b612270838383614741565b604051633221782160e01b81526001600160a01b0386811660048301528581166024830152604482018590528381166064830152608482018390525f91829182917f00000000000000000000000000000000000000000000000000000000000000009091169063322178219060a4016060604051808303815f875af1158015614013573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906140379190615f18565b90945092509050801561405f5761404f88888861423f565b61405987876142bc565b50614203565b506001600160a01b0386165f90815261119c602090815260408083208884529091529020548015614203575f81815261119a60205260409020546001600160a01b03169250826141c15760405163724ef50160e01b8152600481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063724ef50190602401602060405180830381865afa15801561410c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906141309190615a42565b604051638abf92c960e01b8152600481018390529093507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690638abf92c990602401602060405180830381865afa158015614196573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906141ba9190615a5d565b91506141de565b5f81815261119a6020526040902054600160a01b900461ffff1691505b6001600160a01b0387165f90815261119c602090815260408083208984529091528120555b509550959350505050565b610f0484848484614777565b5f805f61422c8a8a8a8a8a8a8a6147f9565b919c909b50909950975050505050505050565b6040516001600160a01b038316602482015260448101829052610f04907f00000000000000000000000000000000000000000000000000000000000000009060640160408051601f198184030181529190526020810180516001600160e01b03166349928fdd60e11b1790526001600160a01b03861691906143c6565b6001600160a01b0382165f90815261119c602090815260408083208484529091528120548015611006576001600160a01b0384165f81815261119c60209081526040808320878452909152808220829055518392869290915f80516020615f9f8339815191529190a45060019392505050565b604051602481018590526001600160a01b03841660448201526064810183905261ffff821660848201525f906136149060a40160408051601f198184030181529190526020810180516001600160e01b031663031d3a6b60e51b1790526143f1565b6001600160a01b038083165f908152611f4e602090815260408083208584529091529020541680610e035761196f838361480b565b60605f6143d485858561484a565b92509050806115e8576115e8826148cf565b5f61196f83836148f8565b5f60606144317f000000000000000000000000000000000000000000000000000000000000000084614421612d17565b6001600160a01b0316919061484a565b90925090508115801561446a5750805160208201207f931c7fca1aa4d9db6fb474c5724471bc286fbb3d892ec4f390fa55323a23dc0c14155b1561447857614478816148cf565b50919050565b6001600160a01b038116156144a6576040516357a016b360e01b815260040160405180910390fd5b6040516323b872dd60e01b81523060048201526001600160a01b038381166024830152604482018590528516906323b872dd906064015f604051808303815f87803b1580156144f3575f80fd5b505af1158015614505573d5f803e3d5ffd5b5050505050505050565b6001600160a01b038083165f908152611f4e60209081526040808320858452909152902054168061454457612270838361497d565b61454c612d17565b6001600160a01b0316816001600160a01b031614612270576040516332f3b03360e01b81526001600160a01b03821660048201526024016109f0565b6001600160a01b0382165f90815261129360209081526040808320848452909152902054421015610d8857604051634917db3f60e01b815260040160405180910390fd5b6001600160a01b0382165f9081526123376020908152604080832084845290915290206145f7612d17565b60018201546001600160a01b03918216911603612270576122708383614a69565b6146228282614a69565b610d888282614bf5565b8047101561467c5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e636500000060448201526064016109f0565b5f826001600160a01b0316826040515f6040518083038185875af1925050503d805f81146146c5576040519150601f19603f3d011682016040523d82523d5f602084013e6146ca565b606091505b50509050806122705760405162461bcd60e51b815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260448201527f6563697069656e74206d6179206861766520726576657274656400000000000060648201526084016109f0565b6001600160a01b038084165f908152611f4e602090815260408083208684529091529020541680610f0457610f04848484614c37565b6001600160a01b038085165f908152611f4e602090815260408083208784529091529020541680156147ed57816001600160a01b0316816001600160a01b0316146147e0576040516332f3b03360e01b81526001600160a01b03821660048201526024016109f0565b5f91506147ed8585614c6c565b61102b85858585614cbe565b5f805f61422c8a8a8a8a8a8a8a614e6f565b6001600160a01b038083165f9081526117766020908152604080832085845282528083205483526117779091529020600201541680610e03575f61196f565b5f6060836001600160a01b0316838660405160200161486a929190615f4d565b60408051601f198184030181529082905261488491615f83565b5f604051808303815f865af19150503d805f81146148bd576040519150601f19603f3d011682016040523d82523d5f602084013e6148c2565b606091505b5090969095509350505050565b8051156148df5780518082602001fd5b604051633cfe059f60e01b815260040160405180910390fd5b6001600160a01b038083165f908152611f4e6020908152604080832085845290915281205490911680156149735761492e612d17565b6001600160a01b0316816001600160a01b03161461496a5760405163c89fba3b60e01b81526001600160a01b03821660048201526024016109f0565b60019150611006565b610dff8484614e81565b6001600160a01b0382165f90815261177660209081526040808320848452909152812054908190036149b3576122708383614f35565b5f81815261177760205260408120906149ca612d17565b905081600501545f03614a1d5760028201546001600160a01b03828116911614614a18576002820154604051637322937760e11b81526001600160a01b0390911660048201526024016109f0565b61102b565b60068201546001600160a01b03828116911614614a5e576006820154604051637322937760e11b81526001600160a01b0390911660048201526024016109f0565b61102b836001613862565b6001600160a01b0382165f9081526123376020908152604080832084845290915290205463ffffffff164211610d88576001600160a01b038281165f90815261233760209081526040808320858452808352818420825160a081018452815463ffffffff8082168352600160201b82046001600160601b03908116848901908152600160801b9093046001600160801b031684880152600185018054808c1660608701908152600160a01b8204851660808801528d8c5297909952989094556001600160c01b0319909616909655915182519551935163345db49360e01b8152908716600482015294909316602485015291166044830152917f0000000000000000000000000000000000000000000000000000000000000000169063345db493906064015f604051808303815f87803b158015614ba5575f80fd5b505af1158015614bb7573d5f803e3d5ffd5b50506040518492506001600160a01b03861691507f30c264456cbd17f5f67d7534654161414f34c0e6cc1b7500e169b7a7aea4afc0905f90a3505050565b6001600160a01b038083165f908152611f4e602090815260408083208584529091529020805490911615614c2d57614c2d8383614c6c565b6122708383614f85565b6001600160a01b0383165f90815261177660209081526040808320858452909152812054900361227057612270838383614f8f565b6001600160a01b0382165f818152611f4e60209081526040808320858452909152808220829055518392917faa6271d89a385571e237d3e7254ccc7c09f68055e6e9b410ed08233a8b9a05cf91a35050565b6001600160a01b0384165f908152611776602090815260408083208684529091529020548015614e63575f818152611777602052604081206005810154909103614e12576001600160a01b03831615801590614d2a575060028101546001600160a01b03848116911614155b15614d59576002810154604051637322937760e11b81526001600160a01b0390911660048201526024016109f0565b6002808201546001600160a01b038881165f908152611776602090815260408083208b84528252808320839055878352611777909152812080546001600160a01b0319168155600181018290559384018190556003840181905560048401819055600584018190556006840181905560079093018390551690614de1908290899089906130b8565b5060405183907f5603897cc9b1e866f3f7395ffc6638776041f21c094d0b4e748ff44c407fa362905f90a250614e5e565b60068101546001600160a01b03848116911614614e53576006810154604051637322937760e11b81526001600160a01b0390911660048201526024016109f0565b614e5e826001613862565b5f9250505b61102b8585858561447e565b5f805f61422c8a8a8a8a8a8a8a614fa4565b6001600160a01b0382165f908152611776602090815260408083208484529091528120548015614f2f575f81815261177760205260409020614ec1612d17565b60028201546001600160a01b03908116911614614f0257600281015460405163c0221c7f60e01b81526001600160a01b0390911660048201526024016109f0565b600581015415614f255760405163a2a745e960e01b815260040160405180910390fd5b6001925050611006565b5f610dff565b816001600160a01b03166323b872dd614f4c612d17565b6040516001600160e01b031960e084901b1681526001600160a01b03909116600482015230602482015260448101849052606401613edd565b610d888282614fc0565b614f998383614fca565b612270838383615070565b5f805f614fb18a8a614fca565b61422c8a8a8a8a8a8a8a615088565b610d888282614588565b6001600160a01b0382165f9081526112936020908152604080832084845290915290205415610d88576001600160a01b0382165f9081526112936020908152604080832084845290915281205561501f612d17565b6001600160a01b031681836001600160a01b03167fc8a67fb17ad40fabf835a5c96e0438644464257af6f325f44f1615aabc0e41ba5f60405161506491815260200190565b60405180910390a45050565b61507c8184845f6130b8565b506122708383836152a6565b5f805f865f0361509f57505f915081905080615299565b6060805f806150b28e8e8e8e8e8d612307565b8451959c5091995092975090955090935091505f906001146150d657614e206150db565b620334505b90505f5b855181101561514e576151258682815181106150fd576150fd615aaa565b602002602001015186838151811061511757615117615aaa565b602002602001015184613cf7565b84818151811061513757615137615aaa565b6020026020010151880197508060010190506150df565b5061515c8d87614e20613cf7565b6151897f000000000000000000000000000000000000000000000000000000000000000089614e20613cf7565b82156152035761519c8b84614e20613cf7565b8d8f6001600160a01b03167f141b92fd9766c80ab120598ea2f6be9802470ec59b5446dd9bf46214ead8d08e8d865f6040516151f6939291906001600160a01b039390931683526020830191909152604082015260600190565b60405180910390a3968201965b6001600160a01b038a161561529357811561523a57855f03615228579581019561522d565b948101945b61523a8a83614e20613cf7565b8d8f6001600160a01b03167f27a4dd4ff659a9e6354fb079b2208365e5b83f55c22a4150eee2bca89501cb988c8560405161528a9291906001600160a01b03929092168252602082015260400190565b60405180910390a35b50505050505b9750975097945050505050565b6122708383835f61420e565b5080546152be90615a78565b5f825580601f106152cd575050565b601f0160209004905f5260205f2090810190610c7591905b808211156152f8575f81556001016152e5565b5090565b5f806040838503121561530d575f80fd5b50508035926020909101359150565b5f6020828403121561532c575f80fd5b5035919050565b6001600160a01b0381168114610c75575f80fd5b5f8060408385031215615358575f80fd5b823561536381615333565b946020939093013593505050565b5f805f8060808587031215615384575f80fd5b843561538f81615333565b93506020850135925060408501356153a681615333565b9396929550929360600135925050565b5f80604083850312156153c7575f80fd5b8235915060208301356153d981615333565b809150509250929050565b5f805f80608085870312156153f7575f80fd5b843561540281615333565b966020860135965060408601359560600135945092505050565b5f5b8381101561543657818101518382015260200161541e565b50505f910152565b5f815180845261545581602086016020860161541c565b601f01601f19169290920160200192915050565b606081525f61547b606083018661543e565b90506001600160a01b038416602083015261ffff83166040830152949350505050565b604081525f6154b0604083018561543e565b905061ffff831660208301529392505050565b5f805f604084860312156154d5575f80fd5b83359250602084013567ffffffffffffffff808211156154f3575f80fd5b818601915086601f830112615506575f80fd5b813581811115615514575f80fd5b8760208260061b8501011115615528575f80fd5b6020830194508093505050509250925092565b602080825282518282018190525f9190848201906040850190845b8181101561557b5783516001600160a01b031683529284019291840191600101615556565b50909695505050505050565b61ffff81168114610c75575f80fd5b5f8083601f8401126155a6575f80fd5b50813567ffffffffffffffff8111156155bd575f80fd5b6020830191508360208260051b8501011115611938575f80fd5b5f805f805f606086880312156155eb575f80fd5b853567ffffffffffffffff80821115615602575f80fd5b818801915088601f830112615615575f80fd5b813581811115615623575f80fd5b896020828501011115615634575f80fd5b602092830197509550908701359061564b82615587565b90935060408701359080821115615660575f80fd5b5061566d88828901615596565b969995985093965092949392505050565b5f805f60608486031215615690575f80fd5b833561569b81615333565b95602085013595506040909401359392505050565b5f805f80608085870312156156c3575f80fd5b84356156ce81615333565b9350602085013592506040850135915060608501356156ec81615333565b939692955090935050565b5f805f60408486031215615709575f80fd5b83359250602084013567ffffffffffffffff811115615726575f80fd5b61573286828701615596565b9497909650939450505050565b5f805f805f60a08688031215615753575f80fd5b853561575e81615333565b97602087013597506040870135966060810135965060800135945092505050565b5f805f805f8060c08789031215615794575f80fd5b863561579f81615333565b95506020870135945060408701356157b681615333565b93506060870135925060808701356157cd81615333565b915060a08701356157dd81615587565b809150509295509295509295565b5f815180845260208085019450602084015f5b838110156158235781516001600160a01b0316875295820195908201906001016157fe565b509495945050505050565b5f815180845260208085019450602084015f5b8381101561582357815187529582019590820190600101615841565b86815260c060208201525f61587560c08301886157eb565b8281036040840152615887818861582e565b60608401969096525050608081019290925260a0909101529392505050565b86815285602082015260c060408201525f6158c460c08301876157eb565b82810360608401526158d6818761582e565b9150508360808301526001600160a01b03831660a0830152979650505050505050565b5f805f6060848603121561590b575f80fd5b8335925060208401359150604084013561592481615333565b809150509250925092565b5f805f60608486031215615941575f80fd5b505081359360208301359350604090920135919050565b5f60208284031215615968575f80fd5b8151801515811461196f575f80fd5b634e487b7160e01b5f52604160045260245ffd5b604051601f8201601f1916810167ffffffffffffffff811182821017156159b4576159b4615977565b604052919050565b5f602082840312156159cc575f80fd5b815167ffffffffffffffff808211156159e3575f80fd5b818401915084601f8301126159f6575f80fd5b815181811115615a0857615a08615977565b615a1b601f8201601f191660200161598b565b9150808252856020828501011115615a31575f80fd5b61347581602084016020860161541c565b5f60208284031215615a52575f80fd5b815161196f81615333565b5f60208284031215615a6d575f80fd5b815161196f81615587565b600181811c90821680615a8c57607f821691505b60208210810361447857634e487b7160e01b5f52602260045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b5f60208284031215615ace575f80fd5b813561196f81615333565b5f8060408385031215615aea575f80fd5b8251915060208301516153d981615587565b634e487b7160e01b5f52601160045260245ffd5b5f63ffffffff808316818103615b2857615b28615afc565b6001019392505050565b601f82111561227057805f5260205f20601f840160051c81016020851015615b575750805b601f840160051c820191505b8181101561102b575f8155600101615b63565b815167ffffffffffffffff811115615b9057615b90615977565b615ba481615b9e8454615a78565b84615b32565b602080601f831160018114615bd7575f8415615bc05750858301515b5f19600386901b1c1916600185901b178555613f06565b5f85815260208120601f198616915b82811015615c0557888601518255948401946001909101908401615be6565b5085821015615c2257878501515f19600388901b60f8161c191681555b5050505050600190811b01905550565b60408152826040820152828460608301375f606084830101525f6060601f19601f860116830101905061ffff83166020830152949350505050565b80820180821115610e0357610e03615afc565b5f60208284031215615c90575f80fd5b5051919050565b60208082528181018390525f908460408401835b86811015615cd9578235615cbe81615333565b6001600160a01b031682529183019190830190600101615cab565b509695505050505050565b5f61012082019050825182526020830151151560208301526001600160a01b036040840151166040830152606083015160608301526080830151608083015260a0830151615d3d60a08401826001600160a01b03169052565b5060c0830151615d5860c08401826001600160a01b03169052565b5060e0830151615d6e60e084018261ffff169052565b50610100928301516001600160a01b0316919092015290565b5f67ffffffffffffffff821115615da057615da0615977565b5060051b60200190565b5f82601f830112615db9575f80fd5b81516020615dce615dc983615d87565b61598b565b8083825260208201915060208460051b870101935086841115615def575f80fd5b602086015b84811015615cd95780518352918301918301615df4565b5f805f805f8060c08789031215615e20575f80fd5b8651955060208088015167ffffffffffffffff80821115615e3f575f80fd5b818a0191508a601f830112615e52575f80fd5b8151615e60615dc982615d87565b81815260059190911b8301840190848101908d831115615e7e575f80fd5b938501935b82851015615ea5578451615e9681615333565b82529385019390850190615e83565b60408d0151909a509450505080831115615ebd575f80fd5b5050615ecb89828a01615daa565b945050606087015192506080870151915060a087015190509295509295509295565b61ffff841681526001600160a01b0383166020820152606060408201525f613614606083018461543e565b5f805f60608486031215615f2a575f80fd5b835192506020840151615f3c81615333565b604085015190925061592481615587565b5f8351615f5e81846020880161541c565b60609390931b6bffffffffffffffffffffffff19169190920190815260140192915050565b5f8251615f9481846020870161541c565b919091019291505056fe2ea2946ee16c4a1d0ec58464194022e54432a6d7db359835ddf283555f2c8eeea264697066735822122016ca89a3a588dbb7395241fff8a98b3e360495f9f4619f720ae5c0c742af0ebc64736f6c6343000817003300000000000000000000000067df244584b67e8c51b10ad610aaffa9a402fdb600000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d504430000000000000000000000000000000000000000000000000000000000015180000000000000000000000000762340b8a40cdd5bfc3edd94265899fda345d0e30000000000000000000000009742a1df1121886c8ba3b764fd3e47218da85d8c00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb7

Deployed Bytecode

0x608060405260043610610250575f3560e01c8063614b151c11610143578063895633ba116100c2578063b01ef60811610087578063e2cf968b11610062578063e2cf968b14610920578063e5d1e7231461093f578063f7a2da231461095e575f80fd5b8063b01ef608146108db578063b3a4074e146108ee578063b6aff8c11461090d575f80fd5b8063895633ba1461073e5780639e64ba6c146107705780639e79b41f146107d8578063ac71045e14610866578063af1e1de3146108aa575f80fd5b8063798bac8d11610108578063798bac8d1461066f5780637b3a58841461068e5780637e043795146106ad5780638098531d146106ee57806387a4fdcb1461070d575f80fd5b8063614b151c146105d85780636512ed2d146105eb5780636a90a8271461060a57806370f0c5d91461063c5780637430e0c614610650575f80fd5b80633c58e54d116101cf5780634635256e116101945780634635256e1461051e57806347e357401461055c5780634eb123171461057b5780634fca06c61461059a578063574c8229146105b9575f80fd5b80633c58e54d146104595780633e9e8bf814610487578063401cf150146104b4578063442559a2146104e0578063445738d8146104ff575f80fd5b8063262907c511610215578063262907c51461037e57806329e0e1601461039d5780632ab2b52b146103bc5780632e06db961461040b5780632fbbb25a1461043a575f80fd5b806303ec16d7146102a457806321506fff146102c357806321561935146102e257806321dbd9aa14610301578063228b13181461034c575f80fd5b366102a057336001600160a01b037f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d50443161461029e5760405163551c9c2760e11b815260040160405180910390fd5b005b5f80fd5b3480156102af575f80fd5b5061029e6102be3660046152fc565b610990565b3480156102ce575f80fd5b5061029e6102dd36600461531c565b610a83565b3480156102ed575f80fd5b5061029e6102fc366004615347565b610c78565b34801561030c575f80fd5b507f0000000000000000000000009742a1df1121886c8ba3b764fd3e47218da85d8c5b6040516001600160a01b0390911681526020015b60405180910390f35b348015610357575f80fd5b507f00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb761032f565b348015610389575f80fd5b5061032f610398366004615347565b610d8c565b3480156103a8575f80fd5b5061029e6103b7366004615371565b610e09565b3480156103c7575f80fd5b506103fd6103d6366004615347565b6001600160a01b039091165f90815261177660209081526040808320938352929052205490565b604051908152602001610343565b348015610416575f80fd5b5061042a6104253660046153b6565b610f0a565b6040519015158152602001610343565b348015610445575f80fd5b5061029e6104543660046153e4565b61100d565b348015610464575f80fd5b5061047861047336600461531c565b611032565b60405161034393929190615469565b348015610492575f80fd5b506104a66104a13660046153b6565b6112d3565b60405161034392919061549e565b3480156104bf575f80fd5b506104d36104ce3660046154c3565b611471565b604051610343919061553b565b3480156104eb575f80fd5b506103fd6104fa366004615347565b6115f0565b34801561050a575f80fd5b506103fd6105193660046155d7565b6116d5565b348015610529575f80fd5b5061053d610538366004615347565b6118cc565b604080516001600160a01b039093168352602083019190915201610343565b348015610567575f80fd5b506103fd61057636600461531c565b61193f565b348015610586575f80fd5b5061029e61059536600461567e565b611976565b3480156105a5575f80fd5b5061032f6105b4366004615347565b611ab0565b3480156105c4575f80fd5b5061029e6105d336600461567e565b611abb565b6103fd6105e63660046156b0565b611b1b565b3480156105f6575f80fd5b5061029e6106053660046156f7565b611e55565b348015610615575f80fd5b507f000000000000000000000000762340b8a40cdd5bfc3edd94265899fda345d0e361032f565b348015610647575f80fd5b506103fd612025565b34801561065b575f80fd5b5061029e61066a36600461531c565b61207b565b34801561067a575f80fd5b5061029e61068936600461567e565b6120c9565b348015610699575f80fd5b5061029e6106a836600461531c565b612275565b3480156106b8575f80fd5b506103fd6106c7366004615347565b6001600160a01b039091165f90815261129360209081526040808320938352929052205490565b3480156106f9575f80fd5b506103fd61070836600461573f565b6122e5565b348015610718575f80fd5b5061072c61072736600461577f565b612307565b6040516103439695949392919061585d565b348015610749575f80fd5b507f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d5044361032f565b34801561077b575f80fd5b5061032f61078a36600461531c565b5f9081526117776020526040908190206006810154600290910154600160a01b9182900467ffffffffffffffff1691900490911b73ffffffffffffffffffffffff0000000000000000161790565b3480156107e3575f80fd5b506107f76107f236600461531c565b61247d565b60405161034391905f610100820190506001600160a01b0380845116835260208401516020840152806040850151166040840152606084015160608401526080840151608084015260a084015160a08401528060c08501511660c08401525060e083015160e083015292915050565b348015610871575f80fd5b50610885610880366004615347565b61258e565b604080516001600160a01b039094168452602084019290925290820152606001610343565b3480156108b5575f80fd5b506108c96108c436600461567e565b61260a565b604051610343969594939291906158a6565b61029e6108e93660046156b0565b612679565b3480156108f9575f80fd5b506103fd6109083660046153e4565b612715565b61029e61091b3660046158f9565b612945565b34801561092b575f80fd5b5061029e61093a36600461592f565b612c5d565b34801561094a575f80fd5b506103fd610959366004615347565b612c99565b348015610969575f80fd5b507f00000000000000000000000067df244584b67e8c51b10ad610aaffa9a402fdb661032f565b61099981612cf7565b5f82815261177760205260408120906109b0612d17565b60028301549091506001600160a01b038083169116146109f9576002820154604051632600954360e21b81526001600160a01b0390911660048201526024015b60405180910390fd5b600582015415610a1c57604051635aea7c4760e01b815260040160405180910390fd5b82826007015403610a4057604051634b669ac760e01b815260040160405180910390fd5b6007820183905560405183815284907f0c0f2662914f0cd1e952db2aa425901cb00e7c1f507687d22cb04e836d55d9c7906020015b60405180910390a250505050565b610a8b612d25565b5f8181526117776020908152604080832081516101608101835281546001600160a01b039081168252600183015494820194909452600282015480851693820193909352600160a01b928390046001600160601b0316606082015260038201546080820152600482015460a0820152600582015460c0820152600682015493841660e082015291830467ffffffffffffffff16610100830152600160e01b90920463ffffffff1661012082015260079091015461014082015290610b4d612d17565b9050806001600160a01b031682604001516001600160a01b031614610b96576040808301519051632600954360e21b81526001600160a01b0390911660048201526024016109f0565b60c082015115610bb957604051635aea7c4760e01b815260040160405180910390fd5b81516001600160a01b03165f9081526117766020908152604080832082860180518552908352818420849055868452611777909252822080546001600160a01b03191681556001810183905560028101839055600381018390556004810183905560058101839055600681018390556007019190915582519051610c3e919083612d80565b60405183907f14b9c40404d5b41deb481f9a40b8aeb2bf4b47679b38cf757075a66ed510f7f1905f90a25050610c756001610b8755565b50565b610c80612d25565b6001600160a01b038083165f908152611f4e6020908152604080832085845290915281205490911690610cb1612d17565b90506001600160a01b038216610cda5760405163604fc74160e11b815260040160405180910390fd5b806001600160a01b0316826001600160a01b031614610d1757604051637824da0d60e11b81526001600160a01b03831660048201526024016109f0565b6001600160a01b0384165f908152611f4e60209081526040808320868452909152812055610d46848484612d80565b60405183906001600160a01b038616907f70c7877531c04c7d9caa8a7eca127384f04e8a6ee58b63f778ce5401d8bcae41905f90a35050610d886001610b8755565b5050565b6001600160a01b0382165f908152612337602090815260408083208484529091528120805463ffffffff16421115610dc7575f915050610e03565b80546001820154600160801b90910460201b73ffffffffffffffffffffffffffffffff0000000016600160a01b90910463ffffffff16175b9150505b92915050565b610e11612d25565b6001600160a01b0384165f908152612337602090815260408083208684529091529020805463ffffffff16421115610e67578054604051638c9e57cf60e01b815263ffffffff90911660048201526024016109f0565b8054600160201b90046001600160601b0316821115610ead578054604051632423736160e01b8152600160201b9091046001600160601b031660048201526024016109f0565b60018101546001600160a01b03848116911614610eee57600181015460405163a7d95dc360e01b81526001600160a01b0390911660048201526024016109f0565b610ef88585612d8b565b50610f046001610b8755565b50505050565b6040516332ac730f60e01b8152600481018390526001600160a01b0382811660248301525f917f00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb7909116906332ac730f90604401602060405180830381865afa158015610f79573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f9d9190615958565b905080610e03575f83815261119a60205260409020546001600160a01b03168015611006575f84815261119b602090815260408083206001600160a01b038716845290915290205460ff1680610dff5750806001600160a01b0316836001600160a01b03161491505b5092915050565b6110188484836120c9565b61102b611023612d17565b8585856130b8565b5050505050565b60605f807f00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb76001600160a01b031663b4a0f56f856040518263ffffffff1660e01b815260040161108491815260200190565b5f60405180830381865afa15801561109e573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526110c591908101906159bc565b925082515f146111e75760405163724ef50160e01b8152600481018590527f00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb76001600160a01b03169063724ef50190602401602060405180830381865afa158015611132573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111569190615a42565b604051638abf92c960e01b8152600481018690529092507f00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb76001600160a01b031690638abf92c990602401602060405180830381865afa1580156111bc573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111e09190615a5d565b90506112cc565b5f84815261119a60209081526040808320815160608101835281546001600160a01b0381168252600160a01b900461ffff1693810193909352600181018054919284019161123490615a78565b80601f016020809104026020016040519081016040528092919081815260200182805461126090615a78565b80156112ab5780601f10611282576101008083540402835291602001916112ab565b820191905f5260205f20905b81548152906001019060200180831161128e57829003601f168201915b505050505081525050905080604001519350805f0151925080602001519150505b9193909250565b60605f6112de61347e565b5f84815261119a60205260409020546001600160a01b03848116911614611369575f84815261119a60205260409020546001600160a01b03166113345760405163031dea5760e21b815260040160405180910390fd5b5f84815261119a60205260409081902054905163131e7a7360e11b81526001600160a01b0390911660048201526024016109f0565b5f84815261119a60205260409020600101805461138590615a78565b80601f01602080910402602001604051908101604052809291908181526020018280546113b190615a78565b80156113fc5780601f106113d3576101008083540402835291602001916113fc565b820191905f5260205f20905b8154815290600101906020018083116113df57829003601f168201915b5050505f87815261119a6020526040812080546001600160b01b031981168255949650600160a01b90940461ffff169450915061143e905060018301826152b2565b505060405184907ff1e9dfc0a0390550f6542ae820ee8fb346d6893de8f624a3e07087f15776b585905f90a29250929050565b606061147b61347e565b8167ffffffffffffffff81111561149457611494615977565b6040519080825280602002602001820160405280156114bd578160200160208202803683370190505b5090505f5b828110156115e8575f8484838181106114dd576114dd615aaa565b6114f39260206040909202019081019150615abe565b90505f85858481811061150857611508615aaa565b6001600160a01b0385165f90815261119c60209081526040808320938102959095018101358083529290529290922054919250508714611590576001600160a01b0382165f81815261119c602090815260408083208584529091529081902054905163285df36960e21b815260048101929092526024820183905260448201526064016109f0565b61159a82826134f4565b8484815181106115ac576115ac615aaa565b6001600160a01b0392831660209182029290920181019190915292165f90815261119c83526040808220928252919092528120556001016114c2565b509392505050565b5f7f00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb76001600160a01b0316638f40f378848461162c87876134ff565b60405160e085901b6001600160e01b03191681526001600160a01b039384166004820152602481019290925290911660448201526064016040805180830381865afa15801561167d573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116a19190615ad9565b5090505f819003610e0357506001600160a01b03919091165f90815261119c60209081526040808320938352929052205490565b5f806116df612d17565b61119954909150600160201b900460ff161561173c57611735858289898080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061358192505050565b9150611854565b6113888561ffff16111561176357604051632b7b866160e01b815260040160405180910390fd5b61119980545f906117799063ffffffff16615b10565b91906101000a81548163ffffffff021916908363ffffffff160217905563ffffffff1691506040518060600160405280826001600160a01b031681526020018661ffff16815260200188888080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92018290525093909452505084815261119a6020908152604091829020845181549286015161ffff16600160a01b026001600160b01b03199093166001600160a01b03909116179190911781559083015190915060018201906118509082615b76565b5050505b806001600160a01b0316827f9eee3ce0e6f7eeabd69ecf363898e9f490dbfda9ad953e1019a2c6aeceb4a7ef89898960405161189293929190615c32565b60405180910390a361119954600160201b900460ff16156118b7576118b7828261361d565b6118c2828585611e55565b5095945050505050565b6001600160a01b038083165f908152611f4e60209081526040808320858452909152812054909116908161190257505f19611938565b506001600160a01b0383165f908152611f4e60209081526040808320858452909152902054600160a01b90046001600160601b03165b9250929050565b5f8181526117776020526040812060058101548203611962576007015492915050565b61196f8160070154613691565b9392505050565b6001600160a01b0383165f908152611293602090815260408083208584529091529020548190036119ba5760405163e5c3f26360e01b815260040160405180910390fd5b6119c483836136b2565b8015611a30576119d381421190565b156119f15760405163dbaca9d160e01b815260040160405180910390fd5b6119ff6303c2670042615c6d565b811115611a3057611a146303c2670042615c6d565b60405163857c0df160e01b81526004016109f091815260200190565b6001600160a01b0383165f908152611293602090815260408083208584529091529020819055611a5e612d17565b6001600160a01b031682846001600160a01b03167fc8a67fb17ad40fabf835a5c96e0438644464257af6f325f44f1615aabc0e41ba84604051611aa391815260200190565b60405180910390a4505050565b5f61196f83836134f4565b611ac583836136b2565b5f611ad084846134f4565b9050611ade818585856130b8565b610f0457815f03611b0257604051631cc0fcd760e31b815260040160405180910390fd5b60405163f000942d60e01b815260040160405180910390fd5b5f611b278585856136d9565b15611b3357505f611e4d565b611b3d8585613744565b15611b5b576040516383a483f560e01b815260040160405180910390fd5b6001600160a01b0385165f90815261233760209081526040808320878452909152812090611b87612d17565b825490915063ffffffff16421115611c3357604051634ec58ed760e01b81526001600160a01b038281166004830152602482018790527f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d504431690634ec58ed790349060440160206040518083038185885af1158015611c07573d5f803e3d5ffd5b50505050506040513d601f19601f82011682018060405250810190611c2c9190615c80565b9250611d40565b81545f90611c5090600160201b90046001600160601b0316613691565b905080861015611c7657604051637205187360e11b8152600481018290526024016109f0565b60018301548354604051635fdec56160e01b81526001600160a01b03928316600482015263ffffffff82166024820152600160201b9091046001600160601b031660448201528382166064820152608481018890527f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d5044390911690635fdec56190349060a40160206040518083038185885af1158015611d17573d5f803e3d5ffd5b50505050506040513d601f19601f82011682018060405250810190611d3c9190615c80565b9350505b6001820180546001600160a01b0319166001600160a01b0383811691909117909155825463ffffffff85166fffffffffffffffffffffffffffffffff1990911617600160201b6001600160601b038816021783557f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d50443811690851603611dc3575f93505b81546001600160801b03908116602086811c909216600160801b0217835560018301805463ffffffff60a01b1916600160a01b63ffffffff881602179055604080518781529182018590526001600160a01b03838116928992918b16917ece0a712e4e277ac7b34942865f0de7a5629dffe0539b70423ad5ff1ed6ab42910160405180910390a450505b949350505050565b5f819003611e75576040516274040b60e91b815260040160405180910390fd5b611ea58383835f818110611e8b57611e8b615aaa565b9050602002016020810190611ea09190615abe565b61378e565b15611ef35760015b81811015611eed57611ee584848484818110611ecb57611ecb615aaa565b9050602002016020810190611ee09190615abe565b61361d565b600101611ead565b50611fe6565b611efc836137df565b5f5b81811015611fe4575f838383818110611f1957611f19615aaa565b9050602002016020810190611f2e9190615abe565b5f86815261119b602090815260408083206001600160a01b038516845290915290205490915060ff1615611f755760405163199e223b60e21b815260040160405180910390fd5b611f7d612d17565b6001600160a01b0316816001600160a01b031603611fae576040516343e2197f60e01b815260040160405180910390fd5b5f85815261119b602090815260408083206001600160a01b03909416835292905220805460ff1916600190811790915501611efe565b505b827fd5a31bd2d34d303249ac7f54bfc7578390f90f5d39cb39813f67190fa36f5c178383604051612018929190615c97565b60405180910390a2505050565b5f61202e61347e565b61119954600160201b900460ff161561205a57604051635cb2b73360e01b815260040160405180910390fd5b50611199805464ff00000000198116600160201b1790915563ffffffff1690565b612083612d25565b5f818152611777602052604081206005015490036120b4576040516325b56c7d60e11b815260040160405180910390fd5b6120be815f613862565b610c756001610b8755565b6120d1612d25565b6120dc838383613abf565b612265576001600160601b03811115612108576040516335ec82cb60e01b815260040160405180910390fd5b6001600160a01b038381165f908152611f4e60209081526040808320868452909152902080549091811690600160a01b90046001600160601b03168314801561215957506001600160a01b03811615155b1561217757604051635b4a879b60e11b815260040160405180910390fd5b81546001600160a01b0316600160a01b6001600160601b038516021782555f61219e612d17565b90506001600160a01b0382166121d6576121b88686613b1f565b82546001600160a01b0319166001600160a01b038216178355612213565b806001600160a01b0316826001600160a01b031614612213576040516334bec8c760e11b81526001600160a01b03831660048201526024016109f0565b806001600160a01b031685876001600160a01b03167ffcc77ea8bdcce862f43b7fb00fe6b0eb90d6aeead27d3800d9257cf7a05f9d968760405161225991815260200190565b60405180910390a45050505b6122706001610b8755565b505050565b61227e81613b29565b6122b85761228b816137df565b5f81815261119a6020526040812080546001600160b01b0319168155906122b560018301826152b2565b50505b60405181907f2a9aeaf340ca0da469c1f7e3d513c0e6c9cd287016f29d257a4ef70e13dc441c905f90a250565b5f6122f286868585612715565b90506118c26122ff612d17565b8787876130b8565b5f6060805f805f806040518061012001604052806123427f00000000000000000000000000000000000000000000000000000000000001f490565b81526020017f0000000000000000000000000000000000000000000000000000000000000000151581526020018e6001600160a01b031681526020018d81526020018b81526020018c6001600160a01b031681526020018a6001600160a01b031681526020018961ffff1681526020016123ba612d17565b6001600160a01b031681525090507f0000000000000000000000009742a1df1121886c8ba3b764fd3e47218da85d8c6001600160a01b0316630e13eac1826040518263ffffffff1660e01b81526004016124149190615ce4565b5f60405180830381865afa15801561242e573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526124559190810190615e0b565b809750819850829950839a50849b50859c505050505050505096509650965096509650969050565b6124d86040518061010001604052805f6001600160a01b031681526020015f81526020015f6001600160a01b031681526020015f81526020015f81526020015f81526020015f6001600160a01b031681526020015f81525090565b5f8281526117776020526040812060068101549091600160e01b90910463ffffffff169081900361252657507f00000000000000000000000000000000000000000000000000000000000151805b604080516101008101825283546001600160a01b03908116825260018501546020830152600285015481169282019290925260608101929092526103846080830152600583015460a083015260068301541660c082015260079091015460e082015292915050565b6001600160a01b0382165f90815261233760209081526040808320848452909152812080548291829163ffffffff164211156125d3575f805f93509350935050612603565b600181015490546001600160a01b03909116935063ffffffff81169250600160201b90046001600160601b031690505b9250925092565b5f806060805f8061261b89896134ff565b905061262b8989838a5f80612307565b50939950919650945092505f90505b835181101561266c5783818151811061265557612655615aaa565b60200260200101518601955080600101905061263a565b5093975093979195509350565b6001600160a01b0384165f908152611f4e6020908152604080832086845290915290208054600160a01b90046001600160601b03168310156126e25780546040516316b5016f60e01b8152600160a01b9091046001600160601b031660048201526024016109f0565b80546001600160a01b031661270a57604051633692386160e21b815260040160405180910390fd5b61102b858584613b6f565b5f61271e612d25565b61272783612cf7565b815f03612756577f000000000000000000000000000000000000000000000000000000000001518091506127a6565b62093a8082111561277f5760405163ccd285bd60e01b815262093a8060048201526024016109f0565b6103848210156127a65760405163494c8c0760e11b815261038460048201526024016109f0565b6127ae613cc7565b90506127ba8585613b1f565b6001600160a01b0385165f9081526117766020908152604080832087845290915290205415612825576001600160a01b0385165f9081526117766020908152604080832087845290915290819020549051637618a00360e01b815260048101919091526024016109f0565b5f61282e612d17565b6001600160a01b038781165f818152611776602090815260408083208b84528252808320889055878352611777909152902080546001600160a01b0319908116909217815560018101899055600281018054909216928416929092179055600781018690559091507f000000000000000000000000000000000000000000000000000000000001518084146128dd576006810180546001600160e01b0316600160e01b63ffffffff8716021790555b6040805185815261038460208201529081018690526060810184905286906001600160a01b03808a1691908516907f1062dd3b35f12b4064331244d00f40c1d4831965e4285654157a2409c6217cff9060800160405180910390a45050611e4d6001610b8755565b61294d612d25565b5f8381526117776020526040812060078101549091036129805760405163125197d160e01b815260040160405180910390fd5b348310156129a15760405163e2bbc1e360e01b815260040160405180910390fd5b60058101545f6129af612d17565b90507f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d504436001600160a01b0316846001600160a01b0316036129ee575f93505b6001600160a01b038416151580612a0457508115155b15612a5e576002830180546001600160a01b0316604086901c6001600160601b0316600160a01b9081029190911790915560068401805467ffffffffffffffff60a01b191667ffffffffffffffff87169092029190911790555b815f03612b1a578260070154851015612a92578260070154604051630c79bdc760e21b81526004016109f091815260200190565b82546001840154612aac916001600160a01b031690613ced565b600783018590556006830180546001600160a01b0319166001600160a01b0383161790819055600160e01b900463ffffffff165f819003612b0a57507f00000000000000000000000000000000000000000000000000000000000151805b4201600584018190559150612bff565b612b2382421190565b15612b4457604051633feeb88d60e01b8152600481018390526024016109f0565b60068301546001600160a01b03808316911603612b7457604051631c280aed60e31b815260040160405180910390fd5b5f612b828460070154613691565b905080861015612ba85760405163cd698a1960e01b8152600481018290526024016109f0565b50600783018054600685018054928890556001600160a01b038481166001600160a01b0319851617909155909116426103840180851015612bee57600586018190559350835b50612bfc8183614e20613cf7565b50505b612c09855f613e6e565b60408051868152602081018490526001600160a01b0383169188917f26ea3ebbda62eb1baef13e1c237dddd956c87f80b2801f2616d806d52557b121910160405180910390a35050506122706001610b8755565b612c678382610990565b5f8381526117776020526040902061102b612c80612d17565b825460018401546001600160a01b0390911690866130b8565b6001600160a01b0382165f908152612337602090815260408083208484529091528120805463ffffffff164211612ced578054612ce590600160201b90046001600160601b0316613691565b915050610e03565b5060019392505050565b805f03610c7557604051631d4b87f360e11b815260040160405180910390fd5b5f612d20613f3c565b905090565b6002610b875403612d785760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c0060448201526064016109f0565b6002610b8755565b612270838383613f7d565b6001600160a01b038281165f90815261233760209081526040808320858452808352818420825160a081018452815463ffffffff8082168352600160201b82046001600160601b03908116848901908152600160801b9093046001600160801b031684880152600185018054808c1660608701908152600160a01b8204851660808801528d8c5297909952989094556001600160c01b031990961690965591518251955193516313723ecf60e21b8152908716600482015294909316602485015291166044830152917f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d504431690634dc8fb3c906064015f604051808303815f87803b158015612e97575f80fd5b505af1158015612ea9573d5f803e3d5ffd5b505050505f612eb6612d17565b90505f80612ed8838787876060015188602001516001600160601b0316613f88565b6040516331a9108f60e11b81526004810188905291935091505f906001600160a01b03881690636352211e90602401602060405180830381865afa158015612f22573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612f469190615a42565b9050306001600160a01b03821603612f6d57612f68878787606001518761420e565b612fd9565b60608501516040516323b872dd60e01b81526001600160a01b038681166004830152918216602482015260448101889052908816906323b872dd906064015f604051808303815f87803b158015612fc2575f80fd5b505af1158015612fd4573d5f803e3d5ffd5b505050505b5f805f61302f8a8a898b602001516001600160601b03166130288d604001518e6080015173ffffffffffffffffffffffffffffffff0000000060209290921b9190911663ffffffff9091161790565b8b8b61421a565b92509250925087606001516001600160a01b0316898b6001600160a01b03167f1cb8adb37d6d35e94cd0695ca39895b84371864713f5ca7eada52af9ff23744b8a8787876040516130a494939291906001600160a01b0394909416845260208401929092526040830152606082015260800190565b60405180910390a450505050505050505050565b6040516311e81e6f60e31b81526001600160a01b0384811660048301526024820184905285811660448301525f9182917f00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb71690638f40f378906064016040805180830381865afa15801561312e573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906131529190615ad9565b509050825f036131b05780156131975761316d86868661423f565b8084866001600160a01b03165f80516020615f9f83398151915260405160405180910390a4600191505b6131a185856142bc565b806131a95750815b9150613475565b8281036131c9576131c185856142bc565b915050611e4d565b6132618386867f00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb76001600160a01b0316638abf92c9886040518263ffffffff1660e01b815260040161321d91815260200190565b602060405180830381865afa158015613238573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061325c9190615a5d565b61432f565b156132de578015613292578084866001600160a01b03165f80516020615f9f83398151915260405160405180910390a45b61329c85856142bc565b508284866001600160a01b03167fb17e0c916df75a12480835f00b3927cb871bbe00bacf819f81a1d92f9ff7f38d60405160405180910390a460019150613475565b8015613319576132ef86868661423f565b8084866001600160a01b03165f80516020615f9f83398151915260405160405180910390a4600191505b6001600160a01b0385165f90815261119c6020908152604080832087845290915290205483810361334b575050611e4d565b5f84815261119a60205260409020546001600160a01b0316806133815760405163031dea5760e21b815260040160405180910390fd5b5f85815261119b602090815260408083206001600160a01b038c16845290915290205460ff161580156133c65750876001600160a01b0316816001600160a01b031614155b156133e457604051631ba4e8d560e21b815260040160405180910390fd5b6001600160a01b0387165f90815261119c6020908152604080832089845290915290208590558115613436578186886001600160a01b03165f80516020615f9f83398151915260405160405180910390a45b8486886001600160a01b03167fb17e0c916df75a12480835f00b3927cb871bbe00bacf819f81a1d92f9ff7f38d60405160405180910390a46001935050505b50949350505050565b336001600160a01b037f00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb716146134f257604051637383019160e11b81526001600160a01b037f00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb71660048201526024016109f0565b565b5f61196f8383614391565b5f61350a83836134f4565b90506001600160a01b038116610e03576040516331a9108f60e11b8152600481018390526001600160a01b03841690636352211e90602401602060405180830381865afa15801561355d573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061196f9190615a42565b5f806135fe7f00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb78686866040516024016135bc93929190615eed565b60408051601f198184030181529190526020810180516001600160e01b031663d5adf48960e01b1790526135ee612d17565b6001600160a01b031691906143c6565b9050808060200190518101906136149190615c80565b95945050505050565b604051602481018390526001600160a01b0382166044820152612270907f00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb79060640160408051601f198184030181529190526020810180516001600160e01b031663081de21760e31b1790526135ee612d17565b5f600a82048082036136a85761196f836001615c6d565b61196f8382615c6d565b6136bc82826143e6565b610d88576040516338e4293b60e21b815260040160405180910390fd5b6001600160a01b038084165f908152611f4e60209081526040808320868452909152812080549192909116158061372057508054600160a01b90046001600160601b031683105b1561372e575f91505061196f565b61373985855f613b6f565b506001949350505050565b6001600160a01b0382165f908152611776602090815260408083208484529091528120548015801590610dff57505f90815261177760205260409020600501544211159392505050565b604051602481018390526001600160a01b03821660448201525f9061196f9060640160408051601f198184030181529190526020810180516001600160e01b031663081de21760e31b1790526143f1565b5f81815261119a60205260409020546001600160a01b03166137ff612d17565b6001600160a01b0316816001600160a01b031614610d88576001600160a01b03811661383e5760405163031dea5760e21b815260040160405180910390fd5b60405163b39cb29b60e01b81526001600160a01b03821660048201526024016109f0565b5f828152611777602090815260409182902082516101608101845281546001600160a01b039081168252600183015493820193909352600282015480841694820194909452600160a01b938490046001600160601b0316606082015260038201546080820152600482015460a0820152600582015460c08201819052600683015493841660e083015293830467ffffffffffffffff16610100820152600160e01b90920463ffffffff1661012083015260070154610140820152904211613944578060c001516040516301d00bfb60e51b81526004016109f091815260200190565b5f806139678360400151845f015185602001518660e00151876101400151613f88565b84516001600160a01b03165f908152611776602090815260408083208289015184528252808320839055898352611777909152812080546001600160a01b0319168155600181018290556002810182905560038101829055600481018290556005810182905560068101829055600701559092509050836139f9576139f9835f015184602001518560e001515f61447e565b5f805f613a46865f0151876020015188604001518961014001518a610100015167ffffffffffffffff1660408c606001516001600160601b03166001600160a01b0316901b178a8a61421a565b9250925092508560e001516001600160a01b031686604001516001600160a01b0316897f2edb0e99c6ac35be6731dab554c1d1fa1b7beb675090dbb09fb14e615aca1c4a868686604051613aad939291909283526020830191909152604082015260600190565b60405180910390a45050505050505050565b6001600160a01b0383165f908152612337602090815260408083208584529091528120805463ffffffff16421180613b0757508054600160201b90046001600160601b031683115b15613b15575f91505061196f565b6137398585612d8b565b610d88828261450f565b5f610e0382604051602401613b4091815260200190565b60408051601f198184030181529190526020810180516001600160e01b0316630852cd8d60e31b1790526143f1565b613b77612d25565b613b818383614588565b6001600160a01b038381165f908152611f4e60209081526040808320868452808352818420825180840190935280549586168352600160a01b9095046001600160601b0316828401528684529091529155613bdc84846145cc565b613bf481602001516001600160601b03166001613e6e565b5f613bfd612d17565b90505f80613c1e845f015188888688602001516001600160601b0316613f88565b91509150613c2e8787855f61420e565b5f805f613c508a8a895f01518a602001516001600160601b03168c8a8a61421a565b8951604080516001600160a01b038c8116825260208201879052918101859052606081018490529497509295509093508116918b918d16907fd28c0a7dd63bc853a4e36306655da9f8c0b29ff9d0605bb976ae420e46a999309060800160405180910390a4505050505050506122706001610b8755565b5f61138d545f03613cdd5761138d805460010190555b5061138d80546001810190915590565b610d888282614618565b815f03613d0357505050565b7f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d504436001600160a01b0316836001600160a01b031603613d60577f00000000000000000000000067df244584b67e8c51b10ad610aaffa9a402fdb692505b5f836001600160a01b03168383906040515f60405180830381858888f193505050503d805f8114613dac576040519150601f19603f3d011682016040523d82523d5f602084013e613db1565b606091505b5050905080610f045760405163aa67c91960e01b81526001600160a01b0385811660048301527f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d50443169063aa67c9199085906024015f604051808303818588803b158015613e1c575f80fd5b505af1158015613e2e573d5f803e3d5ffd5b5050505050836001600160a01b03167fa2201512569adb2d513531dfd69b66df50bd5cffb8c1bbe65a4611f9e1eadbd184604051610a7591815260200190565b34821115613f0e577f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d504436001600160a01b031663452f2b8f613ead612d17565b6040516001600160e01b031960e084901b1681526001600160a01b03909116600482015234850360248201526044015b5f604051808303815f87803b158015613ef4575f80fd5b505af1158015613f06573d5f803e3d5ffd5b505050505050565b808015613f1a57503482105b15610d8857610d88823403613f2d612d17565b6001600160a01b03169061462c565b336001600160a01b037f000000000000000000000000762340b8a40cdd5bfc3edd94265899fda345d0e3168103613f7a575060131936013560601c90565b90565b612270838383614741565b604051633221782160e01b81526001600160a01b0386811660048301528581166024830152604482018590528381166064830152608482018390525f91829182917f00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb79091169063322178219060a4016060604051808303815f875af1158015614013573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906140379190615f18565b90945092509050801561405f5761404f88888861423f565b61405987876142bc565b50614203565b506001600160a01b0386165f90815261119c602090815260408083208884529091529020548015614203575f81815261119a60205260409020546001600160a01b03169250826141c15760405163724ef50160e01b8152600481018290527f00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb76001600160a01b03169063724ef50190602401602060405180830381865afa15801561410c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906141309190615a42565b604051638abf92c960e01b8152600481018390529093507f00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb76001600160a01b031690638abf92c990602401602060405180830381865afa158015614196573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906141ba9190615a5d565b91506141de565b5f81815261119a6020526040902054600160a01b900461ffff1691505b6001600160a01b0387165f90815261119c602090815260408083208984529091528120555b509550959350505050565b610f0484848484614777565b5f805f61422c8a8a8a8a8a8a8a6147f9565b919c909b50909950975050505050505050565b6040516001600160a01b038316602482015260448101829052610f04907f00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb79060640160408051601f198184030181529190526020810180516001600160e01b03166349928fdd60e11b1790526001600160a01b03861691906143c6565b6001600160a01b0382165f90815261119c602090815260408083208484529091528120548015611006576001600160a01b0384165f81815261119c60209081526040808320878452909152808220829055518392869290915f80516020615f9f8339815191529190a45060019392505050565b604051602481018590526001600160a01b03841660448201526064810183905261ffff821660848201525f906136149060a40160408051601f198184030181529190526020810180516001600160e01b031663031d3a6b60e51b1790526143f1565b6001600160a01b038083165f908152611f4e602090815260408083208584529091529020541680610e035761196f838361480b565b60605f6143d485858561484a565b92509050806115e8576115e8826148cf565b5f61196f83836148f8565b5f60606144317f00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb784614421612d17565b6001600160a01b0316919061484a565b90925090508115801561446a5750805160208201207f931c7fca1aa4d9db6fb474c5724471bc286fbb3d892ec4f390fa55323a23dc0c14155b1561447857614478816148cf565b50919050565b6001600160a01b038116156144a6576040516357a016b360e01b815260040160405180910390fd5b6040516323b872dd60e01b81523060048201526001600160a01b038381166024830152604482018590528516906323b872dd906064015f604051808303815f87803b1580156144f3575f80fd5b505af1158015614505573d5f803e3d5ffd5b5050505050505050565b6001600160a01b038083165f908152611f4e60209081526040808320858452909152902054168061454457612270838361497d565b61454c612d17565b6001600160a01b0316816001600160a01b031614612270576040516332f3b03360e01b81526001600160a01b03821660048201526024016109f0565b6001600160a01b0382165f90815261129360209081526040808320848452909152902054421015610d8857604051634917db3f60e01b815260040160405180910390fd5b6001600160a01b0382165f9081526123376020908152604080832084845290915290206145f7612d17565b60018201546001600160a01b03918216911603612270576122708383614a69565b6146228282614a69565b610d888282614bf5565b8047101561467c5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e636500000060448201526064016109f0565b5f826001600160a01b0316826040515f6040518083038185875af1925050503d805f81146146c5576040519150601f19603f3d011682016040523d82523d5f602084013e6146ca565b606091505b50509050806122705760405162461bcd60e51b815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260448201527f6563697069656e74206d6179206861766520726576657274656400000000000060648201526084016109f0565b6001600160a01b038084165f908152611f4e602090815260408083208684529091529020541680610f0457610f04848484614c37565b6001600160a01b038085165f908152611f4e602090815260408083208784529091529020541680156147ed57816001600160a01b0316816001600160a01b0316146147e0576040516332f3b03360e01b81526001600160a01b03821660048201526024016109f0565b5f91506147ed8585614c6c565b61102b85858585614cbe565b5f805f61422c8a8a8a8a8a8a8a614e6f565b6001600160a01b038083165f9081526117766020908152604080832085845282528083205483526117779091529020600201541680610e03575f61196f565b5f6060836001600160a01b0316838660405160200161486a929190615f4d565b60408051601f198184030181529082905261488491615f83565b5f604051808303815f865af19150503d805f81146148bd576040519150601f19603f3d011682016040523d82523d5f602084013e6148c2565b606091505b5090969095509350505050565b8051156148df5780518082602001fd5b604051633cfe059f60e01b815260040160405180910390fd5b6001600160a01b038083165f908152611f4e6020908152604080832085845290915281205490911680156149735761492e612d17565b6001600160a01b0316816001600160a01b03161461496a5760405163c89fba3b60e01b81526001600160a01b03821660048201526024016109f0565b60019150611006565b610dff8484614e81565b6001600160a01b0382165f90815261177660209081526040808320848452909152812054908190036149b3576122708383614f35565b5f81815261177760205260408120906149ca612d17565b905081600501545f03614a1d5760028201546001600160a01b03828116911614614a18576002820154604051637322937760e11b81526001600160a01b0390911660048201526024016109f0565b61102b565b60068201546001600160a01b03828116911614614a5e576006820154604051637322937760e11b81526001600160a01b0390911660048201526024016109f0565b61102b836001613862565b6001600160a01b0382165f9081526123376020908152604080832084845290915290205463ffffffff164211610d88576001600160a01b038281165f90815261233760209081526040808320858452808352818420825160a081018452815463ffffffff8082168352600160201b82046001600160601b03908116848901908152600160801b9093046001600160801b031684880152600185018054808c1660608701908152600160a01b8204851660808801528d8c5297909952989094556001600160c01b0319909616909655915182519551935163345db49360e01b8152908716600482015294909316602485015291166044830152917f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d50443169063345db493906064015f604051808303815f87803b158015614ba5575f80fd5b505af1158015614bb7573d5f803e3d5ffd5b50506040518492506001600160a01b03861691507f30c264456cbd17f5f67d7534654161414f34c0e6cc1b7500e169b7a7aea4afc0905f90a3505050565b6001600160a01b038083165f908152611f4e602090815260408083208584529091529020805490911615614c2d57614c2d8383614c6c565b6122708383614f85565b6001600160a01b0383165f90815261177660209081526040808320858452909152812054900361227057612270838383614f8f565b6001600160a01b0382165f818152611f4e60209081526040808320858452909152808220829055518392917faa6271d89a385571e237d3e7254ccc7c09f68055e6e9b410ed08233a8b9a05cf91a35050565b6001600160a01b0384165f908152611776602090815260408083208684529091529020548015614e63575f818152611777602052604081206005810154909103614e12576001600160a01b03831615801590614d2a575060028101546001600160a01b03848116911614155b15614d59576002810154604051637322937760e11b81526001600160a01b0390911660048201526024016109f0565b6002808201546001600160a01b038881165f908152611776602090815260408083208b84528252808320839055878352611777909152812080546001600160a01b0319168155600181018290559384018190556003840181905560048401819055600584018190556006840181905560079093018390551690614de1908290899089906130b8565b5060405183907f5603897cc9b1e866f3f7395ffc6638776041f21c094d0b4e748ff44c407fa362905f90a250614e5e565b60068101546001600160a01b03848116911614614e53576006810154604051637322937760e11b81526001600160a01b0390911660048201526024016109f0565b614e5e826001613862565b5f9250505b61102b8585858561447e565b5f805f61422c8a8a8a8a8a8a8a614fa4565b6001600160a01b0382165f908152611776602090815260408083208484529091528120548015614f2f575f81815261177760205260409020614ec1612d17565b60028201546001600160a01b03908116911614614f0257600281015460405163c0221c7f60e01b81526001600160a01b0390911660048201526024016109f0565b600581015415614f255760405163a2a745e960e01b815260040160405180910390fd5b6001925050611006565b5f610dff565b816001600160a01b03166323b872dd614f4c612d17565b6040516001600160e01b031960e084901b1681526001600160a01b03909116600482015230602482015260448101849052606401613edd565b610d888282614fc0565b614f998383614fca565b612270838383615070565b5f805f614fb18a8a614fca565b61422c8a8a8a8a8a8a8a615088565b610d888282614588565b6001600160a01b0382165f9081526112936020908152604080832084845290915290205415610d88576001600160a01b0382165f9081526112936020908152604080832084845290915281205561501f612d17565b6001600160a01b031681836001600160a01b03167fc8a67fb17ad40fabf835a5c96e0438644464257af6f325f44f1615aabc0e41ba5f60405161506491815260200190565b60405180910390a45050565b61507c8184845f6130b8565b506122708383836152a6565b5f805f865f0361509f57505f915081905080615299565b6060805f806150b28e8e8e8e8e8d612307565b8451959c5091995092975090955090935091505f906001146150d657614e206150db565b620334505b90505f5b855181101561514e576151258682815181106150fd576150fd615aaa565b602002602001015186838151811061511757615117615aaa565b602002602001015184613cf7565b84818151811061513757615137615aaa565b6020026020010151880197508060010190506150df565b5061515c8d87614e20613cf7565b6151897f00000000000000000000000067df244584b67e8c51b10ad610aaffa9a402fdb689614e20613cf7565b82156152035761519c8b84614e20613cf7565b8d8f6001600160a01b03167f141b92fd9766c80ab120598ea2f6be9802470ec59b5446dd9bf46214ead8d08e8d865f6040516151f6939291906001600160a01b039390931683526020830191909152604082015260600190565b60405180910390a3968201965b6001600160a01b038a161561529357811561523a57855f03615228579581019561522d565b948101945b61523a8a83614e20613cf7565b8d8f6001600160a01b03167f27a4dd4ff659a9e6354fb079b2208365e5b83f55c22a4150eee2bca89501cb988c8560405161528a9291906001600160a01b03929092168252602082015260400190565b60405180910390a35b50505050505b9750975097945050505050565b6122708383835f61420e565b5080546152be90615a78565b5f825580601f106152cd575050565b601f0160209004905f5260205f2090810190610c7591905b808211156152f8575f81556001016152e5565b5090565b5f806040838503121561530d575f80fd5b50508035926020909101359150565b5f6020828403121561532c575f80fd5b5035919050565b6001600160a01b0381168114610c75575f80fd5b5f8060408385031215615358575f80fd5b823561536381615333565b946020939093013593505050565b5f805f8060808587031215615384575f80fd5b843561538f81615333565b93506020850135925060408501356153a681615333565b9396929550929360600135925050565b5f80604083850312156153c7575f80fd5b8235915060208301356153d981615333565b809150509250929050565b5f805f80608085870312156153f7575f80fd5b843561540281615333565b966020860135965060408601359560600135945092505050565b5f5b8381101561543657818101518382015260200161541e565b50505f910152565b5f815180845261545581602086016020860161541c565b601f01601f19169290920160200192915050565b606081525f61547b606083018661543e565b90506001600160a01b038416602083015261ffff83166040830152949350505050565b604081525f6154b0604083018561543e565b905061ffff831660208301529392505050565b5f805f604084860312156154d5575f80fd5b83359250602084013567ffffffffffffffff808211156154f3575f80fd5b818601915086601f830112615506575f80fd5b813581811115615514575f80fd5b8760208260061b8501011115615528575f80fd5b6020830194508093505050509250925092565b602080825282518282018190525f9190848201906040850190845b8181101561557b5783516001600160a01b031683529284019291840191600101615556565b50909695505050505050565b61ffff81168114610c75575f80fd5b5f8083601f8401126155a6575f80fd5b50813567ffffffffffffffff8111156155bd575f80fd5b6020830191508360208260051b8501011115611938575f80fd5b5f805f805f606086880312156155eb575f80fd5b853567ffffffffffffffff80821115615602575f80fd5b818801915088601f830112615615575f80fd5b813581811115615623575f80fd5b896020828501011115615634575f80fd5b602092830197509550908701359061564b82615587565b90935060408701359080821115615660575f80fd5b5061566d88828901615596565b969995985093965092949392505050565b5f805f60608486031215615690575f80fd5b833561569b81615333565b95602085013595506040909401359392505050565b5f805f80608085870312156156c3575f80fd5b84356156ce81615333565b9350602085013592506040850135915060608501356156ec81615333565b939692955090935050565b5f805f60408486031215615709575f80fd5b83359250602084013567ffffffffffffffff811115615726575f80fd5b61573286828701615596565b9497909650939450505050565b5f805f805f60a08688031215615753575f80fd5b853561575e81615333565b97602087013597506040870135966060810135965060800135945092505050565b5f805f805f8060c08789031215615794575f80fd5b863561579f81615333565b95506020870135945060408701356157b681615333565b93506060870135925060808701356157cd81615333565b915060a08701356157dd81615587565b809150509295509295509295565b5f815180845260208085019450602084015f5b838110156158235781516001600160a01b0316875295820195908201906001016157fe565b509495945050505050565b5f815180845260208085019450602084015f5b8381101561582357815187529582019590820190600101615841565b86815260c060208201525f61587560c08301886157eb565b8281036040840152615887818861582e565b60608401969096525050608081019290925260a0909101529392505050565b86815285602082015260c060408201525f6158c460c08301876157eb565b82810360608401526158d6818761582e565b9150508360808301526001600160a01b03831660a0830152979650505050505050565b5f805f6060848603121561590b575f80fd5b8335925060208401359150604084013561592481615333565b809150509250925092565b5f805f60608486031215615941575f80fd5b505081359360208301359350604090920135919050565b5f60208284031215615968575f80fd5b8151801515811461196f575f80fd5b634e487b7160e01b5f52604160045260245ffd5b604051601f8201601f1916810167ffffffffffffffff811182821017156159b4576159b4615977565b604052919050565b5f602082840312156159cc575f80fd5b815167ffffffffffffffff808211156159e3575f80fd5b818401915084601f8301126159f6575f80fd5b815181811115615a0857615a08615977565b615a1b601f8201601f191660200161598b565b9150808252856020828501011115615a31575f80fd5b61347581602084016020860161541c565b5f60208284031215615a52575f80fd5b815161196f81615333565b5f60208284031215615a6d575f80fd5b815161196f81615587565b600181811c90821680615a8c57607f821691505b60208210810361447857634e487b7160e01b5f52602260045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b5f60208284031215615ace575f80fd5b813561196f81615333565b5f8060408385031215615aea575f80fd5b8251915060208301516153d981615587565b634e487b7160e01b5f52601160045260245ffd5b5f63ffffffff808316818103615b2857615b28615afc565b6001019392505050565b601f82111561227057805f5260205f20601f840160051c81016020851015615b575750805b601f840160051c820191505b8181101561102b575f8155600101615b63565b815167ffffffffffffffff811115615b9057615b90615977565b615ba481615b9e8454615a78565b84615b32565b602080601f831160018114615bd7575f8415615bc05750858301515b5f19600386901b1c1916600185901b178555613f06565b5f85815260208120601f198616915b82811015615c0557888601518255948401946001909101908401615be6565b5085821015615c2257878501515f19600388901b60f8161c191681555b5050505050600190811b01905550565b60408152826040820152828460608301375f606084830101525f6060601f19601f860116830101905061ffff83166020830152949350505050565b80820180821115610e0357610e03615afc565b5f60208284031215615c90575f80fd5b5051919050565b60208082528181018390525f908460408401835b86811015615cd9578235615cbe81615333565b6001600160a01b031682529183019190830190600101615cab565b509695505050505050565b5f61012082019050825182526020830151151560208301526001600160a01b036040840151166040830152606083015160608301526080830151608083015260a0830151615d3d60a08401826001600160a01b03169052565b5060c0830151615d5860c08401826001600160a01b03169052565b5060e0830151615d6e60e084018261ffff169052565b50610100928301516001600160a01b0316919092015290565b5f67ffffffffffffffff821115615da057615da0615977565b5060051b60200190565b5f82601f830112615db9575f80fd5b81516020615dce615dc983615d87565b61598b565b8083825260208201915060208460051b870101935086841115615def575f80fd5b602086015b84811015615cd95780518352918301918301615df4565b5f805f805f8060c08789031215615e20575f80fd5b8651955060208088015167ffffffffffffffff80821115615e3f575f80fd5b818a0191508a601f830112615e52575f80fd5b8151615e60615dc982615d87565b81815260059190911b8301840190848101908d831115615e7e575f80fd5b938501935b82851015615ea5578451615e9681615333565b82529385019390850190615e83565b60408d0151909a509450505080831115615ebd575f80fd5b5050615ecb89828a01615daa565b945050606087015192506080870151915060a087015190509295509295509295565b61ffff841681526001600160a01b0383166020820152606060408201525f613614606083018461543e565b5f805f60608486031215615f2a575f80fd5b835192506020840151615f3c81615333565b604085015190925061592481615587565b5f8351615f5e81846020880161541c565b60609390931b6bffffffffffffffffffffffff19169190920190815260140192915050565b5f8251615f9481846020870161541c565b919091019291505056fe2ea2946ee16c4a1d0ec58464194022e54432a6d7db359835ddf283555f2c8eeea264697066735822122016ca89a3a588dbb7395241fff8a98b3e360495f9f4619f720ae5c0c742af0ebc64736f6c63430008170033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

00000000000000000000000067df244584b67e8c51b10ad610aaffa9a402fdb600000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d504430000000000000000000000000000000000000000000000000000000000015180000000000000000000000000762340b8a40cdd5bfc3edd94265899fda345d0e30000000000000000000000009742a1df1121886c8ba3b764fd3e47218da85d8c00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb7

-----Decoded View---------------
Arg [0] : treasury (address): 0x67Df244584b67E8C51B10aD610aAfFa9a402FdB6
Arg [1] : feth (address): 0x49128CF8ABE9071ee24540a296b5DED3F9D50443
Arg [2] : duration (uint256): 86400
Arg [3] : router (address): 0x762340B8a40Cdd5BFC3eDD94265899FDa345D0E3
Arg [4] : marketUtils (address): 0x9742A1df1121886c8BA3b764FD3e47218Da85d8c
Arg [5] : worldsNft (address): 0x69525Dac489e4718964B5615c3D794a25d62bEb7

-----Encoded View---------------
6 Constructor Arguments found :
Arg [0] : 00000000000000000000000067df244584b67e8c51b10ad610aaffa9a402fdb6
Arg [1] : 00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d50443
Arg [2] : 0000000000000000000000000000000000000000000000000000000000015180
Arg [3] : 000000000000000000000000762340b8a40cdd5bfc3edd94265899fda345d0e3
Arg [4] : 0000000000000000000000009742a1df1121886c8ba3b764fd3e47218da85d8c
Arg [5] : 00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb7


Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.