ETH Price: $2,633.87 (+1.87%)

Contract

0x76e61Bea3eC3594F0AD9d6EAdDa91cd2df2FbB08
 

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
177215122023-07-18 17:11:23400 days ago1689700283  Contract Creation0 ETH
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
NFTMarket

Compiler Version
v0.8.20+commit.a1b79de6

Optimization Enabled:
Yes with 3500 runs

Other Settings:
default evmVersion
File 1 of 37 : 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 "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";

import "./mixins/shared/Constants.sol";
import "./mixins/shared/FETHNode.sol";
import "./mixins/shared/FoundationTreasuryNode.sol";
import "./mixins/shared/MarketFees.sol";
import "./mixins/shared/MarketSharedCore.sol";
import "./mixins/shared/RouterContext.sol";
import "./mixins/shared/SendValueWithFallbackWithdraw.sol";

import "./mixins/nftMarket/NFTMarketAuction.sol";
import "./mixins/nftMarket/NFTMarketBuyPrice.sol";
import "./mixins/nftMarket/NFTMarketCore.sol";
import "./mixins/nftMarket/NFTMarketOffer.sol";
import "./mixins/nftMarket/NFTMarketPrivateSaleGap.sol";
import "./mixins/nftMarket/NFTMarketReserveAuction.sol";
import "./mixins/nftMarket/NFTMarketExhibition.sol";

/**
 * @title A market for NFTs on Foundation.
 * @notice The Foundation marketplace is a contract which allows traders to buy and sell NFTs.
 * It supports buying and selling via auctions, private sales, buy price, and offers.
 * @dev All sales in the Foundation market will pay the creator 10% royalties on secondary sales. This is not specific
 * to NFTs minted on Foundation, it should work for any NFT. If royalty information was not defined when the NFT was
 * originally deployed, it may be added using the [Royalty Registry](https://royaltyregistry.xyz/) which will be
 * respected by our market contract.
 * @author batu-inal & HardlyDifficult
 */
contract NFTMarket is
  Initializable,
  FoundationTreasuryNode,
  ContextUpgradeable,
  RouterContext,
  FETHNode,
  MarketSharedCore,
  NFTMarketCore,
  ReentrancyGuardUpgradeable,
  SendValueWithFallbackWithdraw,
  MarketFees,
  NFTMarketExhibition,
  NFTMarketAuction,
  NFTMarketReserveAuction,
  NFTMarketPrivateSaleGap,
  NFTMarketBuyPrice,
  NFTMarketOffer
{
  /**
   * @notice Set immutable variables for the implementation contract.
   * @dev Using immutable instead of constants allows us to use different values on testnet.
   * @param treasury The Foundation Treasury contract address.
   * @param feth The FETH ERC-20 token contract address.
   * @param royaltyRegistry The Royalty Registry contract address.
   * @param duration The duration of the auction in seconds.
   */
  constructor(
    address payable treasury,
    address feth,
    address royaltyRegistry,
    uint256 duration,
    address router
  )
    FoundationTreasuryNode(treasury)
    FETHNode(feth)
    MarketFees(
      /* protocolFeeInBasisPoints: */
      500,
      royaltyRegistry,
      /* assumePrimarySale: */
      false
    )
    NFTMarketReserveAuction(duration)
    RouterContext(router)
  {
    _disableInitializers();
  }

  /**
   * @notice Called once to configure the contract after the initial proxy deployment.
   * @dev This farms the initialize call out to inherited contracts as needed to initialize mutable variables.
   */
  function initialize() external initializer {
    NFTMarketAuction._initializeNFTMarketAuction();
  }

  /**
   * @inheritdoc NFTMarketCore
   */
  function _beforeAuctionStarted(
    address nftContract,
    uint256 tokenId
  ) internal override(NFTMarketCore, NFTMarketBuyPrice, NFTMarketOffer) {
    // This is a no-op function required to avoid compile errors.
    super._beforeAuctionStarted(nftContract, tokenId);
  }

  /**
   * @inheritdoc NFTMarketCore
   */
  function _transferFromEscrow(
    address nftContract,
    uint256 tokenId,
    address recipient,
    address authorizeSeller
  ) internal override(NFTMarketCore, NFTMarketReserveAuction, NFTMarketBuyPrice) {
    // This is a no-op function required to avoid compile errors.
    super._transferFromEscrow(nftContract, tokenId, recipient, authorizeSeller);
  }

  /**
   * @inheritdoc NFTMarketCore
   */
  function _transferFromEscrowIfAvailable(
    address nftContract,
    uint256 tokenId,
    address recipient
  ) internal override(NFTMarketCore, NFTMarketReserveAuction, NFTMarketBuyPrice) {
    // This is a no-op function required to avoid compile errors.
    super._transferFromEscrowIfAvailable(nftContract, tokenId, recipient);
  }

  /**
   * @inheritdoc NFTMarketCore
   */
  function _transferToEscrow(
    address nftContract,
    uint256 tokenId
  ) internal override(NFTMarketCore, NFTMarketReserveAuction, NFTMarketBuyPrice) {
    // This is a no-op function required to avoid compile errors.
    super._transferToEscrow(nftContract, tokenId);
  }

  /**
   * @inheritdoc MarketSharedCore
   */
  function _getSellerOf(
    address nftContract,
    uint256 tokenId
  )
    internal
    view
    override(MarketSharedCore, NFTMarketReserveAuction, NFTMarketBuyPrice)
    returns (address payable seller)
  {
    // This is a no-op function required to avoid compile errors.
    seller = super._getSellerOf(nftContract, tokenId);
  }

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

File 2 of 37 : IRoyaltyRegistry.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/// @author: manifold.xyz

import "@openzeppelin/contracts/utils/introspection/IERC165.sol";

/**
 * @dev Royalty registry interface
 */
interface IRoyaltyRegistry is IERC165 {
    event RoyaltyOverride(address owner, address tokenAddress, address royaltyAddress);

    /**
     * Override the location of where to look up royalty information for a given token contract.
     * Allows for backwards compatibility and implementation of royalty logic for contracts that did not previously support them.
     *
     * @param tokenAddress    - The token address you wish to override
     * @param royaltyAddress  - The royalty override address
     */
    function setRoyaltyLookupAddress(address tokenAddress, address royaltyAddress) external returns (bool);

    /**
     * Returns royalty address location.  Returns the tokenAddress by default, or the override if it exists
     *
     * @param tokenAddress    - The token address you are looking up the royalty for
     */
    function getRoyaltyLookupAddress(address tokenAddress) external view returns (address);

    /**
     * Returns the token address that an overrideAddress is set for.
     * Note: will not be accurate if the override was created before this function was added.
     *
     * @param overrideAddress - The override address you are looking up the token for
     */
    function getOverrideLookupTokenAddress(address overrideAddress) external view returns (address);

    /**
     * Whether or not the message sender can override the royalty address for the given token address
     *
     * @param tokenAddress    - The token address you are looking up the royalty for
     */
    function overrideAllowed(address tokenAddress) external view returns (bool);
}

File 3 of 37 : 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 4 of 37 : 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 5 of 37 : 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 6 of 37 : 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 7 of 37 : 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 8 of 37 : ERC165Checker.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/introspection/ERC165Checker.sol)

pragma solidity ^0.8.0;

import "./IERC165.sol";

/**
 * @dev Library used to query support of an interface declared via {IERC165}.
 *
 * Note that these functions return the actual result of the query: they do not
 * `revert` if an interface is not supported. It is up to the caller to decide
 * what to do in these cases.
 */
library ERC165Checker {
    // As per the EIP-165 spec, no interface should ever match 0xffffffff
    bytes4 private constant _INTERFACE_ID_INVALID = 0xffffffff;

    /**
     * @dev Returns true if `account` supports the {IERC165} interface.
     */
    function supportsERC165(address account) internal view returns (bool) {
        // Any contract that implements ERC165 must explicitly indicate support of
        // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid
        return
            supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId) &&
            !supportsERC165InterfaceUnchecked(account, _INTERFACE_ID_INVALID);
    }

    /**
     * @dev Returns true if `account` supports the interface defined by
     * `interfaceId`. Support for {IERC165} itself is queried automatically.
     *
     * See {IERC165-supportsInterface}.
     */
    function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) {
        // query support of both ERC165 as per the spec and support of _interfaceId
        return supportsERC165(account) && supportsERC165InterfaceUnchecked(account, interfaceId);
    }

    /**
     * @dev Returns a boolean array where each value corresponds to the
     * interfaces passed in and whether they're supported or not. This allows
     * you to batch check interfaces for a contract where your expectation
     * is that some interfaces may not be supported.
     *
     * See {IERC165-supportsInterface}.
     *
     * _Available since v3.4._
     */
    function getSupportedInterfaces(
        address account,
        bytes4[] memory interfaceIds
    ) internal view returns (bool[] memory) {
        // an array of booleans corresponding to interfaceIds and whether they're supported or not
        bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length);

        // query support of ERC165 itself
        if (supportsERC165(account)) {
            // query support of each interface in interfaceIds
            for (uint256 i = 0; i < interfaceIds.length; i++) {
                interfaceIdsSupported[i] = supportsERC165InterfaceUnchecked(account, interfaceIds[i]);
            }
        }

        return interfaceIdsSupported;
    }

    /**
     * @dev Returns true if `account` supports all the interfaces defined in
     * `interfaceIds`. Support for {IERC165} itself is queried automatically.
     *
     * Batch-querying can lead to gas savings by skipping repeated checks for
     * {IERC165} support.
     *
     * See {IERC165-supportsInterface}.
     */
    function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) {
        // query support of ERC165 itself
        if (!supportsERC165(account)) {
            return false;
        }

        // query support of each interface in interfaceIds
        for (uint256 i = 0; i < interfaceIds.length; i++) {
            if (!supportsERC165InterfaceUnchecked(account, interfaceIds[i])) {
                return false;
            }
        }

        // all interfaces supported
        return true;
    }

    /**
     * @notice Query if a contract implements an interface, does not check ERC165 support
     * @param account The address of the contract to query for support of an interface
     * @param interfaceId The interface identifier, as specified in ERC-165
     * @return true if the contract at account indicates support of the interface with
     * identifier interfaceId, false otherwise
     * @dev Assumes that account contains a contract that supports ERC165, otherwise
     * the behavior of this method is undefined. This precondition can be checked
     * with {supportsERC165}.
     *
     * Some precompiled contracts will falsely indicate support for a given interface, so caution
     * should be exercised when using this function.
     *
     * Interface identification is specified in ERC-165.
     */
    function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) {
        // prepare call
        bytes memory encodedParams = abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId);

        // perform static call
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly {
            success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0x00)
        }

        return success && returnSize >= 0x20 && returnValue > 0;
    }
}

File 9 of 37 : 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 10 of 37 : 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 11 of 37 : 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 12 of 37 : 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 exhibitionId,
    address seller
  ) external view returns (bool allowedSeller);

  function getExhibitionPaymentDetails(
    uint256 exhibitionId
  ) external view returns (address payable curator, uint16 takeRateInBasisPoints);
}

File 13 of 37 : 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 14 of 37 : 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 15 of 37 : 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 setBuyPrice(address nftContract, uint256 tokenId, uint256 price) external;
}

File 16 of 37 : 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 createReserveAuctionV3(
    address nftContract,
    uint256 tokenId,
    uint256 exhibitionId,
    uint256 reservePrice,
    uint256 duration
  ) external returns (uint256 auctionId);
}

File 17 of 37 : IGetFees.sol
// SPDX-License-Identifier: MIT OR Apache-2.0

pragma solidity ^0.8.18;

/**
 * @notice An interface for communicating fees to 3rd party marketplaces.
 * @dev Originally implemented in mainnet contract 0x44d6e8933f8271abcf253c72f9ed7e0e4c0323b3
 */
interface IGetFees {
  /**
   * @notice Get the recipient addresses to which creator royalties should be sent.
   * @dev The expected royalty amounts are communicated with `getFeeBps`.
   * @param tokenId The ID of the NFT to get royalties for.
   * @return recipients An array of addresses to which royalties should be sent.
   */
  function getFeeRecipients(uint256 tokenId) external view returns (address payable[] memory recipients);

  /**
   * @notice Get the creator royalty amounts to be sent to each recipient, in basis points.
   * @dev The expected recipients are communicated with `getFeeRecipients`.
   * @param tokenId The ID of the NFT to get royalties for.
   * @return royaltiesInBasisPoints The array of fees to be sent to each recipient, in basis points.
   */
  function getFeeBps(uint256 tokenId) external view returns (uint256[] memory royaltiesInBasisPoints);
}

File 18 of 37 : IGetRoyalties.sol
// SPDX-License-Identifier: MIT OR Apache-2.0

pragma solidity ^0.8.18;

interface IGetRoyalties {
  /**
   * @notice Get the creator royalties to be sent.
   * @dev The data is the same as when calling `getFeeRecipients` and `getFeeBps` separately.
   * @param tokenId The ID of the NFT to get royalties for.
   * @return recipients An array of addresses to which royalties should be sent.
   * @return royaltiesInBasisPoints The array of fees to be sent to each recipient, in basis points.
   */
  function getRoyalties(
    uint256 tokenId
  ) external view returns (address payable[] memory recipients, uint256[] memory royaltiesInBasisPoints);
}

File 19 of 37 : IOwnable.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.18;

interface IOwnable {
  /**
   * @dev Returns the address of the current owner.
   */
  function owner() external view returns (address);
}

File 20 of 37 : IRoyaltyInfo.sol
// SPDX-License-Identifier: MIT OR Apache-2.0

pragma solidity ^0.8.18;

/**
 * @notice Interface for EIP-2981: NFT Royalty Standard.
 * For more see: https://eips.ethereum.org/EIPS/eip-2981.
 */
interface IRoyaltyInfo {
  /**
   * @notice Get the creator royalties to be sent.
   * @param tokenId The ID of the NFT to get royalties for.
   * @param salePrice The total price of the sale.
   * @return receiver The address to which royalties should be sent.
   * @return royaltyAmount The total amount that should be sent to the `receiver`.
   */
  function royaltyInfo(
    uint256 tokenId,
    uint256 salePrice
  ) external view returns (address receiver, uint256 royaltyAmount);
}

File 21 of 37 : ITokenCreator.sol
// SPDX-License-Identifier: MIT OR Apache-2.0

pragma solidity ^0.8.18;

interface ITokenCreator {
  /**
   * @notice Returns the creator of this NFT collection.
   * @param tokenId The ID of the NFT to get the creator payment address for.
   * @return creator The creator of this collection.
   */
  function tokenCreator(uint256 tokenId) external view returns (address payable creator);
}

File 22 of 37 : ArrayLibrary.sol
// SPDX-License-Identifier: MIT OR Apache-2.0

pragma solidity ^0.8.18;

/**
 * @title Helper functions for arrays.
 * @author batu-inal & HardlyDifficult
 */
library ArrayLibrary {
  /**
   * @notice Reduces the size of an array if it's greater than the specified max size,
   * using the first maxSize elements.
   */
  function capLength(address payable[] memory data, uint256 maxLength) internal pure {
    if (data.length > maxLength) {
      assembly {
        mstore(data, maxLength)
      }
    }
  }

  /**
   * @notice Reduces the size of an array if it's greater than the specified max size,
   * using the first maxSize elements.
   */
  function capLength(uint256[] memory data, uint256 maxLength) internal pure {
    if (data.length > maxLength) {
      assembly {
        mstore(data, maxLength)
      }
    }
  }
}

File 23 of 37 : 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 37 : 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 Called once to configure the contract after the initial proxy deployment.
   * @dev This sets the initial auction id to 1, making the first auction cheaper
   * and id 0 represents no auction found.
   */
  function _initializeNFTMarketAuction() internal {
    nextAuctionId = 1;
  }

  /**
   * @notice Returns id to assign to the next auction.
   */
  function _getNextAndIncrementAuctionId() internal returns (uint256) {
    // AuctionId cannot overflow 256 bits.
    unchecked {
      return nextAuctionId++;
    }
  }

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

File 25 of 37 : 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/routes/INFTMarketBuyNow.sol";

import "../shared/MarketFees.sol";
import "../shared/FoundationTreasuryNode.sol";
import "../shared/FETHNode.sol";
import "../shared/MarketSharedCore.sol";
import "../shared/SendValueWithFallbackWithdraw.sol";

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

/// @param buyPrice The current buy price set for this NFT.
error NFTMarketBuyPrice_Cannot_Buy_At_Lower_Price(uint256 buyPrice);
error NFTMarketBuyPrice_Cannot_Buy_Unset_Price();
error NFTMarketBuyPrice_Cannot_Cancel_Unset_Price();
/// @param owner The current owner of this NFT.
error NFTMarketBuyPrice_Only_Owner_Can_Cancel_Price(address owner);
/// @param owner The current owner of this NFT.
error NFTMarketBuyPrice_Only_Owner_Can_Set_Price(address owner);
error NFTMarketBuyPrice_Price_Already_Set();
error NFTMarketBuyPrice_Price_Too_High();
/// @param seller The current owner of this NFT.
error NFTMarketBuyPrice_Seller_Mismatch(address seller);

/**
 * @title Allows sellers to set a buy price of their NFTs that may be accepted and instantly transferred to the buyer.
 * @notice NFTs with a buy price set are escrowed in the market contract.
 * @author batu-inal & HardlyDifficult
 */
abstract contract NFTMarketBuyPrice is
  INFTMarketBuyNow,
  FoundationTreasuryNode,
  ContextUpgradeable,
  FETHNode,
  MarketSharedCore,
  NFTMarketCore,
  ReentrancyGuardUpgradeable,
  SendValueWithFallbackWithdraw,
  MarketFees,
  NFTMarketExhibition
{
  using AddressUpgradeable for address payable;

  /// @notice Stores the buy price details for a specific NFT.
  /// @dev The struct is packed into a single slot to optimize gas.
  struct BuyPrice {
    /// @notice The current owner of this NFT which set a buy price.
    /// @dev A zero price is acceptable so a non-zero address determines whether a price has been set.
    address payable seller;
    /// @notice The current buy price set for this NFT.
    uint96 price;
  }

  /// @notice Stores the current buy price for each NFT.
  mapping(address => mapping(uint256 => BuyPrice)) private nftContractToTokenIdToBuyPrice;

  /**
   * @notice Emitted when an NFT is bought by accepting the buy price,
   * indicating that the NFT has been transferred and revenue from the sale distributed.
   * @dev The total buy price that was accepted is `totalFees` + `creatorRev` + `sellerRev`.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @param buyer The address of the collector that purchased the NFT using `buy`.
   * @param seller The address of the seller which originally set the buy price.
   * @param totalFees The amount of ETH that was sent to Foundation & referrals for this sale.
   * @param creatorRev The amount of ETH that was sent to the creator for this sale.
   * @param sellerRev The amount of ETH that was sent to the owner for this sale.
   */
  event BuyPriceAccepted(
    address indexed nftContract,
    uint256 indexed tokenId,
    address indexed seller,
    address buyer,
    uint256 totalFees,
    uint256 creatorRev,
    uint256 sellerRev
  );
  /**
   * @notice Emitted when the buy price is removed by the owner of an NFT.
   * @dev The NFT is transferred back to the owner unless it's still escrowed for another market tool,
   * e.g. listed for sale in an auction.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   */
  event BuyPriceCanceled(address indexed nftContract, uint256 indexed tokenId);
  /**
   * @notice Emitted when a buy price is invalidated due to other market activity.
   * @dev This occurs when the buy price is no longer eligible to be accepted,
   * e.g. when a bid is placed in an auction for this NFT.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   */
  event BuyPriceInvalidated(address indexed nftContract, uint256 indexed tokenId);
  /**
   * @notice Emitted when a buy price is set by the owner of an NFT.
   * @dev The NFT is transferred into the market contract for escrow unless it was already escrowed,
   * e.g. for auction listing.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @param seller The address of the NFT owner which set the buy price.
   * @param price The price of the NFT.
   */
  event BuyPriceSet(address indexed nftContract, uint256 indexed tokenId, address indexed seller, uint256 price);

  /**
   * @notice [DEPRECATED] use `buyV2` instead.
   * Buy the NFT at the set buy price.
   * `msg.value` must be <= `maxPrice` and any delta will be taken from the account's available FETH balance.
   * @dev `maxPrice` protects the buyer in case a the price is increased but allows the transaction to continue
   * when the price is reduced (and any surplus funds provided are refunded).
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @param maxPrice The maximum price to pay for the NFT.
   */
  function buy(address nftContract, uint256 tokenId, uint256 maxPrice) external payable {
    buyV2(nftContract, tokenId, maxPrice, payable(0));
  }

  /**
   * @notice Buy the NFT at the set buy price.
   * `msg.value` must be <= `maxPrice` and any delta will be taken from the account's available FETH balance.
   * @dev `maxPrice` protects the buyer in case a the price is increased but allows the transaction to continue
   * when the price is reduced (and any surplus funds provided are refunded).
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @param maxPrice The maximum price to pay for the NFT.
   * @param referrer The address of the referrer.
   */
  function buyV2(address nftContract, uint256 tokenId, uint256 maxPrice, address payable referrer) public payable {
    BuyPrice storage buyPrice = nftContractToTokenIdToBuyPrice[nftContract][tokenId];
    if (buyPrice.price > maxPrice) {
      revert NFTMarketBuyPrice_Cannot_Buy_At_Lower_Price(buyPrice.price);
    } else if (buyPrice.seller == address(0)) {
      revert NFTMarketBuyPrice_Cannot_Buy_Unset_Price();
    }

    _buy(nftContract, tokenId, referrer);
  }

  /**
   * @notice Removes the buy price set for an NFT.
   * @dev The NFT is transferred back to the owner unless it's still escrowed for another market tool,
   * e.g. listed for sale in an auction.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   */
  function cancelBuyPrice(address nftContract, uint256 tokenId) external nonReentrant {
    address seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller;
    address sender = _msgSender();
    if (seller == address(0)) {
      // This check is redundant with the next one, but done in order to provide a more clear error message.
      revert NFTMarketBuyPrice_Cannot_Cancel_Unset_Price();
    } else if (seller != sender) {
      revert NFTMarketBuyPrice_Only_Owner_Can_Cancel_Price(seller);
    }

    // Remove the buy price
    delete nftContractToTokenIdToBuyPrice[nftContract][tokenId];

    // Transfer the NFT back to the owner if it is not listed in auction.
    _transferFromEscrowIfAvailable(nftContract, tokenId, sender);

    emit BuyPriceCanceled(nftContract, tokenId);
  }

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

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

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

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

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

    address payable sender = payable(_msgSender());

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

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

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

  /**
   * @notice If there is a buy price at this price or lower, accept that and return true.
   */
  function _autoAcceptBuyPrice(
    address nftContract,
    uint256 tokenId,
    uint256 maxPrice
  ) internal override returns (bool) {
    BuyPrice storage buyPrice = nftContractToTokenIdToBuyPrice[nftContract][tokenId];
    if (buyPrice.seller == address(0) || buyPrice.price > maxPrice) {
      // No buy price was found, or the price is too high.
      return false;
    }

    _buy(nftContract, tokenId, payable(0));
    return true;
  }

  /**
   * @inheritdoc NFTMarketCore
   * @dev Invalidates the buy price on a auction start, if one is found.
   */
  function _beforeAuctionStarted(address nftContract, uint256 tokenId) internal virtual override {
    BuyPrice storage buyPrice = nftContractToTokenIdToBuyPrice[nftContract][tokenId];
    if (buyPrice.seller != address(0)) {
      // A buy price was set for this NFT, invalidate it.
      _invalidateBuyPrice(nftContract, tokenId);
    }
    super._beforeAuctionStarted(nftContract, tokenId);
  }

  /**
   * @notice Process the purchase of an NFT at the current buy price.
   * @dev The caller must confirm that the seller != address(0) before calling this function.
   */
  function _buy(address nftContract, uint256 tokenId, address payable referrer) private nonReentrant {
    BuyPrice memory buyPrice = nftContractToTokenIdToBuyPrice[nftContract][tokenId];

    // Remove the buy now price
    delete nftContractToTokenIdToBuyPrice[nftContract][tokenId];

    // Cancel the buyer's offer if there is one in order to free up their FETH balance
    // even if they don't need the FETH for this specific purchase.
    _cancelSendersOffer(nftContract, tokenId);

    _tryUseFETHBalance(buyPrice.price, true);

    address sender = _msgSender();

    (
      address payable sellerReferrerPaymentAddress,
      uint16 sellerReferrerTakeRateInBasisPoints
    ) = _getExhibitionForPayment(nftContract, tokenId);

    // Transfer the NFT to the buyer.
    // The seller was already authorized when the buyPrice was set originally set.
    _transferFromEscrow(nftContract, tokenId, sender, address(0));

    // Distribute revenue for this sale.
    (uint256 totalFees, uint256 creatorRev, uint256 sellerRev) = _distributeFunds(
      nftContract,
      tokenId,
      buyPrice.seller,
      buyPrice.price,
      referrer,
      sellerReferrerPaymentAddress,
      sellerReferrerTakeRateInBasisPoints
    );

    emit BuyPriceAccepted(nftContract, tokenId, buyPrice.seller, sender, totalFees, creatorRev, sellerRev);
  }

  /**
   * @notice Clear a buy price and emit BuyPriceInvalidated.
   * @dev The caller must confirm the buy price is set before calling this function.
   */
  function _invalidateBuyPrice(address nftContract, uint256 tokenId) private {
    delete nftContractToTokenIdToBuyPrice[nftContract][tokenId];
    emit BuyPriceInvalidated(nftContract, tokenId);
  }

  /**
   * @inheritdoc NFTMarketCore
   * @dev Invalidates the buy price if one is found before transferring the NFT.
   * This will revert if there is a buy price set but the `authorizeSeller` is not the owner.
   */
  function _transferFromEscrow(
    address nftContract,
    uint256 tokenId,
    address recipient,
    address authorizeSeller
  ) internal virtual override {
    address seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller;
    if (seller != address(0)) {
      // A buy price was set for this NFT.
      // `authorizeSeller != address(0) &&` could be added when other mixins use this flow.
      // ATM that additional check would never return false.
      if (seller != authorizeSeller) {
        // When there is a buy price set, the `buyPrice.seller` is the owner of the NFT.
        revert NFTMarketBuyPrice_Seller_Mismatch(seller);
      }
      // The seller authorization has been confirmed.
      authorizeSeller = address(0);

      // Invalidate the buy price as the NFT will no longer be in escrow.
      _invalidateBuyPrice(nftContract, tokenId);
    }

    super._transferFromEscrow(nftContract, tokenId, recipient, authorizeSeller);
  }

  /**
   * @inheritdoc NFTMarketCore
   * @dev Checks if there is a buy price set, if not then allow the transfer to proceed.
   */
  function _transferFromEscrowIfAvailable(
    address nftContract,
    uint256 tokenId,
    address recipient
  ) internal virtual override {
    address seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller;
    if (seller == address(0)) {
      // A buy price has been set for this NFT so it should remain in escrow.
      super._transferFromEscrowIfAvailable(nftContract, tokenId, recipient);
    }
  }

  /**
   * @inheritdoc NFTMarketCore
   * @dev Checks if the NFT is already in escrow for buy now.
   */
  function _transferToEscrow(address nftContract, uint256 tokenId) internal virtual override {
    address seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller;
    if (seller == address(0)) {
      // The NFT is not in escrow for buy now.
      super._transferToEscrow(nftContract, tokenId);
    } else if (seller != _msgSender()) {
      // When there is a buy price set, the `seller` is the owner of the NFT.
      revert NFTMarketBuyPrice_Seller_Mismatch(seller);
    }
  }

  /**
   * @notice Returns the buy price details for an NFT if one is available.
   * @dev If no price is found, seller will be address(0) and price will be max uint256.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @return seller The address of the owner that listed a buy price for this NFT.
   * Returns `address(0)` if there is no buy price set for this NFT.
   * @return price The price of the NFT.
   * Returns max uint256 if there is no buy price set for this NFT (since a price of 0 is supported).
   */
  function getBuyPrice(address nftContract, uint256 tokenId) external view returns (address seller, uint256 price) {
    seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller;
    if (seller == address(0)) {
      return (seller, type(uint256).max);
    }
    price = nftContractToTokenIdToBuyPrice[nftContract][tokenId].price;
  }

  /**
   * @inheritdoc MarketSharedCore
   * @dev Returns the seller if there is a buy price set for this NFT, otherwise
   * bubbles the call up for other considerations.
   */
  function _getSellerOf(
    address nftContract,
    uint256 tokenId
  ) internal view virtual override returns (address payable seller) {
    seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller;
    if (seller == address(0)) {
      seller = super._getSellerOf(nftContract, tokenId);
    }
  }

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

File 26 of 37 : 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();

/**
 * @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 Cancel the `msg.sender`'s offer if there is one, freeing up their FETH balance.
   * @dev This should be used when it does not make sense to keep the original offer around,
   * e.g. if a collector accepts a Buy Price then keeping the offer around is not necessary.
   */
  function _cancelSendersOffer(address nftContract, uint256 tokenId) internal virtual;

  /**
   * @notice Transfers the NFT from escrow and clears any state tracking this escrowed NFT.
   * @param authorizeSeller The address of the seller pending authorization.
   * Once it's been authorized by one of the escrow managers, it should be set to address(0)
   * indicated that it's no longer pending authorization.
   */
  function _transferFromEscrow(
    address nftContract,
    uint256 tokenId,
    address recipient,
    address authorizeSeller
  ) internal virtual {
    if (authorizeSeller != address(0)) {
      revert NFTMarketCore_Seller_Not_Found();
    }
    IERC721(nftContract).transferFrom(address(this), recipient, tokenId);
  }

  /**
   * @notice Transfers the NFT from escrow unless there is another reason for it to remain in escrow.
   */
  function _transferFromEscrowIfAvailable(address nftContract, uint256 tokenId, address recipient) internal virtual {
    _transferFromEscrow(nftContract, tokenId, recipient, address(0));
  }

  /**
   * @notice Transfers an NFT into escrow,
   * if already there this requires the msg.sender is authorized to manage the sale of this NFT.
   */
  function _transferToEscrow(address nftContract, uint256 tokenId) internal virtual {
    IERC721(nftContract).transferFrom(_msgSender(), address(this), tokenId);
  }

  /**
   * @dev Determines the minimum amount when increasing an existing offer or bid.
   */
  function _getMinIncrement(uint256 currentAmount) internal pure returns (uint256) {
    uint256 minIncrement = currentAmount;
    unchecked {
      minIncrement /= MIN_PERCENT_INCREMENT_DENOMINATOR;
    }
    if (minIncrement == 0) {
      // Since minIncrement reduces from the currentAmount, this cannot overflow.
      // The next amount must be at least 1 wei greater than the current.
      return currentAmount + 1;
    }

    return minIncrement + currentAmount;
  }

  /**
   * @inheritdoc MarketSharedCore
   */
  function _getSellerOrOwnerOf(
    address nftContract,
    uint256 tokenId
  ) internal view override returns (address payable sellerOrOwner) {
    sellerOrOwner = _getSellerOf(nftContract, tokenId);
    if (sellerOrOwner == address(0)) {
      sellerOrOwner = payable(IERC721(nftContract).ownerOf(tokenId));
    }
  }

  /**
   * @notice Checks if an escrowed NFT is currently in active auction.
   * @return Returns false if the auction has ended, even if it has not yet been settled.
   */
  function _isInActiveAuction(address nftContract, uint256 tokenId) internal view virtual returns (bool);

  /**
   * @notice This empty reserved space is put in place to allow future versions to add new
   * variables without shifting down storage in the inheritance chain.
   * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
   * @dev 50 slots were consumed by adding `ReentrancyGuard`.
   */
  uint256[450] private __gap;
}

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

