Overview
ETH Balance
0 ETH
Eth Value
$0.00More Info
Private Name Tags
ContractCreator
Latest 1 internal transaction
Advanced mode:
Parent Transaction Hash | Block | From | To | |||
---|---|---|---|---|---|---|
19925711 | 178 days ago | Contract Creation | 0 ETH |
Loading...
Loading
Contract Source Code Verified (Exact Match)
Contract Name:
NFTMarket
Compiler Version
v0.8.25+commit.b61c2a91
Optimization Enabled:
Yes with 1337000 runs
Other Settings:
paris 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 { ContextUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; import { FETHNode } from "./mixins/shared/FETHNode.sol"; import { FoundationTreasuryNodeV1 } from "./mixins/shared/FoundationTreasuryNodeV1.sol"; import { MarketFees } from "./mixins/shared/MarketFees.sol"; import { MarketSharedCore } from "./mixins/shared/MarketSharedCore.sol"; import { WorldsNftNode } from "./mixins/shared/WorldsNftNode.sol"; import { RouterContextSingle } from "./mixins/shared/RouterContextSingle.sol"; import { GapSendValueWithFallbackWithdrawV1 } from "./mixins/shared/GapSendValueWithFallbackWithdrawV1.sol"; import { SendValueWithFallbackWithdraw } from "./mixins/shared/SendValueWithFallbackWithdraw.sol"; import { NFTMarketAuction } from "./mixins/nftMarket/NFTMarketAuction.sol"; import { NFTMarketBuyPrice } from "./mixins/nftMarket/NFTMarketBuyPrice.sol"; import { NFTMarketCore } from "./mixins/nftMarket/NFTMarketCore.sol"; import { NFTMarketWorlds } from "./mixins/nftMarket/NFTMarketWorlds.sol"; import { NFTMarketOffer } from "./mixins/nftMarket/NFTMarketOffer.sol"; import { NFTMarketPrivateSaleGap } from "./mixins/nftMarket/NFTMarketPrivateSaleGap.sol"; import { NFTMarketReserveAuction } from "./mixins/nftMarket/NFTMarketReserveAuction.sol"; import { NFTMarketScheduling } from "./mixins/nftMarket/NFTMarketScheduling.sol"; import { NFTMarketWorldsAPIs } from "./mixins/nftMarket/NFTMarketWorldsAPIs.sol"; /** * @title A market for NFTs on Foundation. * @notice The Foundation marketplace is a contract which allows traders to buy and sell NFTs. * It supports buying and selling via auctions, private sales, buy price, and offers. * @dev All sales in the Foundation market will pay the creator 10% royalties on secondary sales. This is not specific * to NFTs minted on Foundation, it should work for any NFT. If royalty information was not defined when the NFT was * originally deployed, it may be added using the [Royalty Registry](https://royaltyregistry.xyz/) which will be * respected by our market contract. * @author batu-inal & HardlyDifficult */ contract NFTMarket is WorldsNftNode, NFTMarketWorldsAPIs, Initializable, FoundationTreasuryNodeV1, ContextUpgradeable, RouterContextSingle, FETHNode, MarketSharedCore, NFTMarketCore, ReentrancyGuardUpgradeable, GapSendValueWithFallbackWithdrawV1, SendValueWithFallbackWithdraw, MarketFees, NFTMarketWorlds, NFTMarketScheduling, NFTMarketAuction, NFTMarketReserveAuction, NFTMarketPrivateSaleGap, NFTMarketBuyPrice, NFTMarketOffer { //////////////////////////////////////////////////////////////// // Setup //////////////////////////////////////////////////////////////// /** * @notice Set immutable variables for the implementation contract. * @dev Using immutable instead of constants allows us to use different values on testnet. * @param treasury The Foundation Treasury contract address. * @param feth The FETH ERC-20 token contract address. * @param router The trusted router contract address. * @param marketUtils The MarketUtils contract address. * @param worldsNft The Worlds NFT contract address. */ constructor( address payable treasury, address feth, address router, address marketUtils, address worldsNft ) FoundationTreasuryNodeV1(treasury) FETHNode(feth) WorldsNftNode(worldsNft) MarketFees( /* protocolFeeInBasisPoints: */ 500, marketUtils, /* assumePrimarySale: */ false ) RouterContextSingle(router) {} /** * @notice Initialize mutable state. * @param networkAuctionIdOffset The first auction created on this network will use * `auctionId = networkAuctionIdOffset + 1`. */ function initialize(uint256 networkAuctionIdOffset) external reinitializer(2) { _initializeNFTMarketAuction(networkAuctionIdOffset); } //////////////////////////////////////////////////////////////// // Inheritance Requirements // (no-ops to avoid compile errors) //////////////////////////////////////////////////////////////// /** * @inheritdoc NFTMarketCore */ function _beforeAuctionStarted( address nftContract, uint256 tokenId ) internal override(NFTMarketCore, NFTMarketScheduling, NFTMarketReserveAuction, NFTMarketBuyPrice, NFTMarketOffer) { super._beforeAuctionStarted(nftContract, tokenId); } /** * @inheritdoc MarketFees */ function _distributeFunds( DistributeFundsParams memory params ) internal virtual override(MarketFees, NFTMarketBuyPrice, NFTMarketReserveAuction, NFTMarketScheduling) returns (uint256 totalFees, uint256 creatorRev, uint256 sellerRev) { (totalFees, creatorRev, sellerRev) = super._distributeFunds(params); } /** * @inheritdoc MarketSharedCore */ function _getSellerOf( address nftContract, uint256 tokenId ) internal view override(MarketSharedCore, NFTMarketReserveAuction, NFTMarketBuyPrice) returns (address payable seller) { seller = super._getSellerOf(nftContract, tokenId); } /** * @inheritdoc NFTMarketCore */ function _isAuthorizedScheduleUpdate( address nftContract, uint256 tokenId ) internal view override(NFTMarketCore, NFTMarketReserveAuction, NFTMarketBuyPrice) returns (bool canUpdateNft) { canUpdateNft = super._isAuthorizedScheduleUpdate(nftContract, tokenId); } /** * @inheritdoc RouterContextSingle */ function _msgSender() internal view override(ContextUpgradeable, RouterContextSingle) returns (address sender) { sender = super._msgSender(); } /** * @inheritdoc NFTMarketCore */ function _transferFromEscrow( address nftContract, uint256 tokenId, address recipient, address authorizeSeller ) internal override(NFTMarketCore, NFTMarketReserveAuction, NFTMarketBuyPrice) { super._transferFromEscrow(nftContract, tokenId, recipient, authorizeSeller); } /** * @inheritdoc NFTMarketCore */ function _transferFromEscrowIfAvailable( address nftContract, uint256 tokenId, address originalSeller ) internal override( NFTMarketCore, NFTMarketWorlds, NFTMarketOffer, NFTMarketScheduling, NFTMarketReserveAuction, NFTMarketBuyPrice ) { super._transferFromEscrowIfAvailable(nftContract, tokenId, originalSeller); } /** * @inheritdoc NFTMarketCore */ function _transferToEscrow( address nftContract, uint256 tokenId ) internal override(NFTMarketCore, NFTMarketReserveAuction, NFTMarketBuyPrice) { super._transferToEscrow(nftContract, tokenId); } }
// 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 {Initializable} from "../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 (last updated v4.9.4) (utils/Context.sol) pragma solidity ^0.8.0; import {Initializable} from "../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; } function _contextSuffixLength() internal view virtual returns (uint256) { return 0; } /** * @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 v5.0.0) (token/ERC721/IERC721.sol) pragma solidity ^0.8.20; import {IERC165} from "../../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 address zero. * * 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 v5.0.0) (utils/introspection/IERC165.sol) pragma solidity ^0.8.20; /** * @dev Interface of the ERC165 standard, as defined in the * https://eips.ethereum.org/EIPS/eip-165[EIP]. * * Implementers can declare support of contract interfaces, which can then be * queried by others ({ERC165Checker}). * * For an implementation, see {ERC165}. */ interface IERC165 { /** * @dev Returns true if this contract implements the interface defined by * `interfaceId`. See the corresponding * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] * to learn more about how these ids are created. * * This function call must use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool); }
// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; /** * @notice Interface for functions the market uses in FETH. * @author batu-inal & HardlyDifficult */ interface IFethMarket { function depositFor(address account) external payable; function marketLockupFor(address account, uint256 amount) external payable returns (uint256 expiration); function marketWithdrawFrom(address from, uint256 amount) external; function marketWithdrawLocked(address account, uint256 expiration, uint256 amount) external; function marketUnlockFor(address account, uint256 expiration, uint256 amount) external; function marketChangeLockup( address unlockFrom, uint256 unlockExpiration, uint256 unlockAmount, address lockupFor, uint256 lockupAmount ) external payable returns (uint256 expiration); }
// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; import "../../mixins/shared/MarketStructs.sol"; interface IMarketUtils { function getTransactionBreakdown( MarketTransactionOptions calldata options ) external view returns ( uint256 protocolFeeAmount, address payable[] memory creatorRecipients, uint256[] memory creatorShares, uint256 sellerRev, uint256 buyReferrerFee, uint256 sellerReferrerFee ); }
// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; import "../../mixins/shared/MarketStructs.sol"; /** * @title Interface with NFTMarket getters which are used by the router. * @author HardlyDifficult */ interface INFTMarketGetters { function getBuyPrice(address nftContract, uint256 tokenId) external view returns (address seller, uint256 price); function getSaleStartsAt(address nftContract, uint256 tokenId) external view returns (uint256 saleStartsAt); function getReserveAuction(uint256 auctionId) external view returns (ReserveAuction memory auction); function getReserveAuctionIdFor(address nftContract, uint256 tokenId) external view returns (uint256 auctionId); }
// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; interface IWorldsSharedMarket { error WorldsInventoryByNft_Not_In_A_World(); function getDefaultTakeRate(uint256 worldId) external view returns (uint16 defaultTakeRateInBasisPoints); function addToWorldByCollectionV2( uint256 worldId, address nftContract, uint16 takeRateInBasisPoints, bytes calldata approvalData ) external; function addToWorldByNftV2( uint256 worldId, address nftContract, uint256 nftTokenId, uint16 takeRateInBasisPoints, bytes calldata approvalData ) external; function removeFromWorldByNft(address nftContract, uint256 nftTokenId) external; function getAssociationByCollection( address nftContract, address seller ) external view returns (uint256 worldId, uint16 takeRateInBasisPoints); function getAssociationByNft( address nftContract, uint256 nftTokenId, address seller ) external view returns (uint256 worldId, uint16 takeRateInBasisPoints); function getPaymentAddress(uint256 worldId) external view returns (address payable worldPaymentAddress); }
// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; interface IWorldsSoldByNft { //////////////////////////////////////////////////////////////// // NFT specific //////////////////////////////////////////////////////////////// function soldInWorldByNft( address seller, address nftContract, uint256 nftTokenId, address buyer, uint256 salePrice ) external returns (uint256 worldId, address payable worldPaymentAddress, 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 cancelBuyPrice(address nftContract, uint256 tokenId) external; 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 cancelReserveAuction(uint256 auctionId) external; function createReserveAuction( address nftContract, uint256 tokenId, uint256 reservePrice, uint256 duration ) external returns (uint256 auctionId); function updateReserveAuction(uint256 auctionId, uint256 reservePrice) external; }
// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; interface INFTMarketScheduling { function setSaleStartsAt(address nftContract, uint256 tokenId, uint256 saleStartsAt) external; }
// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; import "../mixins/shared/Constants.sol"; library ReserveAuctionLibrary { error ReserveAuctionLibrary_Must_Set_Non_Zero_Reserve_Price(); error ReserveAuctionLibrary_Exceeds_Max_Duration(uint256 maxDuration); error ReserveAuctionLibrary_Less_Than_Extension_Duration(uint256 extensionDuration); error ReserveAuctionLibrary_Only_Callable_By_Seller(address seller); function validateReserveAuctionConfig( uint256 reservePrice, uint256 duration ) internal pure returns (uint256 updatedDuration) { validateReserveAuctionPrice(reservePrice); if (duration == 0) { updatedDuration = DEFAULT_DURATION; } else { if (duration > MAX_DURATION) { // This ensures that math in this file will not overflow due to a huge duration. revert ReserveAuctionLibrary_Exceeds_Max_Duration(MAX_DURATION); } if (duration < EXTENSION_DURATION) { // The auction duration configuration must be greater than the extension window of 2 minutes revert ReserveAuctionLibrary_Less_Than_Extension_Duration(EXTENSION_DURATION); } updatedDuration = duration; } } /// @notice Confirms that the reserve price is not zero. function validateReserveAuctionPrice(uint256 reservePrice) internal pure { if (reservePrice == 0) { revert ReserveAuctionLibrary_Must_Set_Non_Zero_Reserve_Price(); } } function validateCalledBySeller(address msgSender, address seller) internal pure { if (msgSender != seller) { revert ReserveAuctionLibrary_Only_Callable_By_Seller(seller); } } }
// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; error RouteCallLibrary_Call_Failed_Without_Revert_Reason(); /** * @title A library for calling external contracts with an address appended to the calldata. * @author HardlyDifficult */ library RouteCallLibrary { /** * @notice Routes a call to the specified contract, appending the from address to the end of the calldata. * If the call reverts, this will revert the transaction and the original reason is bubbled up. * @param from The address to use as the msg sender when calling the contract. * @param to The contract address to call. * @param callData The call data to use when calling the contract, without the sender appended. */ function routeCallTo(address from, address to, bytes memory callData) internal returns (bytes memory returnData) { // Forward the call, with the packed from address appended, to the specified contract. bool success; (success, returnData) = tryRouteCallTo(from, to, callData); // If the call failed, bubble up the revert reason. if (!success) { revertWithError(returnData); } } /** * @notice Routes a call to the specified contract, appending the from address to the end of the calldata. * This will not revert even if the external call fails. * @param from The address to use as the msg sender when calling the contract. * @param to The contract address to call. * @param callData The call data to use when calling the contract, without the sender appended. * @dev Consumers should look for positive confirmation that if the transaction is not successful, the returned revert * reason is expected as an acceptable reason to ignore. Generically ignoring reverts will lead to out-of-gas errors * being ignored and result in unexpected behavior. */ function tryRouteCallTo( address from, address to, bytes memory callData ) internal returns (bool success, bytes memory returnData) { // Forward the call, with the packed from address appended, to the specified contract. // solhint-disable-next-line avoid-low-level-calls (success, returnData) = to.call(abi.encodePacked(callData, from)); } /** * @notice Bubbles up the original revert reason of a low-level call failure where possible. * @dev Copied from OZ's `Address.sol` library, with a minor modification to the final revert scenario. * This should only be used when a low-level call fails. */ function revertWithError(bytes memory returnData) internal pure { // Look for revert reason and bubble it up if present if (returnData.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly /// @solidity memory-safe-assembly assembly { let returnData_size := mload(returnData) revert(add(32, returnData), returnData_size) } } else { revert RouteCallLibrary_Call_Failed_Without_Revert_Reason(); } } /** * @notice Extracts the appended sender address from the calldata. * @dev This uses the last 20 bytes of the calldata, with no guarantees that an address has indeed been appended. * If this is used for a call that was not routed with `routeCallTo`, the address returned will be incorrect (and * may be address(0)). */ function extractAppendedSenderAddress() internal pure returns (address sender) { assembly { // The router appends the msg.sender to the end of the calldata // source: https://github.com/opengsn/gsn/blob/v3.0.0-beta.3/packages/contracts/src/ERC2771Recipient.sol#L48 sender := shr(96, calldataload(sub(calldatasize(), 20))) } } }
// 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; error NFTMarketAuction_Already_Initialized(); /// @notice Assigns a default to the sequence ID used for auctions, allowing different networks to use a unique range. function _initializeNFTMarketAuction(uint256 networkAuctionIdOffset) internal { if (networkAuctionIdOffset != 0) { // If the offset is 0, then ignore this and continue using the value in storage. // This scenario would apply to L1 networks only, which have a non-zero offset to preserve. if ($nextAuctionId != 0) { // Explicitly checking this here instead of leaning on initializer to be extra cautious of errors during future // upgrades. revert NFTMarketAuction_Already_Initialized(); } $nextAuctionId = networkAuctionIdOffset + 1; } } /** * @notice Returns id to assign to the next auction. */ function _getNextAndIncrementAuctionId() internal returns (uint256) { // AuctionId cannot overflow 256 bits. unchecked { if ($nextAuctionId == 0) { // Ensures that the first auctionId is 1. ++$nextAuctionId; } // Returns the current nextAuctionId instead of ++nextAuctionId to ensure the sequence ID is preserved on mainnet. return $nextAuctionId++; } } /** * @notice This empty reserved space is put in place to allow future versions to add new variables without shifting * down storage in the inheritance chain. See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[1_000] private __gap; }
// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; import "../../interfaces/internal/INFTMarketGetters.sol"; import "../../interfaces/internal/routes/INFTMarketBuyNow.sol"; import "../shared/MarketFees.sol"; import "../shared/FETHNode.sol"; import "../shared/MarketSharedCore.sol"; import "../shared/WorldsNftNode.sol"; import "../shared/SendValueWithFallbackWithdraw.sol"; import "./NFTMarketCore.sol"; import "./NFTMarketScheduling.sol"; import "./NFTMarketWorlds.sol"; /// @param buyPrice The current buy price set for this NFT. error NFTMarketBuyPrice_Cannot_Buy_At_Lower_Price(uint256 buyPrice); error NFTMarketBuyPrice_Cannot_Buy_Unset_Price(); error NFTMarketBuyPrice_Cannot_Cancel_Unset_Price(); /// @param owner The current owner of this NFT. error NFTMarketBuyPrice_Only_Owner_Can_Cancel_Price(address owner); /// @param owner The current owner of this NFT. error NFTMarketBuyPrice_Only_Owner_Can_Set_Price(address owner); /// @param owner The current owner of this NFT. error NFTMarketBuyPrice_Only_Owner_Can_Update_Nft(address owner); /// @param owner The current owner of this NFT. error NFTMarketBuyPrice_Only_Owner_Can_Update_Sale_Starts_At(address owner); error NFTMarketBuyPrice_Price_Already_Set(); error NFTMarketBuyPrice_Price_Too_High(); /// @param seller The current owner of this NFT. error NFTMarketBuyPrice_Seller_Mismatch(address seller); error NFTMarketBuyPrice_Listing_Is_Not_Active(uint256 startTime); /** * @title Allows sellers to set a buy price of their NFTs that may be accepted and instantly transferred to the buyer. * @notice NFTs with a buy price set are escrowed in the market contract. * @author batu-inal & HardlyDifficult */ abstract contract NFTMarketBuyPrice is INFTMarketGetters, INFTMarketBuyNow, WorldsNftNode, ContextUpgradeable, FETHNode, MarketSharedCore, NFTMarketCore, ReentrancyGuardUpgradeable, SendValueWithFallbackWithdraw, MarketFees, NFTMarketWorlds, NFTMarketScheduling { using AddressUpgradeable for address payable; /// @notice Stores the buy price details for a specific NFT. /// @dev The struct is packed into a single slot to optimize gas. struct BuyPrice { /// @notice The current owner of this NFT which set a buy price. /// @dev A zero price is acceptable so a non-zero address determines whether a price has been set. address payable seller; /// @notice The current buy price set for this NFT. uint96 price; } /// @notice Stores the current buy price for each NFT. mapping(address => mapping(uint256 => BuyPrice)) private nftContractToTokenIdToBuyPrice; /** * @notice Emitted when an NFT is bought by accepting the buy price, * indicating that the NFT has been transferred and revenue from the sale distributed. * @dev The total buy price that was accepted is `totalFees` + `creatorRev` + `sellerRev`. * @param nftContract The address of the NFT contract. * @param tokenId The id of the NFT. * @param buyer The address of the collector that purchased the NFT using `buy`. * @param seller The address of the seller which originally set the buy price. * @param totalFees The amount of ETH that was sent to Foundation & referrals for this sale. * @param creatorRev The amount of ETH that was sent to the creator for this sale. * @param sellerRev The amount of ETH that was sent to the owner for this sale. */ event BuyPriceAccepted( address indexed nftContract, uint256 indexed tokenId, address indexed seller, address buyer, uint256 totalFees, uint256 creatorRev, uint256 sellerRev ); /** * @notice Emitted when the buy price is removed by the owner of an NFT. * @dev The NFT is transferred back to the owner unless it's still escrowed for another market tool, * e.g. listed for sale in an auction. * @param nftContract The address of the NFT contract. * @param tokenId The id of the NFT. */ event BuyPriceCanceled(address indexed nftContract, uint256 indexed tokenId); /** * @notice Emitted when a buy price is invalidated due to other market activity. * @dev This occurs when the buy price is no longer eligible to be accepted, * e.g. when a bid is placed in an auction for this NFT. * @param nftContract The address of the NFT contract. * @param tokenId The id of the NFT. */ event BuyPriceInvalidated(address indexed nftContract, uint256 indexed tokenId); /** * @notice Emitted when a buy price is set by the owner of an NFT. * @dev The NFT is transferred into the market contract for escrow unless it was already escrowed, * e.g. for auction listing. * @param nftContract The address of the NFT contract. * @param tokenId The id of the NFT. * @param seller The address of the NFT owner which set the buy price. * @param price The price of the NFT. */ event BuyPriceSet(address indexed nftContract, uint256 indexed tokenId, address indexed seller, uint256 price); /** * @notice Buy the NFT at the set buy price. * `msg.value` must be <= `maxPrice` and any delta will be taken from the account's available FETH balance. * @dev `maxPrice` protects the buyer in case a the price is increased but allows the transaction to continue * when the price is reduced (and any surplus funds provided are refunded). * @param nftContract The address of the NFT contract. * @param tokenId The id of the NFT. * @param maxPrice The maximum price to pay for the NFT. * @param referrer The address of the referrer. */ function buyV2(address nftContract, uint256 tokenId, uint256 maxPrice, address payable referrer) external payable { BuyPrice storage buyPrice = nftContractToTokenIdToBuyPrice[nftContract][tokenId]; if (buyPrice.price > maxPrice) { revert NFTMarketBuyPrice_Cannot_Buy_At_Lower_Price(buyPrice.price); } else if (buyPrice.seller == address(0)) { revert NFTMarketBuyPrice_Cannot_Buy_Unset_Price(); } _buy(nftContract, tokenId, referrer); } /** * @notice Removes the buy price set for an NFT. * @dev The NFT is transferred back to the owner unless it's still escrowed for another market tool, * e.g. listed for sale in an auction. * @param nftContract The address of the NFT contract. * @param tokenId The id of the NFT. */ function cancelBuyPrice(address nftContract, uint256 tokenId) external nonReentrant { address seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller; address sender = _msgSender(); if (seller == address(0)) { // This check is redundant with the next one, but done in order to provide a more clear error message. revert NFTMarketBuyPrice_Cannot_Cancel_Unset_Price(); } else if (seller != sender) { revert NFTMarketBuyPrice_Only_Owner_Can_Cancel_Price(seller); } // Remove the buy price delete nftContractToTokenIdToBuyPrice[nftContract][tokenId]; // Transfer the NFT back to the owner if it is not listed in auction. _transferFromEscrowIfAvailable(nftContract, tokenId, seller); emit BuyPriceCanceled(nftContract, tokenId); } /** * @notice Sets the buy price for an NFT and escrows it in the market contract. * A 0 price is acceptable and valid price you can set, enabling a giveaway to the first collector that calls `buy`. * @dev If there is an offer for this amount or higher, that will be accepted instead of setting a buy price. * @param nftContract The address of the NFT contract. * @param tokenId The id of the NFT. * @param price The price at which someone could buy this NFT. */ function setBuyPrice(address nftContract, uint256 tokenId, uint256 price) 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); } function _isAuthorizedScheduleUpdate( address nftContract, uint256 tokenId ) internal view virtual override returns (bool canUpdateNft) { address seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller; if (seller != address(0)) { if (seller != _msgSender()) { revert NFTMarketBuyPrice_Only_Owner_Can_Update_Nft(seller); } canUpdateNft = true; } else { canUpdateNft = super._isAuthorizedScheduleUpdate(nftContract, tokenId); } } /** * @notice If there is a buy price at this price or lower, accept that and return true. */ function _autoAcceptBuyPrice( address nftContract, uint256 tokenId, uint256 maxPrice ) internal override returns (bool) { BuyPrice storage buyPrice = nftContractToTokenIdToBuyPrice[nftContract][tokenId]; if (buyPrice.seller == address(0) || buyPrice.price > maxPrice) { // No buy price was found, or the price is too high. return false; } _buy(nftContract, tokenId, payable(0)); return true; } /** * @inheritdoc NFTMarketCore * @dev Invalidates the buy price on a auction start, if one is found. */ function _beforeAuctionStarted( address nftContract, uint256 tokenId ) internal virtual override(NFTMarketCore, NFTMarketScheduling) { BuyPrice storage buyPrice = nftContractToTokenIdToBuyPrice[nftContract][tokenId]; if (buyPrice.seller != address(0)) { // A buy price was set for this NFT, invalidate it. _invalidateBuyPrice(nftContract, tokenId); } super._beforeAuctionStarted(nftContract, tokenId); } /** * @notice Process the purchase of an NFT at the current buy price. * @dev The caller must confirm that the seller != address(0) before calling this function. */ function _buy(address nftContract, uint256 tokenId, address payable referrer) private nonReentrant { _validateSaleStartsAtHasBeenReached(nftContract, tokenId); BuyPrice memory buyPrice = nftContractToTokenIdToBuyPrice[nftContract][tokenId]; // Remove the buy now price delete nftContractToTokenIdToBuyPrice[nftContract][tokenId]; // Cancel the buyer's offer if there is one in order to free up their FETH balance // even if they don't need the FETH for this specific purchase. _cancelSendersOffer(nftContract, tokenId); address payable buyer = payable(_msgSender()); _tryUseFETHBalance({ fromAccount: buyer, totalAmount: buyPrice.price, shouldRefundSurplus: true }); (address payable sellerReferrerPaymentAddress, uint16 sellerReferrerTakeRateInBasisPoints) = _getWorldForPayment( buyPrice.seller, nftContract, tokenId, buyer, buyPrice.price ); // Transfer the NFT to the buyer. // The seller was already authorized when the buyPrice was set originally set. _transferFromEscrow(nftContract, tokenId, buyer, address(0)); // Distribute revenue for this sale. (uint256 totalFees, uint256 creatorRev, uint256 sellerRev) = _distributeFunds( DistributeFundsParams({ nftContract: nftContract, firstTokenId: tokenId, nftCount: 1, nftRecipientIfKnown: buyer, seller: buyPrice.seller, price: buyPrice.price, buyReferrer: referrer, sellerReferrerPaymentAddress: sellerReferrerPaymentAddress, sellerReferrerTakeRateInBasisPoints: sellerReferrerTakeRateInBasisPoints, fixedProtocolFeeInWei: 0 }) ); emit BuyPriceAccepted(nftContract, tokenId, buyPrice.seller, buyer, totalFees, creatorRev, sellerRev); } /** * @notice Clear a buy price and emit BuyPriceInvalidated. * @dev The caller must confirm the buy price is set before calling this function. */ function _invalidateBuyPrice(address nftContract, uint256 tokenId) private { delete nftContractToTokenIdToBuyPrice[nftContract][tokenId]; emit BuyPriceInvalidated(nftContract, tokenId); } /** * @inheritdoc NFTMarketCore * @dev Invalidates the buy price if one is found before transferring the NFT. * This will revert if there is a buy price set but the `authorizeSeller` is not the owner. */ function _transferFromEscrow( address nftContract, uint256 tokenId, address recipient, address authorizeSeller ) internal virtual override { address seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller; if (seller != address(0)) { // A buy price was set for this NFT. // `authorizeSeller != address(0) &&` could be added when other mixins use this flow. // ATM that additional check would never return false. if (seller != authorizeSeller) { // When there is a buy price set, the `buyPrice.seller` is the owner of the NFT. revert NFTMarketBuyPrice_Seller_Mismatch(seller); } // The seller authorization has been confirmed. authorizeSeller = address(0); // Invalidate the buy price as the NFT will no longer be in escrow. _invalidateBuyPrice(nftContract, tokenId); } super._transferFromEscrow(nftContract, tokenId, recipient, authorizeSeller); } /** * @inheritdoc NFTMarketCore * @dev Checks if there is a buy price set, if not then allow the transfer to proceed. */ function _transferFromEscrowIfAvailable( address nftContract, uint256 tokenId, address originalSeller ) internal virtual override(NFTMarketCore, NFTMarketWorlds, NFTMarketScheduling) { address seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller; // If a buy price has been set for this NFT then it should remain in escrow. if (seller == address(0)) { // Otherwise continue to attempt the transfer. super._transferFromEscrowIfAvailable(nftContract, tokenId, originalSeller); } } /** * @inheritdoc NFTMarketCore * @dev Checks if the NFT is already in escrow for buy now. */ function _transferToEscrow(address nftContract, uint256 tokenId) internal virtual override { address seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller; if (seller == address(0)) { // The NFT is not in escrow for buy now. super._transferToEscrow(nftContract, tokenId); } else if (seller != _msgSender()) { // When there is a buy price set, the `seller` is the owner of the NFT. revert NFTMarketBuyPrice_Seller_Mismatch(seller); } } /** * @notice Returns the buy price details for an NFT if one is available. * @dev If no price is found, seller will be address(0) and price will be max uint256. * @param nftContract The address of the NFT contract. * @param tokenId The id of the NFT. * @return seller The address of the owner that listed a buy price for this NFT. * Returns `address(0)` if there is no buy price set for this NFT. * @return price The price of the NFT. * Returns max uint256 if there is no buy price set for this NFT (since a price of 0 is supported). */ function getBuyPrice(address nftContract, uint256 tokenId) external view returns (address seller, uint256 price) { seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller; if (seller == address(0)) { return (seller, type(uint256).max); } price = nftContractToTokenIdToBuyPrice[nftContract][tokenId].price; } /** * @inheritdoc MarketSharedCore * @dev Returns the seller if there is a buy price set for this NFT, otherwise * bubbles the call up for other considerations. */ function _getSellerOf( address nftContract, uint256 tokenId ) internal view virtual override returns (address payable seller) { seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller; if (seller == address(0)) { seller = super._getSellerOf(nftContract, tokenId); } } //////////////////////////////////////////////////////////////// // Inheritance Requirements // (no-ops to avoid compile errors) //////////////////////////////////////////////////////////////// /** * @inheritdoc MarketFees */ function _distributeFunds( DistributeFundsParams memory params ) internal virtual override(MarketFees, NFTMarketScheduling) returns (uint256 totalFees, uint256 creatorRev, uint256 sellerRev) { (totalFees, creatorRev, sellerRev) = super._distributeFunds(params); } /** * @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(); error NFTMarketCore_Can_Not_Update_Unlisted_Nft(); /** * @title A place for common modifiers and functions used by various NFTMarket mixins, if any. * @dev This also leaves a gap which can be used to add a new mixin to the top of the inheritance tree. * @author batu-inal & HardlyDifficult */ abstract contract NFTMarketCore is ContextUpgradeable, MarketSharedCore { using AddressUpgradeable for address; using AddressUpgradeable for address payable; /** * @notice If there is a buy price at this amount or lower, accept that and return true. */ function _autoAcceptBuyPrice(address nftContract, uint256 tokenId, uint256 amount) internal virtual returns (bool); /** * @notice If there is a valid offer at the given price or higher, accept that and return true. */ function _autoAcceptOffer(address nftContract, uint256 tokenId, uint256 minAmount) internal virtual returns (bool); /** * @notice Notify implementors when an auction has received its first bid. * Once a bid is received the sale is guaranteed to the auction winner * and other sale mechanisms become unavailable. * @dev Implementors of this interface should update internal state to reflect an auction has been kicked off. */ function _beforeAuctionStarted(address /*nftContract*/, uint256 /*tokenId*/) internal virtual { // No-op } /** * @notice Requires that an NFT is listed for sale, not in active auction, and the msg.sender is the seller which * listed the NFT. */ function _authorizeScheduleUpdate(address nftContract, uint256 tokenId) internal view { if (!_isAuthorizedScheduleUpdate(nftContract, tokenId)) { revert NFTMarketCore_Can_Not_Update_Unlisted_Nft(); } } /** * @notice Confirms permission to update the schedule for an NFT. * @return canUpdateNft True if the NFT is listed for sale and authorize checks did not revert. * @dev Verifies that the NFT is listed, not in active auction, and the sender is the owner. */ function _isAuthorizedScheduleUpdate( address /*nftContract*/, uint256 /*tokenId*/ ) internal view virtual returns (bool canUpdateNft) { // False by default, may be set to true by a market tool mixin if the NFT is listed. } /** * @notice Cancel the `msg.sender`'s offer if there is one, freeing up their FETH balance. * @dev This should be used when it does not make sense to keep the original offer around, * e.g. if a collector accepts a Buy Price then keeping the offer around is not necessary. */ function _cancelSendersOffer(address nftContract, uint256 tokenId) internal virtual; /** * @notice Transfers the NFT from escrow and clears any state tracking this escrowed NFT. * @param authorizeSeller The address of the seller pending authorization. * Once it's been authorized by one of the escrow managers, it should be set to address(0) * indicated that it's no longer pending authorization. */ function _transferFromEscrow( address nftContract, uint256 tokenId, address recipient, address authorizeSeller ) internal virtual { if (authorizeSeller != address(0)) { revert NFTMarketCore_Seller_Not_Found(); } IERC721(nftContract).transferFrom(address(this), recipient, tokenId); } /** * @notice Transfers the NFT from escrow unless there is another reason for it to remain in escrow. */ function _transferFromEscrowIfAvailable( address nftContract, uint256 tokenId, address originalSeller ) internal virtual { _transferFromEscrow(nftContract, tokenId, originalSeller, address(0)); } /** * @notice Transfers an NFT into escrow, * if already there this requires the msg.sender is authorized to manage the sale of this NFT. */ function _transferToEscrow(address nftContract, uint256 tokenId) internal virtual { IERC721(nftContract).transferFrom(_msgSender(), address(this), tokenId); } /** * @dev Determines the minimum amount when increasing an existing offer or bid. */ function _getMinIncrement(uint256 currentAmount) internal pure returns (uint256) { uint256 minIncrement = currentAmount; unchecked { minIncrement /= MIN_PERCENT_INCREMENT_DENOMINATOR; } if (minIncrement == 0) { // Since minIncrement reduces from the currentAmount, this cannot overflow. // The next amount must be at least 1 wei greater than the current. return currentAmount + 1; } return minIncrement + currentAmount; } /** * @inheritdoc MarketSharedCore */ function _getSellerOrOwnerOf( address nftContract, uint256 tokenId ) internal view override returns (address payable sellerOrOwner) { sellerOrOwner = _getSellerOf(nftContract, tokenId); if (sellerOrOwner == address(0)) { sellerOrOwner = payable(IERC721(nftContract).ownerOf(tokenId)); } } /** * @notice Checks if an escrowed NFT is currently in active auction. * @return Returns false if the auction has ended, even if it has not yet been settled. */ function _isInActiveAuction(address nftContract, uint256 tokenId) internal view virtual returns (bool); /** * @notice This empty reserved space is put in place to allow future versions to add new variables without shifting * down storage in the inheritance chain. See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps * @dev 50 slots were consumed by adding `ReentrancyGuard`. */ uint256[450] private __gap; }
// 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/FETHNode.sol"; import "../shared/SendValueWithFallbackWithdraw.sol"; import "./NFTMarketCore.sol"; import "./NFTMarketWorlds.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 ContextUpgradeable, FETHNode, NFTMarketCore, ReentrancyGuardUpgradeable, SendValueWithFallbackWithdraw, MarketFees, NFTMarketWorlds { 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) = _getWorldForPayment( sender, nftContract, tokenId, offer.buyer, offer.amount ); // Transfer the NFT to the buyer. address owner = IERC721(nftContract).ownerOf(tokenId); if (owner == address(this)) { // The NFT is currently in escrow (e.g. it has a buy price set) // This should revert if `msg.sender` is not the owner of this NFT or if the NFT is in active auction. _transferFromEscrow(nftContract, tokenId, offer.buyer, sender); } else { // NFT should be in the seller's wallet. If attempted by the wrong sender or if the market is not approved this // will revert. IERC721(nftContract).transferFrom(sender, offer.buyer, tokenId); } // Distribute revenue for this sale leveraging the ETH received from the FETH contract in the line above. (uint256 totalFees, uint256 creatorRev, uint256 sellerRev) = _distributeFunds( DistributeFundsParams({ nftContract: nftContract, firstTokenId: tokenId, nftCount: 1, nftRecipientIfKnown: offer.buyer, seller: sender, price: offer.amount, buyReferrer: _getOfferReferrerFromSlots(offer.offerReferrerAddressSlot0, offer.offerReferrerAddressSlot1), sellerReferrerPaymentAddress: sellerReferrerPaymentAddress, sellerReferrerTakeRateInBasisPoints: sellerReferrerTakeRateInBasisPoints, fixedProtocolFeeInWei: 0 }) ); 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))); } /** * @inheritdoc NFTMarketCore */ function _transferFromEscrowIfAvailable( address nftContract, uint256 tokenId, address originalSeller ) internal virtual override(NFTMarketCore, NFTMarketWorlds) { super._transferFromEscrowIfAvailable(nftContract, tokenId, originalSeller); } /** * @notice This empty reserved space is put in place to allow future versions to add new variables without shifting * down storage in the inheritance chain. See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[1_000] private __gap; }
// 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; // solhint-disable max-line-length import { INFTMarketReserveAuction } from "../../interfaces/internal/routes/INFTMarketReserveAuction.sol"; import { INFTMarketGetters } from "../../interfaces/internal/INFTMarketGetters.sol"; import { ContextUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; import "../shared/Constants.sol"; import { ReserveAuctionLibrary } from "../../libraries/ReserveAuctionLibrary.sol"; import { TimeLibrary } from "../../libraries/TimeLibrary.sol"; import { ReserveAuction } from "../shared/MarketStructs.sol"; import { FETHNode } from "../shared/FETHNode.sol"; import { MarketFees } from "../shared/MarketFees.sol"; import { MarketSharedCore } from "../shared/MarketSharedCore.sol"; import { WorldsNftNode } from "../shared/WorldsNftNode.sol"; import { SendValueWithFallbackWithdraw } from "../shared/SendValueWithFallbackWithdraw.sol"; import { NFTMarketAuction } from "./NFTMarketAuction.sol"; import { NFTMarketCore } from "./NFTMarketCore.sol"; import { NFTMarketWorlds } from "./NFTMarketWorlds.sol"; import { NFTMarketScheduling } from "./NFTMarketScheduling.sol"; // solhint-enable max-line-length /// @param auctionId The already listed auctionId for this NFT. error NFTMarketReserveAuction_Already_Listed(uint256 auctionId); /// @param minAmount The minimum amount that must be bid in order for it to be accepted. error NFTMarketReserveAuction_Bid_Must_Be_At_Least_Min_Amount(uint256 minAmount); /// @param reservePrice The current reserve price. error NFTMarketReserveAuction_Cannot_Bid_Lower_Than_Reserve_Price(uint256 reservePrice); /// @param endTime The timestamp at which the auction had ended. error NFTMarketReserveAuction_Cannot_Bid_On_Ended_Auction(uint256 endTime); error NFTMarketReserveAuction_Cannot_Bid_On_Nonexistent_Auction(); error NFTMarketReserveAuction_Cannot_Finalize_Already_Settled_Auction(); /// @param endTime The timestamp at which the auction will end. error NFTMarketReserveAuction_Cannot_Finalize_Auction_In_Progress(uint256 endTime); error NFTMarketReserveAuction_Cannot_Rebid_Over_Outstanding_Bid(); error NFTMarketReserveAuction_Cannot_Update_Auction_In_Progress(); error NFTMarketReserveAuction_Cannot_Update_Nft_While_Auction_In_Progress(); error NFTMarketReserveAuction_Cannot_Update_Sale_Starts_At_Auction_In_Progress(); /// @param 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_Sale_Starts_At(address owner); error NFTMarketReserveAuction_Price_Already_Set(); /** * @title Allows the owner of an NFT to list it in auction. * @notice NFTs in auction are escrowed in the market contract. * @dev There is room to optimize the storage for auctions, significantly reducing gas costs. * This may be done in the future, but for now it will remain as is in order to ease upgrade compatibility. * @author batu-inal & HardlyDifficult & reggieag */ abstract contract NFTMarketReserveAuction is INFTMarketGetters, INFTMarketReserveAuction, WorldsNftNode, ContextUpgradeable, FETHNode, MarketSharedCore, NFTMarketCore, ReentrancyGuardUpgradeable, SendValueWithFallbackWithdraw, MarketFees, NFTMarketWorlds, NFTMarketScheduling, NFTMarketAuction { using TimeLibrary for uint256; /// @notice Stores the auction configuration for a specific NFT. /// @dev This allows us to modify the storage struct without changing external APIs. struct ReserveAuctionStorage { // Slot 0 /// @notice The address of the NFT contract. address nftContract; // (96-bits free space) // Slot 1 /// @notice The id of the NFT. uint256 tokenId; // (slot full) // Slot 2 /// @notice The owner of the NFT which listed it in auction. address payable seller; /// @notice First slot (12 bytes) used for the bidReferrerAddress. /// The bidReferrerAddress is the address used to pay the referrer on finalize. /// @dev This approach is used in order to pack storage, saving gas. uint96 bidReferrerAddressSlot0; // (slot full) // Slot 3 /// @dev This field is no longer used but was previously assigned to. uint256 __gap_was_duration; // (slot full) // Slot 4 /// @dev This field is no longer used but was previous assigned to. uint256 __gap_was_extensionDuration; // (slot full) // Slot 5 /// @notice The time at which this auction will not accept any new bids. /// @dev This is `0` until the first bid is placed. uint256 endTime; // (slot full) // Slot 6 /// @notice The current highest bidder in this auction. /// @dev This is `address(0)` until the first bid is placed. address payable bidder; /// @notice Second slot (8 bytes) used for the bidReferrerAddress. uint64 bidReferrerAddressSlot1; /// @dev Auction duration length in seconds. uint32 duration; // (slot full) // Slot 7 /// @notice The latest price of the NFT in this auction. /// @dev This is set to the reserve price, and then to the highest bid once the auction has started. uint256 amount; // (slot full) } /// @notice The auction configuration for a specific auction id. mapping(address nftContract => mapping(uint256 tokenId => uint256 auctionId)) 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 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 when the auction was created. * Note: The current extension duration may not be the same as the duration when the auction was created. * @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 If an auction has been created but has not yet received bids, it may be canceled by the seller. * @dev The NFT is transferred back to the owner unless there is still a buy price set. * @param auctionId The id of the auction to cancel. */ function cancelReserveAuction(uint256 auctionId) external nonReentrant { ReserveAuctionStorage memory auction = auctionIdToAuction[auctionId]; address sender = _msgSender(); ReserveAuctionLibrary.validateCalledBySeller(sender, auction.seller); if (auction.endTime != 0) { revert NFTMarketReserveAuction_Cannot_Update_Auction_In_Progress(); } // Remove the auction. delete nftContractToTokenIdToAuctionId[auction.nftContract][auction.tokenId]; delete auctionIdToAuction[auctionId]; // Transfer the NFT unless it still has a buy price set. _transferFromEscrowIfAvailable(auction.nftContract, auction.tokenId, sender); emit ReserveAuctionCanceled(auctionId); } /** * @notice Creates an auction for the given NFT. * The NFT is held in escrow until the auction is finalized or canceled. * @param nftContract The address of the NFT contract. * @param tokenId The id of the NFT. * @param reservePrice The initial reserve price for the auction. * @param duration The length of the auction, in seconds. * @return auctionId The id of the auction that was created. */ function createReserveAuction( address nftContract, uint256 tokenId, uint256 reservePrice, uint256 duration ) external nonReentrant returns (uint256 auctionId) { duration = ReserveAuctionLibrary.validateReserveAuctionConfig(reservePrice, duration); auctionId = _getNextAndIncrementAuctionId(); // If the `msg.sender` is not the owner of the NFT, transferring into escrow should fail. _transferToEscrow(nftContract, tokenId); // This check must be after _transferToEscrow in case auto-settle was required if (nftContractToTokenIdToAuctionId[nftContract][tokenId] != 0) { revert NFTMarketReserveAuction_Already_Listed(nftContractToTokenIdToAuctionId[nftContract][tokenId]); } // Store the auction details address payable sender = payable(_msgSender()); nftContractToTokenIdToAuctionId[nftContract][tokenId] = auctionId; ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId]; auction.nftContract = nftContract; auction.tokenId = tokenId; auction.seller = sender; auction.amount = reservePrice; if (duration != DEFAULT_DURATION) { // If duration is DEFAULT_DURATION, we don't need to write to storage. // Safe cast is not required since duration is capped by MAX_DURATION. auction.duration = uint32(duration); } emit ReserveAuctionCreated({ seller: sender, nftContract: nftContract, tokenId: tokenId, duration: duration, extensionDuration: EXTENSION_DURATION, reservePrice: reservePrice, auctionId: auctionId }); } /** * @notice Once the countdown has expired for an auction, anyone can settle the auction. * This will send the NFT to the highest bidder and distribute revenue for this sale. * @param auctionId The id of the auction to settle. */ function finalizeReserveAuction(uint256 auctionId) external nonReentrant { if (auctionIdToAuction[auctionId].endTime == 0) { revert NFTMarketReserveAuction_Cannot_Finalize_Already_Settled_Auction(); } _finalizeReserveAuction({ auctionId: auctionId, keepInEscrow: false }); } /** * @notice Place a bid in an auction. * A bidder may place a bid which is at least the amount defined by `getMinBidAmount`. * If this is the first bid on the auction, the countdown will begin. * If there is already an outstanding bid, the previous bidder will be refunded at this time * and if the bid is placed in the final moments of the auction, the countdown may be extended. * @dev `amount` - `msg.value` is withdrawn from the bidder's FETH balance. * @param auctionId The id of the auction to bid on. * @param amount The amount to bid, if this is more than `msg.value` funds will be withdrawn from your FETH balance. * @param referrer The address of the referrer of this bid, or 0 if n/a. */ function placeBidV2(uint256 auctionId, uint256 amount, address payable referrer) external payable nonReentrant { ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId]; if (auction.amount == 0) { // No auction found revert NFTMarketReserveAuction_Cannot_Bid_On_Nonexistent_Auction(); } 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 2 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({ fromAccount: sender, 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 { ReserveAuctionLibrary.validateReserveAuctionPrice(reservePrice); ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId]; ReserveAuctionLibrary.validateCalledBySeller(_msgSender(), auction.seller); if (auction.endTime != 0) { revert NFTMarketReserveAuction_Cannot_Update_Auction_In_Progress(); } if (auction.amount == reservePrice) { revert NFTMarketReserveAuction_Price_Already_Set(); } // Update the current reserve price. auction.amount = reservePrice; emit ReserveAuctionUpdated(auctionId, reservePrice); } /** * @notice 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) = _getWorldForPayment( auction.seller, auction.nftContract, auction.tokenId, auction.bidder, auction.amount ); // Remove the auction. delete nftContractToTokenIdToAuctionId[auction.nftContract][auction.tokenId]; delete auctionIdToAuction[auctionId]; if (!keepInEscrow) { // The seller was authorized when the auction was originally created super._transferFromEscrow({ nftContract: auction.nftContract, tokenId: auction.tokenId, recipient: auction.bidder, authorizeSeller: address(0) }); } // Distribute revenue for this sale. (uint256 totalFees, uint256 creatorRev, uint256 sellerRev) = _distributeFunds( DistributeFundsParams({ nftContract: auction.nftContract, firstTokenId: auction.tokenId, nftCount: 1, nftRecipientIfKnown: auction.bidder, seller: auction.seller, price: auction.amount, buyReferrer: payable( address((uint160(auction.bidReferrerAddressSlot0) << 64) | uint160(auction.bidReferrerAddressSlot1)) ), sellerReferrerPaymentAddress: sellerReferrerPaymentAddress, sellerReferrerTakeRateInBasisPoints: sellerReferrerTakeRateInBasisPoints, fixedProtocolFeeInWei: 0 }) ); emit ReserveAuctionFinalized(auctionId, auction.seller, auction.bidder, totalFees, creatorRev, sellerRev); } function _isAuthorizedScheduleUpdate( address nftContract, uint256 tokenId ) internal view virtual override returns (bool canUpdateNft) { uint256 auctionId = nftContractToTokenIdToAuctionId[nftContract][tokenId]; if (auctionId != 0) { ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId]; ReserveAuctionLibrary.validateCalledBySeller(_msgSender(), auction.seller); if (auction.endTime != 0) { revert NFTMarketReserveAuction_Cannot_Update_Nft_While_Auction_In_Progress(); } canUpdateNft = true; } else { canUpdateNft = super._isAuthorizedScheduleUpdate(nftContract, tokenId); } } /** * @inheritdoc NFTMarketCore * @dev If an auction is found: * - If the auction is over, it will settle the auction and confirm the new seller won the auction. * - If the auction has not received a bid, it will invalidate the auction. * - If the auction is in progress, this will revert. */ function _transferFromEscrow( address nftContract, uint256 tokenId, address recipient, address authorizeSeller ) internal virtual override { uint256 auctionId = nftContractToTokenIdToAuctionId[nftContract][tokenId]; if (auctionId != 0) { ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId]; if (auction.endTime == 0) { // The auction has not received any bids yet so it may be invalided. if (authorizeSeller != address(0) && auction.seller != authorizeSeller) { // The account trying to transfer the NFT is not the current owner. revert NFTMarketReserveAuction_Not_Matching_Seller(auction.seller); } // Remove the auction. delete nftContractToTokenIdToAuctionId[nftContract][tokenId]; delete auctionIdToAuction[auctionId]; emit ReserveAuctionInvalidated(auctionId); } else { // If the auction has ended, the highest bidder will be the new owner // and if the auction is in progress, this will revert. // `authorizeSeller != address(0)` does not apply here since an unsettled auction must go // through this path to know who the authorized seller should be. if (auction.bidder != authorizeSeller) { revert NFTMarketReserveAuction_Not_Matching_Seller(auction.bidder); } // Finalization will revert if the auction has not yet ended. _finalizeReserveAuction({ auctionId: auctionId, keepInEscrow: true }); } // The seller authorization has been confirmed. authorizeSeller = address(0); } super._transferFromEscrow(nftContract, tokenId, recipient, authorizeSeller); } /** * @inheritdoc NFTMarketCore * @dev Checks if there is an auction for this NFT before allowing the transfer to continue. */ function _transferFromEscrowIfAvailable( address nftContract, uint256 tokenId, address originalSeller ) internal virtual override(NFTMarketCore, NFTMarketScheduling, NFTMarketWorlds) { if (nftContractToTokenIdToAuctionId[nftContract][tokenId] == 0) { // No auction was found super._transferFromEscrowIfAvailable(nftContract, tokenId, originalSeller); } } /** * @inheritdoc NFTMarketCore */ function _transferToEscrow(address nftContract, uint256 tokenId) internal virtual override { uint256 auctionId = nftContractToTokenIdToAuctionId[nftContract][tokenId]; if (auctionId == 0) { // NFT is not in auction super._transferToEscrow(nftContract, tokenId); return; } // Using storage saves gas since most of the data is not needed ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId]; address sender = _msgSender(); if (auction.endTime == 0) { // Reserve price set, confirm the seller is a match if (auction.seller != sender) { revert NFTMarketReserveAuction_Not_Matching_Seller(auction.seller); } } else { // Auction in progress, confirm the highest bidder is a match if (auction.bidder != sender) { revert NFTMarketReserveAuction_Not_Matching_Seller(auction.bidder); } // Finalize auction but leave NFT in escrow, reverts if the auction has not ended _finalizeReserveAuction({ auctionId: auctionId, keepInEscrow: true }); } } /** * @notice Returns the minimum amount a bidder must spend to participate in an auction. * Bids must be greater than or equal to this value or they will revert. * @param auctionId The id of the auction to check. * @return minimum The minimum amount for a bid to be accepted. */ function getMinBidAmount(uint256 auctionId) external view returns (uint256 minimum) { ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId]; if (auction.endTime == 0) { return auction.amount; } return _getMinIncrement(auction.amount); } /** * @notice Returns auction details for a given auctionId. * @param auctionId The id of the auction to lookup. */ function getReserveAuction(uint256 auctionId) external view returns (ReserveAuction memory auction) { ReserveAuctionStorage storage auctionStorage = auctionIdToAuction[auctionId]; uint256 duration = auctionStorage.duration; if (duration == 0) { duration = DEFAULT_DURATION; } auction = ReserveAuction( auctionStorage.nftContract, auctionStorage.tokenId, auctionStorage.seller, duration, EXTENSION_DURATION, auctionStorage.endTime, auctionStorage.bidder, auctionStorage.amount ); } /** * @notice Returns the auctionId for a given NFT, or 0 if no auction is found. * @dev If an auction is canceled, it will not be returned. However the auction may be over and pending finalization. * @param nftContract The address of the NFT contract. * @param tokenId The id of the NFT. * @return auctionId The id of the auction, or 0 if no auction is found. */ function getReserveAuctionIdFor(address nftContract, uint256 tokenId) external view returns (uint256 auctionId) { auctionId = nftContractToTokenIdToAuctionId[nftContract][tokenId]; } /** * @notice Returns the referrer for the current highest bid in the auction, or address(0). */ function getReserveAuctionBidReferrer(uint256 auctionId) external view returns (address payable referrer) { ReserveAuctionStorage storage auction = auctionIdToAuction[auctionId]; referrer = payable( address((uint160(auction.bidReferrerAddressSlot0) << 64) | uint160(auction.bidReferrerAddressSlot1)) ); } /** * @inheritdoc MarketSharedCore * @dev Returns the seller that has the given NFT in escrow for an auction, * or bubbles the call up for other considerations. */ function _getSellerOf( address nftContract, uint256 tokenId ) internal view virtual override returns (address payable seller) { seller = auctionIdToAuction[nftContractToTokenIdToAuctionId[nftContract][tokenId]].seller; if (seller == address(0)) { seller = super._getSellerOf(nftContract, tokenId); } } /** * @inheritdoc NFTMarketCore */ function _isInActiveAuction(address nftContract, uint256 tokenId) internal view override returns (bool) { uint256 auctionId = nftContractToTokenIdToAuctionId[nftContract][tokenId]; return auctionId != 0 && !auctionIdToAuction[auctionId].endTime.hasExpired(); } //////////////////////////////////////////////////////////////// // Inheritance Requirements // (no-ops to avoid compile errors) //////////////////////////////////////////////////////////////// /** * @inheritdoc NFTMarketCore */ function _beforeAuctionStarted( address nftContract, uint256 tokenId ) internal virtual override(NFTMarketCore, NFTMarketScheduling) { super._beforeAuctionStarted(nftContract, tokenId); } /** * @inheritdoc MarketFees */ function _distributeFunds( DistributeFundsParams memory params ) internal virtual override(MarketFees, NFTMarketScheduling) returns (uint256 totalFees, uint256 creatorRev, uint256 sellerRev) { (totalFees, creatorRev, sellerRev) = super._distributeFunds(params); } /** * @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 "../../libraries/TimeLibrary.sol"; import "../../interfaces/internal/routes/INFTMarketScheduling.sol"; import "../../interfaces/internal/INFTMarketGetters.sol"; import "../shared/Constants.sol"; import "../shared/MarketFees.sol"; import "./NFTMarketCore.sol"; // Errors when configuring a schedule error NFTMarketScheduling_Sale_Starts_At_Already_Set(); error NFTMarketScheduling_Sale_Starts_At_Is_In_Past(); error NFTMarketScheduling_Sale_Starts_At_Too_Far_In_The_Future(uint256 maxStartsAt); // Errors when validating a schedule error NFTMarketScheduling_Sale_Starts_At_Is_In_Future(); /** * @title Allows listed NFTs to schedule a sale starts at time. * @dev This supports both Auctions and BuyNow. * @author HardlyDifficult & smhutch */ abstract contract NFTMarketScheduling is INFTMarketGetters, INFTMarketScheduling, ContextUpgradeable, NFTMarketCore, MarketFees { using TimeLibrary for uint256; /// @notice Stores the saleStartsAt time for listed NFTs mapping(address => mapping(uint256 => uint256)) private $nftContractToTokenIdToSaleStartsAt; /** * @notice emitted when an a saleStartsAt time is changed for an NFT. * @param nftContract The address of the NFT contract. * @param tokenId The id of the NFT. * @param operator The address that triggered this change. * @param saleStartsAt The time at which the NFT will be available to buy or place bids on. * When zero, this represents that the NFT is unscheduled. * When above zero, this value represents the time in seconds since the Unix epoch. */ event SetSaleStartsAt( address indexed nftContract, uint256 indexed tokenId, address indexed operator, uint256 saleStartsAt ); //////////////////////////////////////////////////////////////// // Configuration //////////////////////////////////////////////////////////////// /** * @notice sets the saleStartsAt time for a listed NFT. * @param nftContract The address of the NFT contract. * @param tokenId The id of the NFT. * @param saleStartsAt The time at which the NFT will be available to buy or place bids on. * When zero, the NFT has no saleStartsAt and can be purchased anytime. * When above zero, this value represents the time in seconds since the Unix epoch. */ function setSaleStartsAt(address nftContract, uint256 tokenId, uint256 saleStartsAt) external { // Check if it's already set first since this may be a common occurrence. if ($nftContractToTokenIdToSaleStartsAt[nftContract][tokenId] == saleStartsAt) { revert NFTMarketScheduling_Sale_Starts_At_Already_Set(); } _authorizeScheduleUpdate(nftContract, tokenId); if (saleStartsAt != 0) { if (saleStartsAt.hasExpired()) { revert NFTMarketScheduling_Sale_Starts_At_Is_In_Past(); } if (saleStartsAt > block.timestamp + MAX_SCHEDULED_TIME_IN_THE_FUTURE) { // Prevent arbitrarily large values from accidentally being set. revert NFTMarketScheduling_Sale_Starts_At_Too_Far_In_The_Future( block.timestamp + MAX_SCHEDULED_TIME_IN_THE_FUTURE ); } } $nftContractToTokenIdToSaleStartsAt[nftContract][tokenId] = saleStartsAt; emit SetSaleStartsAt({ nftContract: nftContract, tokenId: tokenId, operator: _msgSender(), saleStartsAt: saleStartsAt }); } /** * @notice Returns the saleStartsAt time for a listed NFT. * @param nftContract The address of the NFT contract. * @param tokenId The id of the NFT. * @return saleStartsAt The time at which the NFT will be available to buy or place bids on. * 0 if there is no schedule set and the NFT may be purchased anytime (or is not yet listed). */ function getSaleStartsAt(address nftContract, uint256 tokenId) external view returns (uint256 saleStartsAt) { saleStartsAt = $nftContractToTokenIdToSaleStartsAt[nftContract][tokenId]; } //////////////////////////////////////////////////////////////// // Validation //////////////////////////////////////////////////////////////// function _validateSaleStartsAtHasBeenReached(address nftContract, uint256 tokenId) internal view { if (!$nftContractToTokenIdToSaleStartsAt[nftContract][tokenId].hasBeenReached()) { revert NFTMarketScheduling_Sale_Starts_At_Is_In_Future(); } } /** * @inheritdoc NFTMarketCore * @dev Validates the saleStartsAt time for the NFT when the first bid is placed. */ function _beforeAuctionStarted(address nftContract, uint256 tokenId) internal virtual override { _validateSaleStartsAtHasBeenReached(nftContract, tokenId); super._beforeAuctionStarted(nftContract, tokenId); } //////////////////////////////////////////////////////////////// // Cleanup //////////////////////////////////////////////////////////////// function _clearScheduleIfSet(address nftContract, uint256 tokenId) private { if ($nftContractToTokenIdToSaleStartsAt[nftContract][tokenId] != 0) { // Clear the saleStartsAt time so that it does not apply to the next listing delete $nftContractToTokenIdToSaleStartsAt[nftContract][tokenId]; emit SetSaleStartsAt({ nftContract: nftContract, tokenId: tokenId, operator: _msgSender(), saleStartsAt: 0 }); } } /** * @dev When a sale occurs, clear the schedule if one was set. */ function _distributeFunds( DistributeFundsParams memory params ) internal virtual override returns (uint256 totalFees, uint256 creatorRev, uint256 sellerRev) { _clearScheduleIfSet(params.nftContract, params.firstTokenId); (totalFees, creatorRev, sellerRev) = super._distributeFunds(params); } /** * @dev Called when a listing is canceled. This mixin appears before the market tools in inheritance order, so when * this is called we have already confirmed that the NFT is no longer listed and will indeed leave escrow. */ function _transferFromEscrowIfAvailable( address nftContract, uint256 tokenId, address originalSeller ) internal virtual override { _clearScheduleIfSet(nftContract, tokenId); super._transferFromEscrowIfAvailable(nftContract, tokenId, originalSeller); } /** * @notice This empty reserved space is put in place to allow future versions to add new variables without shifting * down storage in the inheritance chain. See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps * @dev This mixin uses 250 slots in total. */ uint256[249] private __gap; }
// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; import { IWorldsSoldByNft } from "../../interfaces/internal/IWorldsSoldByNft.sol"; import { NFTMarketCore } from "./NFTMarketCore.sol"; import { NFTMarketWorldsAPIs } from "./NFTMarketWorldsAPIs.sol"; /** * @title Enables a curation surface for sellers to exhibit their NFTs. * @author HardlyDifficult * @dev [DEPRECATED] This mixin is being deprecated in favor of the Worlds NFT contract. */ abstract contract NFTMarketWorlds is NFTMarketWorldsAPIs, NFTMarketCore { // Was: // struct Exhibition { // address payable curator; // uint16 takeRateInBasisPoints; // string name; // } // uint32 private $latestExhibitionId; // bool private $worldMigrationCompleted; // mapping(uint256 exhibitionId => Exhibition exhibitionDetails) private $idToExhibition; // mapping(uint256 exhibitionId => mapping(address seller => bool isAllowed)) private $exhibitionIdToSellerToIsAllowed // mapping(address nftContract => mapping(uint256 tokenId => uint256 exhibitionId)) // private $nftContractToTokenIdToExhibitionId; uint256[4] private __gap_was_exhibition; /** * @notice Returns World details if this NFT was assigned to one, and clears the assignment. * @return worldPaymentAddress The address to send the payment to, or address(0) if n/a. * @return takeRateInBasisPoints The rate of the sale which goes to the curator, or 0 if n/a. */ function _getWorldForPayment( address seller, address nftContract, uint256 tokenId, address buyer, uint256 salePrice ) internal returns (address payable worldPaymentAddress, uint16 takeRateInBasisPoints) { uint256 worldId; (worldId, worldPaymentAddress, takeRateInBasisPoints) = IWorldsSoldByNft(worlds).soldInWorldByNft( seller, nftContract, tokenId, buyer, salePrice ); if (worldId != 0) { // Clear the World association on sale _removeFromWorldByNft(seller, nftContract, tokenId); } } /** * @inheritdoc NFTMarketCore * @dev Removes the NFT from the World if it's listed. */ function _transferFromEscrowIfAvailable( address nftContract, uint256 tokenId, address originalSeller ) internal virtual override { // Clear the World association when removed from escrow. _removeFromWorldByNftIfListed(originalSeller, nftContract, tokenId); super._transferFromEscrowIfAvailable(nftContract, tokenId, originalSeller); } /** * @notice This empty reserved space is put in place to allow future versions to add new variables without shifting * down storage in the inheritance chain. See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps * @dev This file uses a total of 250 slots. */ uint256[246] private __gap; }
// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; import { IWorldsSharedMarket } from "../../interfaces/internal/IWorldsSharedMarket.sol"; import { RouteCallLibrary } from "../../libraries/RouteCallLibrary.sol"; import { WorldsNftNode } from "../shared/WorldsNftNode.sol"; /** * @title Routes calls to the Worlds contract, specifying to use the current _msgSender() as the caller. * @author HardlyDifficult */ abstract contract NFTMarketWorldsAPIs is WorldsNftNode { using RouteCallLibrary for address; /** * @param seller This is the seller which had originally listed the NFT, and may not be the current msg.sender. */ function _removeFromWorldByNft(address seller, address nftContract, uint256 nftTokenId) internal { seller.routeCallTo(worlds, abi.encodeCall(IWorldsSharedMarket.removeFromWorldByNft, (nftContract, nftTokenId))); } /** * @param seller This is the seller which had originally listed the NFT, and may not be the current msg.sender. */ function _removeFromWorldByNftIfListed(address seller, address nftContract, uint256 nftTokenId) internal { (uint256 worldId, ) = IWorldsSharedMarket(worlds).getAssociationByNft(nftContract, nftTokenId, seller); if (worldId != 0) { seller.routeCallTo(worlds, abi.encodeCall(IWorldsSharedMarket.removeFromWorldByNft, (nftContract, nftTokenId))); } } }
// 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; /// @dev The `role` type used to validate drop collections have granted this market access to mint. bytes32 constant MINTER_ROLE = keccak256("MINTER_ROLE"); //////////////////////////////////////////////////////////////// // Royalties & Take Rates //////////////////////////////////////////////////////////////// /// @dev The max take rate a World can have. uint256 constant MAX_WORLD_TAKE_RATE = 5_000; /// @dev Cap the number of royalty recipients. /// A cap is required to ensure gas costs are not too high when a sale is settled. uint256 constant MAX_ROYALTY_RECIPIENTS = 5; /// @dev Default royalty cut paid out on secondary sales. /// Set to 10% of the secondary sale. uint96 constant ROYALTY_IN_BASIS_POINTS = 1_000; /// @dev Reward paid to referrers when a sale is made. /// Set to 20% of the protocol fee. uint96 constant BUY_REFERRER_IN_BASIS_POINTS = 2000; /// @dev 10%, expressed as a denominator for more efficient calculations. uint256 constant ROYALTY_RATIO = BASIS_POINTS / ROYALTY_IN_BASIS_POINTS; /// @dev 20%, expressed as a denominator for more efficient calculations. uint256 constant BUY_REFERRER_RATIO = BASIS_POINTS / BUY_REFERRER_IN_BASIS_POINTS; //////////////////////////////////////////////////////////////// // Gas Limits //////////////////////////////////////////////////////////////// /// @dev The gas limit used when making external read-only calls. /// This helps to ensure that external calls does not prevent the market from executing. uint256 constant READ_ONLY_GAS_LIMIT = 40_000; /// @dev The gas limit to send ETH to multiple recipients, enough for a 5-way split. uint256 constant SEND_VALUE_GAS_LIMIT_MULTIPLE_RECIPIENTS = 210_000; /// @dev The gas limit to send ETH to a single recipient, enough for a contract with a simple receiver. uint256 constant SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT = 20_000; //////////////////////////////////////////////////////////////// // Collection Type Names //////////////////////////////////////////////////////////////// /// @dev The NFT collection type. string constant NFT_COLLECTION_TYPE = "NFT Collection"; /// @dev The NFT drop collection type. string constant NFT_DROP_COLLECTION_TYPE = "NFT Drop Collection"; /// @dev The NFT timed edition collection type. string constant NFT_TIMED_EDITION_COLLECTION_TYPE = "NFT Timed Edition Collection"; /// @dev The NFT limited edition collection type. string constant NFT_LIMITED_EDITION_COLLECTION_TYPE = "NFT Limited Edition Collection"; /// @dev The Multi-Token (ERC-1155) collection type. string constant MULTI_TOKEN_COLLECTION_TYPE = "Multi-Token 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 The fixed fee charged for each NFT minted. uint256 constant MINT_FEE_IN_WEI = 0.0008 ether; /// @dev Default for how long an auction lasts for once the first bid has been received. uint256 constant DEFAULT_DURATION = 1 days; /// @dev The window for auction extensions, any bid placed in the final 5 minutes /// of an auction will reset the time remaining to 5 minutes. uint256 constant EXTENSION_DURATION = 5 minutes; /// @dev Caps the max duration that may be configured for an auction. uint256 constant MAX_DURATION = 7 days;
// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; import { IFethMarket } from "../../interfaces/internal/IFethMarket.sol"; import { AddressUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; /** * @title A mixin for interacting with the FETH contract. * @author batu-inal & HardlyDifficult */ abstract contract FETHNode { using AddressUpgradeable for address; using AddressUpgradeable for address payable; /// @notice The FETH ERC-20 token for managing escrow and lockup. IFethMarket internal immutable feth; error FETHNode_FETH_Address_Is_Not_A_Contract(); error FETHNode_Only_FETH_Can_Transfer_ETH(); error FethNode_Too_Much_Value_Provided(uint256 expectedValueAmount); 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. Otherwise it will revert if * the msg.value is greater than totalAmount. */ function _tryUseFETHBalance(address payable fromAccount, 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(fromAccount, totalAmount - msg.value); } } else if (totalAmount < msg.value) { if (shouldRefundSurplus) { // Return any surplus ETH to the user. unchecked { // The if above ensures this will not underflow fromAccount.sendValue(msg.value - totalAmount); } } else { revert FethNode_Too_Much_Value_Provided(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; abstract contract FoundationTreasuryNodeInterface { /** * @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 virtual returns (address payable treasuryAddress); }
// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; import { AddressUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; import { IAdminRole } from "../../interfaces/internal/roles/IAdminRole.sol"; import { IOperatorRole } from "../../interfaces/internal/roles/IOperatorRole.sol"; import { FoundationTreasuryNodeInterface } from "./FoundationTreasuryNodeInterface.sol"; /** * @title Stores a reference to Foundation's treasury contract for other mixins to leverage. * @notice The treasury collects fees and defines admin/operator roles. * @author batu-inal & HardlyDifficult */ abstract contract FoundationTreasuryNodeV1 is FoundationTreasuryNodeInterface { 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; error FoundationTreasuryNode_Address_Is_Not_A_Contract(); error FoundationTreasuryNode_Caller_Not_Admin(); error FoundationTreasuryNode_Caller_Not_Operator(); /// @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; } /// @inheritdoc FoundationTreasuryNodeInterface function getFoundationTreasury() public view override returns (address payable treasuryAddress) { treasuryAddress = treasury; } /** * @notice This empty reserved space is put in place to allow future versions to add new variables without shifting * down storage in the inheritance chain. See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps * @dev This mixin uses a total of 2,001 slots. */ uint256[2_000] private __gap; }
// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; /** * @title Reserving space for an old variable that was used in some upgradeable proxy contracts. * @author HardlyDifficult */ abstract contract GapSendValueWithFallbackWithdrawV1 { /// @dev Removing old unused variables in an upgrade safe way. uint256 private __gap_was_pendingWithdrawals; }
// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; import { ContextUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; import { AddressUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; import { IMarketUtils } from "../../interfaces/internal/IMarketUtils.sol"; import "./Constants.sol"; import "./MarketStructs.sol"; import { MarketSharedCore } from "./MarketSharedCore.sol"; import { SendValueWithFallbackWithdraw } from "./SendValueWithFallbackWithdraw.sol"; import { FoundationTreasuryNodeInterface } from "./FoundationTreasuryNodeInterface.sol"; error NFTMarketFees_Market_Utils_Is_Not_A_Contract(); error NFTMarketFees_Invalid_Protocol_Fee(); /** * @title A mixin to distribute funds when an NFT is sold. * @author batu-inal & HardlyDifficult */ abstract contract MarketFees is FoundationTreasuryNodeInterface, ContextUpgradeable, MarketSharedCore, SendValueWithFallbackWithdraw { using AddressUpgradeable for address; using AddressUpgradeable for address payable; struct DistributeFundsParams { address nftContract; uint256 firstTokenId; uint256 nftCount; address nftRecipientIfKnown; address payable seller; uint256 price; address payable buyReferrer; address payable sellerReferrerPaymentAddress; uint16 sellerReferrerTakeRateInBasisPoints; uint256 fixedProtocolFeeInWei; } /** * @dev Removing old unused variables in an upgrade safe way. Was: * uint256 private _primaryFoundationFeeBasisPoints; * uint256 private _secondaryFoundationFeeBasisPoints; * uint256 private _secondaryCreatorFeeBasisPoints; * mapping(address => mapping(uint256 => bool)) private _nftContractToTokenIdToFirstSaleCompleted; */ uint256[4] private __gap_was_fees; /// @notice True for the Drop market which only performs primary sales. False if primary & secondary are supported. bool private immutable assumePrimarySale; /// @notice The fee collected by Foundation for sales facilitated by this market contract. uint256 private immutable defaultProtocolFeeInBasisPoints; /// @notice Reference to our MarketUtils contract. IMarketUtils private immutable marketUtils; /** * @notice Emitted when an NFT sold with a referrer. * @param nftContract The address of the NFT contract. * @param tokenId The id of the NFT. * @param buyReferrer The account which received the buy referral incentive. * @param buyReferrerFee The portion of the protocol fee collected by the buy referrer. * @param buyReferrerSellerFee The portion of the owner revenue collected by the buy referrer (not implemented). */ event BuyReferralPaid( address indexed nftContract, uint256 indexed tokenId, address buyReferrer, uint256 buyReferrerFee, uint256 buyReferrerSellerFee ); /** * @notice Emitted when an NFT is sold when associated with a sell referrer. * @param nftContract The address of the NFT contract. * @param tokenId The id of the NFT. * @param sellerReferrer The account which received the sell referral incentive. * @param sellerReferrerFee The portion of the seller revenue collected by the sell referrer. */ event SellerReferralPaid( address indexed nftContract, uint256 indexed tokenId, address sellerReferrer, uint256 sellerReferrerFee ); /** * @notice Emitted when a fixed protocol fee is paid as part of an NFT purchase. * @param nftContract The address of the NFT contract. * @param firstTokenId The id of the NFT, or the first/lowest id if it applies to multiple NFTs. * @param nftRecipient The account which acquired an NFT from this transaction (may not be the same as the msg.sender) * @param fixedProtocolFeeInWei The total fee collected by the protocol for this sale in wei. * @param nftCount The number of NFTs in this sale. * @dev Some of this amount may have been shared with a referrer, which would be emitted in via the BuyReferrerPaid * event. */ event FixedProtocolFeePaid( address indexed nftContract, uint256 indexed firstTokenId, address indexed nftRecipient, uint256 fixedProtocolFeeInWei, uint256 nftCount ); /** * @notice Sets the immutable variables for this contract. * @param _defaultProtocolFeeInBasisPoints The default protocol fee to use for this market. * @param marketUtilsAddress The address to use for our MarketUtils contract. * @param _assumePrimarySale True for the Drop market which only performs primary sales. * False if primary & secondary are supported. */ constructor(uint16 _defaultProtocolFeeInBasisPoints, address marketUtilsAddress, bool _assumePrimarySale) { if (_defaultProtocolFeeInBasisPoints + BASIS_POINTS / ROYALTY_RATIO >= BASIS_POINTS - MAX_WORLD_TAKE_RATE) { // The protocol fee must leave room for the creator royalties and the max World take rate. // If the protocol fee is invalid, revert. revert NFTMarketFees_Invalid_Protocol_Fee(); } if (!marketUtilsAddress.isContract()) { revert NFTMarketFees_Market_Utils_Is_Not_A_Contract(); } assumePrimarySale = _assumePrimarySale; defaultProtocolFeeInBasisPoints = _defaultProtocolFeeInBasisPoints; marketUtils = IMarketUtils(marketUtilsAddress); } /** * @notice Distributes funds to foundation, creator recipients, and NFT owner after a sale. * @return totalFees The total fees collected by the protocol and referrals, excluding any fixed fee charged. * @dev `virtual` allows other mixins to be notified anytime a sale occurs. */ function _distributeFunds( DistributeFundsParams memory params ) internal virtual returns (uint256 totalFees, uint256 creatorRev, uint256 sellerRev) { address payable[] memory creatorRecipients; uint256[] memory creatorShares; uint256 buyReferrerFee; uint256 sellerReferrerFee; (totalFees, creatorRecipients, creatorShares, sellerRev, buyReferrerFee, sellerReferrerFee) = getFees( params.nftContract, params.firstTokenId, params.seller, params.price, params.buyReferrer, params.sellerReferrerTakeRateInBasisPoints ); // The `getFees` breakdown doesn't yet account for the fixed protocol fee, so we add that in separately. if (params.fixedProtocolFeeInWei != 0) { totalFees += params.fixedProtocolFeeInWei; // The buy referrer is rewarded a portion of this fixed fee as well. if (params.buyReferrer != address(0)) { buyReferrerFee += params.fixedProtocolFeeInWei / BUY_REFERRER_RATIO; totalFees -= buyReferrerFee; } emit FixedProtocolFeePaid( params.nftContract, params.firstTokenId, params.nftRecipientIfKnown, params.fixedProtocolFeeInWei, params.nftCount ); } // 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(params.seller, sellerRev, SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT); // Pay the protocol fee if (totalFees != 0) { getFoundationTreasury().sendValue(totalFees); } // Pay the buy referrer fee if (buyReferrerFee != 0) { _sendValueWithFallbackWithdraw(params.buyReferrer, buyReferrerFee, SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT); emit BuyReferralPaid({ nftContract: params.nftContract, tokenId: params.firstTokenId, buyReferrer: params.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 sale price. totalFees += buyReferrerFee; } } // Remove the fixed fee from the `totalFees` being returned so that all 3 return fields sum to the total sale price. if (params.fixedProtocolFeeInWei != 0) { unchecked { totalFees -= params.fixedProtocolFeeInWei; } } if (params.sellerReferrerPaymentAddress != address(0)) { if (sellerReferrerFee != 0) { // Add the seller referrer fee back to revenue so that all 3 return fields sum to the total sale price. 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( params.sellerReferrerPaymentAddress, sellerReferrerFee, SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT ); } emit SellerReferralPaid( params.nftContract, params.firstTokenId, params.sellerReferrerPaymentAddress, sellerReferrerFee ); } } /// @dev Assumes the caller ensures that fixedProtocolFeeInWei > 0. function _distributeFixedProtocolFee( address nftContract, uint256 firstTokenId, uint256 nftCount, address buyer, uint256 fixedProtocolFeeInWei ) internal { getFoundationTreasury().sendValue(fixedProtocolFeeInWei); emit FixedProtocolFeePaid(nftContract, firstTokenId, buyer, fixedProtocolFeeInWei, nftCount); } /** * @notice Calculates how funds should be distributed for the given sale details. * @dev When the NFT is being sold by the `tokenCreator`, all the seller revenue will * be split with the royalty recipients defined for that NFT. */ function getFees( address nftContract, uint256 tokenId, address payable seller, uint256 price, address payable buyReferrer, uint16 sellerReferrerTakeRateInBasisPoints ) public view returns ( uint256 protocolFeeAmount, address payable[] memory creatorRecipients, uint256[] memory creatorShares, uint256 sellerRev, uint256 buyReferrerFee, uint256 sellerReferrerFee ) { MarketTransactionOptions memory options = MarketTransactionOptions({ // Market info marketTakeRateInBasisPoints: defaultProtocolFeeInBasisPoints, assumePrimarySale: assumePrimarySale, // Sale info nftContract: nftContract, tokenId: tokenId, price: price, seller: seller, // Referrals buyReferrer: buyReferrer, sellerReferrerTakeRateInBasisPoints: sellerReferrerTakeRateInBasisPoints, // Transaction info sender: _msgSender() }); (protocolFeeAmount, creatorRecipients, creatorShares, sellerRev, buyReferrerFee, sellerReferrerFee) = marketUtils .getTransactionBreakdown(options); } /** * @notice Returns how funds will be distributed for a sale at the given price point. * @param nftContract The address of the NFT contract. * @param tokenId The id of the NFT. * @param price The sale price to calculate the fees for. * @return totalFees How much will be sent to the Foundation treasury and/or referrals. * @return creatorRev How much will be sent across all the `creatorRecipients` defined. * @return creatorRecipients The addresses of the recipients to receive a portion of the creator fee. * @return creatorShares The percentage of the creator fee to be distributed to each `creatorRecipient`. * If there is only one `creatorRecipient`, this may be an empty array. * Otherwise `creatorShares.length` == `creatorRecipients.length`. * @return sellerRev How much will be sent to the owner/seller of the NFT. * If the NFT is being sold by the creator, this may be 0 and the full revenue will appear as `creatorRev`. * @return seller The address of the owner of the NFT. * If `sellerRev` is 0, this may be `address(0)`. * @dev Currently in use by the FNDMiddleware `getFees` call (now deprecated). */ function getFeesAndRecipients( address nftContract, uint256 tokenId, uint256 price ) external view returns ( uint256 totalFees, uint256 creatorRev, address payable[] memory creatorRecipients, uint256[] memory creatorShares, uint256 sellerRev, address payable seller ) { seller = _getSellerOrOwnerOf(nftContract, tokenId); (totalFees, creatorRecipients, creatorShares, sellerRev, , ) = getFees({ nftContract: nftContract, tokenId: tokenId, seller: seller, price: price, // Notice: Setting this value is a breaking change for the FNDMiddleware contract. // Will be wired in an upcoming release to communicate the buy referral information. buyReferrer: payable(0), sellerReferrerTakeRateInBasisPoints: 0 }); // Sum the total creator rev from shares unchecked { for (uint256 i = 0; i < creatorShares.length; ++i) { creatorRev += creatorShares[i]; } } } /** * @notice returns the address of the MarketUtils contract. */ function getMarketUtilsAddress() external view returns (address marketUtilsAddress) { marketUtilsAddress = address(marketUtils); } /** * @notice This empty reserved space is put in place to allow future versions to add new variables without shifting * down storage in the inheritance chain. See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps * @dev This mixins uses 504 slots in total. */ uint256[500] private __gap; }
// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; import "./FETHNode.sol"; /** * @title A place for common modifiers and functions used by various market mixins, if any. * @dev This also leaves a gap which can be used to add a new mixin to the top of the inheritance tree. * @author batu-inal & HardlyDifficult */ abstract contract MarketSharedCore is FETHNode { /** * @notice Checks who the seller for an NFT is if listed in this market. * @param nftContract The address of the NFT contract. * @param tokenId The id of the NFT. * @return seller The seller which listed this NFT for sale, or address(0) if not listed. */ function getSellerOf(address nftContract, uint256 tokenId) external view returns (address payable seller) { seller = _getSellerOf(nftContract, tokenId); } /** * @notice Checks who the seller for an NFT is if listed in this market. */ function _getSellerOf(address nftContract, uint256 tokenId) internal view virtual returns (address payable seller) { // Returns address(0) by default. } /** * @notice Checks who the seller for an NFT is if listed in this market or returns the current owner. */ function _getSellerOrOwnerOf( address nftContract, uint256 tokenId ) internal view virtual returns (address payable sellerOrOwner); /** * @notice This empty reserved space is put in place to allow future versions to add new variables without shifting * down storage in the inheritance chain. See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[450] private __gap; }
// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; /// @notice Details about a marketplace sale. struct MarketTransactionOptions { //////////////////////////////////////////////////////////////// // Market config //////////////////////////////////////////////////////////////// /// @notice Percentage of the transaction to go the the market, expressed in basis points. uint256 marketTakeRateInBasisPoints; /// @notice set to true when the token is being sold by it's creator bool assumePrimarySale; //////////////////////////////////////////////////////////////// // Sale info //////////////////////////////////////////////////////////////// /// @notice The contract address of the nft address nftContract; /// @notice The token id of the nft. uint256 tokenId; /// @notice price at which the token is being sold uint256 price; /// @notice address of the account that is selling the token address payable seller; //////////////////////////////////////////////////////////////// // Referrals //////////////////////////////////////////////////////////////// /// @notice Address of the account that referred the buyer. address payable buyReferrer; /// @notice Percentage of the transaction to go the the account which referred the seller, expressed in basis points. uint16 sellerReferrerTakeRateInBasisPoints; //////////////////////////////////////////////////////////////// // Transaction info //////////////////////////////////////////////////////////////// /// @notice The msg.sender which executed the purchase transaction. address sender; } /// @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; }
// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; import "../../libraries/RouteCallLibrary.sol"; error RouterContextSingle_Address_Is_Not_A_Contract(); /** * @title Enables a trusted contract to override the usual msg.sender address. * @author HardlyDifficult */ abstract contract RouterContextSingle is ContextUpgradeable { using AddressUpgradeable for address; address private immutable approvedRouter; constructor(address router) { if (!router.isContract()) { revert RouterContextSingle_Address_Is_Not_A_Contract(); } approvedRouter = router; } /** * @notice Returns the contract which is able to override the msg.sender address. * @return router The address of the trusted router. */ function getApprovedRouterAddress() external view returns (address router) { router = approvedRouter; } /** * @notice Gets the sender of the transaction to use, overriding the usual msg.sender if the caller is a trusted * router. * @dev If the msg.sender is a trusted router contract, then the last 20 bytes of the calldata represents the * authorized sender to use. * If this is used for a call that was not routed with `routeCallTo`, the address returned will be incorrect (and * may be address(0)). */ function _msgSender() internal view virtual override returns (address sender) { sender = super._msgSender(); if (sender == approvedRouter) { sender = RouteCallLibrary.extractAppendedSenderAddress(); } } }
// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; import { AddressUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; import { FETHNode } from "./FETHNode.sol"; import { FoundationTreasuryNodeInterface } from "./FoundationTreasuryNodeInterface.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 FoundationTreasuryNodeInterface, FETHNode { using AddressUpgradeable for address payable; /** * @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; }
// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; error WorldsNftNode_Worlds_NFT_Is_Not_A_Contract(); /** * @title Stores a reference to the Worlds NFT contract for contracts to leverage. * @author HardlyDifficult */ abstract contract WorldsNftNode { using AddressUpgradeable for address; address internal immutable worlds; constructor(address worldsNft) { if (!worldsNft.isContract()) { revert WorldsNftNode_Worlds_NFT_Is_Not_A_Contract(); } worlds = worldsNft; } /** * @notice Returns the address of the Worlds NFT contract. */ function getWorldsNftAddress() external view returns (address worldsNft) { worldsNft = worlds; } // This mixin uses 0 slots. }
{ "optimizer": { "enabled": true, "runs": 1337000 }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "evmVersion": "paris", "metadata": { "useLiteralContent": true }, "libraries": {} }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"inputs":[{"internalType":"address payable","name":"treasury","type":"address"},{"internalType":"address","name":"feth","type":"address"},{"internalType":"address","name":"router","type":"address"},{"internalType":"address","name":"marketUtils","type":"address"},{"internalType":"address","name":"worldsNft","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"FETHNode_FETH_Address_Is_Not_A_Contract","type":"error"},{"inputs":[],"name":"FETHNode_Only_FETH_Can_Transfer_ETH","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedValueAmount","type":"uint256"}],"name":"FethNode_Too_Much_Value_Provided","type":"error"},{"inputs":[],"name":"FoundationTreasuryNode_Address_Is_Not_A_Contract","type":"error"},{"inputs":[],"name":"FoundationTreasuryNode_Caller_Not_Admin","type":"error"},{"inputs":[],"name":"FoundationTreasuryNode_Caller_Not_Operator","type":"error"},{"inputs":[],"name":"NFTMarketAuction_Already_Initialized","type":"error"},{"inputs":[{"internalType":"uint256","name":"buyPrice","type":"uint256"}],"name":"NFTMarketBuyPrice_Cannot_Buy_At_Lower_Price","type":"error"},{"inputs":[],"name":"NFTMarketBuyPrice_Cannot_Buy_Unset_Price","type":"error"},{"inputs":[],"name":"NFTMarketBuyPrice_Cannot_Cancel_Unset_Price","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"NFTMarketBuyPrice_Only_Owner_Can_Cancel_Price","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"NFTMarketBuyPrice_Only_Owner_Can_Set_Price","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"NFTMarketBuyPrice_Only_Owner_Can_Update_Nft","type":"error"},{"inputs":[],"name":"NFTMarketBuyPrice_Price_Already_Set","type":"error"},{"inputs":[],"name":"NFTMarketBuyPrice_Price_Too_High","type":"error"},{"inputs":[{"internalType":"address","name":"seller","type":"address"}],"name":"NFTMarketBuyPrice_Seller_Mismatch","type":"error"},{"inputs":[],"name":"NFTMarketCore_Can_Not_Update_Unlisted_Nft","type":"error"},{"inputs":[],"name":"NFTMarketCore_Seller_Not_Found","type":"error"},{"inputs":[],"name":"NFTMarketFees_Invalid_Protocol_Fee","type":"error"},{"inputs":[],"name":"NFTMarketFees_Market_Utils_Is_Not_A_Contract","type":"error"},{"inputs":[],"name":"NFTMarketOffer_Cannot_Be_Made_While_In_Auction","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentOfferAmount","type":"uint256"}],"name":"NFTMarketOffer_Offer_Below_Min_Amount","type":"error"},{"inputs":[{"internalType":"uint256","name":"expiry","type":"uint256"}],"name":"NFTMarketOffer_Offer_Expired","type":"error"},{"inputs":[{"internalType":"address","name":"currentOfferFrom","type":"address"}],"name":"NFTMarketOffer_Offer_From_Does_Not_Match","type":"error"},{"inputs":[{"internalType":"uint256","name":"minOfferAmount","type":"uint256"}],"name":"NFTMarketOffer_Offer_Must_Be_At_Least_Min_Amount","type":"error"},{"inputs":[{"internalType":"uint256","name":"auctionId","type":"uint256"}],"name":"NFTMarketReserveAuction_Already_Listed","type":"error"},{"inputs":[{"internalType":"uint256","name":"minAmount","type":"uint256"}],"name":"NFTMarketReserveAuction_Bid_Must_Be_At_Least_Min_Amount","type":"error"},{"inputs":[{"internalType":"uint256","name":"reservePrice","type":"uint256"}],"name":"NFTMarketReserveAuction_Cannot_Bid_Lower_Than_Reserve_Price","type":"error"},{"inputs":[{"internalType":"uint256","name":"endTime","type":"uint256"}],"name":"NFTMarketReserveAuction_Cannot_Bid_On_Ended_Auction","type":"error"},{"inputs":[],"name":"NFTMarketReserveAuction_Cannot_Bid_On_Nonexistent_Auction","type":"error"},{"inputs":[],"name":"NFTMarketReserveAuction_Cannot_Finalize_Already_Settled_Auction","type":"error"},{"inputs":[{"internalType":"uint256","name":"endTime","type":"uint256"}],"name":"NFTMarketReserveAuction_Cannot_Finalize_Auction_In_Progress","type":"error"},{"inputs":[],"name":"NFTMarketReserveAuction_Cannot_Rebid_Over_Outstanding_Bid","type":"error"},{"inputs":[],"name":"NFTMarketReserveAuction_Cannot_Update_Auction_In_Progress","type":"error"},{"inputs":[],"name":"NFTMarketReserveAuction_Cannot_Update_Nft_While_Auction_In_Progress","type":"error"},{"inputs":[{"internalType":"address","name":"seller","type":"address"}],"name":"NFTMarketReserveAuction_Not_Matching_Seller","type":"error"},{"inputs":[],"name":"NFTMarketReserveAuction_Price_Already_Set","type":"error"},{"inputs":[],"name":"NFTMarketScheduling_Sale_Starts_At_Already_Set","type":"error"},{"inputs":[],"name":"NFTMarketScheduling_Sale_Starts_At_Is_In_Future","type":"error"},{"inputs":[],"name":"NFTMarketScheduling_Sale_Starts_At_Is_In_Past","type":"error"},{"inputs":[{"internalType":"uint256","name":"maxStartsAt","type":"uint256"}],"name":"NFTMarketScheduling_Sale_Starts_At_Too_Far_In_The_Future","type":"error"},{"inputs":[{"internalType":"uint256","name":"maxDuration","type":"uint256"}],"name":"ReserveAuctionLibrary_Exceeds_Max_Duration","type":"error"},{"inputs":[{"internalType":"uint256","name":"extensionDuration","type":"uint256"}],"name":"ReserveAuctionLibrary_Less_Than_Extension_Duration","type":"error"},{"inputs":[],"name":"ReserveAuctionLibrary_Must_Set_Non_Zero_Reserve_Price","type":"error"},{"inputs":[{"internalType":"address","name":"seller","type":"address"}],"name":"ReserveAuctionLibrary_Only_Callable_By_Seller","type":"error"},{"inputs":[],"name":"RouteCallLibrary_Call_Failed_Without_Revert_Reason","type":"error"},{"inputs":[],"name":"RouterContextSingle_Address_Is_Not_A_Contract","type":"error"},{"inputs":[],"name":"WorldsNftNode_Worlds_NFT_Is_Not_A_Contract","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"seller","type":"address"},{"indexed":false,"internalType":"address","name":"buyer","type":"address"},{"indexed":false,"internalType":"uint256","name":"totalFees","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"creatorRev","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sellerRev","type":"uint256"}],"name":"BuyPriceAccepted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"BuyPriceCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"BuyPriceInvalidated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"seller","type":"address"},{"indexed":false,"internalType":"uint256","name":"price","type":"uint256"}],"name":"BuyPriceSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"address","name":"buyReferrer","type":"address"},{"indexed":false,"internalType":"uint256","name":"buyReferrerFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"buyReferrerSellerFee","type":"uint256"}],"name":"BuyReferralPaid","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nftContract","type":"address"},{"indexed":true,"internalType":"uint256","name":"firstTokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"nftRecipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"fixedProtocolFeeInWei","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"nftCount","type":"uint256"}],"name":"FixedProtocolFeePaid","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":"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":"address","name":"nftContract","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"uint256","name":"saleStartsAt","type":"uint256"}],"name":"SetSaleStartsAt","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"WithdrawalToFETH","type":"event"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"offerFrom","type":"address"},{"internalType":"uint256","name":"minAmount","type":"uint256"}],"name":"acceptOffer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"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":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"reservePrice","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"}],"name":"createReserveAuction","outputs":[{"internalType":"uint256","name":"auctionId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"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":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address payable","name":"seller","type":"address"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"address payable","name":"buyReferrer","type":"address"},{"internalType":"uint16","name":"sellerReferrerTakeRateInBasisPoints","type":"uint16"}],"name":"getFees","outputs":[{"internalType":"uint256","name":"protocolFeeAmount","type":"uint256"},{"internalType":"address payable[]","name":"creatorRecipients","type":"address[]"},{"internalType":"uint256[]","name":"creatorShares","type":"uint256[]"},{"internalType":"uint256","name":"sellerRev","type":"uint256"},{"internalType":"uint256","name":"buyReferrerFee","type":"uint256"},{"internalType":"uint256","name":"sellerReferrerFee","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"}],"name":"getFeesAndRecipients","outputs":[{"internalType":"uint256","name":"totalFees","type":"uint256"},{"internalType":"uint256","name":"creatorRev","type":"uint256"},{"internalType":"address payable[]","name":"creatorRecipients","type":"address[]"},{"internalType":"uint256[]","name":"creatorShares","type":"uint256[]"},{"internalType":"uint256","name":"sellerRev","type":"uint256"},{"internalType":"address payable","name":"seller","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getFethAddress","outputs":[{"internalType":"address","name":"fethAddress","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getFoundationTreasury","outputs":[{"internalType":"address payable","name":"treasuryAddress","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMarketUtilsAddress","outputs":[{"internalType":"address","name":"marketUtilsAddress","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"auctionId","type":"uint256"}],"name":"getMinBidAmount","outputs":[{"internalType":"uint256","name":"minimum","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getMinOfferAmount","outputs":[{"internalType":"uint256","name":"minimum","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getOffer","outputs":[{"internalType":"address","name":"buyer","type":"address"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getOfferReferrer","outputs":[{"internalType":"address payable","name":"referrer","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"auctionId","type":"uint256"}],"name":"getReserveAuction","outputs":[{"components":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address payable","name":"seller","type":"address"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"uint256","name":"extensionDuration","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"address payable","name":"bidder","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct ReserveAuction","name":"auction","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"auctionId","type":"uint256"}],"name":"getReserveAuctionBidReferrer","outputs":[{"internalType":"address payable","name":"referrer","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getReserveAuctionIdFor","outputs":[{"internalType":"uint256","name":"auctionId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getSaleStartsAt","outputs":[{"internalType":"uint256","name":"saleStartsAt","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getSellerOf","outputs":[{"internalType":"address payable","name":"seller","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getWorldsNftAddress","outputs":[{"internalType":"address","name":"worldsNft","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"networkAuctionIdOffset","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address payable","name":"referrer","type":"address"}],"name":"makeOfferV2","outputs":[{"internalType":"uint256","name":"expiration","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"auctionId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address payable","name":"referrer","type":"address"}],"name":"placeBidV2","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"}],"name":"setBuyPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"saleStartsAt","type":"uint256"}],"name":"setSaleStartsAt","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
61016060405234801561001157600080fd5b50604051615e36380380615e36833981016040819052610030916101b4565b6101f4826000868689866001600160a01b0381163b61006257604051631341312360e01b815260040160405180910390fd5b6001600160a01b0390811660805281163b6100905760405163028bba2560e61b815260040160405180910390fd5b6001600160a01b0390811660a05281163b6100be576040516376ee64e560e11b815260040160405180910390fd5b6001600160a01b0390811660c05281163b6100ec5760405163de58082760e01b815260040160405180910390fd5b6001600160a01b031660e05261010661138861271061023f565b6101146103e8612710610258565b61012090612710610258565b61012e9061ffff861661027a565b1061014c57604051630567777b60e41b815260040160405180910390fd5b6001600160a01b0382163b6101745760405163140d37eb60e21b815260040160405180910390fd5b15156101005261ffff909116610120526001600160a01b0316610140525061028d9350505050565b6001600160a01b03811681146101b157600080fd5b50565b600080600080600060a086880312156101cc57600080fd5b85516101d78161019c565b60208701519095506101e88161019c565b60408701519094506101f98161019c565b606087015190935061020a8161019c565b608087015190925061021b8161019c565b809150509295509295909350565b634e487b7160e01b600052601160045260246000fd5b8181038181111561025257610252610229565b92915050565b60008261027557634e487b7160e01b600052601260045260246000fd5b500490565b8082018082111561025257610252610229565b60805160a05160c05160e051610100516101205161014051615ad1610365600039600081816102b00152611a54015260006119790152600061199f0152600081816101e30152818161058e015281816112c60152818161142201528181611527015281816121450152818161295d0152818161369c015281816137c1015281816138ca01526145bb01526000818161049901526139fc0152600081816107c3015281816136ef0152614f4801526000818161030901528181613ac5015281816140260152818161511801526151b90152615ad16000f3fe6080604052600436106101c65760003560e01c80637430e0c6116100f7578063ac71045e11610095578063b6aff8c111610064578063b6aff8c114610781578063e5d1e72314610794578063f7a2da23146107b4578063fe4b84df146107e757600080fd5b8063ac71045e146106ca578063af1e1de31461071c578063b01ef6081461074e578063b3a4074e1461076157600080fd5b806387a4fdcb116100d157806387a4fdcb1461054d578063895633ba1461057f5780639e64ba6c146105b25780639e79b41f1461062d57600080fd5b80637430e0c6146104bd578063798bac8d146104dd5780637e043795146104fd57600080fd5b80632ab2b52b116101645780634eb123171161013e5780634eb12317146104375780634fca06c614610457578063614b151c146104775780636a90a8271461048a57600080fd5b80632ab2b52b1461036d5780634635256e146103cb57806347e357401461041757600080fd5b806321dbd9aa116101a057806321dbd9aa146102a1578063228b1318146102fa578063262907c51461032d57806329e0e1601461034d57600080fd5b806303ec16d71461024157806321506fff14610261578063215619351461028157600080fd5b3661023c573373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000161461023a576040517faa39384e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b005b600080fd5b34801561024d57600080fd5b5061023a61025c36600461527d565b610807565b34801561026d57600080fd5b5061023a61027c36600461529f565b610904565b34801561028d57600080fd5b5061023a61029c3660046152da565b610b3d565b3480156102ad57600080fd5b507f00000000000000000000000000000000000000000000000000000000000000005b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561030657600080fd5b507f00000000000000000000000000000000000000000000000000000000000000006102d0565b34801561033957600080fd5b506102d06103483660046152da565b610ce6565b34801561035957600080fd5b5061023a610368366004615306565b610d90565b34801561037957600080fd5b506103bd6103883660046152da565b73ffffffffffffffffffffffffffffffffffffffff909116600090815261177660209081526040808320938352929052205490565b6040519081526020016102f1565b3480156103d757600080fd5b506103eb6103e63660046152da565b610f10565b6040805173ffffffffffffffffffffffffffffffffffffffff90931683526020830191909152016102f1565b34801561042357600080fd5b506103bd61043236600461529f565b610fd4565b34801561044357600080fd5b5061023a61045236600461534e565b61100c565b34801561046357600080fd5b506102d06104723660046152da565b6111c7565b6103bd610485366004615383565b6111d3565b34801561049657600080fd5b507f00000000000000000000000000000000000000000000000000000000000000006102d0565b3480156104c957600080fd5b5061023a6104d836600461529f565b611633565b3480156104e957600080fd5b5061023a6104f836600461534e565b61169c565b34801561050957600080fd5b506103bd6105183660046152da565b73ffffffffffffffffffffffffffffffffffffffff909116600090815261129360209081526040808320938352929052205490565b34801561055957600080fd5b5061056d6105683660046153dd565b611960565b6040516102f1969594939291906154d0565b34801561058b57600080fd5b507f00000000000000000000000000000000000000000000000000000000000000006102d0565b3480156105be57600080fd5b506102d06105cd36600461529f565b60009081526117776020526040908190206006810154600290910154740100000000000000000000000000000000000000009182900467ffffffffffffffff1691900490911b73ffffffffffffffffffffffff0000000000000000161790565b34801561063957600080fd5b5061064d61064836600461529f565b611b36565b6040516102f1919060006101008201905073ffffffffffffffffffffffffffffffffffffffff80845116835260208401516020840152806040850151166040840152606084015160608401526080840151608084015260a084015160a08401528060c08501511660c08401525060e083015160e083015292915050565b3480156106d657600080fd5b506106ea6106e53660046152da565b611c80565b6040805173ffffffffffffffffffffffffffffffffffffffff90941684526020840192909252908201526060016102f1565b34801561072857600080fd5b5061073c61073736600461534e565b611d1f565b6040516102f19695949392919061551a565b61023a61075c366004615383565b611d92565b34801561076d57600080fd5b506103bd61077c36600461557b565b611eae565b61023a61078f3660046155b6565b6120dd565b3480156107a057600080fd5b506103bd6107af3660046152da565b612524565b3480156107c057600080fd5b507f00000000000000000000000000000000000000000000000000000000000000006102d0565b3480156107f357600080fd5b5061023a61080236600461529f565b612596565b610810816126e0565b60008281526117776020526040902061084a61082a61271a565b600283015473ffffffffffffffffffffffffffffffffffffffff16612729565b600581015415610886576040517f5aea7c4700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b818160070154036108c3576040517f4b669ac700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6007810182905560405182815283907f0c0f2662914f0cd1e952db2aa425901cb00e7c1f507687d22cb04e836d55d9c79060200160405180910390a2505050565b61090c6127a6565b600081815261177760209081526040808320815161016081018352815473ffffffffffffffffffffffffffffffffffffffff908116825260018301549482019490945260028201548085169382019390935274010000000000000000000000000000000000000000928390046bffffffffffffffffffffffff16606082015260038201546080820152600482015460a0820152600582015460c0820152600682015493841660e082015291830467ffffffffffffffff166101008301527c010000000000000000000000000000000000000000000000000000000090920463ffffffff1661012082015260079091015461014082015290610a0b61271a565b9050610a1b818360400151612729565b60c082015115610a57576040517f5aea7c4700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b815173ffffffffffffffffffffffffffffffffffffffff1660009081526117766020908152604080832082860180518552908352818420849055868452611777909252822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001681556001810183905560028101839055600381018390556004810183905560058101839055600681018390556007019190915582519051610b0291908361281b565b60405183907f14b9c40404d5b41deb481f9a40b8aeb2bf4b47679b38cf757075a66ed510f7f190600090a25050610b3a6001610b8755565b50565b610b456127a6565b73ffffffffffffffffffffffffffffffffffffffff8083166000908152611f4e6020908152604080832085845290915281205490911690610b8461271a565b905073ffffffffffffffffffffffffffffffffffffffff8216610bd3576040517fc09f8e8200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614610c55576040517ff049b41a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff831660048201526024015b60405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff84166000908152611f4e60209081526040808320868452909152812055610c9284848461281b565b604051839073ffffffffffffffffffffffffffffffffffffffff8616907f70c7877531c04c7d9caa8a7eca127384f04e8a6ee58b63f778ce5401d8bcae4190600090a35050610ce26001610b8755565b5050565b73ffffffffffffffffffffffffffffffffffffffff82166000908152612337602090815260408083208484529091528120805463ffffffff16421115610d30576000915050610d8a565b8054600182015470010000000000000000000000000000000090910460201b73ffffffffffffffffffffffffffffffff00000000167401000000000000000000000000000000000000000090910463ffffffff16175b9150505b92915050565b610d986127a6565b73ffffffffffffffffffffffffffffffffffffffff84166000908152612337602090815260408083208684529091529020805463ffffffff16421115610e155780546040517f8c9e57cf00000000000000000000000000000000000000000000000000000000815263ffffffff9091166004820152602401610c4c565b805464010000000090046bffffffffffffffffffffffff16821115610e805780546040517f242373610000000000000000000000000000000000000000000000000000000081526401000000009091046bffffffffffffffffffffffff166004820152602401610c4c565b600181015473ffffffffffffffffffffffffffffffffffffffff848116911614610ef45760018101546040517fa7d95dc300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602401610c4c565b610efe8585612826565b50610f0a6001610b8755565b50505050565b73ffffffffffffffffffffffffffffffffffffffff8083166000908152611f4e602090815260408083208584529091528120549091169081610f7357507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610fcd565b5073ffffffffffffffffffffffffffffffffffffffff83166000908152611f4e602090815260408083208584529091529020547401000000000000000000000000000000000000000090046bffffffffffffffffffffffff165b9250929050565b60008181526117776020526040812060058101548203610ff8576007015492915050565b6110058160070154612d11565b9392505050565b73ffffffffffffffffffffffffffffffffffffffff8316600090815261129360209081526040808320858452909152902054819003611077576040517fe5c3f26300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6110818383612d33565b801561111f5761109081421190565b156110c7576040517fdbaca9d100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6110d56303c267004261561e565b81111561111f576110ea6303c267004261561e565b6040517f857c0df1000000000000000000000000000000000000000000000000000000008152600401610c4c91815260200190565b73ffffffffffffffffffffffffffffffffffffffff8316600090815261129360209081526040808320858452909152902081905561115b61271a565b73ffffffffffffffffffffffffffffffffffffffff16828473ffffffffffffffffffffffffffffffffffffffff167fc8a67fb17ad40fabf835a5c96e0438644464257af6f325f44f1615aabc0e41ba846040516111ba91815260200190565b60405180910390a4505050565b60006110058383612d73565b60006111e0858585612d7f565b156111ed5750600061162b565b6111f78585612e10565b1561122e576040517f83a483f500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff851660009081526123376020908152604080832087845290915281209061126861271a565b825490915063ffffffff1642111561133c576040517f4ec58ed700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8281166004830152602482018790527f00000000000000000000000000000000000000000000000000000000000000001690634ec58ed790349060440160206040518083038185885af1158015611310573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906113359190615631565b9250611497565b81546000906113609064010000000090046bffffffffffffffffffffffff16612d11565b90508086101561139f576040517fe40a30e600000000000000000000000000000000000000000000000000000000815260048101829052602401610c4c565b600183015483546040517f5fdec56100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff928316600482015263ffffffff821660248201526401000000009091046bffffffffffffffffffffffff1660448201528382166064820152608481018890527f000000000000000000000000000000000000000000000000000000000000000090911690635fdec56190349060a40160206040518083038185885af115801561146e573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906114939190615631565b9350505b6001820180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691909117909155825463ffffffff85167fffffffffffffffffffffffffffffffff00000000000000000000000000000000909116176401000000006bffffffffffffffffffffffff8816021783557f000000000000000000000000000000000000000000000000000000000000000081169085160361155557600093505b81546fffffffffffffffffffffffffffffffff908116602086811c909216700100000000000000000000000000000000021783556001830180547fffffffffffffffff00000000ffffffffffffffffffffffffffffffffffffffff167401000000000000000000000000000000000000000063ffffffff8816021790556040805187815291820185905273ffffffffffffffffffffffffffffffffffffffff838116928992918b16917ece0a712e4e277ac7b34942865f0de7a5629dffe0539b70423ad5ff1ed6ab42910160405180910390a450505b949350505050565b61163b6127a6565b600081815261177760205260408120600501549003611686576040517f4b6ad8fa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611691816000612e69565b610b3a6001610b8755565b6116a46127a6565b6116af83838361322c565b611950576bffffffffffffffffffffffff8111156116f9576040517f35ec82cb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8381166000908152611f4e602090815260408083208684529091529020805490918116907401000000000000000000000000000000000000000090046bffffffffffffffffffffffff168314801561177b575073ffffffffffffffffffffffffffffffffffffffff811615155b156117b2576040517fb6950f3600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b815473ffffffffffffffffffffffffffffffffffffffff16740100000000000000000000000000000000000000006bffffffffffffffffffffffff85160217825560006117fd61271a565b905073ffffffffffffffffffffffffffffffffffffffff82166118675761182486866132a1565b82547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff82161783556118e4565b8073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16146118e4576040517f697d918e00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83166004820152602401610c4c565b8073ffffffffffffffffffffffffffffffffffffffff16858773ffffffffffffffffffffffffffffffffffffffff167ffcc77ea8bdcce862f43b7fb00fe6b0eb90d6aeead27d3800d9257cf7a05f9d968760405161194491815260200190565b60405180910390a45050505b61195b6001610b8755565b505050565b60006060806000806000806040518061012001604052807f000000000000000000000000000000000000000000000000000000000000000081526020017f0000000000000000000000000000000000000000000000000000000000000000151581526020018e73ffffffffffffffffffffffffffffffffffffffff1681526020018d81526020018b81526020018c73ffffffffffffffffffffffffffffffffffffffff1681526020018a73ffffffffffffffffffffffffffffffffffffffff1681526020018961ffff168152602001611a3761271a565b73ffffffffffffffffffffffffffffffffffffffff1681525090507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16630e13eac1826040518263ffffffff1660e01b8152600401611aab919061564a565b600060405180830381865afa158015611ac8573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052611b0e9190810190615833565b809750819850829950839a50849b50859c505050505050505096509650965096509650969050565b611bc0604051806101000160405280600073ffffffffffffffffffffffffffffffffffffffff16815260200160008152602001600073ffffffffffffffffffffffffffffffffffffffff168152602001600081526020016000815260200160008152602001600073ffffffffffffffffffffffffffffffffffffffff168152602001600081525090565b600082815261177760205260408120600681015490917c010000000000000000000000000000000000000000000000000000000090910463ffffffff1690819003611c0b5750620151805b6040805161010081018252835473ffffffffffffffffffffffffffffffffffffffff9081168252600185015460208301526002850154811692820192909252606081019290925261012c6080830152600583015460a083015260068301541660c082015260079091015460e082015292915050565b73ffffffffffffffffffffffffffffffffffffffff8216600090815261233760209081526040808320848452909152812080548291829163ffffffff16421115611cd557600080600093509350935050611d18565b6001810154905473ffffffffffffffffffffffffffffffffffffffff909116935063ffffffff8116925064010000000090046bffffffffffffffffffffffff1690505b9250925092565b600080606080600080611d3289896132ab565b9050611d438989838a600080611960565b5093995091965094509250600090505b8351811015611d8557838181518110611d6e57611d6e61591d565b602002602001015186019550806001019050611d53565b5093975093979195509350565b73ffffffffffffffffffffffffffffffffffffffff84166000908152611f4e60209081526040808320868452909152902080547401000000000000000000000000000000000000000090046bffffffffffffffffffffffff16831015611e4e5780546040517f16b5016f000000000000000000000000000000000000000000000000000000008152740100000000000000000000000000000000000000009091046bffffffffffffffffffffffff166004820152602401610c4c565b805473ffffffffffffffffffffffffffffffffffffffff16611e9c576040517fda48e18400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611ea7858584613363565b5050505050565b6000611eb86127a6565b611ec283836135b6565b9150611ecc61365b565b9050611ed885856132a1565b73ffffffffffffffffffffffffffffffffffffffff851660009081526117766020908152604080832087845290915290205415611f785773ffffffffffffffffffffffffffffffffffffffff8516600090815261177660209081526040808320878452909152908190205490517f7618a0030000000000000000000000000000000000000000000000000000000081526004810191909152602401610c4c565b6000611f8261271a565b73ffffffffffffffffffffffffffffffffffffffff8781166000818152611776602090815260408083208b84528252808320889055878352611777909152902080547fffffffffffffffffffffffff000000000000000000000000000000000000000090811690921781556001810189905560028101805490921692841692909217905560078101869055909150620151808414612068576006810180547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167c010000000000000000000000000000000000000000000000000000000063ffffffff8716021790555b6040805185815261012c602082015290810186905260608101849052869073ffffffffffffffffffffffffffffffffffffffff808a1691908516907f1062dd3b35f12b4064331244d00f40c1d4831965e4285654157a2409c6217cff9060800160405180910390a4505061162b6001610b8755565b6120e56127a6565b6000838152611777602052604081206007810154909103612132576040517f125197d100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6005810154600061214161271a565b90507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff160361219b57600093505b73ffffffffffffffffffffffffffffffffffffffff84161515806121be57508115155b1561224f5760028301805473ffffffffffffffffffffffffffffffffffffffff16604086901c6bffffffffffffffffffffffff1674010000000000000000000000000000000000000000908102919091179091556006840180547fffffffff0000000000000000ffffffffffffffffffffffffffffffffffffffff1667ffffffffffffffff87169092029190911790555b8160000361235457826007015485101561229d5782600701546040517f31e6f71c000000000000000000000000000000000000000000000000000000008152600401610c4c91815260200190565b825460018401546122c49173ffffffffffffffffffffffffffffffffffffffff1690613683565b600783018590556006830180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff831617908190557c0100000000000000000000000000000000000000000000000000000000900463ffffffff1660008190036123445750620151805b42016005840181905591506124b7565b61235d82421190565b15612397576040517f3feeb88d00000000000000000000000000000000000000000000000000000000815260048101839052602401610c4c565b600683015473ffffffffffffffffffffffffffffffffffffffff8083169116036123ed576040517fe140576800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006123fc8460070154612d11565b90508086101561243b576040517fcd698a1900000000000000000000000000000000000000000000000000000000815260048101829052602401610c4c565b506007830180546006850180549288905573ffffffffffffffffffffffffffffffffffffffff8481167fffffffffffffffffffffffff00000000000000000000000000000000000000008516179091559091164261012c01808510156124a657600586018190559350835b506124b48183614e2061368d565b50505b6124c381866000613875565b604080518681526020810184905273ffffffffffffffffffffffffffffffffffffffff83169188917f26ea3ebbda62eb1baef13e1c237dddd956c87f80b2801f2616d806d52557b121910160405180910390a350505061195b6001610b8755565b73ffffffffffffffffffffffffffffffffffffffff82166000908152612337602090815260408083208484529091528120805463ffffffff16421161258c5780546125849064010000000090046bffffffffffffffffffffffff16612d11565b915050610d8a565b5060019392505050565b600054600290610100900460ff161580156125b8575060005460ff8083169116105b612644576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152608401610c4c565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00001660ff83161761010017905561267e82613991565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff16905560405160ff821681527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15050565b80600003610b3a576040517f7378a92d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006127246139e4565b905090565b8073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614610ce2576040517f0907a37700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610c4c565b6002610b875403612813576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610c4c565b6002610b8755565b61195b838383613a50565b73ffffffffffffffffffffffffffffffffffffffff828116600090815261233760209081526040808320858452808352818420825160a081018452815463ffffffff808216835264010000000082046bffffffffffffffffffffffff9081168489019081527001000000000000000000000000000000009093046fffffffffffffffffffffffffffffffff1684880152600185018054808c1660608701908152740100000000000000000000000000000000000000008204851660808801528d8c5297909952989094557fffffffffffffffff00000000000000000000000000000000000000000000000090961690965591518251955193517f4dc8fb3c000000000000000000000000000000000000000000000000000000008152908716600482015294909316602485015291166044830152917f00000000000000000000000000000000000000000000000000000000000000001690634dc8fb3c90606401600060405180830381600087803b1580156129a157600080fd5b505af11580156129b5573d6000803e3d6000fd5b5050505060006129c361271a565b90506000806129eb838787876060015188602001516bffffffffffffffffffffffff16613a5b565b6040517f6352211e00000000000000000000000000000000000000000000000000000000815260048101889052919350915060009073ffffffffffffffffffffffffffffffffffffffff881690636352211e90602401602060405180830381865afa158015612a5e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a82919061594c565b90503073ffffffffffffffffffffffffffffffffffffffff821603612ab657612ab18787876060015187613b57565b612b4d565b60608501516040517f23b872dd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8681166004830152918216602482015260448101889052908816906323b872dd90606401600060405180830381600087803b158015612b3457600080fd5b505af1158015612b48573d6000803e3d6000fd5b505050505b6000806000612c616040518061014001604052808c73ffffffffffffffffffffffffffffffffffffffff1681526020018b8152602001600181526020018a6060015173ffffffffffffffffffffffffffffffffffffffff1681526020018973ffffffffffffffffffffffffffffffffffffffff1681526020018a602001516bffffffffffffffffffffffff168152602001612c168b604001518c6080015173ffffffffffffffffffffffffffffffff0000000060209290921b9190911663ffffffff9091161790565b73ffffffffffffffffffffffffffffffffffffffff1681526020018873ffffffffffffffffffffffffffffffffffffffff1681526020018761ffff1681526020016000815250613b63565b925092509250876060015173ffffffffffffffffffffffffffffffffffffffff16898b73ffffffffffffffffffffffffffffffffffffffff167f1cb8adb37d6d35e94cd0695ca39895b84371864713f5ca7eada52af9ff23744b8a878787604051612cfd949392919073ffffffffffffffffffffffffffffffffffffffff94909416845260208401929092526040830152606082015260800190565b60405180910390a450505050505050505050565b6000600a8204808203612d295761100583600161561e565b611005838261561e565b612d3d8282613b7e565b610ce2576040517fe390a4ec00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006110058383613b8a565b73ffffffffffffffffffffffffffffffffffffffff8084166000908152611f4e602090815260408083208684529091528120805491929091161580612dea575080547401000000000000000000000000000000000000000090046bffffffffffffffffffffffff1683105b15612df9576000915050611005565b612e0585856000613363565b506001949350505050565b73ffffffffffffffffffffffffffffffffffffffff82166000908152611776602090815260408083208484529091528120548015801590610d865750600090815261177760205260409020600501544211159392505050565b60008281526117776020908152604091829020825161016081018452815473ffffffffffffffffffffffffffffffffffffffff908116825260018301549382019390935260028201548084169482019490945274010000000000000000000000000000000000000000938490046bffffffffffffffffffffffff16606082015260038201546080820152600482015460a0820152600582015460c08201819052600683015493841660e083015293830467ffffffffffffffff166101008201527c010000000000000000000000000000000000000000000000000000000090920463ffffffff1661012083015260070154610140820152904211612fa1578060c001516040517f3a017f60000000000000000000000000000000000000000000000000000000008152600401610c4c91815260200190565b600080612fc68360400151846000015185602001518660e00151876101400151613a5b565b845173ffffffffffffffffffffffffffffffffffffffff166000908152611776602090815260408083208289015184528252808320839055898352611777909152812080547fffffffffffffffffffffffff00000000000000000000000000000000000000001681556001810182905560028101829055600381018290556004810182905560058101829055600681018290556007015590925090508361308057613080836000015184602001518560e001516000613bcd565b6000806000613199604051806101400160405280886000015173ffffffffffffffffffffffffffffffffffffffff16815260200188602001518152602001600181526020018860e0015173ffffffffffffffffffffffffffffffffffffffff168152602001886040015173ffffffffffffffffffffffffffffffffffffffff168152602001886101400151815260200188610100015167ffffffffffffffff1660408a606001516bffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16901b1773ffffffffffffffffffffffffffffffffffffffff1681526020018773ffffffffffffffffffffffffffffffffffffffff1681526020018661ffff1681526020016000815250613b63565b9250925092508560e0015173ffffffffffffffffffffffffffffffffffffffff16866040015173ffffffffffffffffffffffffffffffffffffffff16897f2edb0e99c6ac35be6731dab554c1d1fa1b7beb675090dbb09fb14e615aca1c4a86868660405161321a939291909283526020830191909152604082015260600190565b60405180910390a45050505050505050565b73ffffffffffffffffffffffffffffffffffffffff83166000908152612337602090815260408083208584529091528120805463ffffffff164211806132885750805464010000000090046bffffffffffffffffffffffff1683115b15613297576000915050611005565b612e058585612826565b610ce28282613caf565b60006132b78383612d73565b905073ffffffffffffffffffffffffffffffffffffffff8116610d8a576040517f6352211e0000000000000000000000000000000000000000000000000000000081526004810183905273ffffffffffffffffffffffffffffffffffffffff841690636352211e90602401602060405180830381865afa15801561333f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611005919061594c565b61336b6127a6565b6133758383613d76565b73ffffffffffffffffffffffffffffffffffffffff8381166000908152611f4e60209081526040808320868452808352818420825180840190935280549586168352740100000000000000000000000000000000000000009095046bffffffffffffffffffffffff168284015286845290915291556133f48484613de1565b60006133fe61271a565b905061341e8183602001516bffffffffffffffffffffffff166001613875565b600080613444846000015188888688602001516bffffffffffffffffffffffff16613a5b565b915091506134558787856000613b57565b60008060006135326040518061014001604052808c73ffffffffffffffffffffffffffffffffffffffff1681526020018b8152602001600181526020018873ffffffffffffffffffffffffffffffffffffffff168152602001896000015173ffffffffffffffffffffffffffffffffffffffff16815260200189602001516bffffffffffffffffffffffff1681526020018a73ffffffffffffffffffffffffffffffffffffffff1681526020018773ffffffffffffffffffffffffffffffffffffffff1681526020018661ffff1681526020016000815250613b63565b89516040805173ffffffffffffffffffffffffffffffffffffffff8c8116825260208201879052918101859052606081018490529497509295509093508116918b918d16907fd28c0a7dd63bc853a4e36306655da9f8c0b29ff9d0605bb976ae420e46a999309060800160405180910390a45050505050505061195b6001610b8755565b60006135c1836126e0565b816000036135d3575062015180610d8a565b62093a80821115613615576040517f5b7d3a4100000000000000000000000000000000000000000000000000000000815262093a806004820152602401610c4c565b61012c821015613655576040517f4b7f44ee00000000000000000000000000000000000000000000000000000000815261012c6004820152602401610c4c565b50919050565b600061138d546000036136735761138d805460010190555b5061138d80546001810190915590565b610ce28282613e48565b8160000361369a57505050565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603613711577f000000000000000000000000000000000000000000000000000000000000000092505b60008373ffffffffffffffffffffffffffffffffffffffff16838390604051600060405180830381858888f193505050503d806000811461376e576040519150601f19603f3d011682016040523d82523d6000602084013e613773565b606091505b5050905080610f0a576040517faa67c91900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301527f0000000000000000000000000000000000000000000000000000000000000000169063aa67c9199085906024016000604051808303818588803b15801561380657600080fd5b505af115801561381a573d6000803e3d6000fd5b50505050508373ffffffffffffffffffffffffffffffffffffffff167fa2201512569adb2d513531dfd69b66df50bd5cffb8c1bbe65a4611f9e1eadbd18460405161386791815260200190565b60405180910390a250505050565b3482111561392b576040517f452f2b8f00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff848116600483015234840360248301527f0000000000000000000000000000000000000000000000000000000000000000169063452f2b8f90604401600060405180830381600087803b15801561390e57600080fd5b505af1158015613922573d6000803e3d6000fd5b50505050505050565b3482101561195b57801561395c5761195b73ffffffffffffffffffffffffffffffffffffffff841634849003613e5c565b6040517fabf2f33b00000000000000000000000000000000000000000000000000000000815260048101839052602401610c4c565b8015610b3a5761138d54156139d2576040517feb2e06c000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6139dd81600161561e565b61138d5550565b3373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168103613a4d57507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec36013560601c90565b90565b61195b838383613fb6565b6040517f3221782100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8681166004830152858116602483015260448201859052838116606483015260848201839052600091829182917f00000000000000000000000000000000000000000000000000000000000000009091169063322178219060a4016060604051808303816000875af1158015613b10573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613b349190615969565b909450925090508015613b4c57613b4c888888613ffa565b509550959350505050565b610f0a848484846140dd565b6000806000613b71846141ae565b9196909550909350915050565b600061100583836141bc565b73ffffffffffffffffffffffffffffffffffffffff8083166000908152611f4e602090815260408083208584529091529020541680610d8a576110058383614296565b73ffffffffffffffffffffffffffffffffffffffff811615613c1b576040517f57a016b300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f23b872dd00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff8381166024830152604482018590528516906323b872dd90606401600060405180830381600087803b158015613c9157600080fd5b505af1158015613ca5573d6000803e3d6000fd5b5050505050505050565b73ffffffffffffffffffffffffffffffffffffffff8083166000908152611f4e602090815260408083208584529091529020541680613cf25761195b83836142e4565b613cfa61271a565b73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161461195b576040517f32f3b03300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610c4c565b73ffffffffffffffffffffffffffffffffffffffff8216600090815261129360209081526040808320848452909152902054421015610ce2576040517f4917db3f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff82166000908152612337602090815260408083208484529091529020613e1a61271a565b600182015473ffffffffffffffffffffffffffffffffffffffff91821691160361195b5761195b8383614446565b613e528282614446565b610ce2828261465f565b80471015613ec6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e63650000006044820152606401610c4c565b60008273ffffffffffffffffffffffffffffffffffffffff168260405160006040518083038185875af1925050503d8060008114613f20576040519150601f19603f3d011682016040523d82523d6000602084013e613f25565b606091505b505090508061195b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260448201527f6563697069656e74206d617920686176652072657665727465640000000000006064820152608401610c4c565b73ffffffffffffffffffffffffffffffffffffffff8084166000908152611f4e602090815260408083208684529091529020541680610f0a57610f0a8484846146af565b60405173ffffffffffffffffffffffffffffffffffffffff8316602482015260448101829052610f0a907f000000000000000000000000000000000000000000000000000000000000000090606401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f93251fba0000000000000000000000000000000000000000000000000000000017905273ffffffffffffffffffffffffffffffffffffffff861691906146f2565b73ffffffffffffffffffffffffffffffffffffffff8085166000908152611f4e602090815260408083208784529091529020541680156141a2578173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614614194576040517f32f3b03300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610c4c565b600091506141a2858561471b565b611ea78585858561477b565b6000806000613b71846149b7565b73ffffffffffffffffffffffffffffffffffffffff8083166000908152611f4e6020908152604080832085845290915281205490911680156142855761420061271a565b73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161461427c576040517fc89fba3b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610c4c565b6001915061428f565b610d8684846149c5565b5092915050565b73ffffffffffffffffffffffffffffffffffffffff80831660009081526117766020908152604080832085845282528083205483526117779091529020600201541680610d8a576000611005565b73ffffffffffffffffffffffffffffffffffffffff8216600090815261177660209081526040808320848452909152812054908190036143285761195b8383614a64565b6000818152611777602052604081209061434061271a565b905081600501546000036143c757600282015473ffffffffffffffffffffffffffffffffffffffff8281169116146143c25760028201546040517fe64526ee00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602401610c4c565b611ea7565b600682015473ffffffffffffffffffffffffffffffffffffffff82811691161461443b5760068201546040517fe64526ee00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602401610c4c565b611ea7836001612e69565b73ffffffffffffffffffffffffffffffffffffffff821660009081526123376020908152604080832084845290915290205463ffffffff164211610ce25773ffffffffffffffffffffffffffffffffffffffff828116600090815261233760209081526040808320858452808352818420825160a081018452815463ffffffff808216835264010000000082046bffffffffffffffffffffffff9081168489019081527001000000000000000000000000000000009093046fffffffffffffffffffffffffffffffff1684880152600185018054808c1660608701908152740100000000000000000000000000000000000000008204851660808801528d8c5297909952989094557fffffffffffffffff00000000000000000000000000000000000000000000000090961690965591518251955193517f345db493000000000000000000000000000000000000000000000000000000008152908716600482015294909316602485015291166044830152917f0000000000000000000000000000000000000000000000000000000000000000169063345db49390606401600060405180830381600087803b1580156145ff57600080fd5b505af1158015614613573d6000803e3d6000fd5b505060405184925073ffffffffffffffffffffffffffffffffffffffff861691507f30c264456cbd17f5f67d7534654161414f34c0e6cc1b7500e169b7a7aea4afc090600090a3505050565b73ffffffffffffffffffffffffffffffffffffffff8083166000908152611f4e6020908152604080832085845290915290208054909116156146a5576146a5838361471b565b61195b8383614b17565b73ffffffffffffffffffffffffffffffffffffffff8316600090815261177660209081526040808320858452909152812054900361195b5761195b838383614b21565b60606000614701858585614b36565b92509050806147135761471382614beb565b509392505050565b73ffffffffffffffffffffffffffffffffffffffff82166000818152611f4e60209081526040808320858452909152808220829055518392917faa6271d89a385571e237d3e7254ccc7c09f68055e6e9b410ed08233a8b9a05cf91a35050565b73ffffffffffffffffffffffffffffffffffffffff841660009081526117766020908152604080832086845290915290205480156149ab5760008181526117776020526040812060058101549091036149265773ffffffffffffffffffffffffffffffffffffffff8316158015906148105750600281015473ffffffffffffffffffffffffffffffffffffffff848116911614155b156148655760028101546040517fe64526ee00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602401610c4c565b73ffffffffffffffffffffffffffffffffffffffff8616600090815261177660209081526040808320888452825280832083905584835261177790915280822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001681556001810183905560028101839055600381018390556004810183905560058101839055600681018390556007018290555183917f5603897cc9b1e866f3f7395ffc6638776041f21c094d0b4e748ff44c407fa36291a26149a5565b600681015473ffffffffffffffffffffffffffffffffffffffff84811691161461499a5760068101546040517fe64526ee00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602401610c4c565b6149a5826001612e69565b60009250505b611ea785858585613bcd565b6000806000613b7184614c2d565b73ffffffffffffffffffffffffffffffffffffffff82166000908152611776602090815260408083208484529091528120548015614a5d57600081815261177760205260409020614a1761082a61271a565b600581015415614a53576040517fa2a745e900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600192505061428f565b6000610d86565b8173ffffffffffffffffffffffffffffffffffffffff166323b872dd614a8861271a565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff909116600482015230602482015260448101849052606401600060405180830381600087803b158015614afb57600080fd5b505af1158015614b0f573d6000803e3d6000fd5b505050505050565b610ce28282614c4d565b614b2b8383614c57565b61195b838383614d34565b600060608373ffffffffffffffffffffffffffffffffffffffff168386604051602001614b649291906159d1565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815290829052614b9c91615a11565b6000604051808303816000865af19150503d8060008114614bd9576040519150601f19603f3d011682016040523d82523d6000602084013e614bde565b606091505b5090969095509350505050565b805115614bfb5780518082602001fd5b6040517f3cfe059f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000806000614c4484600001518560200151614c57565b613b7184614d4a565b610ce28282613d76565b73ffffffffffffffffffffffffffffffffffffffff821660009081526112936020908152604080832084845290915290205415610ce25773ffffffffffffffffffffffffffffffffffffffff8216600090815261129360209081526040808320848452909152812055614cc861271a565b73ffffffffffffffffffffffffffffffffffffffff16818373ffffffffffffffffffffffffffffffffffffffff167fc8a67fb17ad40fabf835a5c96e0438644464257af6f325f44f1615aabc0e41ba6000604051614d2891815260200190565b60405180910390a45050565b614d3f8184846150c1565b61195b838383615270565b6000806000606080600080614d7c886000015189602001518a608001518b60a001518c60c001518d6101000151611960565b6101208e0151959c50919950929750909550909350915015614e8957610120880151614da8908861561e565b60c089015190975073ffffffffffffffffffffffffffffffffffffffff1615614e0257614dd96107d0612710615a1d565b886101200151614de99190615a1d565b614df3908361561e565b9150614dff8288615a58565b96505b876060015173ffffffffffffffffffffffffffffffffffffffff168860200151896000015173ffffffffffffffffffffffffffffffffffffffff167f128e6430bdac5e0f43d2f25064b707cadc6dbb27cf69958b934944df4b23c4238b61012001518c60400151604051614e80929190918252602082015260400190565b60405180910390a45b60008451600114614e9c57614e20614ea1565b620334505b905060005b8551811015614f1557614eec868281518110614ec457614ec461591d565b6020026020010151868381518110614ede57614ede61591d565b60200260200101518461368d565b848181518110614efe57614efe61591d565b602002602001015188019750806001019050614ea6565b5050614f28886080015186614e2061368d565b8615614f6e57614f6e73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001688613e5c565b8115614ff157614f858860c0015183614e2061368d565b602088810151895160c08b01516040805173ffffffffffffffffffffffffffffffffffffffff92831681529485018790526000858201525192939116917f141b92fd9766c80ab120598ea2f6be9802470ec59b5446dd9bf46214ead8d08e9181900360600190a3958101955b6101208801511561500757876101200151870396505b60e088015173ffffffffffffffffffffffffffffffffffffffff16156150b6578015615054578460000361503e5794850194615043565b938401935b6150548860e0015182614e2061368d565b602080890151895160e08b01516040805173ffffffffffffffffffffffffffffffffffffffff928316815294850186905292939116917f27a4dd4ff659a9e6354fb079b2208365e5b83f55c22a4150eee2bca89501cb98910160405180910390a35b505050509193909250565b6040517f8f40f37800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83811660048301526024820183905284811660448301526000917f000000000000000000000000000000000000000000000000000000000000000090911690638f40f378906064016040805180830381865afa158015615160573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906151849190615a6b565b5090508015610f0a5760405173ffffffffffffffffffffffffffffffffffffffff8416602482015260448101839052611ea7907f000000000000000000000000000000000000000000000000000000000000000090606401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f93251fba0000000000000000000000000000000000000000000000000000000017905273ffffffffffffffffffffffffffffffffffffffff871691906146f2565b61195b8383836000613b57565b6000806040838503121561529057600080fd5b50508035926020909101359150565b6000602082840312156152b157600080fd5b5035919050565b73ffffffffffffffffffffffffffffffffffffffff81168114610b3a57600080fd5b600080604083850312156152ed57600080fd5b82356152f8816152b8565b946020939093013593505050565b6000806000806080858703121561531c57600080fd5b8435615327816152b8565b935060208501359250604085013561533e816152b8565b9396929550929360600135925050565b60008060006060848603121561536357600080fd5b833561536e816152b8565b95602085013595506040909401359392505050565b6000806000806080858703121561539957600080fd5b84356153a4816152b8565b9350602085013592506040850135915060608501356153c2816152b8565b939692955090935050565b61ffff81168114610b3a57600080fd5b60008060008060008060c087890312156153f657600080fd5b8635615401816152b8565b9550602087013594506040870135615418816152b8565b935060608701359250608087013561542f816152b8565b915060a087013561543f816153cd565b809150509295509295509295565b60008151808452602080850194506020840160005b8381101561549457815173ffffffffffffffffffffffffffffffffffffffff1687529582019590820190600101615462565b509495945050505050565b60008151808452602080850194506020840160005b83811015615494578151875295820195908201906001016154b4565b86815260c0602082015260006154e960c083018861544d565b82810360408401526154fb818861549f565b60608401969096525050608081019290925260a0909101529392505050565b86815285602082015260c06040820152600061553960c083018761544d565b828103606084015261554b818761549f565b91505083608083015273ffffffffffffffffffffffffffffffffffffffff831660a0830152979650505050505050565b6000806000806080858703121561559157600080fd5b843561559c816152b8565b966020860135965060408601359560600135945092505050565b6000806000606084860312156155cb57600080fd5b833592506020840135915060408401356155e4816152b8565b809150509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b80820180821115610d8a57610d8a6155ef565b60006020828403121561564357600080fd5b5051919050565b6000610120820190508251825260208301511515602083015273ffffffffffffffffffffffffffffffffffffffff6040840151166040830152606083015160608301526080830151608083015260a08301516156be60a084018273ffffffffffffffffffffffffffffffffffffffff169052565b5060c08301516156e660c084018273ffffffffffffffffffffffffffffffffffffffff169052565b5060e08301516156fc60e084018261ffff169052565b506101009283015173ffffffffffffffffffffffffffffffffffffffff16919092015290565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff8111828210171561579857615798615722565b604052919050565b600067ffffffffffffffff8211156157ba576157ba615722565b5060051b60200190565b600082601f8301126157d557600080fd5b815160206157ea6157e5836157a0565b615751565b8083825260208201915060208460051b87010193508684111561580c57600080fd5b602086015b848110156158285780518352918301918301615811565b509695505050505050565b60008060008060008060c0878903121561584c57600080fd5b8651955060208088015167ffffffffffffffff8082111561586c57600080fd5b818a0191508a601f83011261588057600080fd5b815161588e6157e5826157a0565b81815260059190911b8301840190848101908d8311156158ad57600080fd5b938501935b828510156158d45784516158c5816152b8565b825293850193908501906158b2565b60408d0151909a5094505050808311156158ed57600080fd5b50506158fb89828a016157c4565b945050606087015192506080870151915060a087015190509295509295509295565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60006020828403121561595e57600080fd5b8151611005816152b8565b60008060006060848603121561597e57600080fd5b835192506020840151615990816152b8565b60408501519092506155e4816153cd565b6000815160005b818110156159c257602081850181015186830152016159a8565b50600093019283525090919050565b60006159dd82856159a1565b60609390931b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000001683525050601401919050565b600061100582846159a1565b600082615a53577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b81810381811115610d8a57610d8a6155ef565b60008060408385031215615a7e57600080fd5b825191506020830151615a90816153cd565b80915050925092905056fea26469706673582212201eaaddb21e4d2997c6d3ad185b60f8e3ddfe6290fd909a50c3461ee837b6f7a964736f6c6343000819003300000000000000000000000067df244584b67e8c51b10ad610aaffa9a402fdb600000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d50443000000000000000000000000762340b8a40cdd5bfc3edd94265899fda345d0e3000000000000000000000000d0e072fac9aa5daed50660f02f1e61cacbe6ecd200000000000000000000000069525dac489e4718964b5615c3d794a25d62beb7
Deployed Bytecode
0x6080604052600436106101c65760003560e01c80637430e0c6116100f7578063ac71045e11610095578063b6aff8c111610064578063b6aff8c114610781578063e5d1e72314610794578063f7a2da23146107b4578063fe4b84df146107e757600080fd5b8063ac71045e146106ca578063af1e1de31461071c578063b01ef6081461074e578063b3a4074e1461076157600080fd5b806387a4fdcb116100d157806387a4fdcb1461054d578063895633ba1461057f5780639e64ba6c146105b25780639e79b41f1461062d57600080fd5b80637430e0c6146104bd578063798bac8d146104dd5780637e043795146104fd57600080fd5b80632ab2b52b116101645780634eb123171161013e5780634eb12317146104375780634fca06c614610457578063614b151c146104775780636a90a8271461048a57600080fd5b80632ab2b52b1461036d5780634635256e146103cb57806347e357401461041757600080fd5b806321dbd9aa116101a057806321dbd9aa146102a1578063228b1318146102fa578063262907c51461032d57806329e0e1601461034d57600080fd5b806303ec16d71461024157806321506fff14610261578063215619351461028157600080fd5b3661023c573373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d50443161461023a576040517faa39384e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b005b600080fd5b34801561024d57600080fd5b5061023a61025c36600461527d565b610807565b34801561026d57600080fd5b5061023a61027c36600461529f565b610904565b34801561028d57600080fd5b5061023a61029c3660046152da565b610b3d565b3480156102ad57600080fd5b507f000000000000000000000000d0e072fac9aa5daed50660f02f1e61cacbe6ecd25b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561030657600080fd5b507f00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb76102d0565b34801561033957600080fd5b506102d06103483660046152da565b610ce6565b34801561035957600080fd5b5061023a610368366004615306565b610d90565b34801561037957600080fd5b506103bd6103883660046152da565b73ffffffffffffffffffffffffffffffffffffffff909116600090815261177660209081526040808320938352929052205490565b6040519081526020016102f1565b3480156103d757600080fd5b506103eb6103e63660046152da565b610f10565b6040805173ffffffffffffffffffffffffffffffffffffffff90931683526020830191909152016102f1565b34801561042357600080fd5b506103bd61043236600461529f565b610fd4565b34801561044357600080fd5b5061023a61045236600461534e565b61100c565b34801561046357600080fd5b506102d06104723660046152da565b6111c7565b6103bd610485366004615383565b6111d3565b34801561049657600080fd5b507f000000000000000000000000762340b8a40cdd5bfc3edd94265899fda345d0e36102d0565b3480156104c957600080fd5b5061023a6104d836600461529f565b611633565b3480156104e957600080fd5b5061023a6104f836600461534e565b61169c565b34801561050957600080fd5b506103bd6105183660046152da565b73ffffffffffffffffffffffffffffffffffffffff909116600090815261129360209081526040808320938352929052205490565b34801561055957600080fd5b5061056d6105683660046153dd565b611960565b6040516102f1969594939291906154d0565b34801561058b57600080fd5b507f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d504436102d0565b3480156105be57600080fd5b506102d06105cd36600461529f565b60009081526117776020526040908190206006810154600290910154740100000000000000000000000000000000000000009182900467ffffffffffffffff1691900490911b73ffffffffffffffffffffffff0000000000000000161790565b34801561063957600080fd5b5061064d61064836600461529f565b611b36565b6040516102f1919060006101008201905073ffffffffffffffffffffffffffffffffffffffff80845116835260208401516020840152806040850151166040840152606084015160608401526080840151608084015260a084015160a08401528060c08501511660c08401525060e083015160e083015292915050565b3480156106d657600080fd5b506106ea6106e53660046152da565b611c80565b6040805173ffffffffffffffffffffffffffffffffffffffff90941684526020840192909252908201526060016102f1565b34801561072857600080fd5b5061073c61073736600461534e565b611d1f565b6040516102f19695949392919061551a565b61023a61075c366004615383565b611d92565b34801561076d57600080fd5b506103bd61077c36600461557b565b611eae565b61023a61078f3660046155b6565b6120dd565b3480156107a057600080fd5b506103bd6107af3660046152da565b612524565b3480156107c057600080fd5b507f00000000000000000000000067df244584b67e8c51b10ad610aaffa9a402fdb66102d0565b3480156107f357600080fd5b5061023a61080236600461529f565b612596565b610810816126e0565b60008281526117776020526040902061084a61082a61271a565b600283015473ffffffffffffffffffffffffffffffffffffffff16612729565b600581015415610886576040517f5aea7c4700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b818160070154036108c3576040517f4b669ac700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6007810182905560405182815283907f0c0f2662914f0cd1e952db2aa425901cb00e7c1f507687d22cb04e836d55d9c79060200160405180910390a2505050565b61090c6127a6565b600081815261177760209081526040808320815161016081018352815473ffffffffffffffffffffffffffffffffffffffff908116825260018301549482019490945260028201548085169382019390935274010000000000000000000000000000000000000000928390046bffffffffffffffffffffffff16606082015260038201546080820152600482015460a0820152600582015460c0820152600682015493841660e082015291830467ffffffffffffffff166101008301527c010000000000000000000000000000000000000000000000000000000090920463ffffffff1661012082015260079091015461014082015290610a0b61271a565b9050610a1b818360400151612729565b60c082015115610a57576040517f5aea7c4700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b815173ffffffffffffffffffffffffffffffffffffffff1660009081526117766020908152604080832082860180518552908352818420849055868452611777909252822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001681556001810183905560028101839055600381018390556004810183905560058101839055600681018390556007019190915582519051610b0291908361281b565b60405183907f14b9c40404d5b41deb481f9a40b8aeb2bf4b47679b38cf757075a66ed510f7f190600090a25050610b3a6001610b8755565b50565b610b456127a6565b73ffffffffffffffffffffffffffffffffffffffff8083166000908152611f4e6020908152604080832085845290915281205490911690610b8461271a565b905073ffffffffffffffffffffffffffffffffffffffff8216610bd3576040517fc09f8e8200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614610c55576040517ff049b41a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff831660048201526024015b60405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff84166000908152611f4e60209081526040808320868452909152812055610c9284848461281b565b604051839073ffffffffffffffffffffffffffffffffffffffff8616907f70c7877531c04c7d9caa8a7eca127384f04e8a6ee58b63f778ce5401d8bcae4190600090a35050610ce26001610b8755565b5050565b73ffffffffffffffffffffffffffffffffffffffff82166000908152612337602090815260408083208484529091528120805463ffffffff16421115610d30576000915050610d8a565b8054600182015470010000000000000000000000000000000090910460201b73ffffffffffffffffffffffffffffffff00000000167401000000000000000000000000000000000000000090910463ffffffff16175b9150505b92915050565b610d986127a6565b73ffffffffffffffffffffffffffffffffffffffff84166000908152612337602090815260408083208684529091529020805463ffffffff16421115610e155780546040517f8c9e57cf00000000000000000000000000000000000000000000000000000000815263ffffffff9091166004820152602401610c4c565b805464010000000090046bffffffffffffffffffffffff16821115610e805780546040517f242373610000000000000000000000000000000000000000000000000000000081526401000000009091046bffffffffffffffffffffffff166004820152602401610c4c565b600181015473ffffffffffffffffffffffffffffffffffffffff848116911614610ef45760018101546040517fa7d95dc300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602401610c4c565b610efe8585612826565b50610f0a6001610b8755565b50505050565b73ffffffffffffffffffffffffffffffffffffffff8083166000908152611f4e602090815260408083208584529091528120549091169081610f7357507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610fcd565b5073ffffffffffffffffffffffffffffffffffffffff83166000908152611f4e602090815260408083208584529091529020547401000000000000000000000000000000000000000090046bffffffffffffffffffffffff165b9250929050565b60008181526117776020526040812060058101548203610ff8576007015492915050565b6110058160070154612d11565b9392505050565b73ffffffffffffffffffffffffffffffffffffffff8316600090815261129360209081526040808320858452909152902054819003611077576040517fe5c3f26300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6110818383612d33565b801561111f5761109081421190565b156110c7576040517fdbaca9d100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6110d56303c267004261561e565b81111561111f576110ea6303c267004261561e565b6040517f857c0df1000000000000000000000000000000000000000000000000000000008152600401610c4c91815260200190565b73ffffffffffffffffffffffffffffffffffffffff8316600090815261129360209081526040808320858452909152902081905561115b61271a565b73ffffffffffffffffffffffffffffffffffffffff16828473ffffffffffffffffffffffffffffffffffffffff167fc8a67fb17ad40fabf835a5c96e0438644464257af6f325f44f1615aabc0e41ba846040516111ba91815260200190565b60405180910390a4505050565b60006110058383612d73565b60006111e0858585612d7f565b156111ed5750600061162b565b6111f78585612e10565b1561122e576040517f83a483f500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff851660009081526123376020908152604080832087845290915281209061126861271a565b825490915063ffffffff1642111561133c576040517f4ec58ed700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8281166004830152602482018790527f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d504431690634ec58ed790349060440160206040518083038185885af1158015611310573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906113359190615631565b9250611497565b81546000906113609064010000000090046bffffffffffffffffffffffff16612d11565b90508086101561139f576040517fe40a30e600000000000000000000000000000000000000000000000000000000815260048101829052602401610c4c565b600183015483546040517f5fdec56100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff928316600482015263ffffffff821660248201526401000000009091046bffffffffffffffffffffffff1660448201528382166064820152608481018890527f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d5044390911690635fdec56190349060a40160206040518083038185885af115801561146e573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906114939190615631565b9350505b6001820180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691909117909155825463ffffffff85167fffffffffffffffffffffffffffffffff00000000000000000000000000000000909116176401000000006bffffffffffffffffffffffff8816021783557f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d5044381169085160361155557600093505b81546fffffffffffffffffffffffffffffffff908116602086811c909216700100000000000000000000000000000000021783556001830180547fffffffffffffffff00000000ffffffffffffffffffffffffffffffffffffffff167401000000000000000000000000000000000000000063ffffffff8816021790556040805187815291820185905273ffffffffffffffffffffffffffffffffffffffff838116928992918b16917ece0a712e4e277ac7b34942865f0de7a5629dffe0539b70423ad5ff1ed6ab42910160405180910390a450505b949350505050565b61163b6127a6565b600081815261177760205260408120600501549003611686576040517f4b6ad8fa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611691816000612e69565b610b3a6001610b8755565b6116a46127a6565b6116af83838361322c565b611950576bffffffffffffffffffffffff8111156116f9576040517f35ec82cb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8381166000908152611f4e602090815260408083208684529091529020805490918116907401000000000000000000000000000000000000000090046bffffffffffffffffffffffff168314801561177b575073ffffffffffffffffffffffffffffffffffffffff811615155b156117b2576040517fb6950f3600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b815473ffffffffffffffffffffffffffffffffffffffff16740100000000000000000000000000000000000000006bffffffffffffffffffffffff85160217825560006117fd61271a565b905073ffffffffffffffffffffffffffffffffffffffff82166118675761182486866132a1565b82547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff82161783556118e4565b8073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16146118e4576040517f697d918e00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83166004820152602401610c4c565b8073ffffffffffffffffffffffffffffffffffffffff16858773ffffffffffffffffffffffffffffffffffffffff167ffcc77ea8bdcce862f43b7fb00fe6b0eb90d6aeead27d3800d9257cf7a05f9d968760405161194491815260200190565b60405180910390a45050505b61195b6001610b8755565b505050565b60006060806000806000806040518061012001604052807f00000000000000000000000000000000000000000000000000000000000001f481526020017f0000000000000000000000000000000000000000000000000000000000000000151581526020018e73ffffffffffffffffffffffffffffffffffffffff1681526020018d81526020018b81526020018c73ffffffffffffffffffffffffffffffffffffffff1681526020018a73ffffffffffffffffffffffffffffffffffffffff1681526020018961ffff168152602001611a3761271a565b73ffffffffffffffffffffffffffffffffffffffff1681525090507f000000000000000000000000d0e072fac9aa5daed50660f02f1e61cacbe6ecd273ffffffffffffffffffffffffffffffffffffffff16630e13eac1826040518263ffffffff1660e01b8152600401611aab919061564a565b600060405180830381865afa158015611ac8573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052611b0e9190810190615833565b809750819850829950839a50849b50859c505050505050505096509650965096509650969050565b611bc0604051806101000160405280600073ffffffffffffffffffffffffffffffffffffffff16815260200160008152602001600073ffffffffffffffffffffffffffffffffffffffff168152602001600081526020016000815260200160008152602001600073ffffffffffffffffffffffffffffffffffffffff168152602001600081525090565b600082815261177760205260408120600681015490917c010000000000000000000000000000000000000000000000000000000090910463ffffffff1690819003611c0b5750620151805b6040805161010081018252835473ffffffffffffffffffffffffffffffffffffffff9081168252600185015460208301526002850154811692820192909252606081019290925261012c6080830152600583015460a083015260068301541660c082015260079091015460e082015292915050565b73ffffffffffffffffffffffffffffffffffffffff8216600090815261233760209081526040808320848452909152812080548291829163ffffffff16421115611cd557600080600093509350935050611d18565b6001810154905473ffffffffffffffffffffffffffffffffffffffff909116935063ffffffff8116925064010000000090046bffffffffffffffffffffffff1690505b9250925092565b600080606080600080611d3289896132ab565b9050611d438989838a600080611960565b5093995091965094509250600090505b8351811015611d8557838181518110611d6e57611d6e61591d565b602002602001015186019550806001019050611d53565b5093975093979195509350565b73ffffffffffffffffffffffffffffffffffffffff84166000908152611f4e60209081526040808320868452909152902080547401000000000000000000000000000000000000000090046bffffffffffffffffffffffff16831015611e4e5780546040517f16b5016f000000000000000000000000000000000000000000000000000000008152740100000000000000000000000000000000000000009091046bffffffffffffffffffffffff166004820152602401610c4c565b805473ffffffffffffffffffffffffffffffffffffffff16611e9c576040517fda48e18400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611ea7858584613363565b5050505050565b6000611eb86127a6565b611ec283836135b6565b9150611ecc61365b565b9050611ed885856132a1565b73ffffffffffffffffffffffffffffffffffffffff851660009081526117766020908152604080832087845290915290205415611f785773ffffffffffffffffffffffffffffffffffffffff8516600090815261177660209081526040808320878452909152908190205490517f7618a0030000000000000000000000000000000000000000000000000000000081526004810191909152602401610c4c565b6000611f8261271a565b73ffffffffffffffffffffffffffffffffffffffff8781166000818152611776602090815260408083208b84528252808320889055878352611777909152902080547fffffffffffffffffffffffff000000000000000000000000000000000000000090811690921781556001810189905560028101805490921692841692909217905560078101869055909150620151808414612068576006810180547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167c010000000000000000000000000000000000000000000000000000000063ffffffff8716021790555b6040805185815261012c602082015290810186905260608101849052869073ffffffffffffffffffffffffffffffffffffffff808a1691908516907f1062dd3b35f12b4064331244d00f40c1d4831965e4285654157a2409c6217cff9060800160405180910390a4505061162b6001610b8755565b6120e56127a6565b6000838152611777602052604081206007810154909103612132576040517f125197d100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6005810154600061214161271a565b90507f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d5044373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff160361219b57600093505b73ffffffffffffffffffffffffffffffffffffffff84161515806121be57508115155b1561224f5760028301805473ffffffffffffffffffffffffffffffffffffffff16604086901c6bffffffffffffffffffffffff1674010000000000000000000000000000000000000000908102919091179091556006840180547fffffffff0000000000000000ffffffffffffffffffffffffffffffffffffffff1667ffffffffffffffff87169092029190911790555b8160000361235457826007015485101561229d5782600701546040517f31e6f71c000000000000000000000000000000000000000000000000000000008152600401610c4c91815260200190565b825460018401546122c49173ffffffffffffffffffffffffffffffffffffffff1690613683565b600783018590556006830180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff831617908190557c0100000000000000000000000000000000000000000000000000000000900463ffffffff1660008190036123445750620151805b42016005840181905591506124b7565b61235d82421190565b15612397576040517f3feeb88d00000000000000000000000000000000000000000000000000000000815260048101839052602401610c4c565b600683015473ffffffffffffffffffffffffffffffffffffffff8083169116036123ed576040517fe140576800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006123fc8460070154612d11565b90508086101561243b576040517fcd698a1900000000000000000000000000000000000000000000000000000000815260048101829052602401610c4c565b506007830180546006850180549288905573ffffffffffffffffffffffffffffffffffffffff8481167fffffffffffffffffffffffff00000000000000000000000000000000000000008516179091559091164261012c01808510156124a657600586018190559350835b506124b48183614e2061368d565b50505b6124c381866000613875565b604080518681526020810184905273ffffffffffffffffffffffffffffffffffffffff83169188917f26ea3ebbda62eb1baef13e1c237dddd956c87f80b2801f2616d806d52557b121910160405180910390a350505061195b6001610b8755565b73ffffffffffffffffffffffffffffffffffffffff82166000908152612337602090815260408083208484529091528120805463ffffffff16421161258c5780546125849064010000000090046bffffffffffffffffffffffff16612d11565b915050610d8a565b5060019392505050565b600054600290610100900460ff161580156125b8575060005460ff8083169116105b612644576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152608401610c4c565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00001660ff83161761010017905561267e82613991565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff16905560405160ff821681527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15050565b80600003610b3a576040517f7378a92d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006127246139e4565b905090565b8073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614610ce2576040517f0907a37700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610c4c565b6002610b875403612813576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610c4c565b6002610b8755565b61195b838383613a50565b73ffffffffffffffffffffffffffffffffffffffff828116600090815261233760209081526040808320858452808352818420825160a081018452815463ffffffff808216835264010000000082046bffffffffffffffffffffffff9081168489019081527001000000000000000000000000000000009093046fffffffffffffffffffffffffffffffff1684880152600185018054808c1660608701908152740100000000000000000000000000000000000000008204851660808801528d8c5297909952989094557fffffffffffffffff00000000000000000000000000000000000000000000000090961690965591518251955193517f4dc8fb3c000000000000000000000000000000000000000000000000000000008152908716600482015294909316602485015291166044830152917f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d504431690634dc8fb3c90606401600060405180830381600087803b1580156129a157600080fd5b505af11580156129b5573d6000803e3d6000fd5b5050505060006129c361271a565b90506000806129eb838787876060015188602001516bffffffffffffffffffffffff16613a5b565b6040517f6352211e00000000000000000000000000000000000000000000000000000000815260048101889052919350915060009073ffffffffffffffffffffffffffffffffffffffff881690636352211e90602401602060405180830381865afa158015612a5e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a82919061594c565b90503073ffffffffffffffffffffffffffffffffffffffff821603612ab657612ab18787876060015187613b57565b612b4d565b60608501516040517f23b872dd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8681166004830152918216602482015260448101889052908816906323b872dd90606401600060405180830381600087803b158015612b3457600080fd5b505af1158015612b48573d6000803e3d6000fd5b505050505b6000806000612c616040518061014001604052808c73ffffffffffffffffffffffffffffffffffffffff1681526020018b8152602001600181526020018a6060015173ffffffffffffffffffffffffffffffffffffffff1681526020018973ffffffffffffffffffffffffffffffffffffffff1681526020018a602001516bffffffffffffffffffffffff168152602001612c168b604001518c6080015173ffffffffffffffffffffffffffffffff0000000060209290921b9190911663ffffffff9091161790565b73ffffffffffffffffffffffffffffffffffffffff1681526020018873ffffffffffffffffffffffffffffffffffffffff1681526020018761ffff1681526020016000815250613b63565b925092509250876060015173ffffffffffffffffffffffffffffffffffffffff16898b73ffffffffffffffffffffffffffffffffffffffff167f1cb8adb37d6d35e94cd0695ca39895b84371864713f5ca7eada52af9ff23744b8a878787604051612cfd949392919073ffffffffffffffffffffffffffffffffffffffff94909416845260208401929092526040830152606082015260800190565b60405180910390a450505050505050505050565b6000600a8204808203612d295761100583600161561e565b611005838261561e565b612d3d8282613b7e565b610ce2576040517fe390a4ec00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006110058383613b8a565b73ffffffffffffffffffffffffffffffffffffffff8084166000908152611f4e602090815260408083208684529091528120805491929091161580612dea575080547401000000000000000000000000000000000000000090046bffffffffffffffffffffffff1683105b15612df9576000915050611005565b612e0585856000613363565b506001949350505050565b73ffffffffffffffffffffffffffffffffffffffff82166000908152611776602090815260408083208484529091528120548015801590610d865750600090815261177760205260409020600501544211159392505050565b60008281526117776020908152604091829020825161016081018452815473ffffffffffffffffffffffffffffffffffffffff908116825260018301549382019390935260028201548084169482019490945274010000000000000000000000000000000000000000938490046bffffffffffffffffffffffff16606082015260038201546080820152600482015460a0820152600582015460c08201819052600683015493841660e083015293830467ffffffffffffffff166101008201527c010000000000000000000000000000000000000000000000000000000090920463ffffffff1661012083015260070154610140820152904211612fa1578060c001516040517f3a017f60000000000000000000000000000000000000000000000000000000008152600401610c4c91815260200190565b600080612fc68360400151846000015185602001518660e00151876101400151613a5b565b845173ffffffffffffffffffffffffffffffffffffffff166000908152611776602090815260408083208289015184528252808320839055898352611777909152812080547fffffffffffffffffffffffff00000000000000000000000000000000000000001681556001810182905560028101829055600381018290556004810182905560058101829055600681018290556007015590925090508361308057613080836000015184602001518560e001516000613bcd565b6000806000613199604051806101400160405280886000015173ffffffffffffffffffffffffffffffffffffffff16815260200188602001518152602001600181526020018860e0015173ffffffffffffffffffffffffffffffffffffffff168152602001886040015173ffffffffffffffffffffffffffffffffffffffff168152602001886101400151815260200188610100015167ffffffffffffffff1660408a606001516bffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16901b1773ffffffffffffffffffffffffffffffffffffffff1681526020018773ffffffffffffffffffffffffffffffffffffffff1681526020018661ffff1681526020016000815250613b63565b9250925092508560e0015173ffffffffffffffffffffffffffffffffffffffff16866040015173ffffffffffffffffffffffffffffffffffffffff16897f2edb0e99c6ac35be6731dab554c1d1fa1b7beb675090dbb09fb14e615aca1c4a86868660405161321a939291909283526020830191909152604082015260600190565b60405180910390a45050505050505050565b73ffffffffffffffffffffffffffffffffffffffff83166000908152612337602090815260408083208584529091528120805463ffffffff164211806132885750805464010000000090046bffffffffffffffffffffffff1683115b15613297576000915050611005565b612e058585612826565b610ce28282613caf565b60006132b78383612d73565b905073ffffffffffffffffffffffffffffffffffffffff8116610d8a576040517f6352211e0000000000000000000000000000000000000000000000000000000081526004810183905273ffffffffffffffffffffffffffffffffffffffff841690636352211e90602401602060405180830381865afa15801561333f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611005919061594c565b61336b6127a6565b6133758383613d76565b73ffffffffffffffffffffffffffffffffffffffff8381166000908152611f4e60209081526040808320868452808352818420825180840190935280549586168352740100000000000000000000000000000000000000009095046bffffffffffffffffffffffff168284015286845290915291556133f48484613de1565b60006133fe61271a565b905061341e8183602001516bffffffffffffffffffffffff166001613875565b600080613444846000015188888688602001516bffffffffffffffffffffffff16613a5b565b915091506134558787856000613b57565b60008060006135326040518061014001604052808c73ffffffffffffffffffffffffffffffffffffffff1681526020018b8152602001600181526020018873ffffffffffffffffffffffffffffffffffffffff168152602001896000015173ffffffffffffffffffffffffffffffffffffffff16815260200189602001516bffffffffffffffffffffffff1681526020018a73ffffffffffffffffffffffffffffffffffffffff1681526020018773ffffffffffffffffffffffffffffffffffffffff1681526020018661ffff1681526020016000815250613b63565b89516040805173ffffffffffffffffffffffffffffffffffffffff8c8116825260208201879052918101859052606081018490529497509295509093508116918b918d16907fd28c0a7dd63bc853a4e36306655da9f8c0b29ff9d0605bb976ae420e46a999309060800160405180910390a45050505050505061195b6001610b8755565b60006135c1836126e0565b816000036135d3575062015180610d8a565b62093a80821115613615576040517f5b7d3a4100000000000000000000000000000000000000000000000000000000815262093a806004820152602401610c4c565b61012c821015613655576040517f4b7f44ee00000000000000000000000000000000000000000000000000000000815261012c6004820152602401610c4c565b50919050565b600061138d546000036136735761138d805460010190555b5061138d80546001810190915590565b610ce28282613e48565b8160000361369a57505050565b7f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d5044373ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603613711577f00000000000000000000000067df244584b67e8c51b10ad610aaffa9a402fdb692505b60008373ffffffffffffffffffffffffffffffffffffffff16838390604051600060405180830381858888f193505050503d806000811461376e576040519150601f19603f3d011682016040523d82523d6000602084013e613773565b606091505b5050905080610f0a576040517faa67c91900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301527f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d50443169063aa67c9199085906024016000604051808303818588803b15801561380657600080fd5b505af115801561381a573d6000803e3d6000fd5b50505050508373ffffffffffffffffffffffffffffffffffffffff167fa2201512569adb2d513531dfd69b66df50bd5cffb8c1bbe65a4611f9e1eadbd18460405161386791815260200190565b60405180910390a250505050565b3482111561392b576040517f452f2b8f00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff848116600483015234840360248301527f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d50443169063452f2b8f90604401600060405180830381600087803b15801561390e57600080fd5b505af1158015613922573d6000803e3d6000fd5b50505050505050565b3482101561195b57801561395c5761195b73ffffffffffffffffffffffffffffffffffffffff841634849003613e5c565b6040517fabf2f33b00000000000000000000000000000000000000000000000000000000815260048101839052602401610c4c565b8015610b3a5761138d54156139d2576040517feb2e06c000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6139dd81600161561e565b61138d5550565b3373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000762340b8a40cdd5bfc3edd94265899fda345d0e3168103613a4d57507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec36013560601c90565b90565b61195b838383613fb6565b6040517f3221782100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8681166004830152858116602483015260448201859052838116606483015260848201839052600091829182917f00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb79091169063322178219060a4016060604051808303816000875af1158015613b10573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613b349190615969565b909450925090508015613b4c57613b4c888888613ffa565b509550959350505050565b610f0a848484846140dd565b6000806000613b71846141ae565b9196909550909350915050565b600061100583836141bc565b73ffffffffffffffffffffffffffffffffffffffff8083166000908152611f4e602090815260408083208584529091529020541680610d8a576110058383614296565b73ffffffffffffffffffffffffffffffffffffffff811615613c1b576040517f57a016b300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f23b872dd00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff8381166024830152604482018590528516906323b872dd90606401600060405180830381600087803b158015613c9157600080fd5b505af1158015613ca5573d6000803e3d6000fd5b5050505050505050565b73ffffffffffffffffffffffffffffffffffffffff8083166000908152611f4e602090815260408083208584529091529020541680613cf25761195b83836142e4565b613cfa61271a565b73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161461195b576040517f32f3b03300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610c4c565b73ffffffffffffffffffffffffffffffffffffffff8216600090815261129360209081526040808320848452909152902054421015610ce2576040517f4917db3f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff82166000908152612337602090815260408083208484529091529020613e1a61271a565b600182015473ffffffffffffffffffffffffffffffffffffffff91821691160361195b5761195b8383614446565b613e528282614446565b610ce2828261465f565b80471015613ec6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e63650000006044820152606401610c4c565b60008273ffffffffffffffffffffffffffffffffffffffff168260405160006040518083038185875af1925050503d8060008114613f20576040519150601f19603f3d011682016040523d82523d6000602084013e613f25565b606091505b505090508061195b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260448201527f6563697069656e74206d617920686176652072657665727465640000000000006064820152608401610c4c565b73ffffffffffffffffffffffffffffffffffffffff8084166000908152611f4e602090815260408083208684529091529020541680610f0a57610f0a8484846146af565b60405173ffffffffffffffffffffffffffffffffffffffff8316602482015260448101829052610f0a907f00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb790606401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f93251fba0000000000000000000000000000000000000000000000000000000017905273ffffffffffffffffffffffffffffffffffffffff861691906146f2565b73ffffffffffffffffffffffffffffffffffffffff8085166000908152611f4e602090815260408083208784529091529020541680156141a2578173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614614194576040517f32f3b03300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610c4c565b600091506141a2858561471b565b611ea78585858561477b565b6000806000613b71846149b7565b73ffffffffffffffffffffffffffffffffffffffff8083166000908152611f4e6020908152604080832085845290915281205490911680156142855761420061271a565b73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161461427c576040517fc89fba3b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610c4c565b6001915061428f565b610d8684846149c5565b5092915050565b73ffffffffffffffffffffffffffffffffffffffff80831660009081526117766020908152604080832085845282528083205483526117779091529020600201541680610d8a576000611005565b73ffffffffffffffffffffffffffffffffffffffff8216600090815261177660209081526040808320848452909152812054908190036143285761195b8383614a64565b6000818152611777602052604081209061434061271a565b905081600501546000036143c757600282015473ffffffffffffffffffffffffffffffffffffffff8281169116146143c25760028201546040517fe64526ee00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602401610c4c565b611ea7565b600682015473ffffffffffffffffffffffffffffffffffffffff82811691161461443b5760068201546040517fe64526ee00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602401610c4c565b611ea7836001612e69565b73ffffffffffffffffffffffffffffffffffffffff821660009081526123376020908152604080832084845290915290205463ffffffff164211610ce25773ffffffffffffffffffffffffffffffffffffffff828116600090815261233760209081526040808320858452808352818420825160a081018452815463ffffffff808216835264010000000082046bffffffffffffffffffffffff9081168489019081527001000000000000000000000000000000009093046fffffffffffffffffffffffffffffffff1684880152600185018054808c1660608701908152740100000000000000000000000000000000000000008204851660808801528d8c5297909952989094557fffffffffffffffff00000000000000000000000000000000000000000000000090961690965591518251955193517f345db493000000000000000000000000000000000000000000000000000000008152908716600482015294909316602485015291166044830152917f00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d50443169063345db49390606401600060405180830381600087803b1580156145ff57600080fd5b505af1158015614613573d6000803e3d6000fd5b505060405184925073ffffffffffffffffffffffffffffffffffffffff861691507f30c264456cbd17f5f67d7534654161414f34c0e6cc1b7500e169b7a7aea4afc090600090a3505050565b73ffffffffffffffffffffffffffffffffffffffff8083166000908152611f4e6020908152604080832085845290915290208054909116156146a5576146a5838361471b565b61195b8383614b17565b73ffffffffffffffffffffffffffffffffffffffff8316600090815261177660209081526040808320858452909152812054900361195b5761195b838383614b21565b60606000614701858585614b36565b92509050806147135761471382614beb565b509392505050565b73ffffffffffffffffffffffffffffffffffffffff82166000818152611f4e60209081526040808320858452909152808220829055518392917faa6271d89a385571e237d3e7254ccc7c09f68055e6e9b410ed08233a8b9a05cf91a35050565b73ffffffffffffffffffffffffffffffffffffffff841660009081526117766020908152604080832086845290915290205480156149ab5760008181526117776020526040812060058101549091036149265773ffffffffffffffffffffffffffffffffffffffff8316158015906148105750600281015473ffffffffffffffffffffffffffffffffffffffff848116911614155b156148655760028101546040517fe64526ee00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602401610c4c565b73ffffffffffffffffffffffffffffffffffffffff8616600090815261177660209081526040808320888452825280832083905584835261177790915280822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001681556001810183905560028101839055600381018390556004810183905560058101839055600681018390556007018290555183917f5603897cc9b1e866f3f7395ffc6638776041f21c094d0b4e748ff44c407fa36291a26149a5565b600681015473ffffffffffffffffffffffffffffffffffffffff84811691161461499a5760068101546040517fe64526ee00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602401610c4c565b6149a5826001612e69565b60009250505b611ea785858585613bcd565b6000806000613b7184614c2d565b73ffffffffffffffffffffffffffffffffffffffff82166000908152611776602090815260408083208484529091528120548015614a5d57600081815261177760205260409020614a1761082a61271a565b600581015415614a53576040517fa2a745e900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600192505061428f565b6000610d86565b8173ffffffffffffffffffffffffffffffffffffffff166323b872dd614a8861271a565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff909116600482015230602482015260448101849052606401600060405180830381600087803b158015614afb57600080fd5b505af1158015614b0f573d6000803e3d6000fd5b505050505050565b610ce28282614c4d565b614b2b8383614c57565b61195b838383614d34565b600060608373ffffffffffffffffffffffffffffffffffffffff168386604051602001614b649291906159d1565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815290829052614b9c91615a11565b6000604051808303816000865af19150503d8060008114614bd9576040519150601f19603f3d011682016040523d82523d6000602084013e614bde565b606091505b5090969095509350505050565b805115614bfb5780518082602001fd5b6040517f3cfe059f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000806000614c4484600001518560200151614c57565b613b7184614d4a565b610ce28282613d76565b73ffffffffffffffffffffffffffffffffffffffff821660009081526112936020908152604080832084845290915290205415610ce25773ffffffffffffffffffffffffffffffffffffffff8216600090815261129360209081526040808320848452909152812055614cc861271a565b73ffffffffffffffffffffffffffffffffffffffff16818373ffffffffffffffffffffffffffffffffffffffff167fc8a67fb17ad40fabf835a5c96e0438644464257af6f325f44f1615aabc0e41ba6000604051614d2891815260200190565b60405180910390a45050565b614d3f8184846150c1565b61195b838383615270565b6000806000606080600080614d7c886000015189602001518a608001518b60a001518c60c001518d6101000151611960565b6101208e0151959c50919950929750909550909350915015614e8957610120880151614da8908861561e565b60c089015190975073ffffffffffffffffffffffffffffffffffffffff1615614e0257614dd96107d0612710615a1d565b886101200151614de99190615a1d565b614df3908361561e565b9150614dff8288615a58565b96505b876060015173ffffffffffffffffffffffffffffffffffffffff168860200151896000015173ffffffffffffffffffffffffffffffffffffffff167f128e6430bdac5e0f43d2f25064b707cadc6dbb27cf69958b934944df4b23c4238b61012001518c60400151604051614e80929190918252602082015260400190565b60405180910390a45b60008451600114614e9c57614e20614ea1565b620334505b905060005b8551811015614f1557614eec868281518110614ec457614ec461591d565b6020026020010151868381518110614ede57614ede61591d565b60200260200101518461368d565b848181518110614efe57614efe61591d565b602002602001015188019750806001019050614ea6565b5050614f28886080015186614e2061368d565b8615614f6e57614f6e73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000067df244584b67e8c51b10ad610aaffa9a402fdb61688613e5c565b8115614ff157614f858860c0015183614e2061368d565b602088810151895160c08b01516040805173ffffffffffffffffffffffffffffffffffffffff92831681529485018790526000858201525192939116917f141b92fd9766c80ab120598ea2f6be9802470ec59b5446dd9bf46214ead8d08e9181900360600190a3958101955b6101208801511561500757876101200151870396505b60e088015173ffffffffffffffffffffffffffffffffffffffff16156150b6578015615054578460000361503e5794850194615043565b938401935b6150548860e0015182614e2061368d565b602080890151895160e08b01516040805173ffffffffffffffffffffffffffffffffffffffff928316815294850186905292939116917f27a4dd4ff659a9e6354fb079b2208365e5b83f55c22a4150eee2bca89501cb98910160405180910390a35b505050509193909250565b6040517f8f40f37800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83811660048301526024820183905284811660448301526000917f00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb790911690638f40f378906064016040805180830381865afa158015615160573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906151849190615a6b565b5090508015610f0a5760405173ffffffffffffffffffffffffffffffffffffffff8416602482015260448101839052611ea7907f00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb790606401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f93251fba0000000000000000000000000000000000000000000000000000000017905273ffffffffffffffffffffffffffffffffffffffff871691906146f2565b61195b8383836000613b57565b6000806040838503121561529057600080fd5b50508035926020909101359150565b6000602082840312156152b157600080fd5b5035919050565b73ffffffffffffffffffffffffffffffffffffffff81168114610b3a57600080fd5b600080604083850312156152ed57600080fd5b82356152f8816152b8565b946020939093013593505050565b6000806000806080858703121561531c57600080fd5b8435615327816152b8565b935060208501359250604085013561533e816152b8565b9396929550929360600135925050565b60008060006060848603121561536357600080fd5b833561536e816152b8565b95602085013595506040909401359392505050565b6000806000806080858703121561539957600080fd5b84356153a4816152b8565b9350602085013592506040850135915060608501356153c2816152b8565b939692955090935050565b61ffff81168114610b3a57600080fd5b60008060008060008060c087890312156153f657600080fd5b8635615401816152b8565b9550602087013594506040870135615418816152b8565b935060608701359250608087013561542f816152b8565b915060a087013561543f816153cd565b809150509295509295509295565b60008151808452602080850194506020840160005b8381101561549457815173ffffffffffffffffffffffffffffffffffffffff1687529582019590820190600101615462565b509495945050505050565b60008151808452602080850194506020840160005b83811015615494578151875295820195908201906001016154b4565b86815260c0602082015260006154e960c083018861544d565b82810360408401526154fb818861549f565b60608401969096525050608081019290925260a0909101529392505050565b86815285602082015260c06040820152600061553960c083018761544d565b828103606084015261554b818761549f565b91505083608083015273ffffffffffffffffffffffffffffffffffffffff831660a0830152979650505050505050565b6000806000806080858703121561559157600080fd5b843561559c816152b8565b966020860135965060408601359560600135945092505050565b6000806000606084860312156155cb57600080fd5b833592506020840135915060408401356155e4816152b8565b809150509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b80820180821115610d8a57610d8a6155ef565b60006020828403121561564357600080fd5b5051919050565b6000610120820190508251825260208301511515602083015273ffffffffffffffffffffffffffffffffffffffff6040840151166040830152606083015160608301526080830151608083015260a08301516156be60a084018273ffffffffffffffffffffffffffffffffffffffff169052565b5060c08301516156e660c084018273ffffffffffffffffffffffffffffffffffffffff169052565b5060e08301516156fc60e084018261ffff169052565b506101009283015173ffffffffffffffffffffffffffffffffffffffff16919092015290565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff8111828210171561579857615798615722565b604052919050565b600067ffffffffffffffff8211156157ba576157ba615722565b5060051b60200190565b600082601f8301126157d557600080fd5b815160206157ea6157e5836157a0565b615751565b8083825260208201915060208460051b87010193508684111561580c57600080fd5b602086015b848110156158285780518352918301918301615811565b509695505050505050565b60008060008060008060c0878903121561584c57600080fd5b8651955060208088015167ffffffffffffffff8082111561586c57600080fd5b818a0191508a601f83011261588057600080fd5b815161588e6157e5826157a0565b81815260059190911b8301840190848101908d8311156158ad57600080fd5b938501935b828510156158d45784516158c5816152b8565b825293850193908501906158b2565b60408d0151909a5094505050808311156158ed57600080fd5b50506158fb89828a016157c4565b945050606087015192506080870151915060a087015190509295509295509295565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60006020828403121561595e57600080fd5b8151611005816152b8565b60008060006060848603121561597e57600080fd5b835192506020840151615990816152b8565b60408501519092506155e4816153cd565b6000815160005b818110156159c257602081850181015186830152016159a8565b50600093019283525090919050565b60006159dd82856159a1565b60609390931b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000001683525050601401919050565b600061100582846159a1565b600082615a53577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b81810381811115610d8a57610d8a6155ef565b60008060408385031215615a7e57600080fd5b825191506020830151615a90816153cd565b80915050925092905056fea26469706673582212201eaaddb21e4d2997c6d3ad185b60f8e3ddfe6290fd909a50c3461ee837b6f7a964736f6c63430008190033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
00000000000000000000000067df244584b67e8c51b10ad610aaffa9a402fdb600000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d50443000000000000000000000000762340b8a40cdd5bfc3edd94265899fda345d0e3000000000000000000000000d0e072fac9aa5daed50660f02f1e61cacbe6ecd200000000000000000000000069525dac489e4718964b5615c3d794a25d62beb7
-----Decoded View---------------
Arg [0] : treasury (address): 0x67Df244584b67E8C51B10aD610aAfFa9a402FdB6
Arg [1] : feth (address): 0x49128CF8ABE9071ee24540a296b5DED3F9D50443
Arg [2] : router (address): 0x762340B8a40Cdd5BFC3eDD94265899FDa345D0E3
Arg [3] : marketUtils (address): 0xD0E072Fac9aA5DaED50660f02f1e61Cacbe6ECd2
Arg [4] : worldsNft (address): 0x69525Dac489e4718964B5615c3D794a25d62bEb7
-----Encoded View---------------
5 Constructor Arguments found :
Arg [0] : 00000000000000000000000067df244584b67e8c51b10ad610aaffa9a402fdb6
Arg [1] : 00000000000000000000000049128cf8abe9071ee24540a296b5ded3f9d50443
Arg [2] : 000000000000000000000000762340b8a40cdd5bfc3edd94265899fda345d0e3
Arg [3] : 000000000000000000000000d0e072fac9aa5daed50660f02f1e61cacbe6ecd2
Arg [4] : 00000000000000000000000069525dac489e4718964b5615c3d794a25d62beb7
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 30 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
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.