Feature Tip: Add private address tag to any address under My Name Tag !
Source Code
| Transaction Hash |
Method
|
Block
|
From
|
|
To
|
||||
|---|---|---|---|---|---|---|---|---|---|
Latest 1 internal transaction
Advanced mode:
| Parent Transaction Hash | Method | Block |
From
|
|
To
|
||
|---|---|---|---|---|---|---|---|
| 0x61018060 | 17721512 | 967 days ago | Contract Creation | 0 ETH |
Loading...
Loading
Loading...
Loading
Cross-Chain Transactions
Loading...
Loading
Contract Name:
NFTMarket
Compiler Version
v0.8.20+commit.a1b79de6
Optimization Enabled:
Yes with 3500 runs
Other Settings:
default evmVersion
Contract Source Code (Solidity Standard Json-Input format)
/*
・
* ★
・ 。
・ ゚☆ 。
* ★ ゚・。 * 。
* ☆ 。・゚*.。
゚ *.。☆。★ ・
` .-:::::-.` `-::---...```
`-:` .:+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();
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @author: manifold.xyz
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
/**
* @dev Royalty registry interface
*/
interface IRoyaltyRegistry is IERC165 {
event RoyaltyOverride(address owner, address tokenAddress, address royaltyAddress);
/**
* Override the location of where to look up royalty information for a given token contract.
* Allows for backwards compatibility and implementation of royalty logic for contracts that did not previously support them.
*
* @param tokenAddress - The token address you wish to override
* @param royaltyAddress - The royalty override address
*/
function setRoyaltyLookupAddress(address tokenAddress, address royaltyAddress) external returns (bool);
/**
* Returns royalty address location. Returns the tokenAddress by default, or the override if it exists
*
* @param tokenAddress - The token address you are looking up the royalty for
*/
function getRoyaltyLookupAddress(address tokenAddress) external view returns (address);
/**
* Returns the token address that an overrideAddress is set for.
* Note: will not be accurate if the override was created before this function was added.
*
* @param overrideAddress - The override address you are looking up the token for
*/
function getOverrideLookupTokenAddress(address overrideAddress) external view returns (address);
/**
* Whether or not the message sender can override the royalty address for the given token address
*
* @param tokenAddress - The token address you are looking up the royalty for
*/
function overrideAllowed(address tokenAddress) external view returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.2;
import "../../utils/AddressUpgradeable.sol";
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
* @custom:oz-retyped-from bool
*/
uint8 private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint8 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
* constructor.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
bool isTopLevelCall = !_initializing;
require(
(isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
"Initializable: contract is already initialized"
);
_initialized = 1;
if (isTopLevelCall) {
_initializing = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: setting the version to 255 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint8 version) {
require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
_initialized = version;
_initializing = true;
_;
_initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
require(!_initializing, "Initializable: contract is initializing");
if (_initialized != type(uint8).max) {
_initialized = type(uint8).max;
emit Initialized(type(uint8).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint8) {
return _initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _initializing;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
import "../proxy/utils/Initializable.sol";
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuardUpgradeable is Initializable {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
function __ReentrancyGuard_init() internal onlyInitializing {
__ReentrancyGuard_init_unchained();
}
function __ReentrancyGuard_init_unchained() internal onlyInitializing {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == _ENTERED;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library AddressUpgradeable {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
import "../proxy/utils/Initializable.sol";
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the caller.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (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;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;
/**
* @notice Interface for functions the market uses in FETH.
* @author batu-inal & HardlyDifficult
*/
interface IFethMarket {
function depositFor(address account) external payable;
function marketLockupFor(address account, uint256 amount) external payable returns (uint256 expiration);
function marketWithdrawFrom(address from, uint256 amount) external;
function marketWithdrawLocked(address account, uint256 expiration, uint256 amount) external;
function marketUnlockFor(address account, uint256 expiration, uint256 amount) external;
function marketChangeLockup(
address unlockFrom,
uint256 unlockExpiration,
uint256 unlockAmount,
address lockupFor,
uint256 lockupAmount
) external payable returns (uint256 expiration);
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;
/**
* @title Declares the type of the collection contract.
* @dev This interface is declared as an ERC-165 interface.
* @author reggieag
*/
interface INFTCollectionType {
function getNFTCollectionType() external view returns (string memory collectionType);
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;
/**
* @notice The required interface for collections in the NFTDropMarket to support exhibitions.
* @author philbirt
*/
interface INFTMarketExhibition {
function isAllowedSellerForExhibition(
uint256 exhibitionId,
address seller
) external view returns (bool allowedSeller);
function getExhibitionPaymentDetails(
uint256 exhibitionId
) external view returns (address payable curator, uint16 takeRateInBasisPoints);
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;
/**
* @notice Interface for AdminRole which wraps the default admin role from
* OpenZeppelin's AccessControl for easy integration.
* @author batu-inal & HardlyDifficult
*/
interface IAdminRole {
function isAdmin(address account) external view returns (bool);
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;
/**
* @notice Interface for OperatorRole which wraps a role from
* OpenZeppelin's AccessControl for easy integration.
* @author batu-inal & HardlyDifficult
*/
interface IOperatorRole {
function isOperator(address account) external view returns (bool);
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;
/**
* @title Interface for routing calls to the NFT Market to set buy now prices.
* @author HardlyDifficult
*/
interface INFTMarketBuyNow {
function setBuyPrice(address nftContract, uint256 tokenId, uint256 price) external;
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;
/**
* @title Interface for routing calls to the NFT Market to create reserve auctions.
* @author HardlyDifficult & reggieag
*/
interface INFTMarketReserveAuction {
function createReserveAuctionV3(
address nftContract,
uint256 tokenId,
uint256 exhibitionId,
uint256 reservePrice,
uint256 duration
) external returns (uint256 auctionId);
}// 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);
}// 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);
}// 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);
}// 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);
}// 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);
}// 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)
}
}
}
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;
/**
* @title Helpers for working with time.
* @author batu-inal & HardlyDifficult
*/
library TimeLibrary {
/**
* @notice Checks if the given timestamp is in the past.
* @dev This helper ensures a consistent interpretation of expiry across the codebase.
* This is different than `hasBeenReached` in that it will return false if the expiry is now.
*/
function hasExpired(uint256 expiry) internal view returns (bool) {
return expiry < block.timestamp;
}
/**
* @notice Checks if the given timestamp is now or in the past.
* @dev This helper ensures a consistent interpretation of expiry across the codebase.
* This is different from `hasExpired` in that it will return true if the timestamp is now.
*/
function hasBeenReached(uint256 timestamp) internal view returns (bool) {
return timestamp <= block.timestamp;
}
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;
/**
* @title An abstraction layer for auctions.
* @dev This contract can be expanded with reusable calls and data as more auction types are added.
* @author batu-inal & HardlyDifficult
*/
abstract contract NFTMarketAuction {
/**
* @notice A global id for auctions of any type.
*/
uint256 private nextAuctionId;
/**
* @notice Called once to configure the contract after the initial proxy deployment.
* @dev This sets the initial auction id to 1, making the first auction cheaper
* and id 0 represents no auction found.
*/
function _initializeNFTMarketAuction() internal {
nextAuctionId = 1;
}
/**
* @notice Returns id to assign to the next auction.
*/
function _getNextAndIncrementAuctionId() internal returns (uint256) {
// AuctionId cannot overflow 256 bits.
unchecked {
return nextAuctionId++;
}
}
/**
* @notice This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[1_000] private __gap;
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;
import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import "../../interfaces/internal/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;
}// 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;
}// 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;
}// 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;
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;
/**
* @title Reserves space previously occupied by private sales.
* @author batu-inal & HardlyDifficult
*/
abstract contract NFTMarketPrivateSaleGap {
// Original data:
// bytes32 private __gap_was_DOMAIN_SEPARATOR;
// mapping(address => mapping(uint256 => mapping(address => mapping(address => mapping(uint256 =>
// mapping(uint256 => bool)))))) private privateSaleInvalidated;
// uint256[999] private __gap;
/**
* @notice This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
* @dev 1 slot was consumed by privateSaleInvalidated.
*/
uint256[1001] private __gap;
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;
import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import "../../interfaces/internal/routes/INFTMarketReserveAuction.sol";
import "../../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;
}// 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;
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;
import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
import "../../interfaces/internal/IFethMarket.sol";
error FETHNode_FETH_Address_Is_Not_A_Contract();
error FETHNode_Only_FETH_Can_Transfer_ETH();
/**
* @title A mixin for interacting with the FETH contract.
* @author batu-inal & HardlyDifficult
*/
abstract contract FETHNode is ContextUpgradeable {
using AddressUpgradeable for address;
using AddressUpgradeable for address payable;
/// @notice The FETH ERC-20 token for managing escrow and lockup.
IFethMarket internal immutable feth;
constructor(address _feth) {
if (!_feth.isContract()) {
revert FETHNode_FETH_Address_Is_Not_A_Contract();
}
feth = IFethMarket(_feth);
}
/**
* @notice Only used by FETH. Any direct transfer from users will revert.
*/
receive() external payable {
if (msg.sender != address(feth)) {
revert FETHNode_Only_FETH_Can_Transfer_ETH();
}
}
/**
* @notice Withdraw the msg.sender's available FETH balance if they requested more than the msg.value provided.
* @dev This may revert if the msg.sender is non-receivable.
* This helper should not be used anywhere that may lead to locked assets.
* @param totalAmount The total amount of ETH required (including the msg.value).
* @param shouldRefundSurplus If true, refund msg.value - totalAmount to the msg.sender.
*/
function _tryUseFETHBalance(uint256 totalAmount, bool shouldRefundSurplus) internal {
if (totalAmount > msg.value) {
// Withdraw additional ETH required from the user's available FETH balance.
unchecked {
// The if above ensures delta will not underflow.
// Withdraw ETH from the user's account in the FETH token contract,
// making the funds available in this contract as ETH.
feth.marketWithdrawFrom(_msgSender(), totalAmount - msg.value);
}
} else if (shouldRefundSurplus && totalAmount < msg.value) {
// Return any surplus ETH to the user.
unchecked {
// The if above ensures this will not underflow
payable(_msgSender()).sendValue(msg.value - totalAmount);
}
}
}
/**
* @notice Gets the FETH contract used to escrow offer funds.
* @return fethAddress The FETH contract address.
*/
function getFethAddress() external view returns (address fethAddress) {
fethAddress = address(feth);
}
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
import "../../interfaces/internal/roles/IAdminRole.sol";
import "../../interfaces/internal/roles/IOperatorRole.sol";
error FoundationTreasuryNode_Address_Is_Not_A_Contract();
error FoundationTreasuryNode_Caller_Not_Admin();
error FoundationTreasuryNode_Caller_Not_Operator();
/**
* @title A mixin that stores a reference to the Foundation treasury contract.
* @notice The treasury collects fees and defines admin/operator roles.
* @author batu-inal & HardlyDifficult
*/
abstract contract FoundationTreasuryNode is Initializable {
using AddressUpgradeable for address payable;
/// @dev This value was replaced with an immutable version.
address payable private __gap_was_treasury;
/// @notice The address of the treasury contract.
address payable private immutable treasury;
/// @notice Requires the caller is a Foundation admin.
modifier onlyFoundationAdmin() {
if (!IAdminRole(treasury).isAdmin(msg.sender)) {
revert FoundationTreasuryNode_Caller_Not_Admin();
}
_;
}
/// @notice Requires the caller is a Foundation operator.
modifier onlyFoundationOperator() {
if (!IOperatorRole(treasury).isOperator(msg.sender)) {
revert FoundationTreasuryNode_Caller_Not_Operator();
}
_;
}
/**
* @notice Set immutable variables for the implementation contract.
* @dev Assigns the treasury contract address.
*/
constructor(address payable _treasury) {
if (!_treasury.isContract()) {
revert FoundationTreasuryNode_Address_Is_Not_A_Contract();
}
treasury = _treasury;
}
/**
* @notice Gets the Foundation treasury contract.
* @dev This call is used in the royalty registry contract.
* @return treasuryAddress The address of the Foundation treasury contract.
*/
function getFoundationTreasury() public view returns (address payable treasuryAddress) {
treasuryAddress = treasury;
}
/**
* @notice This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[2_000] private __gap;
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;
import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
import "@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;
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;
import "./FETHNode.sol";
/**
* @title A place for common modifiers and functions used by various market mixins, if any.
* @dev This also leaves a gap which can be used to add a new mixin to the top of the inheritance tree.
* @author batu-inal & HardlyDifficult
*/
abstract contract MarketSharedCore is FETHNode {
/**
* @notice Checks who the seller for an NFT is if listed in this market.
* @param nftContract The address of the NFT contract.
* @param tokenId The id of the NFT.
* @return seller The seller which listed this NFT for sale, or address(0) if not listed.
*/
function getSellerOf(address nftContract, uint256 tokenId) external view returns (address payable seller) {
seller = _getSellerOf(nftContract, tokenId);
}
/**
* @notice Checks who the seller for an NFT is if listed in this market.
*/
function _getSellerOf(address nftContract, uint256 tokenId) internal view virtual returns (address payable seller) {
// Returns address(0) by default.
}
/**
* @notice Checks who the seller for an NFT is if listed in this market or returns the current owner.
*/
function _getSellerOrOwnerOf(
address nftContract,
uint256 tokenId
) internal view virtual returns (address payable sellerOrOwner);
/**
* @notice This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[450] private __gap;
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;
import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
error RouterContext_Not_A_Contract();
/**
* @title Enables a trusted router contract to override the usual msg.sender address.
* @author HardlyDifficult
*/
abstract contract RouterContext is ContextUpgradeable {
using AddressUpgradeable for address;
address private immutable approvedRouter;
constructor(address router) {
if (!router.isContract()) {
revert RouterContext_Not_A_Contract();
}
approvedRouter = router;
}
/**
* @notice Returns the router contract which is able to override the msg.sender address.
* @return router The address of the trusted router.
*/
function getApprovedRouterAddress() external view returns (address router) {
router = approvedRouter;
}
/**
* @notice Returns the sender of the transaction.
* @dev If the msg.sender is the trusted router contract, then the last 20 bytes of the calldata is the authorized
* sender.
*/
function _msgSender() internal view virtual override returns (address sender) {
sender = super._msgSender();
if (sender == approvedRouter) {
assembly {
// The router appends the msg.sender to the end of the calldata
// source: https://github.com/opengsn/gsn/blob/v3.0.0-beta.3/packages/contracts/src/ERC2771Recipient.sol#L48
sender := shr(96, calldataload(sub(calldatasize(), 20)))
}
}
}
}// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.18;
import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
import "./FETHNode.sol";
import "./FoundationTreasuryNode.sol";
/**
* @title A mixin for sending ETH with a fallback withdraw mechanism.
* @notice Attempt to send ETH and if the transfer fails or runs out of gas, store the balance
* in the FETH token contract for future withdrawal instead.
* @dev This mixin was recently switched to escrow funds in FETH.
* Once we have confirmed all pending balances have been withdrawn, we can remove the escrow tracking here.
* @author batu-inal & HardlyDifficult
*/
abstract contract SendValueWithFallbackWithdraw is FoundationTreasuryNode, FETHNode {
using AddressUpgradeable for address payable;
/// @dev Removing old unused variables in an upgrade safe way.
uint256 private __gap_was_pendingWithdrawals;
/**
* @notice Emitted when escrowed funds are withdrawn to FETH.
* @param user The account which has withdrawn ETH.
* @param amount The amount of ETH which has been withdrawn.
*/
event WithdrawalToFETH(address indexed user, uint256 amount);
/**
* @notice Attempt to send a user or contract ETH.
* If it fails store the amount owned for later withdrawal in FETH.
* @dev This may fail when sending ETH to a contract that is non-receivable or exceeds the gas limit specified.
*/
function _sendValueWithFallbackWithdraw(address payable user, uint256 amount, uint256 gasLimit) internal {
if (amount == 0) {
return;
}
if (user == address(feth)) {
// FETH may revert on ETH transfers and will reject `depositFor` calls to itself, so redirect funds to the
// treasury contract instead.
user = getFoundationTreasury();
}
// Cap the gas to prevent consuming all available gas to block a tx from completing successfully
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = user.call{ value: amount, gas: gasLimit }("");
if (!success) {
// Store the funds that failed to send for the user in the FETH token
feth.depositFor{ value: amount }(user);
emit WithdrawalToFETH(user, amount);
}
}
/**
* @notice This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[999] private __gap;
}{
"optimizer": {
"enabled": true,
"runs": 3500
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"libraries": {}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"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"}]Contract Creation Code
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
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in ETH
0
Multichain Portfolio | 33 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
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.