pragma solidity ^0.8.18;

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

import "../../interfaces/internal/INFTMarketExhibition.sol";
import "../shared/Constants.sol";

/// @param curator The curator for this exhibition.
error NFTMarketExhibition_Caller_Is_Not_Curator(address curator);
error NFTMarketExhibition_Can_Not_Add_Dupe_Seller();
error NFTMarketExhibition_Curator_Automatically_Allowed();
error NFTMarketExhibition_Exhibition_Does_Not_Exist();
error NFTMarketExhibition_Seller_Not_Allowed_In_Exhibition();
error NFTMarketExhibition_Sellers_Required();
error NFTMarketExhibition_Take_Rate_Too_High();

/**
 * @title Enables a curation surface for sellers to exhibit their NFTs.
 * @author HardlyDifficult
 */
abstract contract NFTMarketExhibition is INFTMarketExhibition, ContextUpgradeable {
  /**
   * @notice Stores details about an exhibition.
   */
  struct Exhibition {
    /// @notice The curator which created this exhibition.
    address payable curator;
    /// @notice The rate of the sale which goes to the curator.
    uint16 takeRateInBasisPoints;
    // 80-bits available in the first slot

    /// @notice A name for the exhibition.
    string name;
  }

  /// @notice Tracks the next sequence ID to be assigned to an exhibition.
  uint256 private $latestExhibitionId;

  /// @notice Maps the exhibition ID to their details.
  mapping(uint256 => Exhibition) private $idToExhibition;

  /// @notice Maps an exhibition to the list of sellers allowed to list with it.
  mapping(uint256 => mapping(address => bool)) private $exhibitionIdToSellerToIsAllowed;

  /// @notice Maps an NFT to the exhibition it was listed with.
  mapping(address => mapping(uint256 => uint256)) private $nftContractToTokenIdToExhibitionId;

  /**
   * @notice Emitted when an exhibition is created.
   * @param exhibitionId The ID for this exhibition.
   * @param curator The curator which created this exhibition.
   * @param name The name for this exhibition.
   * @param takeRateInBasisPoints The rate of the sale which goes to the curator.
   */
  event ExhibitionCreated(
    uint256 indexed exhibitionId,
    address indexed curator,
    string name,
    uint16 takeRateInBasisPoints
  );

  /**
   * @notice Emitted when an exhibition is deleted.
   * @param exhibitionId The ID for the exhibition.
   */
  event ExhibitionDeleted(uint256 indexed exhibitionId);

  /**
   * @notice Emitted when an NFT is listed in an exhibition.
   * @param nftContract The contract address of the NFT.
   * @param tokenId The ID of the NFT.
   * @param exhibitionId The ID of the exhibition it was listed with.
   */
  event NftAddedToExhibition(address indexed nftContract, uint256 indexed tokenId, uint256 indexed exhibitionId);

  /**
   * @notice Emitted when an NFT is no longer associated with an exhibition for reasons other than a sale.
   * @param nftContract The contract address of the NFT.
   * @param tokenId The ID of the NFT.
   * @param exhibitionId The ID of the exhibition it was originally listed with.
   */
  event NftRemovedFromExhibition(address indexed nftContract, uint256 indexed tokenId, uint256 indexed exhibitionId);

  /**
   * @notice Emitted when sellers are granted access to list with an exhibition.
   * @param exhibitionId The ID of the exhibition.
   * @param sellers The list of sellers granted access.
   */
  event SellersAddedToExhibition(uint256 indexed exhibitionId, address[] sellers);

  /// @notice Requires the caller to be the curator of the exhibition.
  modifier onlyExhibitionCurator(uint256 exhibitionId) {
    address curator = $idToExhibition[exhibitionId].curator;
    if (curator != _msgSender()) {
      if (curator == address(0)) {
        // If the curator is not a match, check if the exhibition exists in order to provide a better error message.
        revert NFTMarketExhibition_Exhibition_Does_Not_Exist();
      }
      revert NFTMarketExhibition_Caller_Is_Not_Curator(curator);
    }
    _;
  }

  /// @notice Requires the caller pass in some number of sellers
  modifier sellersRequired(address[] calldata sellers) {
    if (sellers.length == 0) {
      revert NFTMarketExhibition_Sellers_Required();
    }
    _;
  }

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

  /**
   * @notice Creates an exhibition.
   * @param name The name for this exhibition.
   * @param takeRateInBasisPoints The rate of the sale which goes to the msg.sender as the curator of this exhibition.
   * @param sellers The list of sellers allowed to list with this exhibition.
   * @dev The list of sellers may be modified after the exhibition is created via addSellersToExhibition,
   *      which only allows for adding (not removing) new sellers.
   */
  function createExhibition(
    string calldata name,
    uint16 takeRateInBasisPoints,
    address[] calldata sellers
  ) external sellersRequired(sellers) returns (uint256 exhibitionId) {
    if (takeRateInBasisPoints > MAX_EXHIBITION_TAKE_RATE) {
      revert NFTMarketExhibition_Take_Rate_Too_High();
    }

    // Create exhibition
    unchecked {
      exhibitionId = ++$latestExhibitionId;
    }
    address payable sender = payable(_msgSender());
    $idToExhibition[exhibitionId] = Exhibition({
      curator: sender,
      takeRateInBasisPoints: takeRateInBasisPoints,
      name: name
    });
    emit ExhibitionCreated({
      exhibitionId: exhibitionId,
      curator: sender,
      name: name,
      takeRateInBasisPoints: takeRateInBasisPoints
    });

    _addSellersToExhibition(exhibitionId, sellers);
  }

  /**
   * @notice Deletes an exhibition created by the msg.sender.
   * @param exhibitionId The ID of the exhibition to delete.
   * @dev Once deleted, any NFTs listed with this exhibition will still be listed but will no longer be associated with
   * or share revenue with the exhibition.
   */
  function deleteExhibition(uint256 exhibitionId) external onlyExhibitionCurator(exhibitionId) {
    delete $idToExhibition[exhibitionId];
    emit ExhibitionDeleted(exhibitionId);
  }

  /**
   * @notice Returns exhibition details for a given ID.
   * @param exhibitionId The ID of the exhibition to look up.
   * @return name The name of the exhibition.
   * @return curator The curator of the exhibition.
   * @return takeRateInBasisPoints The rate of the sale which goes to the curator.
   * @dev If the exhibition does not exist or has since been deleted, the curator will be address(0).
   */
  function getExhibition(
    uint256 exhibitionId
  ) external view returns (string memory name, address payable curator, uint16 takeRateInBasisPoints) {
    Exhibition memory exhibition = $idToExhibition[exhibitionId];
    name = exhibition.name;
    curator = exhibition.curator;
    takeRateInBasisPoints = exhibition.takeRateInBasisPoints;
  }

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

  /**
   * @notice Adds sellers to exhibition.
   * @param exhibitionId The exhibition ID.
   * @param sellers The new list of sellers to be allowed to list with this exhibition.
   */
  function addSellersToExhibition(
    uint256 exhibitionId,
    address[] calldata sellers
  ) external onlyExhibitionCurator(exhibitionId) sellersRequired(sellers) {
    _addSellersToExhibition(exhibitionId, sellers);
  }

  function _addSellersToExhibition(uint256 exhibitionId, address[] calldata sellers) private {
    // Populate allow list
    for (uint256 i = 0; i < sellers.length; ) {
      address seller = sellers[i];
      if ($exhibitionIdToSellerToIsAllowed[exhibitionId][seller]) {
        revert NFTMarketExhibition_Can_Not_Add_Dupe_Seller();
      }
      if (seller == _msgSender()) {
        revert NFTMarketExhibition_Curator_Automatically_Allowed();
      }
      $exhibitionIdToSellerToIsAllowed[exhibitionId][seller] = true;
      unchecked {
        ++i;
      }
    }
    emit SellersAddedToExhibition(exhibitionId, sellers);
  }

  /**
   * @notice Checks if a given seller is approved to list with a given exhibition.
   * @param exhibitionId The ID of the exhibition to check.
   * @param seller The address of the seller to check.
   * @return allowedSeller True if the seller is approved to list with the exhibition.
   */
  function isAllowedSellerForExhibition(
    uint256 exhibitionId,
    address seller
  ) external view returns (bool allowedSeller) {
    address curator = $idToExhibition[exhibitionId].curator;
    if (curator != address(0)) {
      allowedSeller = $exhibitionIdToSellerToIsAllowed[exhibitionId][seller] || seller == curator;
    }
  }

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

  /**
   * @notice Assigns an NFT to an exhibition.
   * @param nftContract The contract address of the NFT.
   * @param tokenId The ID of the NFT.
   * @param exhibitionId The ID of the exhibition to list the NFT with.
   * @dev This call is a no-op if the `exhibitionId` is 0.
   */
  function _addNftToExhibition(address nftContract, uint256 tokenId, uint256 exhibitionId) internal {
    if (exhibitionId != 0) {
      Exhibition storage exhibition = $idToExhibition[exhibitionId];
      if (exhibition.curator == address(0)) {
        revert NFTMarketExhibition_Exhibition_Does_Not_Exist();
      }
      address sender = _msgSender();
      if (!$exhibitionIdToSellerToIsAllowed[exhibitionId][sender] && exhibition.curator != sender) {
        revert NFTMarketExhibition_Seller_Not_Allowed_In_Exhibition();
      }
      $nftContractToTokenIdToExhibitionId[nftContract][tokenId] = exhibitionId;
      emit NftAddedToExhibition(nftContract, tokenId, exhibitionId);
    }
  }

  /**
   * @notice Clears an NFT's association with an exhibition.
   */
  function _removeNftFromExhibition(address nftContract, uint256 tokenId) internal {
    uint256 exhibitionId = $nftContractToTokenIdToExhibitionId[nftContract][tokenId];
    if (exhibitionId != 0) {
      delete $nftContractToTokenIdToExhibitionId[nftContract][tokenId];
      emit NftRemovedFromExhibition(nftContract, tokenId, exhibitionId);
    }
  }

  /**
   * @notice Returns the exhibition ID for a given NFT.
   * @param nftContract The contract address of the NFT.
   * @param tokenId The ID of the NFT.
   * @return exhibitionId The ID of the exhibition this NFT is assigned to, or 0 if it's not assigned to an exhibition.
   */
  function getExhibitionIdForNft(address nftContract, uint256 tokenId) external view returns (uint256 exhibitionId) {
    exhibitionId = $nftContractToTokenIdToExhibitionId[nftContract][tokenId];
  }

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

  /**
   * @notice Returns exhibition details if this NFT was assigned to one, and clears the assignment.
   * @return paymentAddress The address to send the payment to, or address(0) if n/a.
   * @return takeRateInBasisPoints The rate of the sale which goes to the curator, or 0 if n/a.
   * @dev This does not emit NftRemovedFromExhibition, instead it's expected that SellerReferralPaid will be emitted.
   */
  function _getExhibitionForPayment(
    address nftContract,
    uint256 tokenId
  ) internal returns (address payable paymentAddress, uint16 takeRateInBasisPoints) {
    uint256 exhibitionId = $nftContractToTokenIdToExhibitionId[nftContract][tokenId];
    if (exhibitionId != 0) {
      paymentAddress = $idToExhibition[exhibitionId].curator;
      takeRateInBasisPoints = $idToExhibition[exhibitionId].takeRateInBasisPoints;
      delete $nftContractToTokenIdToExhibitionId[nftContract][tokenId];
    }
  }

  /**
   * @notice Returns exhibition payment details for a given ID.
   * @param exhibitionId The ID of the exhibition to look up.
   * @return curator The curator of the exhibition.
   * @return takeRateInBasisPoints The rate of the sale which goes to the curator.
   * @dev If the exhibition does not exist or has since been deleted, the curator will be address(0).
   */
  function getExhibitionPaymentDetails(
    uint256 exhibitionId
  ) external view returns (address payable curator, uint16 takeRateInBasisPoints) {
    Exhibition storage exhibition = $idToExhibition[exhibitionId];
    curator = exhibition.curator;
    takeRateInBasisPoints = exhibition.takeRateInBasisPoints;
  }

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

File 28 of 37 : 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/FoundationTreasuryNode.sol";
import "../shared/FETHNode.sol";
import "../shared/SendValueWithFallbackWithdraw.sol";

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

error NFTMarketOffer_Cannot_Be_Made_While_In_Auction();
/// @param currentOfferAmount The current highest offer available for this NFT.
error NFTMarketOffer_Offer_Below_Min_Amount(uint256 currentOfferAmount);
/// @param expiry The time at which the offer had expired.
error NFTMarketOffer_Offer_Expired(uint256 expiry);
/// @param currentOfferFrom The address of the collector which has made the current highest offer.
error NFTMarketOffer_Offer_From_Does_Not_Match(address currentOfferFrom);
/// @param minOfferAmount The minimum amount that must be offered in order for it to be accepted.
error NFTMarketOffer_Offer_Must_Be_At_Least_Min_Amount(uint256 minOfferAmount);

/**
 * @title Allows collectors to make an offer for an NFT, valid for 24-25 hours.
 * @notice Funds are escrowed in the FETH ERC-20 token contract.
 * @author batu-inal & HardlyDifficult
 */
abstract contract NFTMarketOffer is
  FoundationTreasuryNode,
  ContextUpgradeable,
  FETHNode,
  NFTMarketCore,
  ReentrancyGuardUpgradeable,
  SendValueWithFallbackWithdraw,
  MarketFees,
  NFTMarketExhibition
{
  using AddressUpgradeable for address;
  using TimeLibrary for uint32;

  /// @notice Stores offer details for a specific NFT.
  struct Offer {
    // Slot 1: When increasing an offer, only this slot is updated.
    /// @notice The expiration timestamp of when this offer expires.
    uint32 expiration;
    /// @notice The amount, in wei, of the highest offer.
    uint96 amount;
    /// @notice First slot (of 16B) used for the offerReferrerAddress.
    // The offerReferrerAddress is the address used to pay the
    // referrer on an accepted offer.
    uint128 offerReferrerAddressSlot0;
    // Slot 2: When the buyer changes, both slots need updating
    /// @notice The address of the collector who made this offer.
    address buyer;
    /// @notice Second slot (of 4B) used for the offerReferrerAddress.
    uint32 offerReferrerAddressSlot1;
    // 96 bits (12B) are available in slot 1.
  }

  /// @notice Stores the highest offer for each NFT.
  mapping(address => mapping(uint256 => Offer)) private nftContractToIdToOffer;

  /**
   * @notice Emitted when an offer is accepted,
   * indicating that the NFT has been transferred and revenue from the sale distributed.
   * @dev The accepted total offer amount is `totalFees` + `creatorRev` + `sellerRev`.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @param buyer The address of the collector that made the offer which was accepted.
   * @param seller The address of the seller which accepted the offer.
   * @param totalFees The amount of ETH that was sent to Foundation & referrals for this sale.
   * @param creatorRev The amount of ETH that was sent to the creator for this sale.
   * @param sellerRev The amount of ETH that was sent to the owner for this sale.
   */
  event OfferAccepted(
    address indexed nftContract,
    uint256 indexed tokenId,
    address indexed buyer,
    address seller,
    uint256 totalFees,
    uint256 creatorRev,
    uint256 sellerRev
  );
  /**
   * @notice Emitted when an offer is invalidated due to other market activity.
   * When this occurs, the collector which made the offer has their FETH balance unlocked
   * and the funds are available to place other offers or to be withdrawn.
   * @dev This occurs when the offer is no longer eligible to be accepted,
   * e.g. when a bid is placed in an auction for this NFT.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   */
  event OfferInvalidated(address indexed nftContract, uint256 indexed tokenId);
  /**
   * @notice Emitted when an offer is made.
   * @dev The `amount` of the offer is locked in the FETH ERC-20 contract, guaranteeing that the funds
   * remain available until the `expiration` date.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @param buyer The address of the collector that made the offer to buy this NFT.
   * @param amount The amount, in wei, of the offer.
   * @param expiration The expiration timestamp for the offer.
   */
  event OfferMade(
    address indexed nftContract,
    uint256 indexed tokenId,
    address indexed buyer,
    uint256 amount,
    uint256 expiration
  );

  /**
   * @notice Accept the highest offer for an NFT.
   * @dev The offer must not be expired and the NFT owned + approved by the seller or
   * available in the market contract's escrow.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @param offerFrom The address of the collector that you wish to sell to.
   * If the current highest offer is not from this user, the transaction will revert.
   * This could happen if a last minute offer was made by another collector,
   * and would require the seller to try accepting again.
   * @param minAmount The minimum value of the highest offer for it to be accepted.
   * If the value is less than this amount, the transaction will revert.
   * This could happen if the original offer expires and is replaced with a smaller offer.
   */
  function acceptOffer(
    address nftContract,
    uint256 tokenId,
    address offerFrom,
    uint256 minAmount
  ) external nonReentrant {
    Offer storage offer = nftContractToIdToOffer[nftContract][tokenId];
    // Validate offer expiry and amount
    if (offer.expiration.hasExpired()) {
      revert NFTMarketOffer_Offer_Expired(offer.expiration);
    } else if (offer.amount < minAmount) {
      revert NFTMarketOffer_Offer_Below_Min_Amount(offer.amount);
    }
    // Validate the buyer
    if (offer.buyer != offerFrom) {
      revert NFTMarketOffer_Offer_From_Does_Not_Match(offer.buyer);
    }

    _acceptOffer(nftContract, tokenId);
  }

  /**
   * @notice Make an offer for any NFT which is valid for 24-25 hours.
   * The funds will be locked in the FETH token contract and become available once the offer is outbid or has expired.
   * @dev An offer may be made for an NFT before it is minted, although we generally not recommend you do that.
   * If there is a buy price set at this price or lower, that will be accepted instead of making an offer.
   * `msg.value` must be <= `amount` and any delta will be taken from the account's available FETH balance.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @param amount The amount to offer for this NFT.
   * @param referrer The referrer address for the offer.
   * @return expiration The timestamp for when this offer will expire.
   * This is provided as a return value in case another contract would like to leverage this information,
   * user's should refer to the expiration in the `OfferMade` event log.
   * If the buy price is accepted instead, `0` is returned as the expiration since that's n/a.
   */
  function makeOfferV2(
    address nftContract,
    uint256 tokenId,
    uint256 amount,
    address payable referrer
  ) external payable returns (uint256 expiration) {
    // If there is a buy price set at this price or lower, accept that instead.
    if (_autoAcceptBuyPrice(nftContract, tokenId, amount)) {
      // If the buy price is accepted, `0` is returned as the expiration since that's n/a.
      return 0;
    }

    if (_isInActiveAuction(nftContract, tokenId)) {
      revert NFTMarketOffer_Cannot_Be_Made_While_In_Auction();
    }

    Offer storage offer = nftContractToIdToOffer[nftContract][tokenId];
    address sender = _msgSender();

    if (offer.expiration.hasExpired()) {
      // This is a new offer for the NFT (no other offer found or the previous offer expired)

      // Lock the offer amount in FETH until the offer expires in 24-25 hours.
      expiration = feth.marketLockupFor{ value: msg.value }(sender, amount);
    } else {
      // A previous offer exists and has not expired

      uint256 minIncrement = _getMinIncrement(offer.amount);
      if (amount < minIncrement) {
        // A non-trivial increase in price is required to avoid sniping
        revert NFTMarketOffer_Offer_Must_Be_At_Least_Min_Amount(minIncrement);
      }

      // Unlock the previous offer so that the FETH tokens are available for other offers or to transfer / withdraw
      // and lock the new offer amount in FETH until the offer expires in 24-25 hours.
      expiration = feth.marketChangeLockup{ value: msg.value }(
        offer.buyer,
        offer.expiration,
        offer.amount,
        sender,
        amount
      );
    }

    // Record offer details
    offer.buyer = sender;
    // The FETH contract guarantees that the expiration fits into 32 bits.
    offer.expiration = uint32(expiration);
    // `amount` is capped by the ETH provided, which cannot realistically overflow 96 bits.
    offer.amount = uint96(amount);

    if (referrer == address(feth)) {
      // FETH cannot be paid as a referrer, clear the value instead.
      referrer = payable(0);
    }

    // Set offerReferrerAddressSlot0 to the first 16B of the referrer address.
    // By shifting the referrer 32 bits to the right we obtain the first 16B.
    offer.offerReferrerAddressSlot0 = uint128(uint160(address(referrer)) >> 32);
    // Set offerReferrerAddressSlot1 to the last 4B of the referrer address.
    // By casting the referrer address to 32bits we discard the first 16B.
    offer.offerReferrerAddressSlot1 = uint32(uint160(address(referrer)));

    emit OfferMade(nftContract, tokenId, sender, amount, expiration);
  }

  /**
   * @notice Accept the highest offer for an NFT from the `msg.sender` account.
   * The NFT will be transferred to the buyer and revenue from the sale will be distributed.
   * @dev The caller must validate the expiry and amount before calling this helper.
   * This may invalidate other market tools, such as clearing the buy price if set.
   */
  function _acceptOffer(address nftContract, uint256 tokenId) private {
    Offer memory offer = nftContractToIdToOffer[nftContract][tokenId];

    // Remove offer
    delete nftContractToIdToOffer[nftContract][tokenId];
    // Withdraw ETH from the buyer's account in the FETH token contract.
    feth.marketWithdrawLocked(offer.buyer, offer.expiration, offer.amount);

    address payable sender = payable(_msgSender());

    (
      address payable sellerReferrerPaymentAddress,
      uint16 sellerReferrerTakeRateInBasisPoints
    ) = _getExhibitionForPayment(nftContract, tokenId);

    // Transfer the NFT to the buyer.
    address owner = IERC721(nftContract).ownerOf(tokenId);
    if (owner == address(this)) {
      // The NFT is currently in escrow (e.g. it has a buy price set)
      // This should revert if `msg.sender` is not the owner of this NFT or if the NFT is in active auction.
      _transferFromEscrow(nftContract, tokenId, offer.buyer, sender);
    } else {
      // NFT should be in the seller's wallet. If attempted by the wrong sender or if the market is not approved this
      // will revert.
      IERC721(nftContract).transferFrom(sender, offer.buyer, tokenId);
    }

    // Distribute revenue for this sale leveraging the ETH received from the FETH contract in the line above.
    (uint256 totalFees, uint256 creatorRev, uint256 sellerRev) = _distributeFunds(
      nftContract,
      tokenId,
      sender,
      offer.amount,
      _getOfferReferrerFromSlots(offer.offerReferrerAddressSlot0, offer.offerReferrerAddressSlot1),
      sellerReferrerPaymentAddress,
      sellerReferrerTakeRateInBasisPoints
    );

    emit OfferAccepted(nftContract, tokenId, offer.buyer, sender, totalFees, creatorRev, sellerRev);
  }

  /**
   * @inheritdoc NFTMarketCore
   * @dev Invalidates the highest offer when an auction is kicked off, if one is found.
   */
  function _beforeAuctionStarted(address nftContract, uint256 tokenId) internal virtual override {
    _invalidateOffer(nftContract, tokenId);
    super._beforeAuctionStarted(nftContract, tokenId);
  }

  /**
   * @inheritdoc NFTMarketCore
   */
  function _autoAcceptOffer(address nftContract, uint256 tokenId, uint256 minAmount) internal override returns (bool) {
    Offer storage offer = nftContractToIdToOffer[nftContract][tokenId];
    if (offer.expiration.hasExpired() || offer.amount < minAmount) {
      // No offer found, the most recent offer is now expired, or the highest offer is below the minimum amount.
      return false;
    }

    _acceptOffer(nftContract, tokenId);
    return true;
  }

  /**
   * @inheritdoc NFTMarketCore
   */
  function _cancelSendersOffer(address nftContract, uint256 tokenId) internal override {
    Offer storage offer = nftContractToIdToOffer[nftContract][tokenId];
    if (offer.buyer == _msgSender()) {
      _invalidateOffer(nftContract, tokenId);
    }
  }

  /**
   * @notice Invalidates the offer and frees ETH from escrow, if the offer has not already expired.
   * @dev Offers are not invalidated when the NFT is purchased by accepting the buy price unless it
   * was purchased by the same user.
   * The user which just purchased the NFT may have buyer's remorse and promptly decide they want a fast exit,
   * accepting a small loss to limit their exposure.
   */
  function _invalidateOffer(address nftContract, uint256 tokenId) private {
    if (!nftContractToIdToOffer[nftContract][tokenId].expiration.hasExpired()) {
      // An offer was found and it has not already expired
      Offer memory offer = nftContractToIdToOffer[nftContract][tokenId];

      // Remove offer
      delete nftContractToIdToOffer[nftContract][tokenId];

      // Unlock the offer so that the FETH tokens are available for other offers or to transfer / withdraw
      feth.marketUnlockFor(offer.buyer, offer.expiration, offer.amount);

      emit OfferInvalidated(nftContract, tokenId);
    }
  }

  /**
   * @notice Returns the minimum amount a collector must offer for this NFT in order for the offer to be valid.
   * @dev Offers for this NFT which are less than this value will revert.
   * Once the previous offer has expired smaller offers can be made.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @return minimum The minimum amount that must be offered for this NFT.
   */
  function getMinOfferAmount(address nftContract, uint256 tokenId) external view returns (uint256 minimum) {
    Offer storage offer = nftContractToIdToOffer[nftContract][tokenId];
    if (!offer.expiration.hasExpired()) {
      return _getMinIncrement(offer.amount);
    }
    // Absolute min is anything > 0
    return 1;
  }

  /**
   * @notice Returns details about the current highest offer for an NFT.
   * @dev Default values are returned if there is no offer or the offer has expired.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @return buyer The address of the buyer that made the current highest offer.
   * Returns `address(0)` if there is no offer or the most recent offer has expired.
   * @return expiration The timestamp that the current highest offer expires.
   * Returns `0` if there is no offer or the most recent offer has expired.
   * @return amount The amount being offered for this NFT.
   * Returns `0` if there is no offer or the most recent offer has expired.
   */
  function getOffer(
    address nftContract,
    uint256 tokenId
  ) external view returns (address buyer, uint256 expiration, uint256 amount) {
    Offer storage offer = nftContractToIdToOffer[nftContract][tokenId];
    if (offer.expiration.hasExpired()) {
      // Offer not found or has expired
      return (address(0), 0, 0);
    }

    // An offer was found and it has not yet expired.
    return (offer.buyer, offer.expiration, offer.amount);
  }

  /**
   * @notice Returns the current highest offer's referral for an NFT.
   * @dev Default value of `payable(0)` is returned if
   * there is no offer, the offer has expired or does not have a referral.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @return referrer The payable address of the referrer for the offer.
   */
  function getOfferReferrer(address nftContract, uint256 tokenId) external view returns (address payable referrer) {
    Offer storage offer = nftContractToIdToOffer[nftContract][tokenId];
    if (offer.expiration.hasExpired()) {
      // Offer not found or has expired
      return payable(0);
    }
    return _getOfferReferrerFromSlots(offer.offerReferrerAddressSlot0, offer.offerReferrerAddressSlot1);
  }

  function _getOfferReferrerFromSlots(
    uint128 offerReferrerAddressSlot0,
    uint32 offerReferrerAddressSlot1
  ) private pure returns (address payable referrer) {
    // Stitch offerReferrerAddressSlot0 and offerReferrerAddressSlot1 to obtain the payable offerReferrerAddress.
    // Left shift offerReferrerAddressSlot0 by 32 bits OR it with offerReferrerAddressSlot1.
    referrer = payable(address((uint160(offerReferrerAddressSlot0) << 32) | uint160(offerReferrerAddressSlot1)));
  }

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

File 29 of 37 : 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 37 : 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 "../../libraries/TimeLibrary.sol";

import "../shared/FoundationTreasuryNode.sol";
import "../shared/FETHNode.sol";
import "../shared/MarketFees.sol";
import "../shared/MarketSharedCore.sol";
import "../shared/SendValueWithFallbackWithdraw.sol";

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

/// @param auctionId The already listed auctionId for this NFT.
error NFTMarketReserveAuction_Already_Listed(uint256 auctionId);
/// @param minAmount The minimum amount that must be bid in order for it to be accepted.
error NFTMarketReserveAuction_Bid_Must_Be_At_Least_Min_Amount(uint256 minAmount);
/// @param reservePrice The current reserve price.
error NFTMarketReserveAuction_Cannot_Bid_Lower_Than_Reserve_Price(uint256 reservePrice);
/// @param endTime The timestamp at which the auction had ended.
error NFTMarketReserveAuction_Cannot_Bid_On_Ended_Auction(uint256 endTime);
error NFTMarketReserveAuction_Cannot_Bid_On_Nonexistent_Auction();
error NFTMarketReserveAuction_Cannot_Finalize_Already_Settled_Auction();
/// @param endTime The timestamp at which the auction will end.
error NFTMarketReserveAuction_Cannot_Finalize_Auction_In_Progress(uint256 endTime);
error NFTMarketReserveAuction_Cannot_Rebid_Over_Outstanding_Bid();
error NFTMarketReserveAuction_Cannot_Update_Auction_In_Progress();
/// @param maxDuration The maximum configuration for a duration of the auction, in seconds.
error NFTMarketReserveAuction_Exceeds_Max_Duration(uint256 maxDuration);
/// @param extensionDuration The extension duration, in seconds.
error NFTMarketReserveAuction_Less_Than_Extension_Duration(uint256 extensionDuration);
error NFTMarketReserveAuction_Must_Set_Non_Zero_Reserve_Price();
/// @param seller The current owner of the NFT.
error NFTMarketReserveAuction_Not_Matching_Seller(address seller);
/// @param owner The current owner of the NFT.
error NFTMarketReserveAuction_Only_Owner_Can_Update_Auction(address owner);
error NFTMarketReserveAuction_Price_Already_Set();
error NFTMarketReserveAuction_Too_Much_Value_Provided();

/**
 * @title Allows the owner of an NFT to list it in auction.
 * @notice NFTs in auction are escrowed in the market contract.
 * @dev There is room to optimize the storage for auctions, significantly reducing gas costs.
 * This may be done in the future, but for now it will remain as is in order to ease upgrade compatibility.
 * @author batu-inal & HardlyDifficult & reggieag
 */
abstract contract NFTMarketReserveAuction is
  INFTMarketReserveAuction,
  FoundationTreasuryNode,
  ContextUpgradeable,
  FETHNode,
  MarketSharedCore,
  NFTMarketCore,
  ReentrancyGuardUpgradeable,
  SendValueWithFallbackWithdraw,
  MarketFees,
  NFTMarketExhibition,
  NFTMarketAuction
{
  using TimeLibrary for uint256;

  /// @notice The auction configuration for a specific NFT.
  struct ReserveAuction {
    /// @notice The address of the NFT contract.
    address nftContract;
    /// @notice The id of the NFT.
    uint256 tokenId;
    /// @notice The owner of the NFT which listed it in auction.
    address payable seller;
    /// @notice The duration for this auction.
    uint256 duration;
    /// @notice The extension window for this auction.
    uint256 extensionDuration;
    /// @notice The time at which this auction will not accept any new bids.
    /// @dev This is `0` until the first bid is placed.
    uint256 endTime;
    /// @notice The current highest bidder in this auction.
    /// @dev This is `address(0)` until the first bid is placed.
    address payable bidder;
    /// @notice The latest price of the NFT in this auction.
    /// @dev This is set to the reserve price, and then to the highest bid once the auction has started.
    uint256 amount;
  }

  /// @notice Stores the auction configuration for a specific NFT.
  /// @dev This allows us to modify the storage struct without changing external APIs.
  struct ReserveAuctionStorage {
    // Slot 0
    /// @notice The address of the NFT contract.
    address nftContract;
    // (96-bits free space)

    // Slot 1
    /// @notice The id of the NFT.
    uint256 tokenId;
    // (slot full)

    // Slot 2
    /// @notice The owner of the NFT which listed it in auction.
    address payable seller;
    /// @notice First slot (12 bytes) used for the bidReferrerAddress.
    /// The bidReferrerAddress is the address used to pay the referrer on finalize.
    /// @dev This approach is used in order to pack storage, saving gas.
    uint96 bidReferrerAddressSlot0;
    // (slot full)

    // Slot 3
    /// @dev This field is no longer used but was previously assigned to.
    uint256 __gap_was_duration;
    // (slot full)

    // Slot 4
    /// @dev This field is no longer used but was previous assigned to.
    uint256 __gap_was_extensionDuration;
    // (slot full)

    // Slot 5
    /// @notice The time at which this auction will not accept any new bids.
    /// @dev This is `0` until the first bid is placed.
    uint256 endTime;
    // (slot full)

    // Slot 6
    /// @notice The current highest bidder in this auction.
    /// @dev This is `address(0)` until the first bid is placed.
    address payable bidder;
    /// @notice Second slot (8 bytes) used for the bidReferrerAddress.
    uint64 bidReferrerAddressSlot1;
    /// @dev Auction duration length in seconds.
    uint32 duration;
    // (slot full)

    // Slot 7
    /// @notice The latest price of the NFT in this auction.
    /// @dev This is set to the reserve price, and then to the highest bid once the auction has started.
    uint256 amount;
    // (slot full)
  }

  /// @notice The auction configuration for a specific auction id.
  mapping(address => mapping(uint256 => uint256)) private nftContractToTokenIdToAuctionId;
  /// @notice The auction id for a specific NFT.
  /// @dev This is deleted when an auction is finalized or canceled.
  mapping(uint256 => ReserveAuctionStorage) private auctionIdToAuction;

  /**
   * @dev Removing old unused variables in an upgrade safe way. Was:
   * uint256 private __gap_was_minPercentIncrementInBasisPoints;
   * uint256 private __gap_was_maxBidIncrementRequirement;
   * uint256 private __gap_was_duration;
   * uint256 private __gap_was_extensionDuration;
   * uint256 private __gap_was_goLiveDate;
   */
  uint256[5] private __gap_was_config;

  /// @notice Default for how long an auction lasts for once the first bid has been received.
  uint256 private immutable DEFAULT_DURATION;

  /// @notice The window for auction extensions, any bid placed in the final 15 minutes
  /// of an auction will reset the time remaining to 15 minutes.
  uint256 private constant EXTENSION_DURATION = 15 minutes;

  /// @notice Caps the max duration that may be configured for an auction.
  uint256 private constant MAX_DURATION = 7 days;

  /**
   * @notice Emitted when a bid is placed.
   * @param auctionId The id of the auction this bid was for.
   * @param bidder The address of the bidder.
   * @param amount The amount of the bid.
   * @param endTime The new end time of the auction (which may have been set or extended by this bid).
   */
  event ReserveAuctionBidPlaced(uint256 indexed auctionId, address indexed bidder, uint256 amount, uint256 endTime);
  /**
   * @notice Emitted when an auction is canceled.
   * @dev This is only possible if the auction has not received any bids.
   * @param auctionId The id of the auction that was canceled.
   */
  event ReserveAuctionCanceled(uint256 indexed auctionId);
  /**
   * @notice Emitted when an NFT is listed for auction.
   * @param seller The address of the seller.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @param duration The duration of the auction (always 24-hours).
   * @param extensionDuration The duration of the auction extension window (always 15-minutes).
   * @param reservePrice The reserve price to kick off the auction.
   * @param auctionId The id of the auction that was created.
   */
  event ReserveAuctionCreated(
    address indexed seller,
    address indexed nftContract,
    uint256 indexed tokenId,
    uint256 duration,
    uint256 extensionDuration,
    uint256 reservePrice,
    uint256 auctionId
  );
  /**
   * @notice Emitted when an auction that has already ended is finalized,
   * indicating that the NFT has been transferred and revenue from the sale distributed.
   * @dev The amount of the highest bid / final sale price for this auction
   * is `totalFees` + `creatorRev` + `sellerRev`.
   * @param auctionId The id of the auction that was finalized.
   * @param seller The address of the seller.
   * @param bidder The address of the highest bidder that won the NFT.
   * @param totalFees The amount of ETH that was sent to Foundation & referrals for this sale.
   * @param creatorRev The amount of ETH that was sent to the creator for this sale.
   * @param sellerRev The amount of ETH that was sent to the owner for this sale.
   */
  event ReserveAuctionFinalized(
    uint256 indexed auctionId,
    address indexed seller,
    address indexed bidder,
    uint256 totalFees,
    uint256 creatorRev,
    uint256 sellerRev
  );
  /**
   * @notice Emitted when an auction is invalidated due to other market activity.
   * @dev This occurs when the NFT is sold another way, such as with `buy` or `acceptOffer`.
   * @param auctionId The id of the auction that was invalidated.
   */
  event ReserveAuctionInvalidated(uint256 indexed auctionId);
  /**
   * @notice Emitted when the auction's reserve price is changed.
   * @dev This is only possible if the auction has not received any bids.
   * @param auctionId The id of the auction that was updated.
   * @param reservePrice The new reserve price for the auction.
   */
  event ReserveAuctionUpdated(uint256 indexed auctionId, uint256 reservePrice);

  /// @notice Confirms that the reserve price is not zero.
  modifier onlyValidAuctionConfig(uint256 reservePrice) {
    if (reservePrice == 0) {
      revert NFTMarketReserveAuction_Must_Set_Non_Zero_Reserve_Price();
    }
    _;
  }

  /**
   * @notice Configures the duration for auctions.
   * @param duration The duration for auctions, in seconds.
   */
  constructor(uint256 duration) {
    if (duration > MAX_DURATION) {
      // This ensures that math in this file will not overflow due to a huge duration.
      revert NFTMarketReserveAuction_Exceeds_Max_Duration(MAX_DURATION);
    }
    if (duration < EXTENSION_DURATION) {
      // The auction duration configuration must be greater than the extension window of 15 minutes
      revert NFTMarketReserveAuction_Less_Than_Extension_Duration(EXTENSION_DURATION);
    }

    DEFAULT_DURATION = duration;
  }

  /**
   * @notice If an auction has been created but has not yet received bids, it may be canceled by the seller.
   * @dev The NFT is transferred back to the owner unless there is still a buy price set.
   * @param auctionId The id of the auction to cancel.
   */
  function cancelReserveAuction(uint256 auctionId) external nonReentrant {
    ReserveAuctionStorage memory auction = auctionIdToAuction[auctionId];
    if (auction.seller != _msgSender()) {
      revert NFTMarketReserveAuction_Only_Owner_Can_Update_Auction(auction.seller);
    }
    if (auction.endTime != 0) {
      revert NFTMarketReserveAuction_Cannot_Update_Auction_In_Progress();
    }

    // Remove the auction.
    delete nftContractToTokenIdToAuctionId[auction.nftContract][auction.tokenId];
    delete auctionIdToAuction[auctionId];
    _removeNftFromExhibition(auction.nftContract, auction.tokenId);

    // Transfer the NFT unless it still has a buy price set.
    _transferFromEscrowIfAvailable(auction.nftContract, auction.tokenId, auction.seller);

    emit ReserveAuctionCanceled(auctionId);
  }

  /**
   * @notice [DEPRECATED] use `createReserveAuctionV3` instead.
   * Creates an auction for the given NFT.
   * The NFT is held in escrow until the auction is finalized or canceled.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @param reservePrice The initial reserve price for the auction.
   */
  function createReserveAuction(address nftContract, uint256 tokenId, uint256 reservePrice) external {
    createReserveAuctionV3({
      nftContract: nftContract,
      tokenId: tokenId,
      exhibitionId: 0, // no exhibition
      reservePrice: reservePrice,
      duration: 0 // use default duration
    });
  }

  /**
   * @notice [DEPRECATED] use `createReserveAuctionV3` instead.
   * Creates an auction for the given NFT.
   * The NFT is held in escrow until the auction is finalized or canceled.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @param reservePrice The initial reserve price for the auction.
   * @param exhibitionId The exhibition to list with, or 0 if n/a.
   * @return auctionId The id of the auction that was created.
   */
  function createReserveAuctionV2(
    address nftContract,
    uint256 tokenId,
    uint256 reservePrice,
    uint256 exhibitionId
  ) external returns (uint256 auctionId) {
    auctionId = createReserveAuctionV3({
      nftContract: nftContract,
      tokenId: tokenId,
      exhibitionId: exhibitionId,
      reservePrice: reservePrice,
      duration: 0 // use default duration
    });
  }

  /**
   * @notice Creates an auction for the given NFT.
   * The NFT is held in escrow until the auction is finalized or canceled.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @param exhibitionId The exhibition to list with, or 0 if n/a.
   * @param reservePrice The initial reserve price for the auction.
   * @param duration The length of the auction, in seconds.
   * @return auctionId The id of the auction that was created.
   */
  function createReserveAuctionV3(
    address nftContract,
    uint256 tokenId,
    uint256 exhibitionId,
    uint256 reservePrice,
    uint256 duration
  ) public nonReentrant onlyValidAuctionConfig(reservePrice) returns (uint256 auctionId) {
    if (duration == 0) {
      duration = DEFAULT_DURATION;
    } else {
      if (duration > MAX_DURATION) {
        // This ensures that math in this file will not overflow due to a huge duration.
        revert NFTMarketReserveAuction_Exceeds_Max_Duration(MAX_DURATION);
      }
      if (duration < EXTENSION_DURATION) {
        // The auction duration configuration must be greater than the extension window of 15 minutes
        revert NFTMarketReserveAuction_Less_Than_Extension_Duration(EXTENSION_DURATION);
      }
    }

    auctionId = _getNextAndIncrementAuctionId();

    // If the `msg.sender` is not the owner of the NFT, transferring into escrow should fail.
    _transferToEscrow(nftContract, tokenId);

    // This check must be after _transferToEscrow in case auto-settle was required
    if (nftContractToTokenIdToAuctionId[nftContract][tokenId] != 0) {
      revert NFTMarketReserveAuction_Already_Listed(nftContractToTokenIdToAuctionId[nftContract][tokenId]);
    }

    // Store the auction details
    address payable sender = payable(_msgSender());
    nftContractToTokenIdToAuctionId[nftContract][tokenId] = auctionId;
    ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId];
    auction.nftContract = nftContract;
    auction.tokenId = tokenId;
    auction.seller = sender;
    auction.amount = reservePrice;
    if (duration != DEFAULT_DURATION) {
      // If duration is DEFAULT_DURATION, we don't need to write to storage.
      // Safe cast is not required since duration is capped by MAX_DURATION.
      auction.duration = uint32(duration);
    }

    _addNftToExhibition(nftContract, tokenId, exhibitionId);

    emit ReserveAuctionCreated({
      seller: sender,
      nftContract: nftContract,
      tokenId: tokenId,
      duration: duration,
      extensionDuration: EXTENSION_DURATION,
      reservePrice: reservePrice,
      auctionId: auctionId
    });
  }

  /**
   * @notice Once the countdown has expired for an auction, anyone can settle the auction.
   * This will send the NFT to the highest bidder and distribute revenue for this sale.
   * @param auctionId The id of the auction to settle.
   */
  function finalizeReserveAuction(uint256 auctionId) external nonReentrant {
    if (auctionIdToAuction[auctionId].endTime == 0) {
      revert NFTMarketReserveAuction_Cannot_Finalize_Already_Settled_Auction();
    }
    _finalizeReserveAuction({ auctionId: auctionId, keepInEscrow: false });
  }

  /**
   * @notice [DEPRECATED] use `placeBidV2` instead.
   * Place a bid in an auction.
   * A bidder may place a bid which is at least the value defined by `getMinBidAmount`.
   * If this is the first bid on the auction, the countdown will begin.
   * If there is already an outstanding bid, the previous bidder will be refunded at this time
   * and if the bid is placed in the final moments of the auction, the countdown may be extended.
   * @param auctionId The id of the auction to bid on.
   */
  function placeBid(uint256 auctionId) external payable {
    placeBidV2({ auctionId: auctionId, amount: msg.value, referrer: payable(0) });
  }

  /**
   * @notice Place a bid in an auction.
   * A bidder may place a bid which is at least the amount defined by `getMinBidAmount`.
   * If this is the first bid on the auction, the countdown will begin.
   * If there is already an outstanding bid, the previous bidder will be refunded at this time
   * and if the bid is placed in the final moments of the auction, the countdown may be extended.
   * @dev `amount` - `msg.value` is withdrawn from the bidder's FETH balance.
   * @param auctionId The id of the auction to bid on.
   * @param amount The amount to bid, if this is more than `msg.value` funds will be withdrawn from your FETH balance.
   * @param referrer The address of the referrer of this bid, or 0 if n/a.
   */
  function placeBidV2(uint256 auctionId, uint256 amount, address payable referrer) public payable nonReentrant {
    ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId];

    if (auction.amount == 0) {
      // No auction found
      revert NFTMarketReserveAuction_Cannot_Bid_On_Nonexistent_Auction();
    } else if (amount < msg.value) {
      // The amount is specified by the bidder, so if too much ETH is sent then something went wrong.
      revert NFTMarketReserveAuction_Too_Much_Value_Provided();
    }

    uint256 endTime = auction.endTime;
    address payable sender = payable(_msgSender());

    // Store the bid referral
    if (referrer == address(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 may be
   * changed by the seller.
   * @param auctionId The id of the auction to change.
   * @param reservePrice The new reserve price for this auction.
   */
  function updateReserveAuction(uint256 auctionId, uint256 reservePrice) external onlyValidAuctionConfig(reservePrice) {
    ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId];
    if (auction.seller != _msgSender()) {
      revert NFTMarketReserveAuction_Only_Owner_Can_Update_Auction(auction.seller);
    } else if (auction.endTime != 0) {
      revert NFTMarketReserveAuction_Cannot_Update_Auction_In_Progress();
    } else if (auction.amount == reservePrice) {
      revert NFTMarketReserveAuction_Price_Already_Set();
    }

    // Update the current reserve price.
    auction.amount = reservePrice;

    emit ReserveAuctionUpdated(auctionId, reservePrice);
  }

  /**
   * @notice Settle an auction that has already ended.
   * This will send the NFT to the highest bidder and distribute revenue for this sale.
   * @param keepInEscrow If true, the NFT will be kept in escrow to save gas by avoiding
   * redundant transfers if the NFT should remain in escrow, such as when the new owner
   * sets a buy price or lists it in a new auction.
   */
  function _finalizeReserveAuction(uint256 auctionId, bool keepInEscrow) private {
    ReserveAuctionStorage memory auction = auctionIdToAuction[auctionId];

    if (!auction.endTime.hasExpired()) {
      revert NFTMarketReserveAuction_Cannot_Finalize_Auction_In_Progress(auction.endTime);
    }
    (
      address payable sellerReferrerPaymentAddress,
      uint16 sellerReferrerTakeRateInBasisPoints
    ) = _getExhibitionForPayment(auction.nftContract, auction.tokenId);

    // Remove the auction.
    delete nftContractToTokenIdToAuctionId[auction.nftContract][auction.tokenId];
    delete auctionIdToAuction[auctionId];

    if (!keepInEscrow) {
      // The seller was authorized when the auction was originally created
      super._transferFromEscrow({
        nftContract: auction.nftContract,
        tokenId: auction.tokenId,
        recipient: auction.bidder,
        authorizeSeller: address(0)
      });
    }

    // Distribute revenue for this sale.
    (uint256 totalFees, uint256 creatorRev, uint256 sellerRev) = _distributeFunds(
      auction.nftContract,
      auction.tokenId,
      auction.seller,
      auction.amount,
      payable(address((uint160(auction.bidReferrerAddressSlot0) << 64) | uint160(auction.bidReferrerAddressSlot1))),
      sellerReferrerPaymentAddress,
      sellerReferrerTakeRateInBasisPoints
    );

    emit ReserveAuctionFinalized(auctionId, auction.seller, auction.bidder, totalFees, creatorRev, sellerRev);
  }

  /**
   * @inheritdoc NFTMarketCore
   * @dev If an auction is found:
   *  - If the auction is over, it will settle the auction and confirm the new seller won the auction.
   *  - If the auction has not received a bid, it will invalidate the auction.
   *  - If the auction is in progress, this will revert.
   */
  function _transferFromEscrow(
    address nftContract,
    uint256 tokenId,
    address recipient,
    address authorizeSeller
  ) internal virtual override {
    uint256 auctionId = nftContractToTokenIdToAuctionId[nftContract][tokenId];
    if (auctionId != 0) {
      ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId];
      if (auction.endTime == 0) {
        // The auction has not received any bids yet so it may be invalided.

        if (authorizeSeller != address(0) && auction.seller != authorizeSeller) {
          // The account trying to transfer the NFT is not the current owner.
          revert NFTMarketReserveAuction_Not_Matching_Seller(auction.seller);
        }

        // Remove the auction.
        delete nftContractToTokenIdToAuctionId[nftContract][tokenId];
        delete auctionIdToAuction[auctionId];
        _removeNftFromExhibition(nftContract, tokenId);

        emit ReserveAuctionInvalidated(auctionId);
      } else {
        // If the auction has ended, the highest bidder will be the new owner
        // and if the auction is in progress, this will revert.

        // `authorizeSeller != address(0)` does not apply here since an unsettled auction must go
        // through this path to know who the authorized seller should be.
        if (auction.bidder != authorizeSeller) {
          revert NFTMarketReserveAuction_Not_Matching_Seller(auction.bidder);
        }

        // Finalization will revert if the auction has not yet ended.
        _finalizeReserveAuction({ auctionId: auctionId, keepInEscrow: true });
      }
      // The seller authorization has been confirmed.
      authorizeSeller = address(0);
    }

    super._transferFromEscrow(nftContract, tokenId, recipient, authorizeSeller);
  }

  /**
   * @inheritdoc NFTMarketCore
   * @dev Checks if there is an auction for this NFT before allowing the transfer to continue.
   */
  function _transferFromEscrowIfAvailable(
    address nftContract,
    uint256 tokenId,
    address recipient
  ) internal virtual override {
    if (nftContractToTokenIdToAuctionId[nftContract][tokenId] == 0) {
      // No auction was found

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

  /**
   * @inheritdoc NFTMarketCore
   */
  function _transferToEscrow(address nftContract, uint256 tokenId) internal virtual override {
    uint256 auctionId = nftContractToTokenIdToAuctionId[nftContract][tokenId];
    if (auctionId == 0) {
      // NFT is not in auction
      super._transferToEscrow(nftContract, tokenId);
      return;
    }
    // Using storage saves gas since most of the data is not needed
    ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId];
    address sender = _msgSender();
    if (auction.endTime == 0) {
      // Reserve price set, confirm the seller is a match
      if (auction.seller != sender) {
        revert NFTMarketReserveAuction_Not_Matching_Seller(auction.seller);
      }
    } else {
      // Auction in progress, confirm the highest bidder is a match
      if (auction.bidder != sender) {
        revert NFTMarketReserveAuction_Not_Matching_Seller(auction.bidder);
      }

      // Finalize auction but leave NFT in escrow, reverts if the auction has not ended
      _finalizeReserveAuction({ auctionId: auctionId, keepInEscrow: true });
    }
  }

  /**
   * @notice Returns the minimum amount a bidder must spend to participate in an auction.
   * Bids must be greater than or equal to this value or they will revert.
   * @param auctionId The id of the auction to check.
   * @return minimum The minimum amount for a bid to be accepted.
   */
  function getMinBidAmount(uint256 auctionId) external view returns (uint256 minimum) {
    ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId];
    if (auction.endTime == 0) {
      return auction.amount;
    }
    return _getMinIncrement(auction.amount);
  }

  /**
   * @notice Returns auction details for a given auctionId.
   * @param auctionId The id of the auction to lookup.
   */
  function getReserveAuction(uint256 auctionId) external view returns (ReserveAuction memory auction) {
    ReserveAuctionStorage storage auctionStorage = auctionIdToAuction[auctionId];
    uint256 duration = auctionStorage.duration;
    if (duration == 0) {
      duration = DEFAULT_DURATION;
    }
    auction = ReserveAuction(
      auctionStorage.nftContract,
      auctionStorage.tokenId,
      auctionStorage.seller,
      duration,
      EXTENSION_DURATION,
      auctionStorage.endTime,
      auctionStorage.bidder,
      auctionStorage.amount
    );
  }

  /**
   * @notice Returns the auctionId for a given NFT, or 0 if no auction is found.
   * @dev If an auction is canceled, it will not be returned. However the auction may be over and pending finalization.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @return auctionId The id of the auction, or 0 if no auction is found.
   */
  function getReserveAuctionIdFor(address nftContract, uint256 tokenId) external view returns (uint256 auctionId) {
    auctionId = nftContractToTokenIdToAuctionId[nftContract][tokenId];
  }

  /**
   * @notice Returns the referrer for the current highest bid in the auction, or address(0).
   */
  function getReserveAuctionBidReferrer(uint256 auctionId) external view returns (address payable referrer) {
    ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId];
    referrer = payable(
      address((uint160(auction.bidReferrerAddressSlot0) << 64) | uint160(auction.bidReferrerAddressSlot1))
    );
  }

  /**
   * @inheritdoc MarketSharedCore
   * @dev Returns the seller that has the given NFT in escrow for an auction,
   * or bubbles the call up for other considerations.
   */
  function _getSellerOf(
    address nftContract,
    uint256 tokenId
  ) internal view virtual override returns (address payable seller) {
    seller = auctionIdToAuction[nftContractToTokenIdToAuctionId[nftContract][tokenId]].seller;
    if (seller == address(0)) {
      seller = super._getSellerOf(nftContract, tokenId);
    }
  }

  /**
   * @inheritdoc NFTMarketCore
   */
  function _isInActiveAuction(address nftContract, uint256 tokenId) internal view override returns (bool) {
    uint256 auctionId = nftContractToTokenIdToAuctionId[nftContract][tokenId];
    return auctionId != 0 && !auctionIdToAuction[auctionId].endTime.hasExpired();
  }

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

File 31 of 37 : 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_EXHIBITION_TAKE_RATE = 5_000;

/**
 * @dev Cap the number of royalty recipients.
 * A cap is required to ensure gas costs are not too high when a sale is settled.
 */
uint256 constant MAX_ROYALTY_RECIPIENTS = 5;

/**
 * @dev Default royalty cut paid out on secondary sales.
 * Set to 10% of the secondary sale.
 */
uint96 constant ROYALTY_IN_BASIS_POINTS = 1_000;

/**
 * @dev Reward paid to referrers when a sale is made.
 * Set to 1% of the sale amount. This 1% is deducted from the protocol fee.
 */
uint96 constant BUY_REFERRER_IN_BASIS_POINTS = 100;

/**
 * @dev 10%, expressed as a denominator for more efficient calculations.
 */
uint256 constant ROYALTY_RATIO = BASIS_POINTS / ROYALTY_IN_BASIS_POINTS;

/**
 * @dev 1%, expressed as a denominator for more efficient calculations.
 */
uint256 constant BUY_REFERRER_RATIO = BASIS_POINTS / BUY_REFERRER_IN_BASIS_POINTS;

////////////////////////////////////////////////////////////////
// Gas Limits
////////////////////////////////////////////////////////////////

/**
 * @dev The gas limit used when making external read-only calls.
 * This helps to ensure that external calls does not prevent the market from executing.
 */
uint256 constant READ_ONLY_GAS_LIMIT = 40_000;

/**
 * @dev The gas limit to send ETH to multiple recipients, enough for a 5-way split.
 */
uint256 constant SEND_VALUE_GAS_LIMIT_MULTIPLE_RECIPIENTS = 210_000;

/**
 * @dev The gas limit to send ETH to a single recipient, enough for a contract with a simple receiver.
 */
uint256 constant SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT = 20_000;

////////////////////////////////////////////////////////////////
// Collection Type Names
////////////////////////////////////////////////////////////////

/**
 * @dev The NFT collection type.
 */
string constant NFT_COLLECTION_TYPE = "NFT Collection";

/**
 * @dev The NFT drop collection type.
 */
string constant NFT_DROP_COLLECTION_TYPE = "NFT Drop Collection";

/**
 * @dev The NFT edition collection type.
 */
string constant NFT_TIMED_EDITION_COLLECTION_TYPE = "NFT Timed Edition Collection";

////////////////////////////////////////////////////////////////
// Business Logic
////////////////////////////////////////////////////////////////

/**
 * @dev Limits scheduled start/end times to be less than 2 years in the future.
 */
uint256 constant MAX_SCHEDULED_TIME_IN_THE_FUTURE = 365 days * 2;

/**
 * @dev The minimum increase of 10% required when making an offer or placing a bid.
 */
uint256 constant MIN_PERCENT_INCREMENT_DENOMINATOR = BASIS_POINTS / 1_000;

/**
 * @dev Protocol fee for edition mints in basis points.
 */
uint256 constant EDITION_PROTOCOL_FEE_IN_BASIS_POINTS = 500;

/**
 * @dev Hash of the edition type name.
 * This is precalculated in order to save gas on use.
 * `keccak256(abi.encodePacked(NFT_TIMED_EDITION_COLLECTION_TYPE))`
 */
bytes32 constant editionTypeHash = 0xee2afa3f960e108aca17013728aafa363a0f4485661d9b6f41c6b4ddb55008ee;

File 32 of 37 : 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 33 of 37 : FoundationTreasuryNode.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 A mixin that stores a reference to the Foundation treasury contract.
 * @notice The treasury collects fees and defines admin/operator roles.
 * @author batu-inal & HardlyDifficult
 */
abstract contract FoundationTreasuryNode is Initializable {
  using AddressUpgradeable for address payable;

  /// @dev This value was replaced with an immutable version.
  address payable private __gap_was_treasury;

  /// @notice The address of the treasury contract.
  address payable private immutable treasury;

  /// @notice Requires the caller is a Foundation admin.
  modifier onlyFoundationAdmin() {
    if (!IAdminRole(treasury).isAdmin(msg.sender)) {
      revert FoundationTreasuryNode_Caller_Not_Admin();
    }
    _;
  }

  /// @notice Requires the caller is a Foundation operator.
  modifier onlyFoundationOperator() {
    if (!IOperatorRole(treasury).isOperator(msg.sender)) {
      revert FoundationTreasuryNode_Caller_Not_Operator();
    }
    _;
  }

  /**
   * @notice Set immutable variables for the implementation contract.
   * @dev Assigns the treasury contract address.
   */
  constructor(address payable _treasury) {
    if (!_treasury.isContract()) {
      revert FoundationTreasuryNode_Address_Is_Not_A_Contract();
    }
    treasury = _treasury;
  }

  /**
   * @notice Gets the Foundation treasury contract.
   * @dev This call is used in the royalty registry contract.
   * @return treasuryAddress The address of the Foundation treasury contract.
   */
  function getFoundationTreasury() public view returns (address payable treasuryAddress) {
    treasuryAddress = treasury;
  }

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

File 34 of 37 : 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 "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
import "@manifoldxyz/royalty-registry-solidity/contracts/IRoyaltyRegistry.sol";

import "../../interfaces/internal/INFTCollectionType.sol";
import "../../interfaces/standards/royalties/IGetFees.sol";
import "../../interfaces/standards/royalties/IGetRoyalties.sol";
import "../../interfaces/standards/royalties/IOwnable.sol";
import "../../interfaces/standards/royalties/IRoyaltyInfo.sol";
import "../../interfaces/standards/royalties/ITokenCreator.sol";

import "../../libraries/ArrayLibrary.sol";

import "./Constants.sol";
import "./FoundationTreasuryNode.sol";
import "./SendValueWithFallbackWithdraw.sol";
import "./MarketSharedCore.sol";

error NFTMarketFees_Royalty_Registry_Is_Not_A_Contract();
error NFTMarketFees_Invalid_Protocol_Fee();

/**
 * @title A mixin to distribute funds when an NFT is sold.
 * @author batu-inal & HardlyDifficult
 */
abstract contract MarketFees is
  FoundationTreasuryNode,
  ContextUpgradeable,
  MarketSharedCore,
  SendValueWithFallbackWithdraw
{
  using AddressUpgradeable for address;
  using ArrayLibrary for address payable[];
  using ArrayLibrary for uint256[];
  using ERC165Checker for address;

  /**
   * @dev Removing old unused variables in an upgrade safe way. Was:
   * uint256 private _primaryFoundationFeeBasisPoints;
   * uint256 private _secondaryFoundationFeeBasisPoints;
   * uint256 private _secondaryCreatorFeeBasisPoints;
   * mapping(address => mapping(uint256 => bool)) private _nftContractToTokenIdToFirstSaleCompleted;
   */
  uint256[4] private __gap_was_fees;

  /// @notice The fee collected by Foundation for sales facilitated by this market contract.
  uint256 private immutable DEFAULT_PROTOCOL_FEE_IN_BASIS_POINTS;

  /// @notice The address of the royalty registry which may be used to define royalty overrides for some collections.
  IRoyaltyRegistry private immutable royaltyRegistry;

  /// @notice The address of this contract's implementation.
  /// @dev This is used when making stateless external calls to this contract,
  /// saving gas over hopping through the proxy which is only necessary when accessing state.
  MarketFees private immutable implementationAddress;

  /// @notice True for the Drop market which only performs primary sales. False if primary & secondary are supported.
  bool private immutable assumePrimarySale;

  /**
   * @notice Emitted when an NFT sold with a referrer.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @param buyReferrer The account which received the buy referral incentive.
   * @param buyReferrerFee The portion of the protocol fee collected by the buy referrer.
   * @param buyReferrerSellerFee The portion of the owner revenue collected by the buy referrer (not implemented).
   */
  event BuyReferralPaid(
    address indexed nftContract,
    uint256 indexed tokenId,
    address buyReferrer,
    uint256 buyReferrerFee,
    uint256 buyReferrerSellerFee
  );

  /**
   * @notice Emitted when an NFT is sold when associated with a sell referrer.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @param sellerReferrer The account which received the sell referral incentive.
   * @param sellerReferrerFee The portion of the seller revenue collected by the sell referrer.
   */
  event SellerReferralPaid(
    address indexed nftContract,
    uint256 indexed tokenId,
    address sellerReferrer,
    uint256 sellerReferrerFee
  );

  /**
   * @notice Configures the registry allowing for royalty overrides to be defined.
   * @param _royaltyRegistry The registry to use for royalty overrides.
   * @param _assumePrimarySale True for the Drop market which only performs primary sales.
   * False if primary & secondary are supported.
   */
  constructor(uint16 protocolFeeInBasisPoints, address _royaltyRegistry, bool _assumePrimarySale) {
    if (
      protocolFeeInBasisPoints < BASIS_POINTS / BUY_REFERRER_RATIO ||
      protocolFeeInBasisPoints + BASIS_POINTS / ROYALTY_RATIO >= BASIS_POINTS - MAX_EXHIBITION_TAKE_RATE
    ) {
      /* If the protocol fee is invalid, revert:
       * Protocol fee must be greater than the buy referrer fee since referrer fees are deducted from the protocol fee.
       * The protocol fee must leave room for the creator royalties and the max exhibition take rate.
       */
      revert NFTMarketFees_Invalid_Protocol_Fee();
    }
    DEFAULT_PROTOCOL_FEE_IN_BASIS_POINTS = protocolFeeInBasisPoints;

    if (!_royaltyRegistry.isContract()) {
      // Not using a 165 check since mainnet and goerli are not using the same versions of the registry.
      revert NFTMarketFees_Royalty_Registry_Is_Not_A_Contract();
    }
    royaltyRegistry = IRoyaltyRegistry(_royaltyRegistry);

    assumePrimarySale = _assumePrimarySale;

    // In the constructor, `this` refers to the implementation address. Everywhere else it'll be the proxy.
    implementationAddress = this;
  }

  /**
   * @notice Distributes funds to foundation, creator recipients, and NFT owner after a sale.
   */
  function _distributeFunds(
    address nftContract,
    uint256 tokenId,
    address payable seller,
    uint256 price,
    address payable buyReferrer,
    address payable sellerReferrerPaymentAddress,
    uint16 sellerReferrerTakeRateInBasisPoints
  ) internal returns (uint256 totalFees, uint256 creatorRev, uint256 sellerRev) {
    if (price == 0) {
      // When the sale price is 0, there are no revenue to distribute.
      return (0, 0, 0);
    }

    address payable[] memory creatorRecipients;
    uint256[] memory creatorShares;

    uint256 buyReferrerFee;
    uint256 sellerReferrerFee;
    (totalFees, creatorRecipients, creatorShares, sellerRev, buyReferrerFee, sellerReferrerFee) = getFees(
      nftContract,
      tokenId,
      seller,
      price,
      buyReferrer,
      sellerReferrerTakeRateInBasisPoints
    );

    // Pay the creator(s)
    // If just a single recipient was defined, use a larger gas limit in order to support in-contract split logic.
    uint256 creatorGasLimit = creatorRecipients.length == 1
      ? SEND_VALUE_GAS_LIMIT_MULTIPLE_RECIPIENTS
      : SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT;
    unchecked {
      for (uint256 i = 0; i < creatorRecipients.length; ++i) {
        _sendValueWithFallbackWithdraw(creatorRecipients[i], creatorShares[i], creatorGasLimit);
        // Sum the total creator rev from shares
        // creatorShares is in ETH so creatorRev will not overflow here.
        creatorRev += creatorShares[i];
      }
    }

    // Pay the seller
    _sendValueWithFallbackWithdraw(seller, sellerRev, SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT);

    // Pay the protocol fee
    _sendValueWithFallbackWithdraw(getFoundationTreasury(), totalFees, SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT);

    // Pay the buy referrer fee
    if (buyReferrerFee != 0) {
      _sendValueWithFallbackWithdraw(buyReferrer, buyReferrerFee, SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT);
      emit BuyReferralPaid({
        nftContract: nftContract,
        tokenId: tokenId,
        buyReferrer: buyReferrer,
        buyReferrerFee: buyReferrerFee,
        buyReferrerSellerFee: 0
      });
      unchecked {
        // Add the referrer fee back into the total fees so that all 3 return fields sum to the total price for events
        totalFees += buyReferrerFee;
      }
    }

    if (sellerReferrerPaymentAddress != address(0)) {
      if (sellerReferrerFee != 0) {
        // Add the seller referrer fee back to revenue so that all 3 return fields sum to the total price for events.
        unchecked {
          if (sellerRev == 0) {
            // When sellerRev is 0, this is a primary sale and all revenue is attributed to the "creator".
            creatorRev += sellerReferrerFee;
          } else {
            sellerRev += sellerReferrerFee;
          }
        }

        _sendValueWithFallbackWithdraw(
          sellerReferrerPaymentAddress,
          sellerReferrerFee,
          SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT
        );
      }

      emit SellerReferralPaid(nftContract, tokenId, sellerReferrerPaymentAddress, sellerReferrerFee);
    }
  }

  /**
   * @notice Returns how funds will be distributed for a sale at the given price point.
   * @param nftContract The address of the NFT contract.
   * @param tokenId The id of the NFT.
   * @param price The sale price to calculate the fees for.
   * @return totalFees How much will be sent to the Foundation treasury and/or referrals.
   * @return creatorRev How much will be sent across all the `creatorRecipients` defined.
   * @return creatorRecipients The addresses of the recipients to receive a portion of the creator fee.
   * @return creatorShares The percentage of the creator fee to be distributed to each `creatorRecipient`.
   * If there is only one `creatorRecipient`, this may be an empty array.
   * Otherwise `creatorShares.length` == `creatorRecipients.length`.
   * @return sellerRev How much will be sent to the owner/seller of the NFT.
   * If the NFT is being sold by the creator, this may be 0 and the full revenue will appear as `creatorRev`.
   * @return seller The address of the owner of the NFT.
   * If `sellerRev` is 0, this may be `address(0)`.
   * @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),
      exhibitionTakeRateInBasisPoints: 0
    });

    // Sum the total creator rev from shares
    unchecked {
      for (uint256 i = 0; i < creatorShares.length; ++i) {
        creatorRev += creatorShares[i];
      }
    }
  }

  /**
   * @notice Returns the address of the registry allowing for royalty configuration overrides.
   * @dev See https://royaltyregistry.xyz/
   * @return registry The address of the royalty registry contract.
   */
  function getRoyaltyRegistry() external view returns (address registry) {
    registry = address(royaltyRegistry);
  }

  /**
   * @notice **For internal use only.**
   * @dev This function is external to allow using try/catch but is not intended for external use.
   * This checks the token creator.
   */
  function internalGetTokenCreator(
    address nftContract,
    uint256 tokenId
  ) external view returns (address payable creator) {
    creator = ITokenCreator(nftContract).tokenCreator{ gas: READ_ONLY_GAS_LIMIT }(tokenId);
  }

  /**
   * @notice **For internal use only.**
   * @dev This function is external to allow using try/catch but is not intended for external use.
   * If ERC2981 royalties (or getRoyalties) are defined by the NFT contract, allow this standard to define immutable
   * royalties that cannot be later changed via the royalty registry.
   */
  function internalGetImmutableRoyalties(
    address nftContract,
    uint256 tokenId
  ) external view returns (address payable[] memory recipients, uint256[] memory splitPerRecipientInBasisPoints) {
    // 1st priority: ERC-2981
    if (nftContract.supportsERC165InterfaceUnchecked(type(IRoyaltyInfo).interfaceId)) {
      try IRoyaltyInfo(nftContract).royaltyInfo{ gas: READ_ONLY_GAS_LIMIT }(tokenId, BASIS_POINTS) returns (
        address receiver,
        uint256 royaltyAmount
      ) {
        // Manifold contracts return (address(this), 0) when royalties are not defined
        // - so ignore results when the amount is 0
        if (royaltyAmount > 0) {
          recipients = new address payable[](1);
          recipients[0] = payable(receiver);
          splitPerRecipientInBasisPoints = new uint256[](1);
          // The split amount is assumed to be 100% when only 1 recipient is returned
          return (recipients, splitPerRecipientInBasisPoints);
        }
      } catch {
        // Fall through
      }
    }

    // 2nd priority: getRoyalties
    if (nftContract.supportsERC165InterfaceUnchecked(type(IGetRoyalties).interfaceId)) {
      try IGetRoyalties(nftContract).getRoyalties{ gas: READ_ONLY_GAS_LIMIT }(tokenId) returns (
        address payable[] memory _recipients,
        uint256[] memory recipientBasisPoints
      ) {
        if (_recipients.length != 0 && _recipients.length == recipientBasisPoints.length) {
          return (_recipients, recipientBasisPoints);
        }
      } catch {
        // Fall through
      }
    }
  }

  /**
   * @notice **For internal use only.**
   * @dev This function is external to allow using try/catch but is not intended for external use.
   * This checks for royalties defined in the royalty registry or via a non-standard royalty API.
   */
  function internalGetMutableRoyalties(
    address nftContract,
    uint256 tokenId,
    address payable creator
  ) external view returns (address payable[] memory recipients, uint256[] memory splitPerRecipientInBasisPoints) {
    /* Overrides must support ERC-165 when registered, except for overrides defined by the registry owner.
       If that results in an override w/o 165 we may need to upgrade the market to support or ignore that override. */
    // The registry requires overrides are not 0 and contracts when set.
    // If no override is set, the nftContract address is returned.

    try royaltyRegistry.getRoyaltyLookupAddress{ gas: READ_ONLY_GAS_LIMIT }(nftContract) returns (
      address overrideContract
    ) {
      if (overrideContract != nftContract) {
        nftContract = overrideContract;

        // The functions above are repeated here if an override is set.

        // 3rd priority: ERC-2981 override
        if (nftContract.supportsERC165InterfaceUnchecked(type(IRoyaltyInfo).interfaceId)) {
          try IRoyaltyInfo(nftContract).royaltyInfo{ gas: READ_ONLY_GAS_LIMIT }(tokenId, BASIS_POINTS) returns (
            address receiver,
            uint256 royaltyAmount
          ) {
            // Manifold contracts return (address(this), 0) when royalties are not defined
            // - so ignore results when the amount is 0
            if (royaltyAmount != 0) {
              recipients = new address payable[](1);
              recipients[0] = payable(receiver);
              splitPerRecipientInBasisPoints = new uint256[](1);
              // The split amount is assumed to be 100% when only 1 recipient is returned
              return (recipients, splitPerRecipientInBasisPoints);
            }
          } catch {
            // Fall through
          }
        }

        // 4th priority: getRoyalties override
        if (recipients.length == 0 && nftContract.supportsERC165InterfaceUnchecked(type(IGetRoyalties).interfaceId)) {
          try IGetRoyalties(nftContract).getRoyalties{ gas: READ_ONLY_GAS_LIMIT }(tokenId) returns (
            address payable[] memory _recipients,
            uint256[] memory recipientBasisPoints
          ) {
            if (_recipients.length != 0 && _recipients.length == recipientBasisPoints.length) {
              return (_recipients, recipientBasisPoints);
            }
          } catch {
            // Fall through
          }
        }
      }
    } catch {
      // Ignore out of gas errors and continue using the nftContract address
    }

    // 5th priority: getFee* from contract or override
    if (nftContract.supportsERC165InterfaceUnchecked(type(IGetFees).interfaceId)) {
      try IGetFees(nftContract).getFeeRecipients{ gas: READ_ONLY_GAS_LIMIT }(tokenId) returns (
        address payable[] memory _recipients
      ) {
        if (_recipients.length != 0) {
          try IGetFees(nftContract).getFeeBps{ gas: READ_ONLY_GAS_LIMIT }(tokenId) returns (
            uint256[] memory recipientBasisPoints
          ) {
            if (_recipients.length == recipientBasisPoints.length) {
              return (_recipients, recipientBasisPoints);
            }
          } catch {
            // Fall through
          }
        }
      } catch {
        // Fall through
      }
    }

    // 6th priority: tokenCreator w/ or w/o requiring 165 from contract or override
    if (creator != address(0)) {
      // Only pay the tokenCreator if there wasn't another royalty defined
      recipients = new address payable[](1);
      recipients[0] = creator;
      splitPerRecipientInBasisPoints = new uint256[](1);
      // The split amount is assumed to be 100% when only 1 recipient is returned
      return (recipients, splitPerRecipientInBasisPoints);
    }

    // 7th priority: owner from contract or override
    try IOwnable(nftContract).owner{ gas: READ_ONLY_GAS_LIMIT }() returns (address owner) {
      if (owner != address(0)) {
        // Only pay the owner if there wasn't another royalty defined
        recipients = new address payable[](1);
        recipients[0] = payable(owner);
        splitPerRecipientInBasisPoints = new uint256[](1);
        // The split amount is assumed to be 100% when only 1 recipient is returned
        return (recipients, splitPerRecipientInBasisPoints);
      }
    } catch {
      // Fall through
    }

    // If no valid payment address or creator is found, return 0 recipients
  }

  /**
   * @notice Calculates how funds should be distributed for the given sale details.
   * @dev When the NFT is being sold by the `tokenCreator`, all the seller revenue will
   * be split with the royalty recipients defined for that NFT.
   */
  function getFees(
    address nftContract,
    uint256 tokenId,
    address payable seller,
    uint256 price,
    address payable buyReferrer,
    uint16 exhibitionTakeRateInBasisPoints
  )
    public
    view
    returns (
      uint256 totalFees,
      address payable[] memory creatorRecipients,
      uint256[] memory creatorShares,
      uint256 sellerRev,
      uint256 buyReferrerFee,
      uint256 sellerReferrerFee
    )
  {
    // Calculate the protocol fee
    totalFees = (price * _getProtocolFee(nftContract)) / BASIS_POINTS;

    address payable creator;
    try implementationAddress.internalGetTokenCreator(nftContract, tokenId) returns (address payable _creator) {
      creator = _creator;
    } catch {
      // Fall through
    }

    try implementationAddress.internalGetImmutableRoyalties(nftContract, tokenId) returns (
      address payable[] memory _recipients,
      uint256[] memory _splitPerRecipientInBasisPoints
    ) {
      (creatorRecipients, creatorShares) = (_recipients, _splitPerRecipientInBasisPoints);
    } catch {
      // Fall through
    }

    if (creatorRecipients.length == 0) {
      // Check mutable royalties only if we didn't find results from the immutable API
      try implementationAddress.internalGetMutableRoyalties(nftContract, tokenId, creator) returns (
        address payable[] memory _recipients,
        uint256[] memory _splitPerRecipientInBasisPoints
      ) {
        (creatorRecipients, creatorShares) = (_recipients, _splitPerRecipientInBasisPoints);
      } catch {
        // Fall through
      }
    }

    if (creatorRecipients.length != 0 || assumePrimarySale) {
      uint256 creatorRev;
      if (assumePrimarySale) {
        // All revenue should go to the creator recipients
        unchecked {
          // totalFees is always < price.
          creatorRev = price - totalFees;
        }
        if (creatorRecipients.length == 0) {
          // If no creators were found via the royalty APIs, then set that recipient to the seller's address
          creatorRecipients = new address payable[](1);
          creatorRecipients[0] = seller;
          creatorShares = new uint256[](1);
          // The split amount is assumed to be 100% when only 1 recipient is returned
        }
      } else if (seller == creator || (creatorRecipients.length != 0 && seller == creatorRecipients[0])) {
        // When sold by the creator, all revenue is split if applicable.
        unchecked {
          // totalFees is always < price.
          creatorRev = price - totalFees;
        }
      } else {
        // Rounding favors the owner first, then creator, and foundation last.
        unchecked {
          // Safe math is not required when dividing by a non-zero constant.
          creatorRev = price / ROYALTY_RATIO;
        }
        sellerRev = price - totalFees - creatorRev;
      }

      // Cap the max number of recipients supported
      creatorRecipients.capLength(MAX_ROYALTY_RECIPIENTS);
      creatorShares.capLength(MAX_ROYALTY_RECIPIENTS);

      // Calculate the seller referrer fee when some revenue is awarded to the creator
      if (exhibitionTakeRateInBasisPoints != 0) {
        sellerReferrerFee = (price * exhibitionTakeRateInBasisPoints) / BASIS_POINTS;

        // Subtract the seller referrer fee from the seller revenue so we do not double pay.
        if (sellerRev == 0) {
          // If the seller revenue is 0, this is a primary sale where all seller revenue is attributed to the "creator".
          creatorRev -= sellerReferrerFee;
        } else {
          sellerRev -= sellerReferrerFee;
        }
      }

      // Sum the total shares defined
      uint256 totalShares;
      if (creatorRecipients.length > 1) {
        unchecked {
          for (uint256 i = 0; i < creatorRecipients.length; ++i) {
            if (creatorRecipients[i] == seller) {
              // If the seller is any of the recipients defined, assume a primary sale
              creatorRev += sellerRev;
              sellerRev = 0;
            }
            if (totalShares != type(uint256).max) {
              if (creatorShares[i] > BASIS_POINTS) {
                // If the numbers are >100% we ignore the fee recipients and pay just the first instead
                totalShares = type(uint256).max;
                // Continue the loop in order to detect a potential primary sale condition
              } else {
                totalShares += creatorShares[i];
              }
            }
          }
        }

        if (totalShares == 0 || totalShares == type(uint256).max) {
          // If no shares were defined or shares were out of bounds, pay only the first recipient
          creatorRecipients.capLength(1);
          creatorShares.capLength(1);
        }
      }

      // Send payouts to each additional recipient if more than 1 was defined
      uint256 totalRoyaltiesDistributed;
      for (uint256 i = 1; i < creatorRecipients.length; ) {
        uint256 royalty = (creatorRev * creatorShares[i]) / totalShares;
        totalRoyaltiesDistributed += royalty;
        creatorShares[i] = royalty;
        unchecked {
          ++i;
        }
      }

      // Send the remainder to the 1st creator, rounding in their favor
      creatorShares[0] = creatorRev - totalRoyaltiesDistributed;
    } else {
      // No royalty recipients found.
      unchecked {
        // totalFees is always < price.
        sellerRev = price - totalFees;
      }

      // Calculate the seller referrer fee when there is no creator royalty
      if (exhibitionTakeRateInBasisPoints != 0) {
        sellerReferrerFee = (price * exhibitionTakeRateInBasisPoints) / BASIS_POINTS;

        sellerRev -= sellerReferrerFee;
      }
    }

    if (
      buyReferrer != address(0) &&
      buyReferrer != _msgSender() &&
      buyReferrer != seller &&
      buyReferrer != creator &&
      buyReferrer != address(feth)
    ) {
      unchecked {
        buyReferrerFee = price / BUY_REFERRER_RATIO;

        // buyReferrerFee is always <= totalFees
        totalFees -= buyReferrerFee;
      }
    }
  }

  /**
   * @notice Calculates the protocol fee for the given NFT contract.
   * @dev This returns the contract's default fee but may be overridden to change fees based on the collection type.
   */
  function _getProtocolFee(address /* nftContract */) internal view virtual returns (uint256 protocolFeeInBasisPoints) {
    protocolFeeInBasisPoints = DEFAULT_PROTOCOL_FEE_IN_BASIS_POINTS;
  }

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

File 35 of 37 : 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 36 of 37 : RouterContext.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";

error RouterContext_Not_A_Contract();

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

  address private immutable approvedRouter;

  constructor(address router) {
    if (!router.isContract()) {
      revert RouterContext_Not_A_Contract();
    }
    approvedRouter = router;
  }

  /**
   * @notice Returns the router contract which is able to override the msg.sender address.
   * @return router The address of the trusted router.
   */
  function getApprovedRouterAddress() external view returns (address router) {
    router = approvedRouter;
  }

  /**
   * @notice Returns the sender of the transaction.
   * @dev If the msg.sender is the trusted router contract, then the last 20 bytes of the calldata is the authorized
   * sender.
   */
  function _msgSender() internal view virtual override returns (address sender) {
    sender = super._msgSender();
    if (sender == approvedRouter) {
      assembly {
        // The router appends the msg.sender to the end of the calldata
        // source: https://github.com/opengsn/gsn/blob/v3.0.0-beta.3/packages/contracts/src/ERC2771Recipient.sol#L48
        sender := shr(96, calldataload(sub(calldatasize(), 20)))
      }
    }
  }
}

File 37 of 37 : 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 "./FoundationTreasuryNode.sol";

/**
 * @title A mixin for sending ETH with a fallback withdraw mechanism.
 * @notice Attempt to send ETH and if the transfer fails or runs out of gas, store the balance
 * in the FETH token contract for future withdrawal instead.
 * @dev This mixin was recently switched to escrow funds in FETH.
 * Once we have confirmed all pending balances have been withdrawn, we can remove the escrow tracking here.
 * @author batu-inal & HardlyDifficult
 */
abstract contract SendValueWithFallbackWithdraw is FoundationTreasuryNode, FETHNode {
  using AddressUpgradeable for address payable;

  /// @dev Removing old unused variables in an upgrade safe way.
  uint256 private __gap_was_pendingWithdrawals;

  /**
   * @notice Emitted when escrowed funds are withdrawn to FETH.
   * @param user The account which has withdrawn ETH.
   * @param amount The amount of ETH which has been withdrawn.
   */
  event WithdrawalToFETH(address indexed user, uint256 amount);

  /**
   * @notice Attempt to send a user or contract ETH.
   * If it fails store the amount owned for later withdrawal in FETH.
   * @dev This may fail when sending ETH to a contract that is non-receivable or exceeds the gas limit specified.
   */
  function _sendValueWithFallbackWithdraw(address payable user, uint256 amount, uint256 gasLimit) internal {
    if (amount == 0) {
      return;
    }

    if (user == address(feth)) {
      // FETH may revert on ETH transfers and will reject `depositFor` calls to itself, so redirect funds to the
      // treasury contract instead.
      user = getFoundationTreasury();
    }

    // Cap the gas to prevent consuming all available gas to block a tx from completing successfully
    // solhint-disable-next-line avoid-low-level-calls
    (bool success, ) = user.call{ value: amount, gas: gasLimit }("");
    if (!success) {
      // Store the funds that failed to send for the user in the FETH token
      feth.depositFor{ value: amount }(user);
      emit WithdrawalToFETH(user, amount);
    }
  }

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

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 3500
  },
  "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":"address","name":"royaltyRegistry","type":"address"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"address","name":"router","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":[],"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_Seller_Not_Found","type":"error"},{"inputs":[{"internalType":"address","name":"curator","type":"address"}],"name":"NFTMarketExhibition_Caller_Is_Not_Curator","type":"error"},{"inputs":[],"name":"NFTMarketExhibition_Can_Not_Add_Dupe_Seller","type":"error"},{"inputs":[],"name":"NFTMarketExhibition_Curator_Automatically_Allowed","type":"error"},{"inputs":[],"name":"NFTMarketExhibition_Exhibition_Does_Not_Exist","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":"NFTMarketFees_Invalid_Protocol_Fee","type":"error"},{"inputs":[],"name":"NFTMarketFees_Royalty_Registry_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":[{"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":[],"name":"NFTMarketReserveAuction_Price_Already_Set","type":"error"},{"inputs":[],"name":"NFTMarketReserveAuction_Too_Much_Value_Provided","type":"error"},{"inputs":[],"name":"RouterContext_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":"exhibitionId","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":"exhibitionId","type":"uint256"}],"name":"ExhibitionDeleted","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":"exhibitionId","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":"exhibitionId","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":"exhibitionId","type":"uint256"},{"indexed":false,"internalType":"address[]","name":"sellers","type":"address[]"}],"name":"SellersAddedToExhibition","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":"exhibitionId","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"}],"name":"buy","outputs":[],"stateMutability":"payable","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":"exhibitionId","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"}],"name":"createReserveAuction","outputs":[],"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":"exhibitionId","type":"uint256"}],"name":"createReserveAuctionV2","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":"exhibitionId","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":"exhibitionId","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":"exhibitionId","type":"uint256"}],"name":"getExhibition","outputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"address payable","name":"curator","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":"exhibitionId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"exhibitionId","type":"uint256"}],"name":"getExhibitionPaymentDetails","outputs":[{"internalType":"address payable","name":"curator","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"},{"internalType":"address payable","name":"seller","type":"address"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"address payable","name":"buyReferrer","type":"address"},{"internalType":"uint16","name":"exhibitionTakeRateInBasisPoints","type":"uint16"}],"name":"getFees","outputs":[{"internalType":"uint256","name":"totalFees","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":[{"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 NFTMarketReserveAuction.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":[],"name":"getRoyaltyRegistry","outputs":[{"internalType":"address","name":"registry","type":"address"}],"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":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"internalGetImmutableRoyalties","outputs":[{"internalType":"address payable[]","name":"recipients","type":"address[]"},{"internalType":"uint256[]","name":"splitPerRecipientInBasisPoints","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address payable","name":"creator","type":"address"}],"name":"internalGetMutableRoyalties","outputs":[{"internalType":"address payable[]","name":"recipients","type":"address[]"},{"internalType":"uint256[]","name":"splitPerRecipientInBasisPoints","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"internalGetTokenCreator","outputs":[{"internalType":"address payable","name":"creator","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"exhibitionId","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"}],"name":"placeBid","outputs":[],"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":"uint256","name":"auctionId","type":"uint256"},{"internalType":"uint256","name":"reservePrice","type":"uint256"}],"name":"updateReserveAuction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]

61018060405234801562000011575f80fd5b5060405162006476380380620064768339810160408190526200003491620002ea565b816101f4845f87858a6001600160a01b0381163b620000665760405163028bba2560e61b815260040160405180910390fd5b6001600160a01b0390811660805281163b6200009557604051633d7a0d8f60e11b815260040160405180910390fd5b6001600160a01b0390811660a05281163b620000c45760405163de58082760e01b815260040160405180910390fd5b6001600160a01b031660c052620000df60646127106200036e565b620000ed906127106200036e565b8361ffff1610806200013b57506200010a6113886127106200038e565b6200011a6103e86127106200036e565b62000128906127106200036e565b620001389061ffff8616620003aa565b10155b156200015a57604051630567777b60e41b815260040160405180910390fd5b61ffff831660e0526001600160a01b0382163b6200018b5760405163dd78160760e01b815260040160405180910390fd5b6001600160a01b039091166101005215156101405250306101205262093a80811115620001d55760405163ccd285bd60e01b815262093a8060048201526024015b60405180910390fd5b610384811015620001fe5760405163494c8c0760e11b81526103846004820152602401620001cc565b610160526200020c62000217565b5050505050620003c0565b5f54610100900460ff1615620002805760405162461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b6064820152608401620001cc565b5f5460ff90811614620002d0575f805460ff191660ff9081179091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b565b6001600160a01b0381168114620002e7575f80fd5b50565b5f805f805f60a08688031215620002ff575f80fd5b85516200030c81620002d2565b60208701519095506200031f81620002d2565b60408701519094506200033281620002d2565b6060870151608088015191945092506200034c81620002d2565b809150509295509295909350565b634e487b7160e01b5f52601160045260245ffd5b5f826200038957634e487b7160e01b5f52601260045260245ffd5b500490565b81810381811115620003a457620003a46200035a565b92915050565b80820180821115620003a457620003a46200035a565b60805160a05160c05160e05161010051610120516101405161016051615fc7620004af5f395f818161201b015281816121df01528181612b100152612f8a01525f8181612655015261267d01525f818161246e0152818161251801526125d801525f818161096a015261320401525f6123fb01525f81816102be015281816107620152818161183d0152818161198901528181611a7401528181612a2601528181612e1401528181613a790152818161456601528181614653015281816146f201526151ad01525f818161066f01526147d901525f81816109da0152818161459f01526149c00152615fc75ff3fe6080604052600436106102ae575f3560e01c80637430e0c611610165578063a59ac6dd116100c6578063beb5127c1161007c578063e5d1e72311610062578063e5d1e7231461098e578063efef76f8146109ad578063f7a2da23146109cc575f80fd5b8063beb5127c1461093d578063daa351d41461095c575f80fd5b8063af1e1de3116100ac578063af1e1de3146108e6578063b01ef60814610917578063b6aff8c11461092a575f80fd5b8063a59ac6dd1461088f578063ac71045e146108a2575f80fd5b806387a4fdcb1161011b5780639979ef45116101015780639979ef45146107865780639e64ba6c146107995780639e79b41f14610801575f80fd5b806387a4fdcb14610723578063895633ba14610754575f80fd5b80637b3a58841161014b5780637b3a5884146106d15780638098531d146106f05780638129fc1c1461070f575f80fd5b80637430e0c614610693578063798bac8d146106b2575f80fd5b8063445738d81161020f5780634fca06c6116101c5578063614b151c116101ab578063614b151c1461062f5780636512ed2d146106425780636a90a82714610661575f80fd5b80634fca06c6146105ab57806355daed3e146105ca575f80fd5b806347e35740116101f557806347e357401461054e5780634c542f771461056d5780634ce6931a1461058c575f80fd5b8063445738d8146104f15780634635256e14610510575f80fd5b806329e0e160116102645780632e06db961161024a5780632e06db96146104535780633c58e54d14610482578063442559a2146104b0575f80fd5b806329e0e160146103e55780632ab2b52b14610404575f80fd5b806321506fff1161029457806321506fff14610370578063215619351461038f578063262907c5146103ae575f80fd5b806303ec16d71461031b5780630d7daf3e1461033a575f80fd5b3661031757336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610315576040517faa39384e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b005b5f80fd5b348015610326575f80fd5b50610315610335366004615563565b6109fe565b348015610345575f80fd5b50610359610354366004615597565b610b69565b604051610367929190615631565b60405180910390f35b34801561037b575f80fd5b5061031561038a366004615655565b610d91565b34801561039a575f80fd5b506103156103a9366004615597565b610fd9565b3480156103b9575f80fd5b506103cd6103c8366004615597565b61111f565b6040516001600160a01b039091168152602001610367565b3480156103f0575f80fd5b506103156103ff36600461566c565b6111a9565b34801561040f575f80fd5b5061044561041e366004615597565b6001600160a01b039091165f90815261177660209081526040808320938352929052205490565b604051908152602001610367565b34801561045e575f80fd5b5061047261046d3660046156b1565b611301565b6040519015158152602001610367565b34801561048d575f80fd5b506104a161049c366004615655565b61136a565b604051610367939291906156df565b3480156104bb575f80fd5b506104456104ca366004615597565b6001600160a01b039091165f90815261119c60209081526040808320938352929052205490565b3480156104fc575f80fd5b5061044561050b36600461579c565b61145a565b34801561051b575f80fd5b5061052f61052a366004615597565b61161a565b604080516001600160a01b039093168352602083019190915201610367565b348015610559575f80fd5b50610445610568366004615655565b611691565b348015610578575f80fd5b506103cd610587366004615597565b6116c8565b348015610597575f80fd5b506103156105a6366004615842565b61174f565b3480156105b6575f80fd5b506103cd6105c5366004615597565b61175c565b3480156105d5575f80fd5b5061060d6105e4366004615655565b5f90815261119a60205260409020546001600160a01b03811691600160a01b90910461ffff1690565b604080516001600160a01b03909316835261ffff909116602083015201610367565b61044561063d366004615874565b611767565b34801561064d575f80fd5b5061031561065c3660046158bb565b611b61565b34801561066c575f80fd5b507f00000000000000000000000000000000000000000000000000000000000000006103cd565b34801561069e575f80fd5b506103156106ad366004615655565b611c68565b3480156106bd575f80fd5b506103156106cc366004615842565b611ccf565b3480156106dc575f80fd5b506103156106eb366004615655565b611ee2565b3480156106fb575f80fd5b5061044561070a366004615903565b611fcf565b34801561071a575f80fd5b506103156122b6565b34801561072e575f80fd5b5061074261073d366004615943565b6123ec565b604051610367969594939291906159ab565b34801561075f575f80fd5b507f00000000000000000000000000000000000000000000000000000000000000006103cd565b610315610794366004615655565b612a7c565b3480156107a4575f80fd5b506103cd6107b3366004615655565b5f9081526117776020526040908190206006810154600290910154600160a01b9182900467ffffffffffffffff1691900490911b73ffffffffffffffffffffffff0000000000000000161790565b34801561080c575f80fd5b5061082061081b366004615655565b612a87565b60405161036791905f610100820190506001600160a01b0380845116835260208401516020840152806040850151166040840152606084015160608401526080840151608084015260a084015160a08401528060c08501511660c08401525060e083015160e083015292915050565b61031561089d366004615842565b612b98565b3480156108ad575f80fd5b506108c16108bc366004615597565b612ba4565b604080516001600160a01b039094168452602084019290925290820152606001610367565b3480156108f1575f80fd5b50610905610900366004615842565b612c26565b604051610367969594939291906159f4565b610315610925366004615874565b612c95565b610315610938366004615a47565b612d74565b348015610948575f80fd5b50610445610957366004615a7d565b613155565b348015610967575f80fd5b507f00000000000000000000000000000000000000000000000000000000000000006103cd565b348015610999575f80fd5b506104456109a8366004615597565b613163565b3480156109b8575f80fd5b506103596109c7366004615ab5565b6131c7565b3480156109d7575f80fd5b507f00000000000000000000000000000000000000000000000000000000000000006103cd565b80805f03610a38576040517f3a970fe600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f83815261177760205260409020610a4e613786565b60028201546001600160a01b03908116911614610aad5760028101546040517f9802550c0000000000000000000000000000000000000000000000000000000081526001600160a01b0390911660048201526024015b60405180910390fd5b600581015415610ae9576040517f5aea7c4700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b82816007015403610b26576040517f4b669ac700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6007810183905560405183815284907f0c0f2662914f0cd1e952db2aa425901cb00e7c1f507687d22cb04e836d55d9c7906020015b60405180910390a250505050565b606080610b9f6001600160a01b0385167f2a55205a00000000000000000000000000000000000000000000000000000000613794565b15610ca8576040517f2a55205a0000000000000000000000000000000000000000000000000000000081526004810184905261271060248201526001600160a01b03851690632a55205a90619c409060440160408051808303818786fa93505050508015610c2a575060408051601f3d908101601f19168201909252610c2791810190615ae9565b60015b15610ca8578015610ca557604080516001808252818301909252906020808301908036833701905050935081845f81518110610c6857610c68615b29565b6001600160a01b03929092166020928302919091018201526040805160018082528183019092529182810190803683370190505092505050610d8a565b50505b610cdb6001600160a01b0385167fbb3bafd600000000000000000000000000000000000000000000000000000000613794565b15610d8a576040517fbb3bafd6000000000000000000000000000000000000000000000000000000008152600481018490526001600160a01b0385169063bb3bafd690619c40906024015f604051808303818786fa93505050508015610d6257506040513d5f823e601f3d908101601f19168201604052610d5f9190810190615c5a565b60015b15610d8a57815115801590610d78575080518251145b15610d87579092509050610d8a565b50505b9250929050565b610d99613860565b5f818152611777602090815260409182902082516101608101845281546001600160a01b039081168252600183015493820193909352600282015480841694820194909452600160a01b938490046bffffffffffffffffffffffff16606082015260038201546080820152600482015460a0820152600582015460c0820152600682015492831660e082015292820467ffffffffffffffff16610100840152600160e01b90910463ffffffff1661012083015260070154610140820152610e5e613786565b6001600160a01b031681604001516001600160a01b031614610ebd5760408082015190517f9802550c0000000000000000000000000000000000000000000000000000000081526001600160a01b039091166004820152602401610aa4565b60c081015115610ef9576040517f5aea7c4700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80516001600160a01b03165f90815261177660209081526040808320828501805185529083528184208490558584526117779092528220805473ffffffffffffffffffffffffffffffffffffffff191681556001810183905560028101839055600381018390556004810183905560058101839055600681018390556007019190915581519051610f8a91906138d5565b610fa0815f015182602001518360400151613956565b60405182907f14b9c40404d5b41deb481f9a40b8aeb2bf4b47679b38cf757075a66ed510f7f1905f90a250610fd66001610b8755565b50565b610fe1613860565b6001600160a01b038083165f908152611f4e6020908152604080832085845290915281205490911690611012613786565b90506001600160a01b038216611054576040517fc09f8e8200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806001600160a01b0316826001600160a01b0316146110aa576040517ff049b41a0000000000000000000000000000000000000000000000000000000081526001600160a01b0383166004820152602401610aa4565b6001600160a01b0384165f908152611f4e602090815260408083208684529091528120556110d9848483613956565b60405183906001600160a01b038616907f70c7877531c04c7d9caa8a7eca127384f04e8a6ee58b63f778ce5401d8bcae41905f90a3505061111b6001610b8755565b5050565b6001600160a01b0382165f908152612337602090815260408083208484529091528120805463ffffffff1642111561115a575f9150506111a3565b8054600182015470010000000000000000000000000000000090910460201b73ffffffffffffffffffffffffffffffff0000000016600160a01b90910463ffffffff16175b9150505b92915050565b6111b1613860565b6001600160a01b0384165f908152612337602090815260408083208684529091529020805463ffffffff164211156112205780546040517f8c9e57cf00000000000000000000000000000000000000000000000000000000815263ffffffff9091166004820152602401610aa4565b805464010000000090046bffffffffffffffffffffffff1682111561128b5780546040517f242373610000000000000000000000000000000000000000000000000000000081526401000000009091046bffffffffffffffffffffffff166004820152602401610aa4565b60018101546001600160a01b038481169116146112e55760018101546040517fa7d95dc30000000000000000000000000000000000000000000000000000000081526001600160a01b039091166004820152602401610aa4565b6112ef8585613961565b506112fb6001610b8755565b50505050565b5f82815261119a60205260408120546001600160a01b03168015611363575f84815261119b602090815260408083206001600160a01b038716845290915290205460ff168061119f5750806001600160a01b0316836001600160a01b03161491505b5092915050565b5f81815261119a6020908152604080832081516060808201845282546001600160a01b0381168352600160a01b900461ffff16948201949094526001820180549495948594859492908401916113bf90615cba565b80601f01602080910402602001604051908101604052809291908181526020018280546113eb90615cba565b80156114365780601f1061140d57610100808354040283529160200191611436565b820191905f5260205f20905b81548152906001019060200180831161141957829003601f168201915b50505091909252505050604081015181516020909201519097919650945092505050565b5f8282808303611496576040517fe808160000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6113888661ffff1611156114d6576040517f2b7b866100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61119980546001019081905592505f6114ed613786565b90506040518060600160405280826001600160a01b031681526020018861ffff1681526020018a8a8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92018290525093909452505086815261119a6020908152604091829020845181549286015161ffff16600160a01b027fffffffffffffffffffff000000000000000000000000000000000000000000009093166001600160a01b03909116179190911781559083015190915060018201906115b99082615d37565b50905050806001600160a01b0316847f9eee3ce0e6f7eeabd69ecf363898e9f490dbfda9ad953e1019a2c6aeceb4a7ef8b8b8b6040516115fb93929190615df3565b60405180910390a361160e848787613cfe565b50505095945050505050565b6001600160a01b038083165f908152611f4e60209081526040808320858452909152812054909116908161165057505f19610d8a565b506001600160a01b03929092165f908152611f4e6020908152604080832093835292905220549091600160a01b9091046bffffffffffffffffffffffff1690565b5f81815261177760205260408120600581015482036116b4576007015492915050565b6116c18160070154613e58565b9392505050565b6040517f40c1a064000000000000000000000000000000000000000000000000000000008152600481018290525f906001600160a01b038416906340c1a06490619c40906024016020604051808303818786fa15801561172a573d5f803e3d5ffd5b50505050506040513d601f19601f820116820180604052508101906116c19190615e2e565b6112fb83835f845f611fcf565b5f6116c18383613e79565b5f611773858585613e84565b1561177f57505f611b59565b6117898585613ef4565b156117c0576040517f83a483f500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b0385165f908152612337602090815260408083208784529091528120906117ec613786565b825490915063ffffffff164211156118b1576040517f4ec58ed70000000000000000000000000000000000000000000000000000000081526001600160a01b038281166004830152602482018790527f00000000000000000000000000000000000000000000000000000000000000001690634ec58ed790349060440160206040518083038185885af1158015611885573d5f803e3d5ffd5b50505050506040513d601f19601f820116820180604052508101906118aa9190615e49565b92506119fc565b81545f906118d49064010000000090046bffffffffffffffffffffffff16613e58565b905080861015611913576040517fe40a30e600000000000000000000000000000000000000000000000000000000815260048101829052602401610aa4565b600183015483546040517f5fdec5610000000000000000000000000000000000000000000000000000000081526001600160a01b03928316600482015263ffffffff821660248201526401000000009091046bffffffffffffffffffffffff1660448201528382166064820152608481018890527f000000000000000000000000000000000000000000000000000000000000000090911690635fdec56190349060a40160206040518083038185885af11580156119d3573d5f803e3d5ffd5b50505050506040513d601f19601f820116820180604052508101906119f89190615e49565b9350505b60018201805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383811691909117909155825463ffffffff85167fffffffffffffffffffffffffffffffff00000000000000000000000000000000909116176401000000006bffffffffffffffffffffffff8816021783557f0000000000000000000000000000000000000000000000000000000000000000811690851603611aa1575f93505b81546fffffffffffffffffffffffffffffffff908116602086811c909216700100000000000000000000000000000000021783556001830180547fffffffffffffffff00000000ffffffffffffffffffffffffffffffffffffffff16600160a01b63ffffffff881602179055604080518781529182018590526001600160a01b03838116928992918b16917ece0a712e4e277ac7b34942865f0de7a5629dffe0539b70423ad5ff1ed6ab42910160405180910390a450505b949350505050565b5f83815261119a602052604090205483906001600160a01b0316611b83613786565b6001600160a01b0316816001600160a01b031614611c18576001600160a01b038116611bdb576040517f0c77a95c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517fb39cb29b0000000000000000000000000000000000000000000000000000000081526001600160a01b0382166004820152602401610aa4565b83835f819003611c54576040517fe808160000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611c5f878787613cfe565b50505050505050565b611c70613860565b5f81815261177760205260408120600501549003611cba576040517f4b6ad8fa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611cc4815f613f3e565b610fd66001610b8755565b611cd7613860565b611ce28383836141bb565b611ed2576bffffffffffffffffffffffff811115611d2c576040517f35ec82cb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b038381165f908152611f4e60209081526040808320868452909152902080549091811690600160a01b90046bffffffffffffffffffffffff1683148015611d8257506001600160a01b03811615155b15611db9576040517fb6950f3600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b81546001600160a01b0316600160a01b6bffffffffffffffffffffffff8516021782555f611de5613786565b90506001600160a01b038216611e2a57611dff8686614221565b825473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b038216178355611e80565b806001600160a01b0316826001600160a01b031614611e80576040517f697d918e0000000000000000000000000000000000000000000000000000000081526001600160a01b0383166004820152602401610aa4565b806001600160a01b031685876001600160a01b03167ffcc77ea8bdcce862f43b7fb00fe6b0eb90d6aeead27d3800d9257cf7a05f9d9687604051611ec691815260200190565b60405180910390a45050505b611edd6001610b8755565b505050565b5f81815261119a602052604090205481906001600160a01b0316611f04613786565b6001600160a01b0316816001600160a01b031614611f5c576001600160a01b038116611bdb576040517f0c77a95c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f83815261119a6020526040812080547fffffffffffffffffffff0000000000000000000000000000000000000000000016815590611f9e6001830182615519565b505060405183907f2a9aeaf340ca0da469c1f7e3d513c0e6c9cd287016f29d257a4ef70e13dc441c905f90a2505050565b5f611fd8613860565b82805f03612012576040517f3a970fe600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b825f03612041577f000000000000000000000000000000000000000000000000000000000000000092506120c3565b62093a80831115612083576040517fccd285bd00000000000000000000000000000000000000000000000000000000815262093a806004820152602401610aa4565b6103848310156120c3576040517f9299180e0000000000000000000000000000000000000000000000000000000081526103846004820152602401610aa4565b61138d80546001810190915591506120db8787614221565b6001600160a01b0387165f908152611776602090815260408083208984529091529020541561215f576001600160a01b0387165f90815261177660209081526040808320898452909152908190205490517f7618a0030000000000000000000000000000000000000000000000000000000081526004810191909152602401610aa4565b5f612168613786565b6001600160a01b038981165f818152611776602090815260408083208d845282528083208990558883526117779091529020805473ffffffffffffffffffffffffffffffffffffffff199081169092178155600181018b9055600281018054909216928416929092179055600781018790559091507f00000000000000000000000000000000000000000000000000000000000000008514612239576006810180547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160e01b63ffffffff8816021790555b61224489898961422b565b6040805186815261038460208201529081018790526060810185905288906001600160a01b03808c1691908516907f1062dd3b35f12b4064331244d00f40c1d4831965e4285654157a2409c6217cff9060800160405180910390a45050506122ad6001610b8755565b95945050505050565b5f54610100900460ff16158080156122d457505f54600160ff909116105b806122ed5750303b1580156122ed57505f5460ff166001145b612379576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152608401610aa4565b5f805460ff19166001179055801561239a575f805461ff0019166101001790555b6123a5600161138d55565b8015610fd6575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a150565b5f6060808280806127106124207f00000000000000000000000000000000000000000000000000000000000000008b615e74565b61242a9190615e8b565b6040517f4c542f770000000000000000000000000000000000000000000000000000000081526001600160a01b038e81166004830152602482018e90529197505f917f00000000000000000000000000000000000000000000000000000000000000001690634c542f7790604401602060405180830381865afa9250505080156124d1575060408051601f3d908101601f191682019092526124ce91810190615e2e565b60015b156124d95790505b6040517f0d7daf3e0000000000000000000000000000000000000000000000000000000081526001600160a01b038e81166004830152602482018e90527f00000000000000000000000000000000000000000000000000000000000000001690630d7daf3e906044015f60405180830381865afa92505050801561257e57506040513d5f823e601f3d908101601f1916820160405261257b9190810190615c5a565b60015b156125895790965094505b85515f03612649576040517fefef76f80000000000000000000000000000000000000000000000000000000081526001600160a01b038e81166004830152602482018e905282811660448301527f0000000000000000000000000000000000000000000000000000000000000000169063efef76f8906064015f60405180830381865afa92505050801561263e57506040513d5f823e601f3d908101601f1916820160405261263b9190810190615c5a565b60015b156126495790965094505b855115158061267557507f00000000000000000000000000000000000000000000000000000000000000005b15612977575f7f00000000000000000000000000000000000000000000000000000000000000001561271d57878b03905086515f036127185760408051600180825281830190925290602080830190803683370190505096508b875f815181106126e1576126e1615b29565b6001600160a01b03929092166020928302919091018201526040805160018082528183019092529182810190803683370190505095505b61279d565b816001600160a01b03168c6001600160a01b0316148061277257508651158015906127725750865f8151811061275557612755615b29565b60200260200101516001600160a01b03168c6001600160a01b0316145b156127805750868a0361279d565b50600a8a0480612790898d615eaa565b61279a9190615eaa565b94505b6127a887600561435c565b6127b386600561435c565b61ffff8916156127ff576127106127ce61ffff8b168d615e74565b6127d89190615e8b565b9250845f036127f2576127eb8382615eaa565b90506127ff565b6127fc8386615eaa565b94505b5f6001885111156128d3575f5b88518110156128aa578d6001600160a01b031689828151811061283157612831615b29565b60200260200101516001600160a01b03160361284f575f9692909201915b5f1982146128a25761271088828151811061286c5761286c615b29565b60200260200101511115612883575f1991506128a2565b87818151811061289557612895615b29565b6020026020010151820191505b60010161280c565b508015806128b857505f1981145b156128d3576128c888600161435c565b6128d387600161435c565b5f60015b8951811015612946575f838a83815181106128f4576128f4615b29565b6020026020010151866129079190615e74565b6129119190615e8b565b905061291d8184615ebd565b9250808a838151811061293257612932615b29565b6020908102919091010152506001016128d7565b506129518184615eaa565b885f8151811061296357612963615b29565b6020026020010181815250505050506129b0565b868a03935061ffff8816156129b05761271061299761ffff8a168c615e74565b6129a19190615e8b565b91506129ad8285615eaa565b93505b6001600160a01b038916158015906129e157506129cb613786565b6001600160a01b0316896001600160a01b031614155b80156129ff57508a6001600160a01b0316896001600160a01b031614155b8015612a1d5750806001600160a01b0316896001600160a01b031614155b8015612a5b57507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316896001600160a01b031614155b15612a6c5760648a04925082870396505b5096509650965096509650969050565b610fd681345f612d74565b612ae26040518061010001604052805f6001600160a01b031681526020015f81526020015f6001600160a01b031681526020015f81526020015f81526020015f81526020015f6001600160a01b031681526020015f81525090565b5f8281526117776020526040812060068101549091600160e01b90910463ffffffff1690819003612b3057507f00000000000000000000000000000000000000000000000000000000000000005b604080516101008101825283546001600160a01b03908116825260018501546020830152600285015481169282019290925260608101929092526103846080830152600583015460a083015260068301541660c082015260079091015460e082015292915050565b611edd8383835f612c95565b6001600160a01b0382165f90815261233760209081526040808320848452909152812080548291829163ffffffff16421115612be9575f805f93509350935050612c1f565b600181015490546001600160a01b03909116935063ffffffff8116925064010000000090046bffffffffffffffffffffffff1690505b9250925092565b5f806060805f80612c378989614369565b9050612c478989838a5f806123ec565b50939950919650945092505f90505b8351811015612c8857838181518110612c7157612c71615b29565b602002602001015186019550806001019050612c56565b5093975093979195509350565b6001600160a01b0384165f908152611f4e6020908152604080832086845290915290208054600160a01b90046bffffffffffffffffffffffff16831015612d215780546040517f16b5016f000000000000000000000000000000000000000000000000000000008152600160a01b9091046bffffffffffffffffffffffff166004820152602401610aa4565b80546001600160a01b0316612d62576040517fda48e18400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612d6d858584614404565b5050505050565b612d7c613860565b5f838152611777602052604081206007810154909103612dc8576040517f125197d100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b34831015612e02576040517fe2bbc1e300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60058101545f612e10613786565b90507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316846001600160a01b031603612e4f575f93505b6001600160a01b038416151580612e6557508115155b15612ed8576002830180546001600160a01b0316604086901c6bffffffffffffffffffffffff16600160a01b908102919091179091556006840180547fffffffff0000000000000000ffffffffffffffffffffffffffffffffffffffff1667ffffffffffffffff87169092029190911790555b815f03612fba578260070154851015612f255782600701546040517f31e6f71c000000000000000000000000000000000000000000000000000000008152600401610aa491815260200190565b82546001840154612f3f916001600160a01b03169061454e565b6007830185905560068301805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383161790819055600160e01b900463ffffffff165f819003612faa57507f00000000000000000000000000000000000000000000000000000000000000005b42016005840181905591506130f7565b612fc382421190565b15612ffd576040517f3feeb88d00000000000000000000000000000000000000000000000000000000815260048101839052602401610aa4565b60068301546001600160a01b03808316911603613046576040517fe140576800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f6130548460070154613e58565b905080861015613093576040517fcd698a1900000000000000000000000000000000000000000000000000000000815260048101829052602401610aa4565b50600783018054600685018054928890556001600160a01b0384811673ffffffffffffffffffffffffffffffffffffffff198516179091559091164261038401808510156130e657600586018190559350835b506130f48183614e20614558565b50505b613101855f6146e8565b60408051868152602081018490526001600160a01b0383169188917f26ea3ebbda62eb1baef13e1c237dddd956c87f80b2801f2616d806d52557b121910160405180910390a3505050611edd6001610b8755565b5f6122ad858584865f611fcf565b6001600160a01b0382165f908152612337602090815260408083208484529091528120805463ffffffff1642116131bd5780546131b59064010000000090046bffffffffffffffffffffffff16613e58565b9150506111a3565b5060019392505050565b6040517fde5488af0000000000000000000000000000000000000000000000000000000081526001600160a01b03848116600483015260609182917f0000000000000000000000000000000000000000000000000000000000000000169063de5488af90619c40906024016020604051808303818786fa9350505050801561326c575060408051601f3d908101601f1916820190925261326991810190615e2e565b60015b156134ba57856001600160a01b0316816001600160a01b0316146134b8579450846132c06001600160a01b0382167f2a55205a00000000000000000000000000000000000000000000000000000000613794565b156133ca576040517f2a55205a0000000000000000000000000000000000000000000000000000000081526004810186905261271060248201526001600160a01b03871690632a55205a90619c409060440160408051808303818786fa9350505050801561334b575060408051601f3d908101601f1916820190925261334891810190615ae9565b60015b156133ca5780156133c757604080516001808252818301909252906020808301908036833701905050945081855f8151811061338957613389615b29565b6001600160a01b039290921660209283029190910182015260408051600180825281830190925291828101908036833701905050935050505061377e565b50505b825115801561340757506134076001600160a01b0387167fbb3bafd600000000000000000000000000000000000000000000000000000000613794565b156134b8576040517fbb3bafd6000000000000000000000000000000000000000000000000000000008152600481018690526001600160a01b0387169063bb3bafd690619c40906024015f604051808303818786fa9350505050801561348e57506040513d5f823e601f3d908101601f1916820160405261348b9190810190615c5a565b60015b156134b8578151158015906134a4575080518251145b156134b557909350915061377e9050565b50505b505b6134ed6001600160a01b0386167fb779958400000000000000000000000000000000000000000000000000000000613794565b1561361e576040517fb9c4d9fb000000000000000000000000000000000000000000000000000000008152600481018590526001600160a01b0386169063b9c4d9fb90619c40906024015f604051808303818786fa9350505050801561357457506040513d5f823e601f3d908101601f191682016040526135719190810190615ed0565b60015b1561361e5780511561361c576040517f0ebd4c7f000000000000000000000000000000000000000000000000000000008152600481018690526001600160a01b03871690630ebd4c7f90619c40906024015f604051808303818786fa9350505050801561360257506040513d5f823e601f3d908101601f191682016040526135ff9190810190615f02565b60015b1561361c57805182510361361a57909250905061377e565b505b505b6001600160a01b0383161561369b57604080516001808252818301909252906020808301908036833701905050915082825f8151811061366057613660615b29565b6001600160a01b039290921660209283029190910182015260408051600180825281830190925291828101908036833701905050905061377e565b846001600160a01b0316638da5cb5b619c406040518263ffffffff1660e01b81526004016020604051808303818786fa935050505080156136f9575060408051601f3d908101601f191682019092526136f691810190615e2e565b60015b1561377e576001600160a01b0381161561377c57604080516001808252818301909252906020808301908036833701905050925080835f8151811061374057613740615b29565b6001600160a01b03929092166020928302919091018201526040805160018082528183019092529182810190803683370190505091505061377e565b505b935093915050565b5f61378f6147ce565b905090565b604080517fffffffff000000000000000000000000000000000000000000000000000000008316602480830191909152825180830390910181526044909101909152602080820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01ffc9a70000000000000000000000000000000000000000000000000000000017815282515f9392849283928392918391908a617530fa92503d91505f51905082801561384a575060208210155b801561385557505f81115b979650505050505050565b6002610b8754036138cd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610aa4565b6002610b8755565b6001600160a01b0382165f90815261119c602090815260408083208484529091529020548015611edd576001600160a01b0383165f81815261119c60209081526040808320868452909152808220829055518392859290917f2ea2946ee16c4a1d0ec58464194022e54432a6d7db359835ddf283555f2c8eee9190a4505050565b611edd83838361482b565b6001600160a01b038281165f90815261233760209081526040808320858452808352818420825160a081018452815463ffffffff808216835264010000000082046bffffffffffffffffffffffff9081168489019081527001000000000000000000000000000000009093046fffffffffffffffffffffffffffffffff1684880152600185018054808c1660608701908152600160a01b8204851660808801528d8c5297909952989094557fffffffffffffffff00000000000000000000000000000000000000000000000090961690965591518251955193517f4dc8fb3c000000000000000000000000000000000000000000000000000000008152908716600482015294909316602485015291166044830152917f00000000000000000000000000000000000000000000000000000000000000001690634dc8fb3c906064015f604051808303815f87803b158015613aba575f80fd5b505af1158015613acc573d5f803e3d5ffd5b505050505f613ad9613786565b90505f80613ae78686614861565b6040517f6352211e0000000000000000000000000000000000000000000000000000000081526004810188905291935091505f906001600160a01b03881690636352211e90602401602060405180830381865afa158015613b4a573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190613b6e9190615e2e565b9050306001600160a01b03821603613b9557613b9087878760600151876148db565b613c1a565b60608501516040517f23b872dd0000000000000000000000000000000000000000000000000000000081526001600160a01b038681166004830152918216602482015260448101889052908816906323b872dd906064015f604051808303815f87803b158015613c03575f80fd5b505af1158015613c15573d5f803e3d5ffd5b505050505b5f805f613c758a8a898b602001516bffffffffffffffffffffffff16613c6e8d604001518e6080015173ffffffffffffffffffffffffffffffff0000000060209290921b9190911663ffffffff9091161790565b8b8b6148e7565b92509250925087606001516001600160a01b0316898b6001600160a01b03167f1cb8adb37d6d35e94cd0695ca39895b84371864713f5ca7eada52af9ff23744b8a878787604051613cea94939291906001600160a01b0394909416845260208401929092526040830152606082015260800190565b60405180910390a450505050505050505050565b5f5b81811015613e18575f838383818110613d1b57613d1b615b29565b9050602002016020810190613d309190615f34565b5f86815261119b602090815260408083206001600160a01b038516845290915290205490915060ff1615613d90576040517f667888ec00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b613d98613786565b6001600160a01b0316816001600160a01b031603613de2576040517f43e2197f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f85815261119b602090815260408083206001600160a01b03909416835292905220805460ff1916600190811790915501613d00565b50827fd5a31bd2d34d303249ac7f54bfc7578390f90f5d39cb39813f67190fa36f5c178383604051613e4b929190615f4f565b60405180910390a2505050565b5f600a8204808203613e6f576116c1836001615ebd565b6116c18382615ebd565b5f6116c18383614b05565b6001600160a01b038084165f908152611f4e602090815260408083208684529091528120805491929091161580613ed057508054600160a01b90046bffffffffffffffffffffffff1683105b15613ede575f9150506116c1565b613ee985855f614404565b506001949350505050565b6001600160a01b0382165f90815261177660209081526040808320848452909152812054801580159061119f57505f90815261177760205260409020600501544211159392505050565b5f828152611777602090815260409182902082516101608101845281546001600160a01b039081168252600183015493820193909352600282015480841694820194909452600160a01b938490046bffffffffffffffffffffffff16606082015260038201546080820152600482015460a0820152600582015460c08201819052600683015493841660e083015293830467ffffffffffffffff16610100820152600160e01b90920463ffffffff166101208301526007015461014082015290421161403e578060c001516040517f3a017f60000000000000000000000000000000000000000000000000000000008152600401610aa491815260200190565b5f80614051835f01518460200151614861565b84516001600160a01b03165f9081526117766020908152604080832082890151845282528083208390558983526117779091528120805473ffffffffffffffffffffffffffffffffffffffff19168155600181018290556002810182905560038101829055600481018290556005810182905560068101829055600701559092509050836140f0576140f0835f015184602001518560e001515f614b3a565b5f805f614142865f0151876020015188604001518961014001518a610100015167ffffffffffffffff1660408c606001516bffffffffffffffffffffffff166001600160a01b0316901b178a8a6148e7565b9250925092508560e001516001600160a01b031686604001516001600160a01b0316897f2edb0e99c6ac35be6731dab554c1d1fa1b7beb675090dbb09fb14e615aca1c4a8686866040516141a9939291909283526020830191909152604082015260600190565b60405180910390a45050505050505050565b6001600160a01b0383165f908152612337602090815260408083208584529091528120805463ffffffff164211806142095750805464010000000090046bffffffffffffffffffffffff1683115b15614217575f9150506116c1565b613ee98585613961565b61111b8282614bfd565b8015611edd575f81815261119a6020526040902080546001600160a01b0316614280576040517f0c77a95c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f614289613786565b5f84815261119b602090815260408083206001600160a01b038516845290915290205490915060ff161580156142cc575081546001600160a01b03828116911614155b15614303576040517f6e93a35400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b0385165f81815261119c60209081526040808320888452909152808220869055518592879290917fb17e0c916df75a12480835f00b3927cb871bbe00bacf819f81a1d92f9ff7f38d9190a45050505050565b808251111561111b579052565b5f6143748383613e79565b90506001600160a01b0381166111a3576040517f6352211e000000000000000000000000000000000000000000000000000000008152600481018390526001600160a01b03841690636352211e90602401602060405180830381865afa1580156143e0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116c19190615e2e565b61440c613860565b6001600160a01b038381165f908152611f4e60209081526040808320868452808352818420825180840190935280549586168352600160a01b9095046bffffffffffffffffffffffff1682840152868452909152915561446c8484614c8f565b61448981602001516bffffffffffffffffffffffff1660016146e8565b5f614492613786565b90505f806144a08787614861565b915091506144b08787855f6148db565b5f805f6144d78a8a895f01518a602001516bffffffffffffffffffffffff168c8a8a6148e7565b8951604080516001600160a01b038c8116825260208201879052918101859052606081018490529497509295509093508116918b918d16907fd28c0a7dd63bc853a4e36306655da9f8c0b29ff9d0605bb976ae420e46a999309060800160405180910390a450505050505050611edd6001610b8755565b61111b8282614cdb565b815f0361456457505050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316836001600160a01b0316036145c1577f000000000000000000000000000000000000000000000000000000000000000092505b5f836001600160a01b03168383906040515f60405180830381858888f193505050503d805f811461460d576040519150601f19603f3d011682016040523d82523d5f602084013e614612565b606091505b50509050806112fb576040517faa67c9190000000000000000000000000000000000000000000000000000000081526001600160a01b0385811660048301527f0000000000000000000000000000000000000000000000000000000000000000169063aa67c9199085906024015f604051808303818588803b158015614696575f80fd5b505af11580156146a8573d5f803e3d5ffd5b5050505050836001600160a01b03167fa2201512569adb2d513531dfd69b66df50bd5cffb8c1bbe65a4611f9e1eadbd184604051610b5b91815260200190565b348211156147a0577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663452f2b8f614727613786565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b1681526001600160a01b03909116600482015234850360248201526044015b5f604051808303815f87803b158015614786575f80fd5b505af1158015614798573d5f803e3d5ffd5b505050505050565b8080156147ac57503482105b1561111b5761111b8234036147bf613786565b6001600160a01b031690614cef565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016810361482857507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec36013560601c5b90565b6001600160a01b038084165f908152611f4e6020908152604080832086845290915290205416806112fb576112fb848484614e38565b6001600160a01b0382165f90815261119c60209081526040808320848452909152812054819080156148d3575f81815261119a60209081526040808320546001600160a01b03898116855261119c84528285208986529093529083209290925581169350600160a01b900461ffff1691505b509250929050565b6112fb84848484614e6d565b5f805f865f036148fe57505f915081905080614af8565b6060805f806149118e8e8e8e8e8d6123ec565b8451959c5091995092975090955090935091505f9060011461493557614e2061493a565b620334505b90505f5b85518110156149ad5761498486828151811061495c5761495c615b29565b602002602001015186838151811061497657614976615b29565b602002602001015184614558565b84818151811061499657614996615b29565b60200260200101518801975080600101905061493e565b506149bb8d87614e20614558565b6149e87f000000000000000000000000000000000000000000000000000000000000000089614e20614558565b8215614a62576149fb8b84614e20614558565b8d8f6001600160a01b03167f141b92fd9766c80ab120598ea2f6be9802470ec59b5446dd9bf46214ead8d08e8d865f604051614a55939291906001600160a01b039390931683526020830191909152604082015260600190565b60405180910390a3968201965b6001600160a01b038a1615614af2578115614a9957855f03614a875795810195614a8c565b948101945b614a998a83614e20614558565b8d8f6001600160a01b03167f27a4dd4ff659a9e6354fb079b2208365e5b83f55c22a4150eee2bca89501cb988c85604051614ae99291906001600160a01b03929092168252602082015260400190565b60405180910390a35b50505050505b9750975097945050505050565b6001600160a01b038083165f908152611f4e6020908152604080832085845290915290205416806111a3576116c18383614f08565b6001600160a01b03811615614b7b576040517f57a016b300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f23b872dd0000000000000000000000000000000000000000000000000000000081523060048201526001600160a01b038381166024830152604482018590528516906323b872dd906064015f604051808303815f87803b158015614be1575f80fd5b505af1158015614bf3573d5f803e3d5ffd5b5050505050505050565b6001600160a01b038083165f908152611f4e602090815260408083208584529091529020541680614c3257611edd8383614f47565b614c3a613786565b6001600160a01b0316816001600160a01b031614611edd576040517f32f3b0330000000000000000000000000000000000000000000000000000000081526001600160a01b0382166004820152602401610aa4565b6001600160a01b0382165f908152612337602090815260408083208484529091529020614cba613786565b60018201546001600160a01b03918216911603611edd57611edd8383615065565b614ce58282615065565b61111b828261523e565b80471015614d59576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e63650000006044820152606401610aa4565b5f826001600160a01b0316826040515f6040518083038185875af1925050503d805f8114614da2576040519150601f19603f3d011682016040523d82523d5f602084013e614da7565b606091505b5050905080611edd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260448201527f6563697069656e74206d617920686176652072657665727465640000000000006064820152608401610aa4565b6001600160a01b0383165f908152611776602090815260408083208584529091528120549003611edd57611edd838383615276565b6001600160a01b038085165f908152611f4e60209081526040808320878452909152902054168015614efc57816001600160a01b0316816001600160a01b031614614eef576040517f32f3b0330000000000000000000000000000000000000000000000000000000081526001600160a01b0382166004820152602401610aa4565b5f9150614efc8585615282565b612d6d858585856152d4565b6001600160a01b038083165f90815261177660209081526040808320858452825280832054835261177790915290206002015416806111a3575f6116c1565b6001600160a01b0382165f9081526117766020908152604080832084845290915281205490819003614f7d57611edd83836154b1565b5f8181526117776020526040812090614f94613786565b905081600501545f036150005760028201546001600160a01b03828116911614614ffb5760028201546040517fe64526ee0000000000000000000000000000000000000000000000000000000081526001600160a01b039091166004820152602401610aa4565b612d6d565b60068201546001600160a01b0382811691161461505a5760068201546040517fe64526ee0000000000000000000000000000000000000000000000000000000081526001600160a01b039091166004820152602401610aa4565b612d6d836001613f3e565b6001600160a01b0382165f9081526123376020908152604080832084845290915290205463ffffffff16421161111b576001600160a01b038281165f90815261233760209081526040808320858452808352818420825160a081018452815463ffffffff808216835264010000000082046bffffffffffffffffffffffff9081168489019081527001000000000000000000000000000000009093046fffffffffffffffffffffffffffffffff1684880152600185018054808c1660608701908152600160a01b8204851660808801528d8c5297909952989094557fffffffffffffffff00000000000000000000000000000000000000000000000090961690965591518251955193517f345db493000000000000000000000000000000000000000000000000000000008152908716600482015294909316602485015291166044830152917f0000000000000000000000000000000000000000000000000000000000000000169063345db493906064015f604051808303815f87803b1580156151ee575f80fd5b505af1158015615200573d5f803e3d5ffd5b50506040518492506001600160a01b03861691507f30c264456cbd17f5f67d7534654161414f34c0e6cc1b7500e169b7a7aea4afc0905f90a3505050565b6001600160a01b038083165f908152611f4e602090815260408083208584529091529020805490911615611edd57611edd8383615282565b611edd8383835f6148db565b6001600160a01b0382165f818152611f4e60209081526040808320858452909152808220829055518392917faa6271d89a385571e237d3e7254ccc7c09f68055e6e9b410ed08233a8b9a05cf91a35050565b6001600160a01b0384165f9081526117766020908152604080832086845290915290205480156154a5575f81815261177760205260408120600581015490910361543b576001600160a01b03831615801590615340575060028101546001600160a01b03848116911614155b156153885760028101546040517fe64526ee0000000000000000000000000000000000000000000000000000000081526001600160a01b039091166004820152602401610aa4565b6001600160a01b0386165f9081526117766020908152604080832088845282528083208390558483526117779091528120805473ffffffffffffffffffffffffffffffffffffffff191681556001810182905560028101829055600381018290556004810182905560058101829055600681018290556007015561540c86866138d5565b60405182907f5603897cc9b1e866f3f7395ffc6638776041f21c094d0b4e748ff44c407fa362905f90a26154a0565b60068101546001600160a01b038481169116146154955760068101546040517fe64526ee0000000000000000000000000000000000000000000000000000000081526001600160a01b039091166004820152602401610aa4565b6154a0826001613f3e565b5f9250505b612d6d85858585614b3a565b816001600160a01b03166323b872dd6154c8613786565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b1681526001600160a01b0390911660048201523060248201526044810184905260640161476f565b50805461552590615cba565b5f825580601f10615534575050565b601f0160209004905f5260205f2090810190610fd691905b8082111561555f575f815560010161554c565b5090565b5f8060408385031215615574575f80fd5b50508035926020909101359150565b6001600160a01b0381168114610fd6575f80fd5b5f80604083850312156155a8575f80fd5b82356155b381615583565b946020939093013593505050565b5f8151808452602080850194508084015f5b838110156155f85781516001600160a01b0316875295820195908201906001016155d3565b509495945050505050565b5f8151808452602080850194508084015f5b838110156155f857815187529582019590820190600101615615565b604081525f61564360408301856155c1565b82810360208401526122ad8185615603565b5f60208284031215615665575f80fd5b5035919050565b5f805f806080858703121561567f575f80fd5b843561568a81615583565b93506020850135925060408501356156a181615583565b9396929550929360600135925050565b5f80604083850312156156c2575f80fd5b8235915060208301356156d481615583565b809150509250929050565b606081525f84518060608401525f5b8181101561570b57602081880181015160808684010152016156ee565b505f608082850101526080601f19601f8301168401019150506001600160a01b038416602083015261ffff83166040830152949350505050565b803561ffff81168114615756575f80fd5b919050565b5f8083601f84011261576b575f80fd5b50813567ffffffffffffffff811115615782575f80fd5b6020830191508360208260051b8501011115610d8a575f80fd5b5f805f805f606086880312156157b0575f80fd5b853567ffffffffffffffff808211156157c7575f80fd5b818801915088601f8301126157da575f80fd5b8135818111156157e8575f80fd5b8960208285010111156157f9575f80fd5b6020830197508096505061580f60208901615745565b94506040880135915080821115615824575f80fd5b506158318882890161575b565b969995985093965092949392505050565b5f805f60608486031215615854575f80fd5b833561585f81615583565b95602085013595506040909401359392505050565b5f805f8060808587031215615887575f80fd5b843561589281615583565b9350602085013592506040850135915060608501356158b081615583565b939692955090935050565b5f805f604084860312156158cd575f80fd5b83359250602084013567ffffffffffffffff8111156158ea575f80fd5b6158f68682870161575b565b9497909650939450505050565b5f805f805f60a08688031215615917575f80fd5b853561592281615583565b97602087013597506040870135966060810135965060800135945092505050565b5f805f805f8060c08789031215615958575f80fd5b863561596381615583565b955060208701359450604087013561597a81615583565b935060608701359250608087013561599181615583565b915061599f60a08801615745565b90509295509295509295565b86815260c060208201525f6159c360c08301886155c1565b82810360408401526159d58188615603565b60608401969096525050608081019290925260a0909101529392505050565b86815285602082015260c060408201525f615a1260c08301876155c1565b8281036060840152615a248187615603565b9150508360808301526001600160a01b03831660a0830152979650505050505050565b5f805f60608486031215615a59575f80fd5b83359250602084013591506040840135615a7281615583565b809150509250925092565b5f805f8060808587031215615a90575f80fd5b8435615a9b81615583565b966020860135965060408601359560600135945092505050565b5f805f60608486031215615ac7575f80fd5b8335615ad281615583565b9250602084013591506040840135615a7281615583565b5f8060408385031215615afa575f80fd5b8251615b0581615583565b6020939093015192949293505050565b634e487b7160e01b5f52604160045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b604051601f8201601f1916810167ffffffffffffffff81118282101715615b6657615b66615b15565b604052919050565b5f67ffffffffffffffff821115615b8757615b87615b15565b5060051b60200190565b5f82601f830112615ba0575f80fd5b81516020615bb5615bb083615b6e565b615b3d565b82815260059290921b84018101918181019086841115615bd3575f80fd5b8286015b84811015615bf7578051615bea81615583565b8352918301918301615bd7565b509695505050505050565b5f82601f830112615c11575f80fd5b81516020615c21615bb083615b6e565b82815260059290921b84018101918181019086841115615c3f575f80fd5b8286015b84811015615bf75780518352918301918301615c43565b5f8060408385031215615c6b575f80fd5b825167ffffffffffffffff80821115615c82575f80fd5b615c8e86838701615b91565b93506020850151915080821115615ca3575f80fd5b50615cb085828601615c02565b9150509250929050565b600181811c90821680615cce57607f821691505b602082108103615cec57634e487b7160e01b5f52602260045260245ffd5b50919050565b601f821115611edd575f81815260208120601f850160051c81016020861015615d185750805b601f850160051c820191505b8181101561479857828155600101615d24565b815167ffffffffffffffff811115615d5157615d51615b15565b615d6581615d5f8454615cba565b84615cf2565b602080601f831160018114615d98575f8415615d815750858301515b5f19600386901b1c1916600185901b178555614798565b5f85815260208120601f198616915b82811015615dc657888601518255948401946001909101908401615da7565b5085821015615de357878501515f19600388901b60f8161c191681555b5050505050600190811b01905550565b60408152826040820152828460608301375f606084830101525f6060601f19601f860116830101905061ffff83166020830152949350505050565b5f60208284031215615e3e575f80fd5b81516116c181615583565b5f60208284031215615e59575f80fd5b5051919050565b634e487b7160e01b5f52601160045260245ffd5b80820281158282048414176111a3576111a3615e60565b5f82615ea557634e487b7160e01b5f52601260045260245ffd5b500490565b818103818111156111a3576111a3615e60565b808201808211156111a3576111a3615e60565b5f60208284031215615ee0575f80fd5b815167ffffffffffffffff811115615ef6575f80fd5b61119f84828501615b91565b5f60208284031215615f12575f80fd5b815167ffffffffffffffff811115615f28575f80fd5b61119f84828501615c02565b5f60208284031215615f44575f80fd5b81356116c181615583565b60208082528181018390525f908460408401835b86811015615bf7578235615f7681615583565b6001600160a01b031682529183019190830190600101615f6356fea2646970667358221220da526576c5c96b5f5f5d5263ff411bdd62173673faf47920335eb25973a93be064736f6c6343000814003300000000000000000000000067df244584b67e8c51b10ad610aaffa9a402fdb600000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d50443000000000000000000000000ad2184fb5dbcfc05d8f056542fb25b04fa32a95d0000000000000000000000000000000000000000000000000000000000015180000000000000000000000000762340b8a40cdd5bfc3edd94265899fda345d0e3

Deployed Bytecode

0x6080604052600436106102ae575f3560e01c80637430e0c611610165578063a59ac6dd116100c6578063beb5127c1161007c578063e5d1e72311610062578063e5d1e7231461098e578063efef76f8146109ad578063f7a2da23146109cc575f80fd5b8063beb5127c1461093d578063daa351d41461095c575f80fd5b8063af1e1de3116100ac578063af1e1de3146108e6578063b01ef60814610917578063b6aff8c11461092a575f80fd5b8063a59ac6dd1461088f578063ac71045e146108a2575f80fd5b806387a4fdcb1161011b5780639979ef45116101015780639979ef45146107865780639e64ba6c146107995780639e79b41f14610801575f80fd5b806387a4fdcb14610723578063895633ba14610754575f80fd5b80637b3a58841161014b5780637b3a5884146106d15780638098531d146106f05780638129fc1c1461070f575f80fd5b80637430e0c614610693578063798bac8d146106b2575f80fd5b8063445738d81161020f5780634fca06c6116101c5578063614b151c116101ab578063614b151c1461062f5780636512ed2d146106425780636a90a82714610661575f80fd5b80634fca06c6146105ab57806355daed3e146105ca575f80fd5b806347e35740116101f557806347e357401461054e5780634c542f771461056d5780634ce6931a1461058c575f80fd5b8063445738d8146104f15780634635256e14610510575f80fd5b806329e0e160116102645780632e06db961161024a5780632e06db96146104535780633c58e54d14610482578063442559a2146104b0575f80fd5b806329e0e160146103e55780632ab2b52b14610404575f80fd5b806321506fff1161029457806321506fff14610370578063215619351461038f578063262907c5146103ae575f80fd5b806303ec16d71461031b5780630d7daf3e1461033a575f80fd5b3661031757336001600160a01b037f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d504431614610315576040517faa39384e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b005b5f80fd5b348015610326575f80fd5b50610315610335366004615563565b6109fe565b348015610345575f80fd5b50610359610354366004615597565b610b69565b604051610367929190615631565b60405180910390f35b34801561037b575f80fd5b5061031561038a366004615655565b610d91565b34801561039a575f80fd5b506103156103a9366004615597565b610fd9565b3480156103b9575f80fd5b506103cd6103c8366004615597565b61111f565b6040516001600160a01b039091168152602001610367565b3480156103f0575f80fd5b506103156103ff36600461566c565b6111a9565b34801561040f575f80fd5b5061044561041e366004615597565b6001600160a01b039091165f90815261177660209081526040808320938352929052205490565b604051908152602001610367565b34801561045e575f80fd5b5061047261046d3660046156b1565b611301565b6040519015158152602001610367565b34801561048d575f80fd5b506104a161049c366004615655565b61136a565b604051610367939291906156df565b3480156104bb575f80fd5b506104456104ca366004615597565b6001600160a01b039091165f90815261119c60209081526040808320938352929052205490565b3480156104fc575f80fd5b5061044561050b36600461579c565b61145a565b34801561051b575f80fd5b5061052f61052a366004615597565b61161a565b604080516001600160a01b039093168352602083019190915201610367565b348015610559575f80fd5b50610445610568366004615655565b611691565b348015610578575f80fd5b506103cd610587366004615597565b6116c8565b348015610597575f80fd5b506103156105a6366004615842565b61174f565b3480156105b6575f80fd5b506103cd6105c5366004615597565b61175c565b3480156105d5575f80fd5b5061060d6105e4366004615655565b5f90815261119a60205260409020546001600160a01b03811691600160a01b90910461ffff1690565b604080516001600160a01b03909316835261ffff909116602083015201610367565b61044561063d366004615874565b611767565b34801561064d575f80fd5b5061031561065c3660046158bb565b611b61565b34801561066c575f80fd5b507f000000000000000000000000762340b8a40cdd5bfc3edd94265899fda345d0e36103cd565b34801561069e575f80fd5b506103156106ad366004615655565b611c68565b3480156106bd575f80fd5b506103156106cc366004615842565b611ccf565b3480156106dc575f80fd5b506103156106eb366004615655565b611ee2565b3480156106fb575f80fd5b5061044561070a366004615903565b611fcf565b34801561071a575f80fd5b506103156122b6565b34801561072e575f80fd5b5061074261073d366004615943565b6123ec565b604051610367969594939291906159ab565b34801561075f575f80fd5b507f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d504436103cd565b610315610794366004615655565b612a7c565b3480156107a4575f80fd5b506103cd6107b3366004615655565b5f9081526117776020526040908190206006810154600290910154600160a01b9182900467ffffffffffffffff1691900490911b73ffffffffffffffffffffffff0000000000000000161790565b34801561080c575f80fd5b5061082061081b366004615655565b612a87565b60405161036791905f610100820190506001600160a01b0380845116835260208401516020840152806040850151166040840152606084015160608401526080840151608084015260a084015160a08401528060c08501511660c08401525060e083015160e083015292915050565b61031561089d366004615842565b612b98565b3480156108ad575f80fd5b506108c16108bc366004615597565b612ba4565b604080516001600160a01b039094168452602084019290925290820152606001610367565b3480156108f1575f80fd5b50610905610900366004615842565b612c26565b604051610367969594939291906159f4565b610315610925366004615874565b612c95565b610315610938366004615a47565b612d74565b348015610948575f80fd5b50610445610957366004615a7d565b613155565b348015610967575f80fd5b507f000000000000000000000000ad2184fb5dbcfc05d8f056542fb25b04fa32a95d6103cd565b348015610999575f80fd5b506104456109a8366004615597565b613163565b3480156109b8575f80fd5b506103596109c7366004615ab5565b6131c7565b3480156109d7575f80fd5b507f00000000000000000000000067df244584b67e8c51b10ad610aaffa9a402fdb66103cd565b80805f03610a38576040517f3a970fe600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f83815261177760205260409020610a4e613786565b60028201546001600160a01b03908116911614610aad5760028101546040517f9802550c0000000000000000000000000000000000000000000000000000000081526001600160a01b0390911660048201526024015b60405180910390fd5b600581015415610ae9576040517f5aea7c4700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b82816007015403610b26576040517f4b669ac700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6007810183905560405183815284907f0c0f2662914f0cd1e952db2aa425901cb00e7c1f507687d22cb04e836d55d9c7906020015b60405180910390a250505050565b606080610b9f6001600160a01b0385167f2a55205a00000000000000000000000000000000000000000000000000000000613794565b15610ca8576040517f2a55205a0000000000000000000000000000000000000000000000000000000081526004810184905261271060248201526001600160a01b03851690632a55205a90619c409060440160408051808303818786fa93505050508015610c2a575060408051601f3d908101601f19168201909252610c2791810190615ae9565b60015b15610ca8578015610ca557604080516001808252818301909252906020808301908036833701905050935081845f81518110610c6857610c68615b29565b6001600160a01b03929092166020928302919091018201526040805160018082528183019092529182810190803683370190505092505050610d8a565b50505b610cdb6001600160a01b0385167fbb3bafd600000000000000000000000000000000000000000000000000000000613794565b15610d8a576040517fbb3bafd6000000000000000000000000000000000000000000000000000000008152600481018490526001600160a01b0385169063bb3bafd690619c40906024015f604051808303818786fa93505050508015610d6257506040513d5f823e601f3d908101601f19168201604052610d5f9190810190615c5a565b60015b15610d8a57815115801590610d78575080518251145b15610d87579092509050610d8a565b50505b9250929050565b610d99613860565b5f818152611777602090815260409182902082516101608101845281546001600160a01b039081168252600183015493820193909352600282015480841694820194909452600160a01b938490046bffffffffffffffffffffffff16606082015260038201546080820152600482015460a0820152600582015460c0820152600682015492831660e082015292820467ffffffffffffffff16610100840152600160e01b90910463ffffffff1661012083015260070154610140820152610e5e613786565b6001600160a01b031681604001516001600160a01b031614610ebd5760408082015190517f9802550c0000000000000000000000000000000000000000000000000000000081526001600160a01b039091166004820152602401610aa4565b60c081015115610ef9576040517f5aea7c4700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80516001600160a01b03165f90815261177660209081526040808320828501805185529083528184208490558584526117779092528220805473ffffffffffffffffffffffffffffffffffffffff191681556001810183905560028101839055600381018390556004810183905560058101839055600681018390556007019190915581519051610f8a91906138d5565b610fa0815f015182602001518360400151613956565b60405182907f14b9c40404d5b41deb481f9a40b8aeb2bf4b47679b38cf757075a66ed510f7f1905f90a250610fd66001610b8755565b50565b610fe1613860565b6001600160a01b038083165f908152611f4e6020908152604080832085845290915281205490911690611012613786565b90506001600160a01b038216611054576040517fc09f8e8200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806001600160a01b0316826001600160a01b0316146110aa576040517ff049b41a0000000000000000000000000000000000000000000000000000000081526001600160a01b0383166004820152602401610aa4565b6001600160a01b0384165f908152611f4e602090815260408083208684529091528120556110d9848483613956565b60405183906001600160a01b038616907f70c7877531c04c7d9caa8a7eca127384f04e8a6ee58b63f778ce5401d8bcae41905f90a3505061111b6001610b8755565b5050565b6001600160a01b0382165f908152612337602090815260408083208484529091528120805463ffffffff1642111561115a575f9150506111a3565b8054600182015470010000000000000000000000000000000090910460201b73ffffffffffffffffffffffffffffffff0000000016600160a01b90910463ffffffff16175b9150505b92915050565b6111b1613860565b6001600160a01b0384165f908152612337602090815260408083208684529091529020805463ffffffff164211156112205780546040517f8c9e57cf00000000000000000000000000000000000000000000000000000000815263ffffffff9091166004820152602401610aa4565b805464010000000090046bffffffffffffffffffffffff1682111561128b5780546040517f242373610000000000000000000000000000000000000000000000000000000081526401000000009091046bffffffffffffffffffffffff166004820152602401610aa4565b60018101546001600160a01b038481169116146112e55760018101546040517fa7d95dc30000000000000000000000000000000000000000000000000000000081526001600160a01b039091166004820152602401610aa4565b6112ef8585613961565b506112fb6001610b8755565b50505050565b5f82815261119a60205260408120546001600160a01b03168015611363575f84815261119b602090815260408083206001600160a01b038716845290915290205460ff168061119f5750806001600160a01b0316836001600160a01b03161491505b5092915050565b5f81815261119a6020908152604080832081516060808201845282546001600160a01b0381168352600160a01b900461ffff16948201949094526001820180549495948594859492908401916113bf90615cba565b80601f01602080910402602001604051908101604052809291908181526020018280546113eb90615cba565b80156114365780601f1061140d57610100808354040283529160200191611436565b820191905f5260205f20905b81548152906001019060200180831161141957829003601f168201915b50505091909252505050604081015181516020909201519097919650945092505050565b5f8282808303611496576040517fe808160000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6113888661ffff1611156114d6576040517f2b7b866100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61119980546001019081905592505f6114ed613786565b90506040518060600160405280826001600160a01b031681526020018861ffff1681526020018a8a8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92018290525093909452505086815261119a6020908152604091829020845181549286015161ffff16600160a01b027fffffffffffffffffffff000000000000000000000000000000000000000000009093166001600160a01b03909116179190911781559083015190915060018201906115b99082615d37565b50905050806001600160a01b0316847f9eee3ce0e6f7eeabd69ecf363898e9f490dbfda9ad953e1019a2c6aeceb4a7ef8b8b8b6040516115fb93929190615df3565b60405180910390a361160e848787613cfe565b50505095945050505050565b6001600160a01b038083165f908152611f4e60209081526040808320858452909152812054909116908161165057505f19610d8a565b506001600160a01b03929092165f908152611f4e6020908152604080832093835292905220549091600160a01b9091046bffffffffffffffffffffffff1690565b5f81815261177760205260408120600581015482036116b4576007015492915050565b6116c18160070154613e58565b9392505050565b6040517f40c1a064000000000000000000000000000000000000000000000000000000008152600481018290525f906001600160a01b038416906340c1a06490619c40906024016020604051808303818786fa15801561172a573d5f803e3d5ffd5b50505050506040513d601f19601f820116820180604052508101906116c19190615e2e565b6112fb83835f845f611fcf565b5f6116c18383613e79565b5f611773858585613e84565b1561177f57505f611b59565b6117898585613ef4565b156117c0576040517f83a483f500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b0385165f908152612337602090815260408083208784529091528120906117ec613786565b825490915063ffffffff164211156118b1576040517f4ec58ed70000000000000000000000000000000000000000000000000000000081526001600160a01b038281166004830152602482018790527f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d504431690634ec58ed790349060440160206040518083038185885af1158015611885573d5f803e3d5ffd5b50505050506040513d601f19601f820116820180604052508101906118aa9190615e49565b92506119fc565b81545f906118d49064010000000090046bffffffffffffffffffffffff16613e58565b905080861015611913576040517fe40a30e600000000000000000000000000000000000000000000000000000000815260048101829052602401610aa4565b600183015483546040517f5fdec5610000000000000000000000000000000000000000000000000000000081526001600160a01b03928316600482015263ffffffff821660248201526401000000009091046bffffffffffffffffffffffff1660448201528382166064820152608481018890527f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d5044390911690635fdec56190349060a40160206040518083038185885af11580156119d3573d5f803e3d5ffd5b50505050506040513d601f19601f820116820180604052508101906119f89190615e49565b9350505b60018201805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383811691909117909155825463ffffffff85167fffffffffffffffffffffffffffffffff00000000000000000000000000000000909116176401000000006bffffffffffffffffffffffff8816021783557f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d50443811690851603611aa1575f93505b81546fffffffffffffffffffffffffffffffff908116602086811c909216700100000000000000000000000000000000021783556001830180547fffffffffffffffff00000000ffffffffffffffffffffffffffffffffffffffff16600160a01b63ffffffff881602179055604080518781529182018590526001600160a01b03838116928992918b16917ece0a712e4e277ac7b34942865f0de7a5629dffe0539b70423ad5ff1ed6ab42910160405180910390a450505b949350505050565b5f83815261119a602052604090205483906001600160a01b0316611b83613786565b6001600160a01b0316816001600160a01b031614611c18576001600160a01b038116611bdb576040517f0c77a95c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517fb39cb29b0000000000000000000000000000000000000000000000000000000081526001600160a01b0382166004820152602401610aa4565b83835f819003611c54576040517fe808160000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611c5f878787613cfe565b50505050505050565b611c70613860565b5f81815261177760205260408120600501549003611cba576040517f4b6ad8fa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611cc4815f613f3e565b610fd66001610b8755565b611cd7613860565b611ce28383836141bb565b611ed2576bffffffffffffffffffffffff811115611d2c576040517f35ec82cb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b038381165f908152611f4e60209081526040808320868452909152902080549091811690600160a01b90046bffffffffffffffffffffffff1683148015611d8257506001600160a01b03811615155b15611db9576040517fb6950f3600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b81546001600160a01b0316600160a01b6bffffffffffffffffffffffff8516021782555f611de5613786565b90506001600160a01b038216611e2a57611dff8686614221565b825473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b038216178355611e80565b806001600160a01b0316826001600160a01b031614611e80576040517f697d918e0000000000000000000000000000000000000000000000000000000081526001600160a01b0383166004820152602401610aa4565b806001600160a01b031685876001600160a01b03167ffcc77ea8bdcce862f43b7fb00fe6b0eb90d6aeead27d3800d9257cf7a05f9d9687604051611ec691815260200190565b60405180910390a45050505b611edd6001610b8755565b505050565b5f81815261119a602052604090205481906001600160a01b0316611f04613786565b6001600160a01b0316816001600160a01b031614611f5c576001600160a01b038116611bdb576040517f0c77a95c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f83815261119a6020526040812080547fffffffffffffffffffff0000000000000000000000000000000000000000000016815590611f9e6001830182615519565b505060405183907f2a9aeaf340ca0da469c1f7e3d513c0e6c9cd287016f29d257a4ef70e13dc441c905f90a2505050565b5f611fd8613860565b82805f03612012576040517f3a970fe600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b825f03612041577f000000000000000000000000000000000000000000000000000000000001518092506120c3565b62093a80831115612083576040517fccd285bd00000000000000000000000000000000000000000000000000000000815262093a806004820152602401610aa4565b6103848310156120c3576040517f9299180e0000000000000000000000000000000000000000000000000000000081526103846004820152602401610aa4565b61138d80546001810190915591506120db8787614221565b6001600160a01b0387165f908152611776602090815260408083208984529091529020541561215f576001600160a01b0387165f90815261177660209081526040808320898452909152908190205490517f7618a0030000000000000000000000000000000000000000000000000000000081526004810191909152602401610aa4565b5f612168613786565b6001600160a01b038981165f818152611776602090815260408083208d845282528083208990558883526117779091529020805473ffffffffffffffffffffffffffffffffffffffff199081169092178155600181018b9055600281018054909216928416929092179055600781018790559091507f00000000000000000000000000000000000000000000000000000000000151808514612239576006810180547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160e01b63ffffffff8816021790555b61224489898961422b565b6040805186815261038460208201529081018790526060810185905288906001600160a01b03808c1691908516907f1062dd3b35f12b4064331244d00f40c1d4831965e4285654157a2409c6217cff9060800160405180910390a45050506122ad6001610b8755565b95945050505050565b5f54610100900460ff16158080156122d457505f54600160ff909116105b806122ed5750303b1580156122ed57505f5460ff166001145b612379576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152608401610aa4565b5f805460ff19166001179055801561239a575f805461ff0019166101001790555b6123a5600161138d55565b8015610fd6575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a150565b5f6060808280806127106124207f00000000000000000000000000000000000000000000000000000000000001f48b615e74565b61242a9190615e8b565b6040517f4c542f770000000000000000000000000000000000000000000000000000000081526001600160a01b038e81166004830152602482018e90529197505f917f00000000000000000000000076e61bea3ec3594f0ad9d6eadda91cd2df2fbb081690634c542f7790604401602060405180830381865afa9250505080156124d1575060408051601f3d908101601f191682019092526124ce91810190615e2e565b60015b156124d95790505b6040517f0d7daf3e0000000000000000000000000000000000000000000000000000000081526001600160a01b038e81166004830152602482018e90527f00000000000000000000000076e61bea3ec3594f0ad9d6eadda91cd2df2fbb081690630d7daf3e906044015f60405180830381865afa92505050801561257e57506040513d5f823e601f3d908101601f1916820160405261257b9190810190615c5a565b60015b156125895790965094505b85515f03612649576040517fefef76f80000000000000000000000000000000000000000000000000000000081526001600160a01b038e81166004830152602482018e905282811660448301527f00000000000000000000000076e61bea3ec3594f0ad9d6eadda91cd2df2fbb08169063efef76f8906064015f60405180830381865afa92505050801561263e57506040513d5f823e601f3d908101601f1916820160405261263b9190810190615c5a565b60015b156126495790965094505b855115158061267557507f00000000000000000000000000000000000000000000000000000000000000005b15612977575f7f00000000000000000000000000000000000000000000000000000000000000001561271d57878b03905086515f036127185760408051600180825281830190925290602080830190803683370190505096508b875f815181106126e1576126e1615b29565b6001600160a01b03929092166020928302919091018201526040805160018082528183019092529182810190803683370190505095505b61279d565b816001600160a01b03168c6001600160a01b0316148061277257508651158015906127725750865f8151811061275557612755615b29565b60200260200101516001600160a01b03168c6001600160a01b0316145b156127805750868a0361279d565b50600a8a0480612790898d615eaa565b61279a9190615eaa565b94505b6127a887600561435c565b6127b386600561435c565b61ffff8916156127ff576127106127ce61ffff8b168d615e74565b6127d89190615e8b565b9250845f036127f2576127eb8382615eaa565b90506127ff565b6127fc8386615eaa565b94505b5f6001885111156128d3575f5b88518110156128aa578d6001600160a01b031689828151811061283157612831615b29565b60200260200101516001600160a01b03160361284f575f9692909201915b5f1982146128a25761271088828151811061286c5761286c615b29565b60200260200101511115612883575f1991506128a2565b87818151811061289557612895615b29565b6020026020010151820191505b60010161280c565b508015806128b857505f1981145b156128d3576128c888600161435c565b6128d387600161435c565b5f60015b8951811015612946575f838a83815181106128f4576128f4615b29565b6020026020010151866129079190615e74565b6129119190615e8b565b905061291d8184615ebd565b9250808a838151811061293257612932615b29565b6020908102919091010152506001016128d7565b506129518184615eaa565b885f8151811061296357612963615b29565b6020026020010181815250505050506129b0565b868a03935061ffff8816156129b05761271061299761ffff8a168c615e74565b6129a19190615e8b565b91506129ad8285615eaa565b93505b6001600160a01b038916158015906129e157506129cb613786565b6001600160a01b0316896001600160a01b031614155b80156129ff57508a6001600160a01b0316896001600160a01b031614155b8015612a1d5750806001600160a01b0316896001600160a01b031614155b8015612a5b57507f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d504436001600160a01b0316896001600160a01b031614155b15612a6c5760648a04925082870396505b5096509650965096509650969050565b610fd681345f612d74565b612ae26040518061010001604052805f6001600160a01b031681526020015f81526020015f6001600160a01b031681526020015f81526020015f81526020015f81526020015f6001600160a01b031681526020015f81525090565b5f8281526117776020526040812060068101549091600160e01b90910463ffffffff1690819003612b3057507f00000000000000000000000000000000000000000000000000000000000151805b604080516101008101825283546001600160a01b03908116825260018501546020830152600285015481169282019290925260608101929092526103846080830152600583015460a083015260068301541660c082015260079091015460e082015292915050565b611edd8383835f612c95565b6001600160a01b0382165f90815261233760209081526040808320848452909152812080548291829163ffffffff16421115612be9575f805f93509350935050612c1f565b600181015490546001600160a01b03909116935063ffffffff8116925064010000000090046bffffffffffffffffffffffff1690505b9250925092565b5f806060805f80612c378989614369565b9050612c478989838a5f806123ec565b50939950919650945092505f90505b8351811015612c8857838181518110612c7157612c71615b29565b602002602001015186019550806001019050612c56565b5093975093979195509350565b6001600160a01b0384165f908152611f4e6020908152604080832086845290915290208054600160a01b90046bffffffffffffffffffffffff16831015612d215780546040517f16b5016f000000000000000000000000000000000000000000000000000000008152600160a01b9091046bffffffffffffffffffffffff166004820152602401610aa4565b80546001600160a01b0316612d62576040517fda48e18400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612d6d858584614404565b5050505050565b612d7c613860565b5f838152611777602052604081206007810154909103612dc8576040517f125197d100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b34831015612e02576040517fe2bbc1e300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60058101545f612e10613786565b90507f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d504436001600160a01b0316846001600160a01b031603612e4f575f93505b6001600160a01b038416151580612e6557508115155b15612ed8576002830180546001600160a01b0316604086901c6bffffffffffffffffffffffff16600160a01b908102919091179091556006840180547fffffffff0000000000000000ffffffffffffffffffffffffffffffffffffffff1667ffffffffffffffff87169092029190911790555b815f03612fba578260070154851015612f255782600701546040517f31e6f71c000000000000000000000000000000000000000000000000000000008152600401610aa491815260200190565b82546001840154612f3f916001600160a01b03169061454e565b6007830185905560068301805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383161790819055600160e01b900463ffffffff165f819003612faa57507f00000000000000000000000000000000000000000000000000000000000151805b42016005840181905591506130f7565b612fc382421190565b15612ffd576040517f3feeb88d00000000000000000000000000000000000000000000000000000000815260048101839052602401610aa4565b60068301546001600160a01b03808316911603613046576040517fe140576800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f6130548460070154613e58565b905080861015613093576040517fcd698a1900000000000000000000000000000000000000000000000000000000815260048101829052602401610aa4565b50600783018054600685018054928890556001600160a01b0384811673ffffffffffffffffffffffffffffffffffffffff198516179091559091164261038401808510156130e657600586018190559350835b506130f48183614e20614558565b50505b613101855f6146e8565b60408051868152602081018490526001600160a01b0383169188917f26ea3ebbda62eb1baef13e1c237dddd956c87f80b2801f2616d806d52557b121910160405180910390a3505050611edd6001610b8755565b5f6122ad858584865f611fcf565b6001600160a01b0382165f908152612337602090815260408083208484529091528120805463ffffffff1642116131bd5780546131b59064010000000090046bffffffffffffffffffffffff16613e58565b9150506111a3565b5060019392505050565b6040517fde5488af0000000000000000000000000000000000000000000000000000000081526001600160a01b03848116600483015260609182917f000000000000000000000000ad2184fb5dbcfc05d8f056542fb25b04fa32a95d169063de5488af90619c40906024016020604051808303818786fa9350505050801561326c575060408051601f3d908101601f1916820190925261326991810190615e2e565b60015b156134ba57856001600160a01b0316816001600160a01b0316146134b8579450846132c06001600160a01b0382167f2a55205a00000000000000000000000000000000000000000000000000000000613794565b156133ca576040517f2a55205a0000000000000000000000000000000000000000000000000000000081526004810186905261271060248201526001600160a01b03871690632a55205a90619c409060440160408051808303818786fa9350505050801561334b575060408051601f3d908101601f1916820190925261334891810190615ae9565b60015b156133ca5780156133c757604080516001808252818301909252906020808301908036833701905050945081855f8151811061338957613389615b29565b6001600160a01b039290921660209283029190910182015260408051600180825281830190925291828101908036833701905050935050505061377e565b50505b825115801561340757506134076001600160a01b0387167fbb3bafd600000000000000000000000000000000000000000000000000000000613794565b156134b8576040517fbb3bafd6000000000000000000000000000000000000000000000000000000008152600481018690526001600160a01b0387169063bb3bafd690619c40906024015f604051808303818786fa9350505050801561348e57506040513d5f823e601f3d908101601f1916820160405261348b9190810190615c5a565b60015b156134b8578151158015906134a4575080518251145b156134b557909350915061377e9050565b50505b505b6134ed6001600160a01b0386167fb779958400000000000000000000000000000000000000000000000000000000613794565b1561361e576040517fb9c4d9fb000000000000000000000000000000000000000000000000000000008152600481018590526001600160a01b0386169063b9c4d9fb90619c40906024015f604051808303818786fa9350505050801561357457506040513d5f823e601f3d908101601f191682016040526135719190810190615ed0565b60015b1561361e5780511561361c576040517f0ebd4c7f000000000000000000000000000000000000000000000000000000008152600481018690526001600160a01b03871690630ebd4c7f90619c40906024015f604051808303818786fa9350505050801561360257506040513d5f823e601f3d908101601f191682016040526135ff9190810190615f02565b60015b1561361c57805182510361361a57909250905061377e565b505b505b6001600160a01b0383161561369b57604080516001808252818301909252906020808301908036833701905050915082825f8151811061366057613660615b29565b6001600160a01b039290921660209283029190910182015260408051600180825281830190925291828101908036833701905050905061377e565b846001600160a01b0316638da5cb5b619c406040518263ffffffff1660e01b81526004016020604051808303818786fa935050505080156136f9575060408051601f3d908101601f191682019092526136f691810190615e2e565b60015b1561377e576001600160a01b0381161561377c57604080516001808252818301909252906020808301908036833701905050925080835f8151811061374057613740615b29565b6001600160a01b03929092166020928302919091018201526040805160018082528183019092529182810190803683370190505091505061377e565b505b935093915050565b5f61378f6147ce565b905090565b604080517fffffffff000000000000000000000000000000000000000000000000000000008316602480830191909152825180830390910181526044909101909152602080820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01ffc9a70000000000000000000000000000000000000000000000000000000017815282515f9392849283928392918391908a617530fa92503d91505f51905082801561384a575060208210155b801561385557505f81115b979650505050505050565b6002610b8754036138cd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610aa4565b6002610b8755565b6001600160a01b0382165f90815261119c602090815260408083208484529091529020548015611edd576001600160a01b0383165f81815261119c60209081526040808320868452909152808220829055518392859290917f2ea2946ee16c4a1d0ec58464194022e54432a6d7db359835ddf283555f2c8eee9190a4505050565b611edd83838361482b565b6001600160a01b038281165f90815261233760209081526040808320858452808352818420825160a081018452815463ffffffff808216835264010000000082046bffffffffffffffffffffffff9081168489019081527001000000000000000000000000000000009093046fffffffffffffffffffffffffffffffff1684880152600185018054808c1660608701908152600160a01b8204851660808801528d8c5297909952989094557fffffffffffffffff00000000000000000000000000000000000000000000000090961690965591518251955193517f4dc8fb3c000000000000000000000000000000000000000000000000000000008152908716600482015294909316602485015291166044830152917f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d504431690634dc8fb3c906064015f604051808303815f87803b158015613aba575f80fd5b505af1158015613acc573d5f803e3d5ffd5b505050505f613ad9613786565b90505f80613ae78686614861565b6040517f6352211e0000000000000000000000000000000000000000000000000000000081526004810188905291935091505f906001600160a01b03881690636352211e90602401602060405180830381865afa158015613b4a573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190613b6e9190615e2e565b9050306001600160a01b03821603613b9557613b9087878760600151876148db565b613c1a565b60608501516040517f23b872dd0000000000000000000000000000000000000000000000000000000081526001600160a01b038681166004830152918216602482015260448101889052908816906323b872dd906064015f604051808303815f87803b158015613c03575f80fd5b505af1158015613c15573d5f803e3d5ffd5b505050505b5f805f613c758a8a898b602001516bffffffffffffffffffffffff16613c6e8d604001518e6080015173ffffffffffffffffffffffffffffffff0000000060209290921b9190911663ffffffff9091161790565b8b8b6148e7565b92509250925087606001516001600160a01b0316898b6001600160a01b03167f1cb8adb37d6d35e94cd0695ca39895b84371864713f5ca7eada52af9ff23744b8a878787604051613cea94939291906001600160a01b0394909416845260208401929092526040830152606082015260800190565b60405180910390a450505050505050505050565b5f5b81811015613e18575f838383818110613d1b57613d1b615b29565b9050602002016020810190613d309190615f34565b5f86815261119b602090815260408083206001600160a01b038516845290915290205490915060ff1615613d90576040517f667888ec00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b613d98613786565b6001600160a01b0316816001600160a01b031603613de2576040517f43e2197f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f85815261119b602090815260408083206001600160a01b03909416835292905220805460ff1916600190811790915501613d00565b50827fd5a31bd2d34d303249ac7f54bfc7578390f90f5d39cb39813f67190fa36f5c178383604051613e4b929190615f4f565b60405180910390a2505050565b5f600a8204808203613e6f576116c1836001615ebd565b6116c18382615ebd565b5f6116c18383614b05565b6001600160a01b038084165f908152611f4e602090815260408083208684529091528120805491929091161580613ed057508054600160a01b90046bffffffffffffffffffffffff1683105b15613ede575f9150506116c1565b613ee985855f614404565b506001949350505050565b6001600160a01b0382165f90815261177660209081526040808320848452909152812054801580159061119f57505f90815261177760205260409020600501544211159392505050565b5f828152611777602090815260409182902082516101608101845281546001600160a01b039081168252600183015493820193909352600282015480841694820194909452600160a01b938490046bffffffffffffffffffffffff16606082015260038201546080820152600482015460a0820152600582015460c08201819052600683015493841660e083015293830467ffffffffffffffff16610100820152600160e01b90920463ffffffff166101208301526007015461014082015290421161403e578060c001516040517f3a017f60000000000000000000000000000000000000000000000000000000008152600401610aa491815260200190565b5f80614051835f01518460200151614861565b84516001600160a01b03165f9081526117766020908152604080832082890151845282528083208390558983526117779091528120805473ffffffffffffffffffffffffffffffffffffffff19168155600181018290556002810182905560038101829055600481018290556005810182905560068101829055600701559092509050836140f0576140f0835f015184602001518560e001515f614b3a565b5f805f614142865f0151876020015188604001518961014001518a610100015167ffffffffffffffff1660408c606001516bffffffffffffffffffffffff166001600160a01b0316901b178a8a6148e7565b9250925092508560e001516001600160a01b031686604001516001600160a01b0316897f2edb0e99c6ac35be6731dab554c1d1fa1b7beb675090dbb09fb14e615aca1c4a8686866040516141a9939291909283526020830191909152604082015260600190565b60405180910390a45050505050505050565b6001600160a01b0383165f908152612337602090815260408083208584529091528120805463ffffffff164211806142095750805464010000000090046bffffffffffffffffffffffff1683115b15614217575f9150506116c1565b613ee98585613961565b61111b8282614bfd565b8015611edd575f81815261119a6020526040902080546001600160a01b0316614280576040517f0c77a95c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f614289613786565b5f84815261119b602090815260408083206001600160a01b038516845290915290205490915060ff161580156142cc575081546001600160a01b03828116911614155b15614303576040517f6e93a35400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b0385165f81815261119c60209081526040808320888452909152808220869055518592879290917fb17e0c916df75a12480835f00b3927cb871bbe00bacf819f81a1d92f9ff7f38d9190a45050505050565b808251111561111b579052565b5f6143748383613e79565b90506001600160a01b0381166111a3576040517f6352211e000000000000000000000000000000000000000000000000000000008152600481018390526001600160a01b03841690636352211e90602401602060405180830381865afa1580156143e0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116c19190615e2e565b61440c613860565b6001600160a01b038381165f908152611f4e60209081526040808320868452808352818420825180840190935280549586168352600160a01b9095046bffffffffffffffffffffffff1682840152868452909152915561446c8484614c8f565b61448981602001516bffffffffffffffffffffffff1660016146e8565b5f614492613786565b90505f806144a08787614861565b915091506144b08787855f6148db565b5f805f6144d78a8a895f01518a602001516bffffffffffffffffffffffff168c8a8a6148e7565b8951604080516001600160a01b038c8116825260208201879052918101859052606081018490529497509295509093508116918b918d16907fd28c0a7dd63bc853a4e36306655da9f8c0b29ff9d0605bb976ae420e46a999309060800160405180910390a450505050505050611edd6001610b8755565b61111b8282614cdb565b815f0361456457505050565b7f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d504436001600160a01b0316836001600160a01b0316036145c1577f00000000000000000000000067df244584b67e8c51b10ad610aaffa9a402fdb692505b5f836001600160a01b03168383906040515f60405180830381858888f193505050503d805f811461460d576040519150601f19603f3d011682016040523d82523d5f602084013e614612565b606091505b50509050806112fb576040517faa67c9190000000000000000000000000000000000000000000000000000000081526001600160a01b0385811660048301527f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d50443169063aa67c9199085906024015f604051808303818588803b158015614696575f80fd5b505af11580156146a8573d5f803e3d5ffd5b5050505050836001600160a01b03167fa2201512569adb2d513531dfd69b66df50bd5cffb8c1bbe65a4611f9e1eadbd184604051610b5b91815260200190565b348211156147a0577f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d504436001600160a01b031663452f2b8f614727613786565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b1681526001600160a01b03909116600482015234850360248201526044015b5f604051808303815f87803b158015614786575f80fd5b505af1158015614798573d5f803e3d5ffd5b505050505050565b8080156147ac57503482105b1561111b5761111b8234036147bf613786565b6001600160a01b031690614cef565b336001600160a01b037f000000000000000000000000762340b8a40cdd5bfc3edd94265899fda345d0e316810361482857507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec36013560601c5b90565b6001600160a01b038084165f908152611f4e6020908152604080832086845290915290205416806112fb576112fb848484614e38565b6001600160a01b0382165f90815261119c60209081526040808320848452909152812054819080156148d3575f81815261119a60209081526040808320546001600160a01b03898116855261119c84528285208986529093529083209290925581169350600160a01b900461ffff1691505b509250929050565b6112fb84848484614e6d565b5f805f865f036148fe57505f915081905080614af8565b6060805f806149118e8e8e8e8e8d6123ec565b8451959c5091995092975090955090935091505f9060011461493557614e2061493a565b620334505b90505f5b85518110156149ad5761498486828151811061495c5761495c615b29565b602002602001015186838151811061497657614976615b29565b602002602001015184614558565b84818151811061499657614996615b29565b60200260200101518801975080600101905061493e565b506149bb8d87614e20614558565b6149e87f00000000000000000000000067df244584b67e8c51b10ad610aaffa9a402fdb689614e20614558565b8215614a62576149fb8b84614e20614558565b8d8f6001600160a01b03167f141b92fd9766c80ab120598ea2f6be9802470ec59b5446dd9bf46214ead8d08e8d865f604051614a55939291906001600160a01b039390931683526020830191909152604082015260600190565b60405180910390a3968201965b6001600160a01b038a1615614af2578115614a9957855f03614a875795810195614a8c565b948101945b614a998a83614e20614558565b8d8f6001600160a01b03167f27a4dd4ff659a9e6354fb079b2208365e5b83f55c22a4150eee2bca89501cb988c85604051614ae99291906001600160a01b03929092168252602082015260400190565b60405180910390a35b50505050505b9750975097945050505050565b6001600160a01b038083165f908152611f4e6020908152604080832085845290915290205416806111a3576116c18383614f08565b6001600160a01b03811615614b7b576040517f57a016b300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f23b872dd0000000000000000000000000000000000000000000000000000000081523060048201526001600160a01b038381166024830152604482018590528516906323b872dd906064015f604051808303815f87803b158015614be1575f80fd5b505af1158015614bf3573d5f803e3d5ffd5b5050505050505050565b6001600160a01b038083165f908152611f4e602090815260408083208584529091529020541680614c3257611edd8383614f47565b614c3a613786565b6001600160a01b0316816001600160a01b031614611edd576040517f32f3b0330000000000000000000000000000000000000000000000000000000081526001600160a01b0382166004820152602401610aa4565b6001600160a01b0382165f908152612337602090815260408083208484529091529020614cba613786565b60018201546001600160a01b03918216911603611edd57611edd8383615065565b614ce58282615065565b61111b828261523e565b80471015614d59576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e63650000006044820152606401610aa4565b5f826001600160a01b0316826040515f6040518083038185875af1925050503d805f8114614da2576040519150601f19603f3d011682016040523d82523d5f602084013e614da7565b606091505b5050905080611edd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260448201527f6563697069656e74206d617920686176652072657665727465640000000000006064820152608401610aa4565b6001600160a01b0383165f908152611776602090815260408083208584529091528120549003611edd57611edd838383615276565b6001600160a01b038085165f908152611f4e60209081526040808320878452909152902054168015614efc57816001600160a01b0316816001600160a01b031614614eef576040517f32f3b0330000000000000000000000000000000000000000000000000000000081526001600160a01b0382166004820152602401610aa4565b5f9150614efc8585615282565b612d6d858585856152d4565b6001600160a01b038083165f90815261177660209081526040808320858452825280832054835261177790915290206002015416806111a3575f6116c1565b6001600160a01b0382165f9081526117766020908152604080832084845290915281205490819003614f7d57611edd83836154b1565b5f8181526117776020526040812090614f94613786565b905081600501545f036150005760028201546001600160a01b03828116911614614ffb5760028201546040517fe64526ee0000000000000000000000000000000000000000000000000000000081526001600160a01b039091166004820152602401610aa4565b612d6d565b60068201546001600160a01b0382811691161461505a5760068201546040517fe64526ee0000000000000000000000000000000000000000000000000000000081526001600160a01b039091166004820152602401610aa4565b612d6d836001613f3e565b6001600160a01b0382165f9081526123376020908152604080832084845290915290205463ffffffff16421161111b576001600160a01b038281165f90815261233760209081526040808320858452808352818420825160a081018452815463ffffffff808216835264010000000082046bffffffffffffffffffffffff9081168489019081527001000000000000000000000000000000009093046fffffffffffffffffffffffffffffffff1684880152600185018054808c1660608701908152600160a01b8204851660808801528d8c5297909952989094557fffffffffffffffff00000000000000000000000000000000000000000000000090961690965591518251955193517f345db493000000000000000000000000000000000000000000000000000000008152908716600482015294909316602485015291166044830152917f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d50443169063345db493906064015f604051808303815f87803b1580156151ee575f80fd5b505af1158015615200573d5f803e3d5ffd5b50506040518492506001600160a01b03861691507f30c264456cbd17f5f67d7534654161414f34c0e6cc1b7500e169b7a7aea4afc0905f90a3505050565b6001600160a01b038083165f908152611f4e602090815260408083208584529091529020805490911615611edd57611edd8383615282565b611edd8383835f6148db565b6001600160a01b0382165f818152611f4e60209081526040808320858452909152808220829055518392917faa6271d89a385571e237d3e7254ccc7c09f68055e6e9b410ed08233a8b9a05cf91a35050565b6001600160a01b0384165f9081526117766020908152604080832086845290915290205480156154a5575f81815261177760205260408120600581015490910361543b576001600160a01b03831615801590615340575060028101546001600160a01b03848116911614155b156153885760028101546040517fe64526ee0000000000000000000000000000000000000000000000000000000081526001600160a01b039091166004820152602401610aa4565b6001600160a01b0386165f9081526117766020908152604080832088845282528083208390558483526117779091528120805473ffffffffffffffffffffffffffffffffffffffff191681556001810182905560028101829055600381018290556004810182905560058101829055600681018290556007015561540c86866138d5565b60405182907f5603897cc9b1e866f3f7395ffc6638776041f21c094d0b4e748ff44c407fa362905f90a26154a0565b60068101546001600160a01b038481169116146154955760068101546040517fe64526ee0000000000000000000000000000000000000000000000000000000081526001600160a01b039091166004820152602401610aa4565b6154a0826001613f3e565b5f9250505b612d6d85858585614b3a565b816001600160a01b03166323b872dd6154c8613786565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b1681526001600160a01b0390911660048201523060248201526044810184905260640161476f565b50805461552590615cba565b5f825580601f10615534575050565b601f0160209004905f5260205f2090810190610fd691905b8082111561555f575f815560010161554c565b5090565b5f8060408385031215615574575f80fd5b50508035926020909101359150565b6001600160a01b0381168114610fd6575f80fd5b5f80604083850312156155a8575f80fd5b82356155b381615583565b946020939093013593505050565b5f8151808452602080850194508084015f5b838110156155f85781516001600160a01b0316875295820195908201906001016155d3565b509495945050505050565b5f8151808452602080850194508084015f5b838110156155f857815187529582019590820190600101615615565b604081525f61564360408301856155c1565b82810360208401526122ad8185615603565b5f60208284031215615665575f80fd5b5035919050565b5f805f806080858703121561567f575f80fd5b843561568a81615583565b93506020850135925060408501356156a181615583565b9396929550929360600135925050565b5f80604083850312156156c2575f80fd5b8235915060208301356156d481615583565b809150509250929050565b606081525f84518060608401525f5b8181101561570b57602081880181015160808684010152016156ee565b505f608082850101526080601f19601f8301168401019150506001600160a01b038416602083015261ffff83166040830152949350505050565b803561ffff81168114615756575f80fd5b919050565b5f8083601f84011261576b575f80fd5b50813567ffffffffffffffff811115615782575f80fd5b6020830191508360208260051b8501011115610d8a575f80fd5b5f805f805f606086880312156157b0575f80fd5b853567ffffffffffffffff808211156157c7575f80fd5b818801915088601f8301126157da575f80fd5b8135818111156157e8575f80fd5b8960208285010111156157f9575f80fd5b6020830197508096505061580f60208901615745565b94506040880135915080821115615824575f80fd5b506158318882890161575b565b969995985093965092949392505050565b5f805f60608486031215615854575f80fd5b833561585f81615583565b95602085013595506040909401359392505050565b5f805f8060808587031215615887575f80fd5b843561589281615583565b9350602085013592506040850135915060608501356158b081615583565b939692955090935050565b5f805f604084860312156158cd575f80fd5b83359250602084013567ffffffffffffffff8111156158ea575f80fd5b6158f68682870161575b565b9497909650939450505050565b5f805f805f60a08688031215615917575f80fd5b853561592281615583565b97602087013597506040870135966060810135965060800135945092505050565b5f805f805f8060c08789031215615958575f80fd5b863561596381615583565b955060208701359450604087013561597a81615583565b935060608701359250608087013561599181615583565b915061599f60a08801615745565b90509295509295509295565b86815260c060208201525f6159c360c08301886155c1565b82810360408401526159d58188615603565b60608401969096525050608081019290925260a0909101529392505050565b86815285602082015260c060408201525f615a1260c08301876155c1565b8281036060840152615a248187615603565b9150508360808301526001600160a01b03831660a0830152979650505050505050565b5f805f60608486031215615a59575f80fd5b83359250602084013591506040840135615a7281615583565b809150509250925092565b5f805f8060808587031215615a90575f80fd5b8435615a9b81615583565b966020860135965060408601359560600135945092505050565b5f805f60608486031215615ac7575f80fd5b8335615ad281615583565b9250602084013591506040840135615a7281615583565b5f8060408385031215615afa575f80fd5b8251615b0581615583565b6020939093015192949293505050565b634e487b7160e01b5f52604160045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b604051601f8201601f1916810167ffffffffffffffff81118282101715615b6657615b66615b15565b604052919050565b5f67ffffffffffffffff821115615b8757615b87615b15565b5060051b60200190565b5f82601f830112615ba0575f80fd5b81516020615bb5615bb083615b6e565b615b3d565b82815260059290921b84018101918181019086841115615bd3575f80fd5b8286015b84811015615bf7578051615bea81615583565b8352918301918301615bd7565b509695505050505050565b5f82601f830112615c11575f80fd5b81516020615c21615bb083615b6e565b82815260059290921b84018101918181019086841115615c3f575f80fd5b8286015b84811015615bf75780518352918301918301615c43565b5f8060408385031215615c6b575f80fd5b825167ffffffffffffffff80821115615c82575f80fd5b615c8e86838701615b91565b93506020850151915080821115615ca3575f80fd5b50615cb085828601615c02565b9150509250929050565b600181811c90821680615cce57607f821691505b602082108103615cec57634e487b7160e01b5f52602260045260245ffd5b50919050565b601f821115611edd575f81815260208120601f850160051c81016020861015615d185750805b601f850160051c820191505b8181101561479857828155600101615d24565b815167ffffffffffffffff811115615d5157615d51615b15565b615d6581615d5f8454615cba565b84615cf2565b602080601f831160018114615d98575f8415615d815750858301515b5f19600386901b1c1916600185901b178555614798565b5f85815260208120601f198616915b82811015615dc657888601518255948401946001909101908401615da7565b5085821015615de357878501515f19600388901b60f8161c191681555b5050505050600190811b01905550565b60408152826040820152828460608301375f606084830101525f6060601f19601f860116830101905061ffff83166020830152949350505050565b5f60208284031215615e3e575f80fd5b81516116c181615583565b5f60208284031215615e59575f80fd5b5051919050565b634e487b7160e01b5f52601160045260245ffd5b80820281158282048414176111a3576111a3615e60565b5f82615ea557634e487b7160e01b5f52601260045260245ffd5b500490565b818103818111156111a3576111a3615e60565b808201808211156111a3576111a3615e60565b5f60208284031215615ee0575f80fd5b815167ffffffffffffffff811115615ef6575f80fd5b61119f84828501615b91565b5f60208284031215615f12575f80fd5b815167ffffffffffffffff811115615f28575f80fd5b61119f84828501615c02565b5f60208284031215615f44575f80fd5b81356116c181615583565b60208082528181018390525f908460408401835b86811015615bf7578235615f7681615583565b6001600160a01b031682529183019190830190600101615f6356fea2646970667358221220da526576c5c96b5f5f5d5263ff411bdd62173673faf47920335eb25973a93be064736f6c63430008140033

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

00000000000000000000000067df244584b67e8c51b10ad610aaffa9a402fdb600000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d50443000000000000000000000000ad2184fb5dbcfc05d8f056542fb25b04fa32a95d0000000000000000000000000000000000000000000000000000000000015180000000000000000000000000762340b8a40cdd5bfc3edd94265899fda345d0e3

-----Decoded View---------------
Arg [0] : treasury (address): 0x67Df244584b67E8C51B10aD610aAfFa9a402FdB6
Arg [1] : feth (address): 0x49128CF8ABE9071ee24540a296b5DED3F9D50443
Arg [2] : royaltyRegistry (address): 0xaD2184FB5DBcfC05d8f056542fB25b04fa32A95D
Arg [3] : duration (uint256): 86400
Arg [4] : router (address): 0x762340B8a40Cdd5BFC3eDD94265899FDa345D0E3

-----Encoded View---------------
5 Constructor Arguments found :
Arg [0] : 00000000000000000000000067df244584b67e8c51b10ad610aaffa9a402fdb6
Arg [1] : 00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d50443
Arg [2] : 000000000000000000000000ad2184fb5dbcfc05d8f056542fb25b04fa32a95d
Arg [3] : 0000000000000000000000000000000000000000000000000000000000015180
Arg [4] : 000000000000000000000000762340b8a40cdd5bfc3edd94265899fda345d0e3


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.