creation_sourcecode: {{
    "language": "Solidity",
    "sources": {
      "@openzeppelin-upgradeable/contracts/proxy/utils/Initializable.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed\n * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an\n * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer\n * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.\n *\n * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be\n * reused. This mechanism prevents re-execution of each \"step\" but allows the creation of new initialization steps in\n * case an upgrade adds a module that needs to be initialized.\n *\n * For example:\n *\n * [.hljs-theme-light.nopadding]\n * ```solidity\n * contract MyToken is ERC20Upgradeable {\n *     function initialize() initializer public {\n *         __ERC20_init(\"MyToken\", \"MTK\");\n *     }\n * }\n *\n * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {\n *     function initializeV2() reinitializer(2) public {\n *         __ERC20Permit_init(\"MyToken\");\n *     }\n * }\n * ```\n *\n * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as\n * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.\n *\n * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure\n * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.\n *\n * [CAUTION]\n * ====\n * Avoid leaving a contract uninitialized.\n *\n * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation\n * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke\n * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:\n *\n * [.hljs-theme-light.nopadding]\n * ```\n * /// @custom:oz-upgrades-unsafe-allow constructor\n * constructor() {\n *     _disableInitializers();\n * }\n * ```\n * ====\n */\nabstract contract Initializable {\n    /**\n     * @dev Storage of the initializable contract.\n     *\n     * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions\n     * when using with upgradeable contracts.\n     *\n     * @custom:storage-location erc7201:openzeppelin.storage.Initializable\n     */\n    struct InitializableStorage {\n        /**\n         * @dev Indicates that the contract has been initialized.\n         */\n        uint64 _initialized;\n        /**\n         * @dev Indicates that the contract is in the process of being initialized.\n         */\n        bool _initializing;\n    }\n\n    // keccak256(abi.encode(uint256(keccak256(\"openzeppelin.storage.Initializable\")) - 1)) & ~bytes32(uint256(0xff))\n    bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;\n\n    /**\n     * @dev The contract is already initialized.\n     */\n    error InvalidInitialization();\n\n    /**\n     * @dev The contract is not initializing.\n     */\n    error NotInitializing();\n\n    /**\n     * @dev Triggered when the contract has been initialized or reinitialized.\n     */\n    event Initialized(uint64 version);\n\n    /**\n     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,\n     * `onlyInitializing` functions can be used to initialize parent contracts.\n     *\n     * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any\n     * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in\n     * production.\n     *\n     * Emits an {Initialized} event.\n     */\n    modifier initializer() {\n        // solhint-disable-next-line var-name-mixedcase\n        InitializableStorage storage $ = _getInitializableStorage();\n\n        // Cache values to avoid duplicated sloads\n        bool isTopLevelCall = !$._initializing;\n        uint64 initialized = $._initialized;\n\n        // Allowed calls:\n        // - initialSetup: the contract is not in the initializing state and no previous version was\n        //                 initialized\n        // - construction: the contract is initialized at version 1 (no reininitialization) and the\n        //                 current contract is just being deployed\n        bool initialSetup = initialized == 0 && isTopLevelCall;\n        bool construction = initialized == 1 && address(this).code.length == 0;\n\n        if (!initialSetup && !construction) {\n            revert InvalidInitialization();\n        }\n        $._initialized = 1;\n        if (isTopLevelCall) {\n            $._initializing = true;\n        }\n        _;\n        if (isTopLevelCall) {\n            $._initializing = false;\n            emit Initialized(1);\n        }\n    }\n\n    /**\n     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the\n     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be\n     * used to initialize parent contracts.\n     *\n     * A reinitializer may be used after the original initialization step. This is essential to configure modules that\n     * are added through upgrades and that require initialization.\n     *\n     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`\n     * cannot be nested. If one is invoked in the context of another, execution will revert.\n     *\n     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in\n     * a contract, executing them in the right order is up to the developer or operator.\n     *\n     * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.\n     *\n     * Emits an {Initialized} event.\n     */\n    modifier reinitializer(uint64 version) {\n        // solhint-disable-next-line var-name-mixedcase\n        InitializableStorage storage $ = _getInitializableStorage();\n\n        if ($._initializing || $._initialized >= version) {\n            revert InvalidInitialization();\n        }\n        $._initialized = version;\n        $._initializing = true;\n        _;\n        $._initializing = false;\n        emit Initialized(version);\n    }\n\n    /**\n     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the\n     * {initializer} and {reinitializer} modifiers, directly or indirectly.\n     */\n    modifier onlyInitializing() {\n        _checkInitializing();\n        _;\n    }\n\n    /**\n     * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.\n     */\n    function _checkInitializing() internal view virtual {\n        if (!_isInitializing()) {\n            revert NotInitializing();\n        }\n    }\n\n    /**\n     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.\n     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized\n     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called\n     * through proxies.\n     *\n     * Emits an {Initialized} event the first time it is successfully executed.\n     */\n    function _disableInitializers() internal virtual {\n        // solhint-disable-next-line var-name-mixedcase\n        InitializableStorage storage $ = _getInitializableStorage();\n\n        if ($._initializing) {\n            revert InvalidInitialization();\n        }\n        if ($._initialized != type(uint64).max) {\n            $._initialized = type(uint64).max;\n            emit Initialized(type(uint64).max);\n        }\n    }\n\n    /**\n     * @dev Returns the highest version that has been initialized. See {reinitializer}.\n     */\n    function _getInitializedVersion() internal view returns (uint64) {\n        return _getInitializableStorage()._initialized;\n    }\n\n    /**\n     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.\n     */\n    function _isInitializing() internal view returns (bool) {\n        return _getInitializableStorage()._initializing;\n    }\n\n    /**\n     * @dev Returns a pointer to the storage namespace.\n     */\n    // solhint-disable-next-line var-name-mixedcase\n    function _getInitializableStorage() private pure returns (InitializableStorage storage $) {\n        assembly {\n            $.slot := INITIALIZABLE_STORAGE\n        }\n    }\n}\n"
      },
      "@openzeppelin-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/UUPSUpgradeable.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC1822Proxiable} from \"@openzeppelin/contracts/interfaces/draft-IERC1822.sol\";\nimport {ERC1967Utils} from \"@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol\";\nimport {Initializable} from \"./Initializable.sol\";\n\n/**\n * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an\n * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.\n *\n * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is\n * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing\n * `UUPSUpgradeable` with a custom implementation of upgrades.\n *\n * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.\n */\nabstract contract UUPSUpgradeable is Initializable, IERC1822Proxiable {\n    /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n    address private immutable __self = address(this);\n\n    /**\n     * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)`\n     * and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,\n     * while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string.\n     * If the getter returns `\"5.0.0\"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must\n     * be the empty byte string if no function should be called, making it impossible to invoke the `receive` function\n     * during an upgrade.\n     */\n    string public constant UPGRADE_INTERFACE_VERSION = \"5.0.0\";\n\n    /**\n     * @dev The call is from an unauthorized context.\n     */\n    error UUPSUnauthorizedCallContext();\n\n    /**\n     * @dev The storage `slot` is unsupported as a UUID.\n     */\n    error UUPSUnsupportedProxiableUUID(bytes32 slot);\n\n    /**\n     * @dev Check that the execution is being performed through a delegatecall call and that the execution context is\n     * a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case\n     * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a\n     * function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to\n     * fail.\n     */\n    modifier onlyProxy() {\n        _checkProxy();\n        _;\n    }\n\n    /**\n     * @dev Check that the execution is not being performed through a delegate call. This allows a function to be\n     * callable on the implementing contract but not through proxies.\n     */\n    modifier notDelegated() {\n        _checkNotDelegated();\n        _;\n    }\n\n    function __UUPSUpgradeable_init() internal onlyInitializing {\n    }\n\n    function __UUPSUpgradeable_init_unchained() internal onlyInitializing {\n    }\n    /**\n     * @dev Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the\n     * implementation. It is used to validate the implementation's compatibility when performing an upgrade.\n     *\n     * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks\n     * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this\n     * function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.\n     */\n    function proxiableUUID() external view virtual notDelegated returns (bytes32) {\n        return ERC1967Utils.IMPLEMENTATION_SLOT;\n    }\n\n    /**\n     * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call\n     * encoded in `data`.\n     *\n     * Calls {_authorizeUpgrade}.\n     *\n     * Emits an {Upgraded} event.\n     *\n     * @custom:oz-upgrades-unsafe-allow-reachable delegatecall\n     */\n    function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {\n        _authorizeUpgrade(newImplementation);\n        _upgradeToAndCallUUPS(newImplementation, data);\n    }\n\n    /**\n     * @dev Reverts if the execution is not performed via delegatecall or the execution\n     * context is not of a proxy with an ERC1967-compliant implementation pointing to self.\n     * See {_onlyProxy}.\n     */\n    function _checkProxy() internal view virtual {\n        if (\n            address(this) == __self || // Must be called through delegatecall\n            ERC1967Utils.getImplementation() != __self // Must be called through an active proxy\n        ) {\n            revert UUPSUnauthorizedCallContext();\n        }\n    }\n\n    /**\n     * @dev Reverts if the execution is performed via delegatecall.\n     * See {notDelegated}.\n     */\n    function _checkNotDelegated() internal view virtual {\n        if (address(this) != __self) {\n            // Must not be called through delegatecall\n            revert UUPSUnauthorizedCallContext();\n        }\n    }\n\n    /**\n     * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by\n     * {upgradeToAndCall}.\n     *\n     * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.\n     *\n     * ```solidity\n     * function _authorizeUpgrade(address) internal onlyOwner {}\n     * ```\n     */\n    function _authorizeUpgrade(address newImplementation) internal virtual;\n\n    /**\n     * @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call.\n     *\n     * As a security check, {proxiableUUID} is invoked in the new implementation, and the return value\n     * is expected to be the implementation slot in ERC1967.\n     *\n     * Emits an {IERC1967-Upgraded} event.\n     */\n    function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private {\n        try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {\n            if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) {\n                revert UUPSUnsupportedProxiableUUID(slot);\n            }\n            ERC1967Utils.upgradeToAndCall(newImplementation, data);\n        } catch {\n            // The implementation is not UUPS\n            revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation);\n        }\n    }\n}\n"
      },
      "@openzeppelin/contracts/interfaces/draft-IERC1822.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC1822.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified\n * proxy whose upgrades are fully controlled by the current implementation.\n */\ninterface IERC1822Proxiable {\n    /**\n     * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation\n     * address.\n     *\n     * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks\n     * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this\n     * function revert if invoked through a proxy.\n     */\n    function proxiableUUID() external view returns (bytes32);\n}\n"
      },
      "@openzeppelin/contracts/proxy/beacon/IBeacon.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/IBeacon.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev This is the interface that {BeaconProxy} expects of its beacon.\n */\ninterface IBeacon {\n    /**\n     * @dev Must return an address that can be used as a delegate call target.\n     *\n     * {UpgradeableBeacon} will check that this address is a contract.\n     */\n    function implementation() external view returns (address);\n}\n"
      },
      "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Utils.sol)\n\npragma solidity ^0.8.20;\n\nimport {IBeacon} from \"../beacon/IBeacon.sol\";\nimport {Address} from \"../../utils/Address.sol\";\nimport {StorageSlot} from \"../../utils/StorageSlot.sol\";\n\n/**\n * @dev This abstract contract provides getters and event emitting update functions for\n * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.\n */\nlibrary ERC1967Utils {\n    // We re-declare ERC-1967 events here because they can't be used directly from IERC1967.\n    // This will be fixed in Solidity 0.8.21. At that point we should remove these events.\n    /**\n     * @dev Emitted when the implementation is upgraded.\n     */\n    event Upgraded(address indexed implementation);\n\n    /**\n     * @dev Emitted when the admin account has changed.\n     */\n    event AdminChanged(address previousAdmin, address newAdmin);\n\n    /**\n     * @dev Emitted when the beacon is changed.\n     */\n    event BeaconUpgraded(address indexed beacon);\n\n    /**\n     * @dev Storage slot with the address of the current implementation.\n     * This is the keccak-256 hash of \"eip1967.proxy.implementation\" subtracted by 1.\n     */\n    // solhint-disable-next-line private-vars-leading-underscore\n    bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\n\n    /**\n     * @dev The `implementation` of the proxy is invalid.\n     */\n    error ERC1967InvalidImplementation(address implementation);\n\n    /**\n     * @dev The `admin` of the proxy is invalid.\n     */\n    error ERC1967InvalidAdmin(address admin);\n\n    /**\n     * @dev The `beacon` of the proxy is invalid.\n     */\n    error ERC1967InvalidBeacon(address beacon);\n\n    /**\n     * @dev An upgrade function sees `msg.value > 0` that may be lost.\n     */\n    error ERC1967NonPayable();\n\n    /**\n     * @dev Returns the current implementation address.\n     */\n    function getImplementation() internal view returns (address) {\n        return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value;\n    }\n\n    /**\n     * @dev Stores a new address in the EIP1967 implementation slot.\n     */\n    function _setImplementation(address newImplementation) private {\n        if (newImplementation.code.length == 0) {\n            revert ERC1967InvalidImplementation(newImplementation);\n        }\n        StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation;\n    }\n\n    /**\n     * @dev Performs implementation upgrade with additional setup call if data is nonempty.\n     * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected\n     * to avoid stuck value in the contract.\n     *\n     * Emits an {IERC1967-Upgraded} event.\n     */\n    function upgradeToAndCall(address newImplementation, bytes memory data) internal {\n        _setImplementation(newImplementation);\n        emit Upgraded(newImplementation);\n\n        if (data.length > 0) {\n            Address.functionDelegateCall(newImplementation, data);\n        } else {\n            _checkNonPayable();\n        }\n    }\n\n    /**\n     * @dev Storage slot with the admin of the contract.\n     * This is the keccak-256 hash of \"eip1967.proxy.admin\" subtracted by 1.\n     */\n    // solhint-disable-next-line private-vars-leading-underscore\n    bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;\n\n    /**\n     * @dev Returns the current admin.\n     *\n     * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using\n     * the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\n     * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`\n     */\n    function getAdmin() internal view returns (address) {\n        return StorageSlot.getAddressSlot(ADMIN_SLOT).value;\n    }\n\n    /**\n     * @dev Stores a new address in the EIP1967 admin slot.\n     */\n    function _setAdmin(address newAdmin) private {\n        if (newAdmin == address(0)) {\n            revert ERC1967InvalidAdmin(address(0));\n        }\n        StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin;\n    }\n\n    /**\n     * @dev Changes the admin of the proxy.\n     *\n     * Emits an {IERC1967-AdminChanged} event.\n     */\n    function changeAdmin(address newAdmin) internal {\n        emit AdminChanged(getAdmin(), newAdmin);\n        _setAdmin(newAdmin);\n    }\n\n    /**\n     * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.\n     * This is the keccak-256 hash of \"eip1967.proxy.beacon\" subtracted by 1.\n     */\n    // solhint-disable-next-line private-vars-leading-underscore\n    bytes32 internal constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;\n\n    /**\n     * @dev Returns the current beacon.\n     */\n    function getBeacon() internal view returns (address) {\n        return StorageSlot.getAddressSlot(BEACON_SLOT).value;\n    }\n\n    /**\n     * @dev Stores a new beacon in the EIP1967 beacon slot.\n     */\n    function _setBeacon(address newBeacon) private {\n        if (newBeacon.code.length == 0) {\n            revert ERC1967InvalidBeacon(newBeacon);\n        }\n\n        StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon;\n\n        address beaconImplementation = IBeacon(newBeacon).implementation();\n        if (beaconImplementation.code.length == 0) {\n            revert ERC1967InvalidImplementation(beaconImplementation);\n        }\n    }\n\n    /**\n     * @dev Change the beacon and trigger a setup call if data is nonempty.\n     * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected\n     * to avoid stuck value in the contract.\n     *\n     * Emits an {IERC1967-BeaconUpgraded} event.\n     *\n     * CAUTION: Invoking this function has no effect on an instance of {BeaconProxy} since v5, since\n     * it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for\n     * efficiency.\n     */\n    function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal {\n        _setBeacon(newBeacon);\n        emit BeaconUpgraded(newBeacon);\n\n        if (data.length > 0) {\n            Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);\n        } else {\n            _checkNonPayable();\n        }\n    }\n\n    /**\n     * @dev Reverts if `msg.value` is not zero. It can be used to avoid `msg.value` stuck in the contract\n     * if an upgrade doesn't perform an initialization call.\n     */\n    function _checkNonPayable() private {\n        if (msg.value > 0) {\n            revert ERC1967NonPayable();\n        }\n    }\n}\n"
      },
      "@openzeppelin/contracts/token/ERC721/IERC721.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC165} from \"../../utils/introspection/IERC165.sol\";\n\n/**\n * @dev Required interface of an ERC721 compliant contract.\n */\ninterface IERC721 is IERC165 {\n    /**\n     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\n     */\n    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\n\n    /**\n     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\n     */\n    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);\n\n    /**\n     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\n     */\n    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\n\n    /**\n     * @dev Returns the number of tokens in ``owner``'s account.\n     */\n    function balanceOf(address owner) external view returns (uint256 balance);\n\n    /**\n     * @dev Returns the owner of the `tokenId` token.\n     *\n     * Requirements:\n     *\n     * - `tokenId` must exist.\n     */\n    function ownerOf(uint256 tokenId) external view returns (address owner);\n\n    /**\n     * @dev Safely transfers `tokenId` token from `from` to `to`.\n     *\n     * Requirements:\n     *\n     * - `from` cannot be the zero address.\n     * - `to` cannot be the zero address.\n     * - `tokenId` token must exist and be owned by `from`.\n     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon\n     *   a safe transfer.\n     *\n     * Emits a {Transfer} event.\n     */\n    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;\n\n    /**\n     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\n     * are aware of the ERC721 protocol to prevent tokens from being forever locked.\n     *\n     * Requirements:\n     *\n     * - `from` cannot be the zero address.\n     * - `to` cannot be the zero address.\n     * - `tokenId` token must exist and be owned by `from`.\n     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or\n     *   {setApprovalForAll}.\n     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon\n     *   a safe transfer.\n     *\n     * Emits a {Transfer} event.\n     */\n    function safeTransferFrom(address from, address to, uint256 tokenId) external;\n\n    /**\n     * @dev Transfers `tokenId` token from `from` to `to`.\n     *\n     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721\n     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must\n     * understand this adds an external call which potentially creates a reentrancy vulnerability.\n     *\n     * Requirements:\n     *\n     * - `from` cannot be the zero address.\n     * - `to` cannot be the zero address.\n     * - `tokenId` token must be owned by `from`.\n     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n     *\n     * Emits a {Transfer} event.\n     */\n    function transferFrom(address from, address to, uint256 tokenId) external;\n\n    /**\n     * @dev Gives permission to `to` to transfer `tokenId` token to another account.\n     * The approval is cleared when the token is transferred.\n     *\n     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\n     *\n     * Requirements:\n     *\n     * - The caller must own the token or be an approved operator.\n     * - `tokenId` must exist.\n     *\n     * Emits an {Approval} event.\n     */\n    function approve(address to, uint256 tokenId) external;\n\n    /**\n     * @dev Approve or remove `operator` as an operator for the caller.\n     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\n     *\n     * Requirements:\n     *\n     * - The `operator` cannot be the address zero.\n     *\n     * Emits an {ApprovalForAll} event.\n     */\n    function setApprovalForAll(address operator, bool approved) external;\n\n    /**\n     * @dev Returns the account approved for `tokenId` token.\n     *\n     * Requirements:\n     *\n     * - `tokenId` must exist.\n     */\n    function getApproved(uint256 tokenId) external view returns (address operator);\n\n    /**\n     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\n     *\n     * See {setApprovalForAll}\n     */\n    function isApprovedForAll(address owner, address operator) external view returns (bool);\n}\n"
      },
      "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721Receiver.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @title ERC721 token receiver interface\n * @dev Interface for any contract that wants to support safeTransfers\n * from ERC721 asset contracts.\n */\ninterface IERC721Receiver {\n    /**\n     * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}\n     * by `operator` from `from`, this function is called.\n     *\n     * It must return its Solidity selector to confirm the token transfer.\n     * If any other value is returned or the interface is not implemented by the recipient, the transfer will be\n     * reverted.\n     *\n     * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.\n     */\n    function onERC721Received(\n        address operator,\n        address from,\n        uint256 tokenId,\n        bytes calldata data\n    ) external returns (bytes4);\n}\n"
      },
      "@openzeppelin/contracts/utils/Address.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n    /**\n     * @dev The ETH balance of the account is not enough to perform the operation.\n     */\n    error AddressInsufficientBalance(address account);\n\n    /**\n     * @dev There's no code at `target` (it is not a contract).\n     */\n    error AddressEmptyCode(address target);\n\n    /**\n     * @dev A call to an address target failed. The target may have reverted.\n     */\n    error FailedInnerCall();\n\n    /**\n     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n     * `recipient`, forwarding all available gas and reverting on errors.\n     *\n     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n     * of certain opcodes, possibly making contracts go over the 2300 gas limit\n     * imposed by `transfer`, making them unable to receive funds via\n     * `transfer`. {sendValue} removes this limitation.\n     *\n     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n     *\n     * IMPORTANT: because control is transferred to `recipient`, care must be\n     * taken to not create reentrancy vulnerabilities. Consider using\n     * {ReentrancyGuard} or the\n     * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n     */\n    function sendValue(address payable recipient, uint256 amount) internal {\n        if (address(this).balance < amount) {\n            revert AddressInsufficientBalance(address(this));\n        }\n\n        (bool success, ) = recipient.call{value: amount}(\"\");\n        if (!success) {\n            revert FailedInnerCall();\n        }\n    }\n\n    /**\n     * @dev Performs a Solidity function call using a low level `call`. A\n     * plain `call` is an unsafe replacement for a function call: use this\n     * function instead.\n     *\n     * If `target` reverts with a revert reason or custom error, it is bubbled\n     * up by this function (like regular Solidity function calls). However, if\n     * the call reverted with no returned reason, this function reverts with a\n     * {FailedInnerCall} error.\n     *\n     * Returns the raw returned data. To convert to the expected return value,\n     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n     *\n     * Requirements:\n     *\n     * - `target` must be a contract.\n     * - calling `target` with `data` must not revert.\n     */\n    function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n        return functionCallWithValue(target, data, 0);\n    }\n\n    /**\n     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n     * but also transferring `value` wei to `target`.\n     *\n     * Requirements:\n     *\n     * - the calling contract must have an ETH balance of at least `value`.\n     * - the called Solidity function must be `payable`.\n     */\n    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {\n        if (address(this).balance < value) {\n            revert AddressInsufficientBalance(address(this));\n        }\n        (bool success, bytes memory returndata) = target.call{value: value}(data);\n        return verifyCallResultFromTarget(target, success, returndata);\n    }\n\n    /**\n     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n     * but performing a static call.\n     */\n    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n        (bool success, bytes memory returndata) = target.staticcall(data);\n        return verifyCallResultFromTarget(target, success, returndata);\n    }\n\n    /**\n     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n     * but performing a delegate call.\n     */\n    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n        (bool success, bytes memory returndata) = target.delegatecall(data);\n        return verifyCallResultFromTarget(target, success, returndata);\n    }\n\n    /**\n     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target\n     * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an\n     * unsuccessful call.\n     */\n    function verifyCallResultFromTarget(\n        address target,\n        bool success,\n        bytes memory returndata\n    ) internal view returns (bytes memory) {\n        if (!success) {\n            _revert(returndata);\n        } else {\n            // only check if target is a contract if the call was successful and the return data is empty\n            // otherwise we already know that it was a contract\n            if (returndata.length == 0 && target.code.length == 0) {\n                revert AddressEmptyCode(target);\n            }\n            return returndata;\n        }\n    }\n\n    /**\n     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the\n     * revert reason or with a default {FailedInnerCall} error.\n     */\n    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {\n        if (!success) {\n            _revert(returndata);\n        } else {\n            return returndata;\n        }\n    }\n\n    /**\n     * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.\n     */\n    function _revert(bytes memory returndata) private pure {\n        // Look for revert reason and bubble it up if present\n        if (returndata.length > 0) {\n            // The easiest way to bubble the revert reason is using memory via assembly\n            /// @solidity memory-safe-assembly\n            assembly {\n                let returndata_size := mload(returndata)\n                revert(add(32, returndata), returndata_size)\n            }\n        } else {\n            revert FailedInnerCall();\n        }\n    }\n}\n"
      },
      "@openzeppelin/contracts/utils/introspection/IERC165.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Interface of the ERC165 standard, as defined in the\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\n *\n * Implementers can declare support of contract interfaces, which can then be\n * queried by others ({ERC165Checker}).\n *\n * For an implementation, see {ERC165}.\n */\ninterface IERC165 {\n    /**\n     * @dev Returns true if this contract implements the interface defined by\n     * `interfaceId`. See the corresponding\n     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\n     * to learn more about how these ids are created.\n     *\n     * This function call must use less than 30 000 gas.\n     */\n    function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n"
      },
      "@openzeppelin/contracts/utils/math/Math.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Standard math utilities missing in the Solidity language.\n */\nlibrary Math {\n    /**\n     * @dev Muldiv operation overflow.\n     */\n    error MathOverflowedMulDiv();\n\n    enum Rounding {\n        Floor, // Toward negative infinity\n        Ceil, // Toward positive infinity\n        Trunc, // Toward zero\n        Expand // Away from zero\n    }\n\n    /**\n     * @dev Returns the addition of two unsigned integers, with an overflow flag.\n     */\n    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n        unchecked {\n            uint256 c = a + b;\n            if (c < a) return (false, 0);\n            return (true, c);\n        }\n    }\n\n    /**\n     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.\n     */\n    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n        unchecked {\n            if (b > a) return (false, 0);\n            return (true, a - b);\n        }\n    }\n\n    /**\n     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.\n     */\n    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n        unchecked {\n            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the\n            // benefit is lost if 'b' is also tested.\n            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522\n            if (a == 0) return (true, 0);\n            uint256 c = a * b;\n            if (c / a != b) return (false, 0);\n            return (true, c);\n        }\n    }\n\n    /**\n     * @dev Returns the division of two unsigned integers, with a division by zero flag.\n     */\n    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n        unchecked {\n            if (b == 0) return (false, 0);\n            return (true, a / b);\n        }\n    }\n\n    /**\n     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.\n     */\n    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n        unchecked {\n            if (b == 0) return (false, 0);\n            return (true, a % b);\n        }\n    }\n\n    /**\n     * @dev Returns the largest of two numbers.\n     */\n    function max(uint256 a, uint256 b) internal pure returns (uint256) {\n        return a > b ? a : b;\n    }\n\n    /**\n     * @dev Returns the smallest of two numbers.\n     */\n    function min(uint256 a, uint256 b) internal pure returns (uint256) {\n        return a < b ? a : b;\n    }\n\n    /**\n     * @dev Returns the average of two numbers. The result is rounded towards\n     * zero.\n     */\n    function average(uint256 a, uint256 b) internal pure returns (uint256) {\n        // (a + b) / 2 can overflow.\n        return (a & b) + (a ^ b) / 2;\n    }\n\n    /**\n     * @dev Returns the ceiling of the division of two numbers.\n     *\n     * This differs from standard division with `/` in that it rounds towards infinity instead\n     * of rounding towards zero.\n     */\n    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {\n        if (b == 0) {\n            // Guarantee the same behavior as in a regular Solidity division.\n            return a / b;\n        }\n\n        // (a + b - 1) / b can overflow on addition, so we distribute.\n        return a == 0 ? 0 : (a - 1) / b + 1;\n    }\n\n    /**\n     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or\n     * denominator == 0.\n     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by\n     * Uniswap Labs also under MIT license.\n     */\n    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {\n        unchecked {\n            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use\n            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256\n            // variables such that product = prod1 * 2^256 + prod0.\n            uint256 prod0 = x * y; // Least significant 256 bits of the product\n            uint256 prod1; // Most significant 256 bits of the product\n            assembly {\n                let mm := mulmod(x, y, not(0))\n                prod1 := sub(sub(mm, prod0), lt(mm, prod0))\n            }\n\n            // Handle non-overflow cases, 256 by 256 division.\n            if (prod1 == 0) {\n                // Solidity will revert if denominator == 0, unlike the div opcode on its own.\n                // The surrounding unchecked block does not change this fact.\n                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.\n                return prod0 / denominator;\n            }\n\n            // Make sure the result is less than 2^256. Also prevents denominator == 0.\n            if (denominator <= prod1) {\n                revert MathOverflowedMulDiv();\n            }\n\n            ///////////////////////////////////////////////\n            // 512 by 256 division.\n            ///////////////////////////////////////////////\n\n            // Make division exact by subtracting the remainder from [prod1 prod0].\n            uint256 remainder;\n            assembly {\n                // Compute remainder using mulmod.\n                remainder := mulmod(x, y, denominator)\n\n                // Subtract 256 bit number from 512 bit number.\n                prod1 := sub(prod1, gt(remainder, prod0))\n                prod0 := sub(prod0, remainder)\n            }\n\n            // Factor powers of two out of denominator and compute largest power of two divisor of denominator.\n            // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.\n\n            uint256 twos = denominator & (0 - denominator);\n            assembly {\n                // Divide denominator by twos.\n                denominator := div(denominator, twos)\n\n                // Divide [prod1 prod0] by twos.\n                prod0 := div(prod0, twos)\n\n                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.\n                twos := add(div(sub(0, twos), twos), 1)\n            }\n\n            // Shift in bits from prod1 into prod0.\n            prod0 |= prod1 * twos;\n\n            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such\n            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for\n            // four bits. That is, denominator * inv = 1 mod 2^4.\n            uint256 inverse = (3 * denominator) ^ 2;\n\n            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also\n            // works in modular arithmetic, doubling the correct bits in each step.\n            inverse *= 2 - denominator * inverse; // inverse mod 2^8\n            inverse *= 2 - denominator * inverse; // inverse mod 2^16\n            inverse *= 2 - denominator * inverse; // inverse mod 2^32\n            inverse *= 2 - denominator * inverse; // inverse mod 2^64\n            inverse *= 2 - denominator * inverse; // inverse mod 2^128\n            inverse *= 2 - denominator * inverse; // inverse mod 2^256\n\n            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.\n            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is\n            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1\n            // is no longer required.\n            result = prod0 * inverse;\n            return result;\n        }\n    }\n\n    /**\n     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.\n     */\n    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {\n        uint256 result = mulDiv(x, y, denominator);\n        if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {\n            result += 1;\n        }\n        return result;\n    }\n\n    /**\n     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded\n     * towards zero.\n     *\n     * Inspired by Henry S. Warren, Jr.'s \"Hacker's Delight\" (Chapter 11).\n     */\n    function sqrt(uint256 a) internal pure returns (uint256) {\n        if (a == 0) {\n            return 0;\n        }\n\n        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.\n        //\n        // We know that the \"msb\" (most significant bit) of our target number `a` is a power of 2 such that we have\n        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.\n        //\n        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`\n        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`\n        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`\n        //\n        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.\n        uint256 result = 1 << (log2(a) >> 1);\n\n        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,\n        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at\n        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision\n        // into the expected uint128 result.\n        unchecked {\n            result = (result + a / result) >> 1;\n            result = (result + a / result) >> 1;\n            result = (result + a / result) >> 1;\n            result = (result + a / result) >> 1;\n            result = (result + a / result) >> 1;\n            result = (result + a / result) >> 1;\n            result = (result + a / result) >> 1;\n            return min(result, a / result);\n        }\n    }\n\n    /**\n     * @notice Calculates sqrt(a), following the selected rounding direction.\n     */\n    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {\n        unchecked {\n            uint256 result = sqrt(a);\n            return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);\n        }\n    }\n\n    /**\n     * @dev Return the log in base 2 of a positive value rounded towards zero.\n     * Returns 0 if given 0.\n     */\n    function log2(uint256 value) internal pure returns (uint256) {\n        uint256 result = 0;\n        unchecked {\n            if (value >> 128 > 0) {\n                value >>= 128;\n                result += 128;\n            }\n            if (value >> 64 > 0) {\n                value >>= 64;\n                result += 64;\n            }\n            if (value >> 32 > 0) {\n                value >>= 32;\n                result += 32;\n            }\n            if (value >> 16 > 0) {\n                value >>= 16;\n                result += 16;\n            }\n            if (value >> 8 > 0) {\n                value >>= 8;\n                result += 8;\n            }\n            if (value >> 4 > 0) {\n                value >>= 4;\n                result += 4;\n            }\n            if (value >> 2 > 0) {\n                value >>= 2;\n                result += 2;\n            }\n            if (value >> 1 > 0) {\n                result += 1;\n            }\n        }\n        return result;\n    }\n\n    /**\n     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.\n     * Returns 0 if given 0.\n     */\n    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {\n        unchecked {\n            uint256 result = log2(value);\n            return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);\n        }\n    }\n\n    /**\n     * @dev Return the log in base 10 of a positive value rounded towards zero.\n     * Returns 0 if given 0.\n     */\n    function log10(uint256 value) internal pure returns (uint256) {\n        uint256 result = 0;\n        unchecked {\n            if (value >= 10 ** 64) {\n                value /= 10 ** 64;\n                result += 64;\n            }\n            if (value >= 10 ** 32) {\n                value /= 10 ** 32;\n                result += 32;\n            }\n            if (value >= 10 ** 16) {\n                value /= 10 ** 16;\n                result += 16;\n            }\n            if (value >= 10 ** 8) {\n                value /= 10 ** 8;\n                result += 8;\n            }\n            if (value >= 10 ** 4) {\n                value /= 10 ** 4;\n                result += 4;\n            }\n            if (value >= 10 ** 2) {\n                value /= 10 ** 2;\n                result += 2;\n            }\n            if (value >= 10 ** 1) {\n                result += 1;\n            }\n        }\n        return result;\n    }\n\n    /**\n     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.\n     * Returns 0 if given 0.\n     */\n    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {\n        unchecked {\n            uint256 result = log10(value);\n            return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);\n        }\n    }\n\n    /**\n     * @dev Return the log in base 256 of a positive value rounded towards zero.\n     * Returns 0 if given 0.\n     *\n     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.\n     */\n    function log256(uint256 value) internal pure returns (uint256) {\n        uint256 result = 0;\n        unchecked {\n            if (value >> 128 > 0) {\n                value >>= 128;\n                result += 16;\n            }\n            if (value >> 64 > 0) {\n                value >>= 64;\n                result += 8;\n            }\n            if (value >> 32 > 0) {\n                value >>= 32;\n                result += 4;\n            }\n            if (value >> 16 > 0) {\n                value >>= 16;\n                result += 2;\n            }\n            if (value >> 8 > 0) {\n                result += 1;\n            }\n        }\n        return result;\n    }\n\n    /**\n     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.\n     * Returns 0 if given 0.\n     */\n    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {\n        unchecked {\n            uint256 result = log256(value);\n            return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);\n        }\n    }\n\n    /**\n     * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.\n     */\n    function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {\n        return uint8(rounding) % 2 == 1;\n    }\n}\n"
      },
      "@openzeppelin/contracts/utils/math/SafeCast.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SafeCast.sol)\n// This file was procedurally generated from scripts/generate/templates/SafeCast.js.\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow\n * checks.\n *\n * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can\n * easily result in undesired exploitation or bugs, since developers usually\n * assume that overflows raise errors. `SafeCast` restores this intuition by\n * reverting the transaction when such an operation overflows.\n *\n * Using this library instead of the unchecked operations eliminates an entire\n * class of bugs, so it's recommended to use it always.\n */\nlibrary SafeCast {\n    /**\n     * @dev Value doesn't fit in an uint of `bits` size.\n     */\n    error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);\n\n    /**\n     * @dev An int value doesn't fit in an uint of `bits` size.\n     */\n    error SafeCastOverflowedIntToUint(int256 value);\n\n    /**\n     * @dev Value doesn't fit in an int of `bits` size.\n     */\n    error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);\n\n    /**\n     * @dev An uint value doesn't fit in an int of `bits` size.\n     */\n    error SafeCastOverflowedUintToInt(uint256 value);\n\n    /**\n     * @dev Returns the downcasted uint248 from uint256, reverting on\n     * overflow (when the input is greater than largest uint248).\n     *\n     * Counterpart to Solidity's `uint248` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 248 bits\n     */\n    function toUint248(uint256 value) internal pure returns (uint248) {\n        if (value > type(uint248).max) {\n            revert SafeCastOverflowedUintDowncast(248, value);\n        }\n        return uint248(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint240 from uint256, reverting on\n     * overflow (when the input is greater than largest uint240).\n     *\n     * Counterpart to Solidity's `uint240` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 240 bits\n     */\n    function toUint240(uint256 value) internal pure returns (uint240) {\n        if (value > type(uint240).max) {\n            revert SafeCastOverflowedUintDowncast(240, value);\n        }\n        return uint240(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint232 from uint256, reverting on\n     * overflow (when the input is greater than largest uint232).\n     *\n     * Counterpart to Solidity's `uint232` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 232 bits\n     */\n    function toUint232(uint256 value) internal pure returns (uint232) {\n        if (value > type(uint232).max) {\n            revert SafeCastOverflowedUintDowncast(232, value);\n        }\n        return uint232(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint224 from uint256, reverting on\n     * overflow (when the input is greater than largest uint224).\n     *\n     * Counterpart to Solidity's `uint224` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 224 bits\n     */\n    function toUint224(uint256 value) internal pure returns (uint224) {\n        if (value > type(uint224).max) {\n            revert SafeCastOverflowedUintDowncast(224, value);\n        }\n        return uint224(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint216 from uint256, reverting on\n     * overflow (when the input is greater than largest uint216).\n     *\n     * Counterpart to Solidity's `uint216` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 216 bits\n     */\n    function toUint216(uint256 value) internal pure returns (uint216) {\n        if (value > type(uint216).max) {\n            revert SafeCastOverflowedUintDowncast(216, value);\n        }\n        return uint216(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint208 from uint256, reverting on\n     * overflow (when the input is greater than largest uint208).\n     *\n     * Counterpart to Solidity's `uint208` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 208 bits\n     */\n    function toUint208(uint256 value) internal pure returns (uint208) {\n        if (value > type(uint208).max) {\n            revert SafeCastOverflowedUintDowncast(208, value);\n        }\n        return uint208(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint200 from uint256, reverting on\n     * overflow (when the input is greater than largest uint200).\n     *\n     * Counterpart to Solidity's `uint200` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 200 bits\n     */\n    function toUint200(uint256 value) internal pure returns (uint200) {\n        if (value > type(uint200).max) {\n            revert SafeCastOverflowedUintDowncast(200, value);\n        }\n        return uint200(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint192 from uint256, reverting on\n     * overflow (when the input is greater than largest uint192).\n     *\n     * Counterpart to Solidity's `uint192` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 192 bits\n     */\n    function toUint192(uint256 value) internal pure returns (uint192) {\n        if (value > type(uint192).max) {\n            revert SafeCastOverflowedUintDowncast(192, value);\n        }\n        return uint192(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint184 from uint256, reverting on\n     * overflow (when the input is greater than largest uint184).\n     *\n     * Counterpart to Solidity's `uint184` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 184 bits\n     */\n    function toUint184(uint256 value) internal pure returns (uint184) {\n        if (value > type(uint184).max) {\n            revert SafeCastOverflowedUintDowncast(184, value);\n        }\n        return uint184(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint176 from uint256, reverting on\n     * overflow (when the input is greater than largest uint176).\n     *\n     * Counterpart to Solidity's `uint176` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 176 bits\n     */\n    function toUint176(uint256 value) internal pure returns (uint176) {\n        if (value > type(uint176).max) {\n            revert SafeCastOverflowedUintDowncast(176, value);\n        }\n        return uint176(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint168 from uint256, reverting on\n     * overflow (when the input is greater than largest uint168).\n     *\n     * Counterpart to Solidity's `uint168` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 168 bits\n     */\n    function toUint168(uint256 value) internal pure returns (uint168) {\n        if (value > type(uint168).max) {\n            revert SafeCastOverflowedUintDowncast(168, value);\n        }\n        return uint168(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint160 from uint256, reverting on\n     * overflow (when the input is greater than largest uint160).\n     *\n     * Counterpart to Solidity's `uint160` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 160 bits\n     */\n    function toUint160(uint256 value) internal pure returns (uint160) {\n        if (value > type(uint160).max) {\n            revert SafeCastOverflowedUintDowncast(160, value);\n        }\n        return uint160(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint152 from uint256, reverting on\n     * overflow (when the input is greater than largest uint152).\n     *\n     * Counterpart to Solidity's `uint152` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 152 bits\n     */\n    function toUint152(uint256 value) internal pure returns (uint152) {\n        if (value > type(uint152).max) {\n            revert SafeCastOverflowedUintDowncast(152, value);\n        }\n        return uint152(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint144 from uint256, reverting on\n     * overflow (when the input is greater than largest uint144).\n     *\n     * Counterpart to Solidity's `uint144` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 144 bits\n     */\n    function toUint144(uint256 value) internal pure returns (uint144) {\n        if (value > type(uint144).max) {\n            revert SafeCastOverflowedUintDowncast(144, value);\n        }\n        return uint144(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint136 from uint256, reverting on\n     * overflow (when the input is greater than largest uint136).\n     *\n     * Counterpart to Solidity's `uint136` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 136 bits\n     */\n    function toUint136(uint256 value) internal pure returns (uint136) {\n        if (value > type(uint136).max) {\n            revert SafeCastOverflowedUintDowncast(136, value);\n        }\n        return uint136(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint128 from uint256, reverting on\n     * overflow (when the input is greater than largest uint128).\n     *\n     * Counterpart to Solidity's `uint128` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 128 bits\n     */\n    function toUint128(uint256 value) internal pure returns (uint128) {\n        if (value > type(uint128).max) {\n            revert SafeCastOverflowedUintDowncast(128, value);\n        }\n        return uint128(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint120 from uint256, reverting on\n     * overflow (when the input is greater than largest uint120).\n     *\n     * Counterpart to Solidity's `uint120` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 120 bits\n     */\n    function toUint120(uint256 value) internal pure returns (uint120) {\n        if (value > type(uint120).max) {\n            revert SafeCastOverflowedUintDowncast(120, value);\n        }\n        return uint120(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint112 from uint256, reverting on\n     * overflow (when the input is greater than largest uint112).\n     *\n     * Counterpart to Solidity's `uint112` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 112 bits\n     */\n    function toUint112(uint256 value) internal pure returns (uint112) {\n        if (value > type(uint112).max) {\n            revert SafeCastOverflowedUintDowncast(112, value);\n        }\n        return uint112(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint104 from uint256, reverting on\n     * overflow (when the input is greater than largest uint104).\n     *\n     * Counterpart to Solidity's `uint104` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 104 bits\n     */\n    function toUint104(uint256 value) internal pure returns (uint104) {\n        if (value > type(uint104).max) {\n            revert SafeCastOverflowedUintDowncast(104, value);\n        }\n        return uint104(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint96 from uint256, reverting on\n     * overflow (when the input is greater than largest uint96).\n     *\n     * Counterpart to Solidity's `uint96` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 96 bits\n     */\n    function toUint96(uint256 value) internal pure returns (uint96) {\n        if (value > type(uint96).max) {\n            revert SafeCastOverflowedUintDowncast(96, value);\n        }\n        return uint96(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint88 from uint256, reverting on\n     * overflow (when the input is greater than largest uint88).\n     *\n     * Counterpart to Solidity's `uint88` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 88 bits\n     */\n    function toUint88(uint256 value) internal pure returns (uint88) {\n        if (value > type(uint88).max) {\n            revert SafeCastOverflowedUintDowncast(88, value);\n        }\n        return uint88(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint80 from uint256, reverting on\n     * overflow (when the input is greater than largest uint80).\n     *\n     * Counterpart to Solidity's `uint80` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 80 bits\n     */\n    function toUint80(uint256 value) internal pure returns (uint80) {\n        if (value > type(uint80).max) {\n            revert SafeCastOverflowedUintDowncast(80, value);\n        }\n        return uint80(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint72 from uint256, reverting on\n     * overflow (when the input is greater than largest uint72).\n     *\n     * Counterpart to Solidity's `uint72` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 72 bits\n     */\n    function toUint72(uint256 value) internal pure returns (uint72) {\n        if (value > type(uint72).max) {\n            revert SafeCastOverflowedUintDowncast(72, value);\n        }\n        return uint72(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint64 from uint256, reverting on\n     * overflow (when the input is greater than largest uint64).\n     *\n     * Counterpart to Solidity's `uint64` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 64 bits\n     */\n    function toUint64(uint256 value) internal pure returns (uint64) {\n        if (value > type(uint64).max) {\n            revert SafeCastOverflowedUintDowncast(64, value);\n        }\n        return uint64(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint56 from uint256, reverting on\n     * overflow (when the input is greater than largest uint56).\n     *\n     * Counterpart to Solidity's `uint56` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 56 bits\n     */\n    function toUint56(uint256 value) internal pure returns (uint56) {\n        if (value > type(uint56).max) {\n            revert SafeCastOverflowedUintDowncast(56, value);\n        }\n        return uint56(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint48 from uint256, reverting on\n     * overflow (when the input is greater than largest uint48).\n     *\n     * Counterpart to Solidity's `uint48` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 48 bits\n     */\n    function toUint48(uint256 value) internal pure returns (uint48) {\n        if (value > type(uint48).max) {\n            revert SafeCastOverflowedUintDowncast(48, value);\n        }\n        return uint48(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint40 from uint256, reverting on\n     * overflow (when the input is greater than largest uint40).\n     *\n     * Counterpart to Solidity's `uint40` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 40 bits\n     */\n    function toUint40(uint256 value) internal pure returns (uint40) {\n        if (value > type(uint40).max) {\n            revert SafeCastOverflowedUintDowncast(40, value);\n        }\n        return uint40(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint32 from uint256, reverting on\n     * overflow (when the input is greater than largest uint32).\n     *\n     * Counterpart to Solidity's `uint32` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 32 bits\n     */\n    function toUint32(uint256 value) internal pure returns (uint32) {\n        if (value > type(uint32).max) {\n            revert SafeCastOverflowedUintDowncast(32, value);\n        }\n        return uint32(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint24 from uint256, reverting on\n     * overflow (when the input is greater than largest uint24).\n     *\n     * Counterpart to Solidity's `uint24` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 24 bits\n     */\n    function toUint24(uint256 value) internal pure returns (uint24) {\n        if (value > type(uint24).max) {\n            revert SafeCastOverflowedUintDowncast(24, value);\n        }\n        return uint24(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint16 from uint256, reverting on\n     * overflow (when the input is greater than largest uint16).\n     *\n     * Counterpart to Solidity's `uint16` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 16 bits\n     */\n    function toUint16(uint256 value) internal pure returns (uint16) {\n        if (value > type(uint16).max) {\n            revert SafeCastOverflowedUintDowncast(16, value);\n        }\n        return uint16(value);\n    }\n\n    /**\n     * @dev Returns the downcasted uint8 from uint256, reverting on\n     * overflow (when the input is greater than largest uint8).\n     *\n     * Counterpart to Solidity's `uint8` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 8 bits\n     */\n    function toUint8(uint256 value) internal pure returns (uint8) {\n        if (value > type(uint8).max) {\n            revert SafeCastOverflowedUintDowncast(8, value);\n        }\n        return uint8(value);\n    }\n\n    /**\n     * @dev Converts a signed int256 into an unsigned uint256.\n     *\n     * Requirements:\n     *\n     * - input must be greater than or equal to 0.\n     */\n    function toUint256(int256 value) internal pure returns (uint256) {\n        if (value < 0) {\n            revert SafeCastOverflowedIntToUint(value);\n        }\n        return uint256(value);\n    }\n\n    /**\n     * @dev Returns the downcasted int248 from int256, reverting on\n     * overflow (when the input is less than smallest int248 or\n     * greater than largest int248).\n     *\n     * Counterpart to Solidity's `int248` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 248 bits\n     */\n    function toInt248(int256 value) internal pure returns (int248 downcasted) {\n        downcasted = int248(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(248, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int240 from int256, reverting on\n     * overflow (when the input is less than smallest int240 or\n     * greater than largest int240).\n     *\n     * Counterpart to Solidity's `int240` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 240 bits\n     */\n    function toInt240(int256 value) internal pure returns (int240 downcasted) {\n        downcasted = int240(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(240, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int232 from int256, reverting on\n     * overflow (when the input is less than smallest int232 or\n     * greater than largest int232).\n     *\n     * Counterpart to Solidity's `int232` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 232 bits\n     */\n    function toInt232(int256 value) internal pure returns (int232 downcasted) {\n        downcasted = int232(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(232, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int224 from int256, reverting on\n     * overflow (when the input is less than smallest int224 or\n     * greater than largest int224).\n     *\n     * Counterpart to Solidity's `int224` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 224 bits\n     */\n    function toInt224(int256 value) internal pure returns (int224 downcasted) {\n        downcasted = int224(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(224, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int216 from int256, reverting on\n     * overflow (when the input is less than smallest int216 or\n     * greater than largest int216).\n     *\n     * Counterpart to Solidity's `int216` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 216 bits\n     */\n    function toInt216(int256 value) internal pure returns (int216 downcasted) {\n        downcasted = int216(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(216, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int208 from int256, reverting on\n     * overflow (when the input is less than smallest int208 or\n     * greater than largest int208).\n     *\n     * Counterpart to Solidity's `int208` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 208 bits\n     */\n    function toInt208(int256 value) internal pure returns (int208 downcasted) {\n        downcasted = int208(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(208, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int200 from int256, reverting on\n     * overflow (when the input is less than smallest int200 or\n     * greater than largest int200).\n     *\n     * Counterpart to Solidity's `int200` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 200 bits\n     */\n    function toInt200(int256 value) internal pure returns (int200 downcasted) {\n        downcasted = int200(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(200, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int192 from int256, reverting on\n     * overflow (when the input is less than smallest int192 or\n     * greater than largest int192).\n     *\n     * Counterpart to Solidity's `int192` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 192 bits\n     */\n    function toInt192(int256 value) internal pure returns (int192 downcasted) {\n        downcasted = int192(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(192, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int184 from int256, reverting on\n     * overflow (when the input is less than smallest int184 or\n     * greater than largest int184).\n     *\n     * Counterpart to Solidity's `int184` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 184 bits\n     */\n    function toInt184(int256 value) internal pure returns (int184 downcasted) {\n        downcasted = int184(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(184, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int176 from int256, reverting on\n     * overflow (when the input is less than smallest int176 or\n     * greater than largest int176).\n     *\n     * Counterpart to Solidity's `int176` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 176 bits\n     */\n    function toInt176(int256 value) internal pure returns (int176 downcasted) {\n        downcasted = int176(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(176, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int168 from int256, reverting on\n     * overflow (when the input is less than smallest int168 or\n     * greater than largest int168).\n     *\n     * Counterpart to Solidity's `int168` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 168 bits\n     */\n    function toInt168(int256 value) internal pure returns (int168 downcasted) {\n        downcasted = int168(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(168, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int160 from int256, reverting on\n     * overflow (when the input is less than smallest int160 or\n     * greater than largest int160).\n     *\n     * Counterpart to Solidity's `int160` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 160 bits\n     */\n    function toInt160(int256 value) internal pure returns (int160 downcasted) {\n        downcasted = int160(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(160, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int152 from int256, reverting on\n     * overflow (when the input is less than smallest int152 or\n     * greater than largest int152).\n     *\n     * Counterpart to Solidity's `int152` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 152 bits\n     */\n    function toInt152(int256 value) internal pure returns (int152 downcasted) {\n        downcasted = int152(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(152, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int144 from int256, reverting on\n     * overflow (when the input is less than smallest int144 or\n     * greater than largest int144).\n     *\n     * Counterpart to Solidity's `int144` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 144 bits\n     */\n    function toInt144(int256 value) internal pure returns (int144 downcasted) {\n        downcasted = int144(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(144, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int136 from int256, reverting on\n     * overflow (when the input is less than smallest int136 or\n     * greater than largest int136).\n     *\n     * Counterpart to Solidity's `int136` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 136 bits\n     */\n    function toInt136(int256 value) internal pure returns (int136 downcasted) {\n        downcasted = int136(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(136, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int128 from int256, reverting on\n     * overflow (when the input is less than smallest int128 or\n     * greater than largest int128).\n     *\n     * Counterpart to Solidity's `int128` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 128 bits\n     */\n    function toInt128(int256 value) internal pure returns (int128 downcasted) {\n        downcasted = int128(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(128, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int120 from int256, reverting on\n     * overflow (when the input is less than smallest int120 or\n     * greater than largest int120).\n     *\n     * Counterpart to Solidity's `int120` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 120 bits\n     */\n    function toInt120(int256 value) internal pure returns (int120 downcasted) {\n        downcasted = int120(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(120, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int112 from int256, reverting on\n     * overflow (when the input is less than smallest int112 or\n     * greater than largest int112).\n     *\n     * Counterpart to Solidity's `int112` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 112 bits\n     */\n    function toInt112(int256 value) internal pure returns (int112 downcasted) {\n        downcasted = int112(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(112, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int104 from int256, reverting on\n     * overflow (when the input is less than smallest int104 or\n     * greater than largest int104).\n     *\n     * Counterpart to Solidity's `int104` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 104 bits\n     */\n    function toInt104(int256 value) internal pure returns (int104 downcasted) {\n        downcasted = int104(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(104, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int96 from int256, reverting on\n     * overflow (when the input is less than smallest int96 or\n     * greater than largest int96).\n     *\n     * Counterpart to Solidity's `int96` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 96 bits\n     */\n    function toInt96(int256 value) internal pure returns (int96 downcasted) {\n        downcasted = int96(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(96, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int88 from int256, reverting on\n     * overflow (when the input is less than smallest int88 or\n     * greater than largest int88).\n     *\n     * Counterpart to Solidity's `int88` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 88 bits\n     */\n    function toInt88(int256 value) internal pure returns (int88 downcasted) {\n        downcasted = int88(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(88, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int80 from int256, reverting on\n     * overflow (when the input is less than smallest int80 or\n     * greater than largest int80).\n     *\n     * Counterpart to Solidity's `int80` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 80 bits\n     */\n    function toInt80(int256 value) internal pure returns (int80 downcasted) {\n        downcasted = int80(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(80, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int72 from int256, reverting on\n     * overflow (when the input is less than smallest int72 or\n     * greater than largest int72).\n     *\n     * Counterpart to Solidity's `int72` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 72 bits\n     */\n    function toInt72(int256 value) internal pure returns (int72 downcasted) {\n        downcasted = int72(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(72, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int64 from int256, reverting on\n     * overflow (when the input is less than smallest int64 or\n     * greater than largest int64).\n     *\n     * Counterpart to Solidity's `int64` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 64 bits\n     */\n    function toInt64(int256 value) internal pure returns (int64 downcasted) {\n        downcasted = int64(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(64, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int56 from int256, reverting on\n     * overflow (when the input is less than smallest int56 or\n     * greater than largest int56).\n     *\n     * Counterpart to Solidity's `int56` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 56 bits\n     */\n    function toInt56(int256 value) internal pure returns (int56 downcasted) {\n        downcasted = int56(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(56, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int48 from int256, reverting on\n     * overflow (when the input is less than smallest int48 or\n     * greater than largest int48).\n     *\n     * Counterpart to Solidity's `int48` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 48 bits\n     */\n    function toInt48(int256 value) internal pure returns (int48 downcasted) {\n        downcasted = int48(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(48, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int40 from int256, reverting on\n     * overflow (when the input is less than smallest int40 or\n     * greater than largest int40).\n     *\n     * Counterpart to Solidity's `int40` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 40 bits\n     */\n    function toInt40(int256 value) internal pure returns (int40 downcasted) {\n        downcasted = int40(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(40, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int32 from int256, reverting on\n     * overflow (when the input is less than smallest int32 or\n     * greater than largest int32).\n     *\n     * Counterpart to Solidity's `int32` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 32 bits\n     */\n    function toInt32(int256 value) internal pure returns (int32 downcasted) {\n        downcasted = int32(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(32, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int24 from int256, reverting on\n     * overflow (when the input is less than smallest int24 or\n     * greater than largest int24).\n     *\n     * Counterpart to Solidity's `int24` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 24 bits\n     */\n    function toInt24(int256 value) internal pure returns (int24 downcasted) {\n        downcasted = int24(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(24, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int16 from int256, reverting on\n     * overflow (when the input is less than smallest int16 or\n     * greater than largest int16).\n     *\n     * Counterpart to Solidity's `int16` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 16 bits\n     */\n    function toInt16(int256 value) internal pure returns (int16 downcasted) {\n        downcasted = int16(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(16, value);\n        }\n    }\n\n    /**\n     * @dev Returns the downcasted int8 from int256, reverting on\n     * overflow (when the input is less than smallest int8 or\n     * greater than largest int8).\n     *\n     * Counterpart to Solidity's `int8` operator.\n     *\n     * Requirements:\n     *\n     * - input must fit into 8 bits\n     */\n    function toInt8(int256 value) internal pure returns (int8 downcasted) {\n        downcasted = int8(value);\n        if (downcasted != value) {\n            revert SafeCastOverflowedIntDowncast(8, value);\n        }\n    }\n\n    /**\n     * @dev Converts an unsigned uint256 into a signed int256.\n     *\n     * Requirements:\n     *\n     * - input must be less than or equal to maxInt256.\n     */\n    function toInt256(uint256 value) internal pure returns (int256) {\n        // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive\n        if (value > uint256(type(int256).max)) {\n            revert SafeCastOverflowedUintToInt(value);\n        }\n        return int256(value);\n    }\n}\n"
      },
      "@openzeppelin/contracts/utils/StorageSlot.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (utils/StorageSlot.sol)\n// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Library for reading and writing primitive types to specific storage slots.\n *\n * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.\n * This library helps with reading and writing to such slots without the need for inline assembly.\n *\n * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.\n *\n * Example usage to set ERC1967 implementation slot:\n * ```solidity\n * contract ERC1967 {\n *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\n *\n *     function _getImplementation() internal view returns (address) {\n *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\n *     }\n *\n *     function _setImplementation(address newImplementation) internal {\n *         require(newImplementation.code.length > 0);\n *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\n *     }\n * }\n * ```\n */\nlibrary StorageSlot {\n    struct AddressSlot {\n        address value;\n    }\n\n    struct BooleanSlot {\n        bool value;\n    }\n\n    struct Bytes32Slot {\n        bytes32 value;\n    }\n\n    struct Uint256Slot {\n        uint256 value;\n    }\n\n    struct StringSlot {\n        string value;\n    }\n\n    struct BytesSlot {\n        bytes value;\n    }\n\n    /**\n     * @dev Returns an `AddressSlot` with member `value` located at `slot`.\n     */\n    function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {\n        /// @solidity memory-safe-assembly\n        assembly {\n            r.slot := slot\n        }\n    }\n\n    /**\n     * @dev Returns an `BooleanSlot` with member `value` located at `slot`.\n     */\n    function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {\n        /// @solidity memory-safe-assembly\n        assembly {\n            r.slot := slot\n        }\n    }\n\n    /**\n     * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.\n     */\n    function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {\n        /// @solidity memory-safe-assembly\n        assembly {\n            r.slot := slot\n        }\n    }\n\n    /**\n     * @dev Returns an `Uint256Slot` with member `value` located at `slot`.\n     */\n    function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {\n        /// @solidity memory-safe-assembly\n        assembly {\n            r.slot := slot\n        }\n    }\n\n    /**\n     * @dev Returns an `StringSlot` with member `value` located at `slot`.\n     */\n    function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {\n        /// @solidity memory-safe-assembly\n        assembly {\n            r.slot := slot\n        }\n    }\n\n    /**\n     * @dev Returns an `StringSlot` representation of the string storage pointer `store`.\n     */\n    function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {\n        /// @solidity memory-safe-assembly\n        assembly {\n            r.slot := store.slot\n        }\n    }\n\n    /**\n     * @dev Returns an `BytesSlot` with member `value` located at `slot`.\n     */\n    function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {\n        /// @solidity memory-safe-assembly\n        assembly {\n            r.slot := slot\n        }\n    }\n\n    /**\n     * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.\n     */\n    function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {\n        /// @solidity memory-safe-assembly\n        assembly {\n            r.slot := store.slot\n        }\n    }\n}\n"
      },
      "chainlink/v0.8/interfaces/VRFCoordinatorV2Interface.sol": {
        "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface VRFCoordinatorV2Interface {\n  /**\n   * @notice Get configuration relevant for making requests\n   * @return minimumRequestConfirmations global min for request confirmations\n   * @return maxGasLimit global max for request gas limit\n   * @return s_provingKeyHashes list of registered key hashes\n   */\n  function getRequestConfig() external view returns (uint16, uint32, bytes32[] memory);\n\n  /**\n   * @notice Request a set of random words.\n   * @param keyHash - Corresponds to a particular oracle job which uses\n   * that key for generating the VRF proof. Different keyHash's have different gas price\n   * ceilings, so you can select a specific one to bound your maximum per request cost.\n   * @param subId  - The ID of the VRF subscription. Must be funded\n   * with the minimum subscription balance required for the selected keyHash.\n   * @param minimumRequestConfirmations - How many blocks you'd like the\n   * oracle to wait before responding to the request. See SECURITY CONSIDERATIONS\n   * for why you may want to request more. The acceptable range is\n   * [minimumRequestBlockConfirmations, 200].\n   * @param callbackGasLimit - How much gas you'd like to receive in your\n   * fulfillRandomWords callback. Note that gasleft() inside fulfillRandomWords\n   * may be slightly less than this amount because of gas used calling the function\n   * (argument decoding etc.), so you may need to request slightly more than you expect\n   * to have inside fulfillRandomWords. The acceptable range is\n   * [0, maxGasLimit]\n   * @param numWords - The number of uint256 random values you'd like to receive\n   * in your fulfillRandomWords callback. Note these numbers are expanded in a\n   * secure way by the VRFCoordinator from a single random value supplied by the oracle.\n   * @return requestId - A unique identifier of the request. Can be used to match\n   * a request to a response in fulfillRandomWords.\n   */\n  function requestRandomWords(\n    bytes32 keyHash,\n    uint64 subId,\n    uint16 minimumRequestConfirmations,\n    uint32 callbackGasLimit,\n    uint32 numWords\n  ) external returns (uint256 requestId);\n\n  /**\n   * @notice Create a VRF subscription.\n   * @return subId - A unique subscription id.\n   * @dev You can manage the consumer set dynamically with addConsumer/removeConsumer.\n   * @dev Note to fund the subscription, use transferAndCall. For example\n   * @dev  LINKTOKEN.transferAndCall(\n   * @dev    address(COORDINATOR),\n   * @dev    amount,\n   * @dev    abi.encode(subId));\n   */\n  function createSubscription() external returns (uint64 subId);\n\n  /**\n   * @notice Get a VRF subscription.\n   * @param subId - ID of the subscription\n   * @return balance - LINK balance of the subscription in juels.\n   * @return reqCount - number of requests for this subscription, determines fee tier.\n   * @return owner - owner of the subscription.\n   * @return consumers - list of consumer address which are able to use this subscription.\n   */\n  function getSubscription(\n    uint64 subId\n  ) external view returns (uint96 balance, uint64 reqCount, address owner, address[] memory consumers);\n\n  /**\n   * @notice Request subscription owner transfer.\n   * @param subId - ID of the subscription\n   * @param newOwner - proposed new owner of the subscription\n   */\n  function requestSubscriptionOwnerTransfer(uint64 subId, address newOwner) external;\n\n  /**\n   * @notice Request subscription owner transfer.\n   * @param subId - ID of the subscription\n   * @dev will revert if original owner of subId has\n   * not requested that msg.sender become the new owner.\n   */\n  function acceptSubscriptionOwnerTransfer(uint64 subId) external;\n\n  /**\n   * @notice Add a consumer to a VRF subscription.\n   * @param subId - ID of the subscription\n   * @param consumer - New consumer which can use the subscription\n   */\n  function addConsumer(uint64 subId, address consumer) external;\n\n  /**\n   * @notice Remove a consumer from a VRF subscription.\n   * @param subId - ID of the subscription\n   * @param consumer - Consumer to remove from the subscription\n   */\n  function removeConsumer(uint64 subId, address consumer) external;\n\n  /**\n   * @notice Cancel a subscription\n   * @param subId - ID of the subscription\n   * @param to - Where to send the remaining LINK to\n   */\n  function cancelSubscription(uint64 subId, address to) external;\n\n  /*\n   * @notice Check to see if there exists a request commitment consumers\n   * for all consumers and keyhashes for a given sub.\n   * @param subId - ID of the subscription\n   * @return true if there exists at least one unfulfilled request for the subscription, false\n   * otherwise.\n   */\n  function pendingRequestExists(uint64 subId) external view returns (bool);\n}\n"
      },
      "chainlink/v0.8/VRFConsumerBaseV2.sol": {
        "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\n/** ****************************************************************************\n * @notice Interface for contracts using VRF randomness\n * *****************************************************************************\n * @dev PURPOSE\n *\n * @dev Reggie the Random Oracle (not his real job) wants to provide randomness\n * @dev to Vera the verifier in such a way that Vera can be sure he's not\n * @dev making his output up to suit himself. Reggie provides Vera a public key\n * @dev to which he knows the secret key. Each time Vera provides a seed to\n * @dev Reggie, he gives back a value which is computed completely\n * @dev deterministically from the seed and the secret key.\n *\n * @dev Reggie provides a proof by which Vera can verify that the output was\n * @dev correctly computed once Reggie tells it to her, but without that proof,\n * @dev the output is indistinguishable to her from a uniform random sample\n * @dev from the output space.\n *\n * @dev The purpose of this contract is to make it easy for unrelated contracts\n * @dev to talk to Vera the verifier about the work Reggie is doing, to provide\n * @dev simple access to a verifiable source of randomness. It ensures 2 things:\n * @dev 1. The fulfillment came from the VRFCoordinator\n * @dev 2. The consumer contract implements fulfillRandomWords.\n * *****************************************************************************\n * @dev USAGE\n *\n * @dev Calling contracts must inherit from VRFConsumerBase, and can\n * @dev initialize VRFConsumerBase's attributes in their constructor as\n * @dev shown:\n *\n * @dev   contract VRFConsumer {\n * @dev     constructor(<other arguments>, address _vrfCoordinator, address _link)\n * @dev       VRFConsumerBase(_vrfCoordinator) public {\n * @dev         <initialization with other arguments goes here>\n * @dev       }\n * @dev   }\n *\n * @dev The oracle will have given you an ID for the VRF keypair they have\n * @dev committed to (let's call it keyHash). Create subscription, fund it\n * @dev and your consumer contract as a consumer of it (see VRFCoordinatorInterface\n * @dev subscription management functions).\n * @dev Call requestRandomWords(keyHash, subId, minimumRequestConfirmations,\n * @dev callbackGasLimit, numWords),\n * @dev see (VRFCoordinatorInterface for a description of the arguments).\n *\n * @dev Once the VRFCoordinator has received and validated the oracle's response\n * @dev to your request, it will call your contract's fulfillRandomWords method.\n *\n * @dev The randomness argument to fulfillRandomWords is a set of random words\n * @dev generated from your requestId and the blockHash of the request.\n *\n * @dev If your contract could have concurrent requests open, you can use the\n * @dev requestId returned from requestRandomWords to track which response is associated\n * @dev with which randomness request.\n * @dev See \"SECURITY CONSIDERATIONS\" for principles to keep in mind,\n * @dev if your contract could have multiple requests in flight simultaneously.\n *\n * @dev Colliding `requestId`s are cryptographically impossible as long as seeds\n * @dev differ.\n *\n * *****************************************************************************\n * @dev SECURITY CONSIDERATIONS\n *\n * @dev A method with the ability to call your fulfillRandomness method directly\n * @dev could spoof a VRF response with any random value, so it's critical that\n * @dev it cannot be directly called by anything other than this base contract\n * @dev (specifically, by the VRFConsumerBase.rawFulfillRandomness method).\n *\n * @dev For your users to trust that your contract's random behavior is free\n * @dev from malicious interference, it's best if you can write it so that all\n * @dev behaviors implied by a VRF response are executed *during* your\n * @dev fulfillRandomness method. If your contract must store the response (or\n * @dev anything derived from it) and use it later, you must ensure that any\n * @dev user-significant behavior which depends on that stored value cannot be\n * @dev manipulated by a subsequent VRF request.\n *\n * @dev Similarly, both miners and the VRF oracle itself have some influence\n * @dev over the order in which VRF responses appear on the blockchain, so if\n * @dev your contract could have multiple VRF requests in flight simultaneously,\n * @dev you must ensure that the order in which the VRF responses arrive cannot\n * @dev be used to manipulate your contract's user-significant behavior.\n *\n * @dev Since the block hash of the block which contains the requestRandomness\n * @dev call is mixed into the input to the VRF *last*, a sufficiently powerful\n * @dev miner could, in principle, fork the blockchain to evict the block\n * @dev containing the request, forcing the request to be included in a\n * @dev different block with a different hash, and therefore a different input\n * @dev to the VRF. However, such an attack would incur a substantial economic\n * @dev cost. This cost scales with the number of blocks the VRF oracle waits\n * @dev until it calls responds to a request. It is for this reason that\n * @dev that you can signal to an oracle you'd like them to wait longer before\n * @dev responding to the request (however this is not enforced in the contract\n * @dev and so remains effective only in the case of unmodified oracle software).\n */\nabstract contract VRFConsumerBaseV2 {\n  error OnlyCoordinatorCanFulfill(address have, address want);\n  address private immutable vrfCoordinator;\n\n  /**\n   * @param _vrfCoordinator address of VRFCoordinator contract\n   */\n  constructor(address _vrfCoordinator) {\n    vrfCoordinator = _vrfCoordinator;\n  }\n\n  /**\n   * @notice fulfillRandomness handles the VRF response. Your contract must\n   * @notice implement it. See \"SECURITY CONSIDERATIONS\" above for important\n   * @notice principles to keep in mind when implementing your fulfillRandomness\n   * @notice method.\n   *\n   * @dev VRFConsumerBaseV2 expects its subcontracts to have a method with this\n   * @dev signature, and will call it once it has verified the proof\n   * @dev associated with the randomness. (It is triggered via a call to\n   * @dev rawFulfillRandomness, below.)\n   *\n   * @param requestId The Id initially returned by requestRandomness\n   * @param randomWords the VRF output expanded to the requested number of words\n   */\n  function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal virtual;\n\n  // rawFulfillRandomness is called by VRFCoordinator when it receives a valid VRF\n  // proof. rawFulfillRandomness then calls fulfillRandomness, after validating\n  // the origin of the call\n  function rawFulfillRandomWords(uint256 requestId, uint256[] memory randomWords) external {\n    if (msg.sender != vrfCoordinator) {\n      revert OnlyCoordinatorCanFulfill(msg.sender, vrfCoordinator);\n    }\n    fulfillRandomWords(requestId, randomWords);\n  }\n}\n"
      },
      "src/base/Multicall.sol": {
        "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.20;\n\nimport \"../interface/IMulticall.sol\";\n\n/// @title Multicall\n/// @notice Enables calling multiple methods in a single call to the contract\nabstract contract Multicall is IMulticall {\n    /// @inheritdoc IMulticall\n    function multicall(\n        bytes[] calldata data\n    ) external virtual override returns (bytes[] memory) {\n        bytes[] memory results = new bytes[](data.length);\n        for (uint256 i; i < data.length; ) {\n            /// @custom:oz-upgrades-unsafe-allow-reachable delegatecall\n            (bool success, bytes memory result) = address(this).delegatecall(\n                data[i]\n            );\n\n            if (success) {\n                results[i] = result;\n            } else {\n                // Next 4 lines from\n                // https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/\n                // blob/master/contracts/utils/AddressUpgradeable.sol#L229\n                if (result.length > 0) {\n                    assembly {\n                        let returndata_size := mload(result)\n                        revert(add(32, result), returndata_size)\n                    }\n                } else {\n                    revert FailedMulticall();\n                }\n            }\n\n            unchecked {\n                ++i;\n            }\n        }\n\n        return results;\n    }\n}\n"
      },
      "src/Constants.sol": {
        "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.20;\n\nlibrary Constants {\n    /// @notice Flooring protocol\n    /// @dev floor token amount of 1 NFT (with 18 decimals)\n    uint256 public constant FLOOR_TOKEN_AMOUNT = 1_000_000 ether;\n    /// @dev The minimum vip level required to use `proxy collection`\n    uint8 public constant PROXY_COLLECTION_VIP_THRESHOLD = 3;\n\n    /// @notice Rolling Bucket Constant Conf\n    uint256 public constant BUCKET_SPAN_1 = 259199 seconds; // BUCKET_SPAN minus 1, used for rounding up\n    uint256 public constant BUCKET_SPAN = 3 days;\n    uint256 public constant MAX_LOCKING_BUCKET = 240;\n    uint256 public constant MAX_LOCKING_PERIOD = 720 days; // MAX LOCKING BUCKET * BUCKET_SPAN\n\n    /// @notice Auction Config\n    uint256 public constant FREE_AUCTION_PERIOD = 24 hours;\n    uint256 public constant AUCTION_INITIAL_PERIODS = 24 hours;\n    uint256 public constant AUCTION_COMPLETE_GRACE_PERIODS = 2 days;\n    /// @dev admin fee charged per NFT when someone starts aution on expired safebox\n    uint256 public constant AUCTION_ON_EXPIRED_SAFEBOX_COST = 0;\n    /// @dev admin fee charged per NFT when owner starts aution on himself safebox\n    uint256 public constant AUCTION_COST = 100 ether;\n\n    /// @notice Raffle Config\n    uint256 public constant RAFFLE_COST = 500 ether;\n    uint256 public constant RAFFLE_COMPLETE_GRACE_PERIODS = 2 days;\n\n    /// @notice Private offer Config\n    uint256 public constant PRIVATE_OFFER_DURATION = 24 hours;\n    uint256 public constant PRIVATE_OFFER_COMPLETE_GRACE_DURATION = 2 days;\n    uint256 public constant PRIVATE_OFFER_COST = 0;\n\n    uint256 public constant ADD_FREE_NFT_REWARD = 0;\n\n    /// @notice Lock/Unlock config\n    uint256 public constant USER_SAFEBOX_QUOTA_REFRESH_DURATION = 1 days;\n    uint256 public constant USER_REDEMPTION_WAIVER_REFRESH_DURATION = 1 days;\n    /// @notice The max percentage of the collection that one user can lock\n    uint256 public constant USER_COLLECTION_LOCKED_BOUND_PCT = 50;\n    /// @notice The max locking ratio of the collection that the NFTs in the vault can be redeemed\n    uint256 public constant VAULT_REDEMPTION_MAX_LOKING_RATIO = 80;\n    uint256 public constant VAULT_QUOTA_RESET_PERIOD = 5 days;\n\n    /// @notice Activities Fee Rate\n\n    /// @notice Fee rate used to distribute funds that collected from Auctions on expired safeboxes.\n    /// these auction would be settled using credit token\n    uint256 public constant FREE_AUCTION_FEE_RATE_BIPS = 2000; // 20%\n\n    uint256 public constant VIP_LEVEL_COUNT = 8;\n\n    struct AuctionBidOption {\n        uint256 extendDurationSecs;\n        uint256 minimumRaisePct;\n        uint256 vipLevel;\n    }\n\n    function getVipLockingBuckets(\n        uint256 vipLevel\n    ) internal pure returns (uint256 buckets) {\n        require(vipLevel < VIP_LEVEL_COUNT);\n        assembly {\n            switch vipLevel\n            case 1 {\n                buckets := 1\n            }\n            case 2 {\n                buckets := 5\n            }\n            case 3 {\n                buckets := 20\n            }\n            case 4 {\n                buckets := 60\n            }\n            case 5 {\n                buckets := 120\n            }\n            case 6 {\n                buckets := 180\n            }\n            case 7 {\n                buckets := MAX_LOCKING_BUCKET\n            }\n        }\n    }\n\n    function getVipLevel(uint256 totalCredit) internal pure returns (uint8) {\n        if (totalCredit < 30_000 ether) {\n            return 0;\n        } else if (totalCredit < 100_000 ether) {\n            return 1;\n        } else if (totalCredit < 300_000 ether) {\n            return 2;\n        } else if (totalCredit < 1_000_000 ether) {\n            return 3;\n        } else if (totalCredit < 3_000_000 ether) {\n            return 4;\n        } else if (totalCredit < 10_000_000 ether) {\n            return 5;\n        } else if (totalCredit < 30_000_000 ether) {\n            return 6;\n        } else {\n            return 7;\n        }\n    }\n\n    function getVipBalanceRequirements(\n        uint256 vipLevel\n    ) internal pure returns (uint256 required) {\n        require(vipLevel < VIP_LEVEL_COUNT);\n\n        assembly {\n            switch vipLevel\n            case 1 {\n                required := 30000\n            }\n            case 2 {\n                required := 100000\n            }\n            case 3 {\n                required := 300000\n            }\n            case 4 {\n                required := 1000000\n            }\n            case 5 {\n                required := 3000000\n            }\n            case 6 {\n                required := 10000000\n            }\n            case 7 {\n                required := 30000000\n            }\n        }\n\n        /// credit token should be scaled with 18 decimals(1 ether == 10**18)\n        unchecked {\n            return required * 1 ether;\n        }\n    }\n\n    function getBidOption(\n        uint256 idx\n    ) internal pure returns (AuctionBidOption memory) {\n        require(idx < 4);\n        AuctionBidOption[4] memory bidOptions = [\n            AuctionBidOption({\n                extendDurationSecs: 5 minutes,\n                minimumRaisePct: 1,\n                vipLevel: 0\n            }),\n            AuctionBidOption({\n                extendDurationSecs: 8 hours,\n                minimumRaisePct: 10,\n                vipLevel: 3\n            }),\n            AuctionBidOption({\n                extendDurationSecs: 16 hours,\n                minimumRaisePct: 20,\n                vipLevel: 5\n            }),\n            AuctionBidOption({\n                extendDurationSecs: 24 hours,\n                minimumRaisePct: 40,\n                vipLevel: 7\n            })\n        ];\n        return bidOptions[idx];\n    }\n\n    function raffleDurations(\n        uint256 idx\n    ) internal pure returns (uint256 vipLevel, uint256 duration) {\n        require(idx < 6);\n\n        vipLevel = idx;\n        assembly {\n            switch idx\n            case 1 {\n                duration := 1\n            }\n            case 2 {\n                duration := 2\n            }\n            case 3 {\n                duration := 3\n            }\n            case 4 {\n                duration := 5\n            }\n            case 5 {\n                duration := 7\n            }\n        }\n        unchecked {\n            duration *= 1 days;\n        }\n    }\n\n    /// return locking ratio restrictions\n    /// indicates that the vipLevel can utility infinite lock NFTs at corresponding ratio\n    function getLockingRatioForInfinite(\n        uint8 vipLevel\n    ) internal pure returns (uint256 ratio) {\n        assembly {\n            switch vipLevel\n            case 1 {\n                ratio := 0\n            }\n            case 2 {\n                ratio := 0\n            }\n            case 3 {\n                ratio := 20\n            }\n            case 4 {\n                ratio := 30\n            }\n            case 5 {\n                ratio := 40\n            }\n            case 6 {\n                ratio := 50\n            }\n            case 7 {\n                ratio := 80\n            }\n        }\n    }\n\n    /// return locking ratio restrictions\n    /// indicates that the vipLevel can utility safebox to lock NFTs at corresponding ratio\n    function getLockingRatioForSafebox(\n        uint8 vipLevel\n    ) internal pure returns (uint256 ratio) {\n        assembly {\n            switch vipLevel\n            case 1 {\n                ratio := 10\n            }\n            case 2 {\n                ratio := 20\n            }\n            case 3 {\n                ratio := 30\n            }\n            case 4 {\n                ratio := 40\n            }\n            case 5 {\n                ratio := 50\n            }\n            case 6 {\n                ratio := 60\n            }\n            case 7 {\n                ratio := 70\n            }\n        }\n    }\n\n    function getRequiredStakingWithSelfRatio(\n        uint256 requiredStaking,\n        uint256 selfRatio\n    ) internal pure returns (uint256) {\n        if (selfRatio < 10) {\n            return requiredStaking;\n        }\n\n        return ((selfRatio + 1) * requiredStaking) / 10;\n    }\n\n    function getVipRequiredStakingWithDiscount(\n        uint256 requiredStaking,\n        uint8 vipLevel\n    ) internal pure returns (uint256) {\n        if (vipLevel < 3) {\n            return requiredStaking;\n        }\n        unchecked {\n            /// the higher vip level, more discount for staking\n            ///  discount range: 5% - 25%\n            return (requiredStaking * (100 - (vipLevel - 2) * 5)) / 100;\n        }\n    }\n\n    function getRequiredStakingForLockRatio(\n        uint256 locked,\n        uint256 totalManaged\n    ) internal pure returns (uint256) {\n        if (totalManaged <= 0) {\n            return 1200 ether;\n        }\n\n        unchecked {\n            uint256 lockingRatioPct = (locked * 100) / totalManaged;\n            if (lockingRatioPct <= 40) {\n                return 1200 ether;\n            } else if (lockingRatioPct < 60) {\n                return 1320 ether + ((lockingRatioPct - 40) >> 1) * 120 ether;\n            } else if (lockingRatioPct < 70) {\n                return 2640 ether + ((lockingRatioPct - 60) >> 1) * 240 ether;\n            } else if (lockingRatioPct < 80) {\n                return 4080 ether + ((lockingRatioPct - 70) >> 1) * 480 ether;\n            } else if (lockingRatioPct < 90) {\n                return 6960 ether + ((lockingRatioPct - 80) >> 1) * 960 ether;\n            } else if (lockingRatioPct < 100) {\n                /// 108000 * 2^x\n                return (108000 ether << ((lockingRatioPct - 90) >> 1)) / 5;\n            } else {\n                return 345600 ether;\n            }\n        }\n    }\n\n    function getVaultAuctionDurationAtLR(\n        uint256 lockingRatio\n    ) internal pure returns (uint256) {\n        if (lockingRatio < 80) return 1 hours;\n        else if (lockingRatio < 85) return 3 hours;\n        else if (lockingRatio < 90) return 6 hours;\n        else if (lockingRatio < 95) return 12 hours;\n        else return 24 hours;\n    }\n\n    function getSafeboxPeriodQuota(\n        uint8 vipLevel\n    ) internal pure returns (uint16 quota) {\n        assembly {\n            switch vipLevel\n            case 0 {\n                quota := 0\n            }\n            case 1 {\n                quota := 1\n            }\n            case 2 {\n                quota := 2\n            }\n            case 3 {\n                quota := 4\n            }\n            case 4 {\n                quota := 8\n            }\n            case 5 {\n                quota := 16\n            }\n            case 6 {\n                quota := 32\n            }\n            case 7 {\n                quota := 64\n            }\n        }\n    }\n\n    function getSafeboxUserQuota(\n        uint8 vipLevel\n    ) internal pure returns (uint16 quota) {\n        assembly {\n            switch vipLevel\n            case 0 {\n                quota := 0\n            }\n            case 1 {\n                quota := 4\n            }\n            case 2 {\n                quota := 8\n            }\n            case 3 {\n                quota := 16\n            }\n            case 4 {\n                quota := 32\n            }\n            case 5 {\n                quota := 64\n            }\n            case 6 {\n                quota := 128\n            }\n            case 7 {\n                quota := 256\n            }\n        }\n    }\n\n    function getVaultContQuotaAtLR(\n        uint256 lockingRatio\n    ) internal pure returns (uint32 contQuota) {\n        if (lockingRatio <= 70) {\n            return 1;\n        } else if (lockingRatio <= 80) {\n            return 2;\n        } else if (lockingRatio <= 90) {\n            return 4;\n        } else {\n            return 8;\n        }\n    }\n\n    /// two options to redeem from vault\n    /// pay fee with fragment token or consume quota\n    function getVaultQuotaFeeAtLR(\n        uint256 lockingRatio\n    ) internal pure returns (uint32) {\n        if (lockingRatio <= 50) {\n            return 1;\n        } else {\n            return uint32(2 ** ((lockingRatio - 51) / 10 + 1));\n        }\n    }\n\n    function getVaultRedemptionFee(\n        uint256 lockingRatio,\n        uint16 baseRate\n    ) internal pure returns (uint256) {\n        if (lockingRatio <= 50) {\n            return (FLOOR_TOKEN_AMOUNT * baseRate) / 10000;\n        } else {\n            uint256 rate = ((lockingRatio - 51) / 10 + 2) * baseRate;\n            return (FLOOR_TOKEN_AMOUNT * rate) / 10000;\n        }\n    }\n\n    /// @return protocol fee after discount\n    function getListingProtocolFeeWithDiscount(\n        uint256 protocolFee,\n        uint8 vipLevel\n    ) internal pure returns (uint256) {\n        if (vipLevel < 3) {\n            return protocolFee;\n        }\n        unchecked {\n            /// the higher vip level, more discount for protocol fee\n            ///  discount range: 5% - 25%\n            return (protocolFee * (100 - (vipLevel - 2) * 5)) / 100;\n        }\n    }\n}\n"
      },
      "src/Errors.sol": {
        "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.20;\n\nlibrary Errors {\n    /// @notice Safe Box error\n    error SafeBoxHasExpire();\n    error SafeBoxNotExist();\n    error SafeBoxHasNotExpire();\n    error SafeBoxAlreadyExist();\n    error NoMatchingSafeBoxKey();\n    error SafeBoxKeyAlreadyExist();\n\n    /// @notice Auction error\n    error AuctionHasNotCompleted();\n    error AuctionHasExpire();\n    error AuctionBidIsNotHighEnough();\n    error AuctionBidTokenMismatch();\n    error AuctionSelfBid();\n    error AuctionInvalidBidAmount();\n    error AuctionNotExist();\n    error SafeBoxAuctionWindowHasPassed();\n\n    /// @notice Activity common error\n    error NftHasActiveActivities();\n    error ActivityHasNotCompleted();\n    error ActivityHasExpired();\n    error ActivityNotExist();\n\n    /// @notice User account error\n    error InsufficientCredit();\n    error InsufficientBalanceForVipLevel();\n    error NoPrivilege();\n\n    /// @notice Parameter error\n    error InvalidParam();\n    error NftCollectionNotSupported();\n    error NftCollectionAlreadySupported();\n    error ClaimableNftInsufficient();\n    error TokenNotSupported();\n    error PeriodQuotaExhausted();\n    error UserQuotaExhausted();\n}\n"
      },
      "src/Flooring.sol": {
        "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.20;\n\nimport {\n    UUPSUpgradeable\n} from \"@openzeppelin-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol\";\nimport \"chainlink/v0.8/interfaces/VRFCoordinatorV2Interface.sol\";\nimport \"chainlink/v0.8/VRFConsumerBaseV2.sol\";\n\nimport \"./interface/IFlooring.sol\";\nimport \"./interface/IFlooringEvent.sol\";\nimport \"./interface/IFragmentToken.sol\";\n\nimport \"./logic/CollectionKey.sol\";\nimport \"./logic/User.sol\";\nimport \"./logic/Collection.sol\";\nimport \"./logic/Auction.sol\";\nimport \"./logic/Raffle.sol\";\nimport \"./logic/PrivateOffer.sol\";\nimport {\n    CollectionState,\n    UserFloorAccount,\n    FeeConfig\n} from \"./logic/Structs.sol\";\nimport \"./base/Multicall.sol\";\nimport \"./Errors.sol\";\nimport { TrustedUpgradeable } from \"./library/TrustedUpgradeable.sol\";\nimport { Pausable } from \"./library/Pausable.sol\";\n\ncontract Flooring is\n    IFlooring,\n    IFlooringEvent,\n    Multicall,\n    TrustedUpgradeable,\n    UUPSUpgradeable,\n    VRFConsumerBaseV2,\n    Pausable\n{\n    using CollectionLib for CollectionState;\n    using AuctionLib for CollectionState;\n    using RaffleLib for CollectionState;\n    using PrivateOfferLib for CollectionState;\n    using UserLib for UserFloorAccount;\n    using CollectionKeyLib for CollectionKey;\n\n    struct RandomRequestInfo {\n        uint96 typ;\n        address collection;\n        bytes data;\n    }\n\n    /// Information related to Chainlink VRF Randomness Oracle.\n\n    /// The keyhash, which is network dependent.\n    bytes32 internal immutable keyHash;\n    /// Subscription Id, need to get from the Chainlink UI.\n    uint64 internal immutable subId;\n    /// Chainlink VRF Coordinator.\n    VRFCoordinatorV2Interface internal immutable COORDINATOR;\n\n    /// A mapping from VRF request Id to raffle.\n    mapping(uint256 requestId => RandomRequestInfo requestInfo)\n        internal randomnessRequestToReceiver;\n\n    /// This should be the FLC token.\n    address public immutable creditToken;\n\n    /// A mapping from collection id to `CollectionState`.\n    mapping(address collectionId => CollectionState state)\n        internal collectionStates;\n\n    /// A mapping from user address to the `UserFloorAccount`s.\n    mapping(address walletAddr => UserFloorAccount account)\n        internal userFloorAccounts;\n\n    /// A mapping of supported ERC-20 token.\n    mapping(address tokenAddr => bool isSupported) internal supportedTokens;\n\n    /// A mapping from Proxy Collection(wrapped) to underlying Collection.\n    /// eg. Paraspace Derivative Token BAYC(nBAYC) -> BAYC\n    /// Note. we only use proxy collection to transfer NFTs,\n    ///       all other operations should use underlying Collection.(State, Log, CollectionAccount)\n    ///       proxy collection has not `CollectionState`, but use underlying collection's state.\n    ///       proxy collection only is used to lock infinitly.\n    ///       `fragmentNFTs` and `claimRandomNFT` don't support proxy collection\n    ///       Currently, proxy collection id and proxy collection contract address should be same\n    mapping(address proxyCollectionId => address underlyingCollectionId)\n        internal collectionProxy;\n\n    /// A mapping of collection fees configuration\n    /// For different tokens, we may charge different fees\n    mapping(address collectionId => mapping(address tokenAddr => FeeConfig fees))\n        internal collectionFees;\n\n    /// @dev Function IDs so that we can pause individual functions\n    uint256 internal pausedFuncIds;\n\n    constructor(\n        bytes32 _keyHash,\n        uint64 _subId,\n        address _vrfCoordinator,\n        address flcToken\n    ) payable VRFConsumerBaseV2(_vrfCoordinator) {\n        keyHash = _keyHash;\n        subId = _subId;\n        COORDINATOR = VRFCoordinatorV2Interface(_vrfCoordinator);\n        creditToken = flcToken;\n\n        _disableInitializers();\n    }\n\n    /// required by the OZ UUPS module\n    function _authorizeUpgrade(address) internal override onlyOwner {}\n\n    /// required by the pausable\n    function _pauser() internal view virtual override returns (address) {\n        return owner;\n    }\n\n    /// required by the pausable\n    function _funcIds() internal view virtual override returns (uint256) {\n        return pausedFuncIds;\n    }\n\n    /// required by the pausable\n    function _setFuncIds(uint256 funcIds) internal virtual override {\n        pausedFuncIds = funcIds;\n    }\n\n    /// @dev just declare this as payable to reduce gas and bytecode\n    function initialize() public payable initializer {\n        __Trusted_init();\n        __UUPSUpgradeable_init();\n    }\n\n    function supportNewCollection(\n        NewCollectionParam calldata param\n    ) public payable onlyTrusted {\n        CollectionState storage collection = collectionStates[\n            param.collectionId\n        ];\n        bool notExist = collection.nextKeyId == 0;\n        /// incr without checking whether they are exist or not\n        ++collection.nextKeyId;\n        ++collection.nextActivityId;\n        collection.floorToken = IFragmentToken(param.fragmentToken);\n        collection._contractAddr = param.contractAddr;\n        if (notExist) {\n            emit NewCollectionSupported(\n                param.collectionId,\n                param.fragmentToken\n            );\n        }\n    }\n\n    function supportNewToken(\n        address _token,\n        bool addOrRemove\n    ) public payable onlyTrusted {\n        if (supportedTokens[_token] == addOrRemove) {\n            return;\n        } else {\n            /// true - add\n            /// false - remove\n            supportedTokens[_token] = addOrRemove;\n            emit UpdateTokenSupported(_token, addOrRemove);\n        }\n    }\n\n    function updateCollectionFees(\n        address collectionId,\n        address[] calldata tokens,\n        FeeConfig[] calldata fees\n    ) public payable onlyTrusted {\n        for (uint256 i; i < tokens.length; ) {\n            collectionFees[collectionId][tokens[i]] = fees[i];\n            unchecked {\n                ++i;\n            }\n        }\n    }\n\n    function setCollectionProxy(\n        address proxyCollection,\n        address underlying\n    ) public payable onlyTrusted {\n        if (collectionProxy[proxyCollection] == underlying) {\n            return;\n        } else {\n            collectionProxy[proxyCollection] = underlying;\n            emit ProxyCollectionChanged(proxyCollection, underlying);\n        }\n    }\n\n    function withdrawPlatformFee(\n        address token,\n        uint256 amount\n    ) public payable onlyTrusted {\n        /// track platform fee with account, only can withdraw fee accumulated during tx.\n        /// no need to check credit token balance for the account.\n        UserFloorAccount storage userFloorAccount = userFloorAccounts[\n            address(this)\n        ];\n        userFloorAccount.withdraw(msg.sender, token, amount, false);\n    }\n\n    function addAndLockCredit(\n        address onBehalfOf,\n        uint256 amount\n    ) public payable onlyTrusted {\n        UserFloorAccount storage userFloorAccount = userFloorAccounts[\n            onBehalfOf\n        ];\n        userFloorAccount.deposit(onBehalfOf, creditToken, amount, true);\n    }\n\n    function unlockCredit(\n        address receiver,\n        uint256 amount\n    ) public payable onlyTrusted {\n        UserFloorAccount storage userFloorAccount = userFloorAccounts[receiver];\n        userFloorAccount.unlockCredit(amount);\n    }\n\n    function addTokens(\n        address onBehalfOf,\n        address token,\n        uint256 amount\n    ) public payable whenNotPaused(1) {\n        mustSupportedToken(token);\n\n        UserFloorAccount storage userFloorAccount = userFloorAccounts[\n            onBehalfOf\n        ];\n        userFloorAccount.deposit(onBehalfOf, token, amount, false);\n    }\n\n    function removeTokens(\n        address token,\n        uint256 amount,\n        address receiver\n    ) public whenNotPaused(2) {\n        UserFloorAccount storage userFloorAccount = userFloorAccounts[\n            msg.sender\n        ];\n        userFloorAccount.withdraw(\n            receiver,\n            token,\n            amount,\n            token == creditToken\n        );\n    }\n\n    function lockNFTs(\n        CollectionKey key,\n        uint256[] memory nftIds,\n        uint256 expiryTs,\n        uint256 vipLevel,\n        uint256 maxCreditCost,\n        address onBehalfOf\n    ) public whenNotPaused(3) returns (uint256) {\n        mustValidNftIds(nftIds);\n        mustValidExpiryTs(expiryTs);\n        /// only check the first and the last elems as `nftIds` are in ascending order\n        if (!key.isValidTokenList(nftIds[0], nftIds[nftIds.length - 1]))\n            revert Errors.InvalidParam();\n\n        address collectionId = key.id();\n        (\n            CollectionState storage collectionState,\n            address underlying\n        ) = useUnderlyingCollectionState(collectionId);\n        return\n            collectionState.lockNfts(\n                userFloorAccounts[onBehalfOf],\n                LockParam({\n                    proxyCollectionId: collectionId,\n                    collectionId: underlying,\n                    creditToken: creditToken,\n                    nftIds: nftIds,\n                    expiryTs: expiryTs,\n                    vipLevel: uint8(vipLevel),\n                    maxCreditCost: maxCreditCost\n                }),\n                onBehalfOf\n            );\n    }\n\n    function unlockNFTs(\n        address collectionId,\n        uint256 expiryTs,\n        uint256[] memory nftIds,\n        address receiver\n    ) public whenNotPaused(4) {\n        mustValidNftIds(nftIds);\n\n        (\n            CollectionState storage collectionState,\n            address underlying\n        ) = useUnderlyingCollectionState(collectionId);\n        collectionState.unlockNfts(\n            userFloorAccounts[msg.sender],\n            collectionId,\n            underlying,\n            nftIds,\n            expiryTs,\n            receiver\n        );\n    }\n\n    function extendKeys(\n        address collectionId,\n        uint256[] memory nftIds,\n        uint256 expiryTs,\n        uint256 newVipLevel,\n        uint256 maxCreditCost\n    ) public whenNotPaused(5) returns (uint256) {\n        mustValidNftIds(nftIds);\n        mustValidExpiryTs(expiryTs);\n\n        (\n            CollectionState storage collectionState,\n            address underlying\n        ) = useUnderlyingCollectionState(collectionId);\n        return\n            collectionState.extendLockingForKeys(\n                userFloorAccounts[msg.sender],\n                LockParam({\n                    proxyCollectionId: collectionId,\n                    collectionId: underlying,\n                    creditToken: creditToken,\n                    nftIds: nftIds,\n                    expiryTs: expiryTs,\n                    vipLevel: uint8(newVipLevel),\n                    maxCreditCost: maxCreditCost\n                })\n            );\n    }\n\n    function tidyExpiredNFTs(\n        address collectionId,\n        uint256[] memory nftIds\n    ) public whenNotPaused(6) {\n        mustValidNftIds(nftIds);\n        /// expired safeboxes must not be collection\n        CollectionState storage collectionState = useCollectionState(\n            collectionId\n        );\n        collectionState.tidyExpiredNFTs(nftIds, collectionId);\n    }\n\n    function fragmentNFTs(\n        CollectionKey key,\n        uint256[] memory nftIds,\n        address onBehalfOf\n    ) public whenNotPaused(7) {\n        mustValidNftIds(nftIds);\n        /// only check the first and the last elems as `nftIds` are in ascending order\n        if (!key.isValidTokenList(nftIds[0], nftIds[nftIds.length - 1]))\n            revert Errors.InvalidParam();\n\n        CollectionState storage collectionState = useCollectionState(key.id());\n\n        collectionState.fragmentNFTs(\n            userFloorAccounts[onBehalfOf],\n            key.id(),\n            nftIds,\n            onBehalfOf\n        );\n    }\n\n    function claimRandomNFT(\n        address collectionId,\n        uint256 claimCnt,\n        uint256 maxCreditCost,\n        address receiver\n    ) public whenNotPaused(8) returns (uint256) {\n        CollectionState storage collectionState = useCollectionState(\n            collectionId\n        );\n\n        return\n            collectionState.claimRandomNFT(\n                userFloorAccounts,\n                collectionFees[collectionId],\n                collectionId,\n                claimCnt,\n                maxCreditCost,\n                receiver\n            );\n    }\n\n    function initAuctionOnVault(\n        address collectionId,\n        uint256[] memory vaultIdx,\n        address bidToken,\n        uint96 bidAmount\n    ) public whenNotPaused(9) {\n        mustValidNftIds(vaultIdx);\n        CollectionState storage collectionState = useCollectionState(\n            collectionId\n        );\n        collectionState.initAuctionOnVault(\n            userFloorAccounts,\n            collectionFees[collectionId][bidToken],\n            creditToken,\n            collectionId,\n            vaultIdx,\n            bidToken,\n            bidAmount\n        );\n    }\n\n    function initAuctionOnExpiredSafeBoxes(\n        address collectionId,\n        uint256[] memory nftIds,\n        address bidToken,\n        uint256 bidAmount\n    ) public whenNotPaused(10) {\n        mustValidNftIds(nftIds);\n        (\n            CollectionState storage collectionState,\n            address underlying\n        ) = useUnderlyingCollectionState(collectionId);\n        collectionState.initAuctionOnExpiredSafeBoxes(\n            userFloorAccounts,\n            collectionFees[underlying][bidToken],\n            creditToken,\n            underlying,\n            nftIds,\n            bidToken,\n            bidAmount\n        );\n    }\n\n    function ownerInitAuctions(\n        address collectionId,\n        uint256[] memory nftIds,\n        uint256 maxExpiry,\n        address token,\n        uint256 minimumBid\n    ) public whenNotPaused(11) {\n        mustValidNftIds(nftIds);\n\n        (\n            CollectionState storage collectionState,\n            address underlying\n        ) = useUnderlyingCollectionState(collectionId);\n        collectionState.ownerInitAuctions(\n            userFloorAccounts,\n            collectionFees[underlying][token],\n            creditToken,\n            underlying,\n            nftIds,\n            maxExpiry,\n            token,\n            minimumBid\n        );\n    }\n\n    function placeBidOnAuction(\n        address collectionId,\n        uint256 nftId,\n        uint256 bidAmount,\n        uint256 bidOptionIdx\n    ) public payable whenNotPaused(12) {\n        if (msg.value > 0) {\n            addTokens(msg.sender, CurrencyTransfer.NATIVE, msg.value);\n        }\n        (\n            CollectionState storage collectionState,\n            address underlying\n        ) = useUnderlyingCollectionState(collectionId);\n        collectionState.placeBidOnAuction(\n            userFloorAccounts,\n            creditToken,\n            underlying,\n            nftId,\n            bidAmount,\n            bidOptionIdx\n        );\n    }\n\n    function settleAuctions(\n        address collectionId,\n        uint256[] memory nftIds\n    ) public whenNotPaused(13) {\n        mustValidNftIds(nftIds);\n        (\n            CollectionState storage collectionState,\n            address underlying\n        ) = useUnderlyingCollectionState(collectionId);\n        collectionState.settleAuctions(userFloorAccounts, underlying, nftIds);\n    }\n\n    function ownerInitRaffles(\n        RaffleInitParam memory param\n    ) public whenNotPaused(14) {\n        mustValidNftIds(param.nftIds);\n\n        (\n            CollectionState storage collectionState,\n            address underlying\n        ) = useUnderlyingCollectionState(param.collectionId);\n        param.collectionId = underlying;\n\n        collectionState.ownerInitRaffles(\n            userFloorAccounts,\n            collectionFees[underlying][param.ticketToken],\n            param,\n            creditToken\n        );\n    }\n\n    function buyRaffleTickets(\n        address collectionId,\n        uint256 nftId,\n        uint256 ticketCnt\n    ) public payable whenNotPaused(15) {\n        if (msg.value > 0) {\n            addTokens(msg.sender, CurrencyTransfer.NATIVE, msg.value);\n        }\n        (\n            CollectionState storage collectionState,\n            address underlying\n        ) = useUnderlyingCollectionState(collectionId);\n\n        collectionState.buyRaffleTickets(\n            userFloorAccounts,\n            creditToken,\n            underlying,\n            nftId,\n            ticketCnt\n        );\n    }\n\n    function settleRaffles(\n        address collectionId,\n        uint256[] memory nftIds\n    ) public whenNotPaused(16) {\n        mustValidNftIds(nftIds);\n        (\n            CollectionState storage collectionState,\n            address underlying\n        ) = useUnderlyingCollectionState(collectionId);\n\n        (bytes memory toSettleNftIds, uint256 len) = collectionState\n            .prepareSettleRaffles(nftIds);\n        if (len > 0) {\n            uint256 requestId = COORDINATOR.requestRandomWords(\n                keyHash,\n                subId,\n                3,\n                800_000,\n                uint32(len)\n            );\n            randomnessRequestToReceiver[requestId] = RandomRequestInfo({\n                typ: 1,\n                collection: underlying,\n                data: toSettleNftIds\n            });\n        }\n    }\n\n    function _completeSettleRaffles(\n        address collectionId,\n        bytes memory data,\n        uint256[] memory randoms\n    ) private {\n        CollectionState storage collection = collectionStates[collectionId];\n        collection.settleRaffles(\n            userFloorAccounts,\n            collectionId,\n            data,\n            randoms\n        );\n    }\n\n    function ownerInitPrivateOffers(\n        PrivateOfferInitParam memory param\n    ) public whenNotPaused(17) {\n        mustValidNftIds(param.nftIds);\n\n        (\n            CollectionState storage collectionState,\n            address underlying\n        ) = useUnderlyingCollectionState(param.collectionId);\n        param.collectionId = underlying;\n        collectionState.ownerInitPrivateOffers(\n            userFloorAccounts,\n            collectionFees[underlying][param.token],\n            creditToken,\n            param\n        );\n    }\n\n    function modifyOffers(\n        address collectionId,\n        uint256[] memory nftIds,\n        OfferOpType opTy,\n        bytes calldata data\n    ) public whenNotPaused(18) {\n        (\n            CollectionState storage collectionState,\n            address underlying\n        ) = useUnderlyingCollectionState(collectionId);\n        collectionState.modifyOffers(\n            userFloorAccounts,\n            underlying,\n            nftIds,\n            opTy,\n            data\n        );\n    }\n\n    function buyerAcceptPrivateOffers(\n        address collectionId,\n        uint256[] memory nftIds,\n        uint256 maxSafeboxExpiry\n    ) public payable whenNotPaused(19) {\n        mustValidNftIds(nftIds);\n        if (msg.value > 0) {\n            addTokens(msg.sender, CurrencyTransfer.NATIVE, msg.value);\n        }\n        (\n            CollectionState storage collectionState,\n            address underlying\n        ) = useUnderlyingCollectionState(collectionId);\n        collectionState.buyerAcceptPrivateOffers(\n            userFloorAccounts,\n            underlying,\n            creditToken,\n            nftIds,\n            maxSafeboxExpiry\n        );\n    }\n\n    function removeExpiredKeyAndRestoreCredit(\n        address collectionId,\n        uint256[] memory nftIds,\n        address onBehalfOf,\n        bool verifyLocking\n    ) public whenNotPaused(20) returns (uint256) {\n        mustValidNftIds(nftIds);\n\n        (\n            CollectionState storage collectionState,\n            address underlying\n        ) = useUnderlyingCollectionState(collectionId);\n\n        return\n            userFloorAccounts[onBehalfOf].removeExpiredKeysAndRestoreCredits(\n                collectionState,\n                underlying,\n                nftIds,\n                onBehalfOf,\n                verifyLocking\n            );\n    }\n\n    function recalculateAvailableCredit(\n        address onBehalfOf\n    ) public whenNotPaused(21) returns (uint256) {\n        UserFloorAccount storage account = userFloorAccounts[onBehalfOf];\n\n        uint256 minMaintCredit = account.recalculateMinMaintCredit(onBehalfOf);\n        unchecked {\n            /// when locking or extending, we ensure that `minMaintCredit` is less than `totalCredit`\n            /// availableCredit = totalCredit - minMaintCredit\n            return account.tokenBalance(creditToken) - minMaintCredit;\n        }\n    }\n\n    function onERC721Received(\n        address,\n        /*operator*/\n        address,\n        /*from*/\n        uint256,\n        /*tokenId*/\n        bytes calldata /*data*/\n    ) external pure override returns (bytes4) {\n        return this.onERC721Received.selector;\n    }\n\n    function useUnderlyingCollectionState(\n        address collectionId\n    ) private view returns (CollectionState storage, address) {\n        address underlying = collectionProxy[collectionId];\n        if (underlying == address(0)) {\n            underlying = collectionId;\n        }\n\n        return (useCollectionState(underlying), underlying);\n    }\n\n    function useCollectionState(\n        address collectionId\n    ) private view returns (CollectionState storage) {\n        CollectionState storage collection = collectionStates[collectionId];\n        if (collection.nextKeyId == 0) {\n            revert Errors.NftCollectionNotSupported();\n        }\n        return collection;\n    }\n\n    function mustSupportedToken(address token) private view {\n        if (!supportedTokens[token]) revert Errors.TokenNotSupported();\n    }\n\n    function mustValidNftIds(uint256[] memory nftIds) private pure {\n        if (nftIds.length == 0) revert Errors.InvalidParam();\n\n        /// nftIds should be ordered and there should be no duplicate elements.\n        for (uint256 i = 1; i < nftIds.length; ) {\n            unchecked {\n                if (nftIds[i] <= nftIds[i - 1]) {\n                    revert Errors.InvalidParam();\n                }\n                ++i;\n            }\n        }\n    }\n\n    function mustValidExpiryTs(uint256 expiryTs) private view {\n        if (expiryTs != 0 && expiryTs <= block.timestamp) {\n            revert Errors.InvalidParam();\n        }\n    }\n\n    function fulfillRandomWords(\n        uint256 requestId,\n        uint256[] memory randomWords\n    ) internal override {\n        RandomRequestInfo storage info = randomnessRequestToReceiver[requestId];\n\n        _completeSettleRaffles(info.collection, info.data, randomWords);\n\n        delete randomnessRequestToReceiver[requestId];\n    }\n\n    function collectionLockingAt(\n        address collectionId,\n        uint256 startTimestamp,\n        uint256 endTimestamp\n    ) public view returns (uint256[] memory) {\n        return\n            collectionStates[collectionId].getLockingBuckets(\n                startTimestamp,\n                endTimestamp\n            );\n    }\n\n    function extsload(bytes32 slot) external view returns (bytes32 value) {\n        /// @solidity memory-safe-assembly\n        assembly {\n            value := sload(slot)\n        }\n    }\n\n    function extsload(\n        bytes32 startSlot,\n        uint256 nSlots\n    ) external view returns (bytes memory) {\n        bytes memory value = new bytes(nSlots << 5);\n\n        /// @solidity memory-safe-assembly\n        assembly {\n            for {\n                let i := 0\n            } lt(i, nSlots) {\n                i := add(i, 1)\n            } {\n                mstore(add(value, shl(5, add(i, 1))), sload(add(startSlot, i)))\n            }\n        }\n\n        return value;\n    }\n\n    receive() external payable {\n        addTokens(msg.sender, CurrencyTransfer.NATIVE, msg.value);\n    }\n}\n"
      },
      "src/interface/IFlooring.sol": {
        "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.20;\n\nimport \"@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol\";\nimport \"./IMulticall.sol\";\nimport { CollectionKey } from \"../logic/CollectionKey.sol\";\n\ninterface IFlooring is IERC721Receiver, IMulticall {\n    /// Admin Operations\n\n    struct NewCollectionParam {\n        /// @notice The Collection Id in the Floor Protocol\n        /// It is maybe different from the collection contract address\n        address collectionId;\n        /// @notice The Contract Address of the collection\n        address contractAddr;\n        /// @notice The fragment token of the collection\n        address fragmentToken;\n    }\n\n    /// @notice Add new collection for Flooring Protocol\n    function supportNewCollection(\n        NewCollectionParam calldata param\n    ) external payable;\n\n    /// @notice Add new token which will be used as settlement token in Flooring Protocol\n    /// @param addOrRemove `true` means add token, `false` means remove token\n    function supportNewToken(\n        address _tokenAddress,\n        bool addOrRemove\n    ) external payable;\n\n    /// @notice set proxy collection config\n    /// Note. the `tokenId`s of the proxy collection and underlying collection must be correspond one by one\n    /// eg. Paraspace Derivative Token BAYC(nBAYC) -> BAYC\n    function setCollectionProxy(\n        address proxyCollection,\n        address underlyingCollection\n    ) external payable;\n\n    /// @notice withdraw platform fee accumulated.\n    /// Note. withdraw from `address(this)`'s account.\n    function withdrawPlatformFee(\n        address token,\n        uint256 amount\n    ) external payable;\n\n    /// @notice Deposit and lock credit token on behalf of receiver\n    /// user can not withdraw these tokens until `unlockCredit` is called.\n    function addAndLockCredit(\n        address receiver,\n        uint256 amount\n    ) external payable;\n\n    /// @notice Unlock user credit token to allow withdraw\n    /// used to release investors' funds as time goes\n    /// Note. locked credit can be used to operate safeboxes(lock/unlock...)\n    function unlockCredit(address receiver, uint256 amount) external payable;\n\n    /// User Operations\n\n    /// @notice User deposits token to the Floor Contract\n    /// @param onBehalfOf deposit token into `onBehalfOf`'s account.(note. the tokens of msg.sender will be transfered)\n    function addTokens(\n        address onBehalfOf,\n        address token,\n        uint256 amount\n    ) external payable;\n\n    /// @notice User removes token from Floor Contract\n    /// @param receiver who will receive the funds.(note. the token of msg.sender will be transfered)\n    function removeTokens(\n        address token,\n        uint256 amount,\n        address receiver\n    ) external;\n\n    /// @notice Lock specified `nftIds` into Flooring Safeboxes\n    /// and receive corresponding Fragment Tokens of the `collection`\n    /// @param expiryTs when the safeboxes expired, `0` means infinite lock without expiry\n    /// @param vipLevel vip tier required in this lock operation\n    /// @param maxCredit maximum credit can be locked in this operation,\n    ///         if real cost exceeds this limit, the tx will fail\n    /// @param onBehalfOf who will receive the safebox and fragment tokens.\n    ///         (note. the NFTs of the msg.sender will be transfered)\n    function lockNFTs(\n        CollectionKey key,\n        uint256[] memory nftIds,\n        uint256 expiryTs,\n        uint256 vipLevel,\n        uint256 maxCredit,\n        address onBehalfOf\n    ) external returns (uint256);\n\n    /// @notice Extend the exist safeboxes with longer lock duration with more credit token staked\n    /// @param expiryTs new expiry timestamp, should bigger than previous expiry\n    function extendKeys(\n        address collectionId,\n        uint256[] memory nftIds,\n        uint256 expiryTs,\n        uint256 vipLevel,\n        uint256 maxCredit\n    ) external returns (uint256);\n\n    /// @notice Unlock specified `nftIds` which had been locked previously\n    ///         sender's wallet should have enough Fragment Tokens of the `collection`\n    ///         which will be burned to redeem the NFTs\n    /// @param expiryTs the latest nft's expiry, we need this to clear locking records\n    ///                 if the value smaller than the latest nft's expiry, the tx will fail\n    ///                 if part of `nftIds` were locked infinitely, just skip these expiry\n    /// @param receiver who will receive the NFTs.\n    ///                 note. - The safeboxes of the msg.sender will be removed.\n    ///                       - The Fragment Tokens of the msg.sender will be burned.\n    function unlockNFTs(\n        address collectionId,\n        uint256 expiryTs,\n        uint256[] memory nftIds,\n        address receiver\n    ) external;\n\n    /// @notice Fragment specified `nftIds` into Floor Vault and receive Fragment Tokens without any locking\n    ///         after fragmented, any one has enough Fragment Tokens can redeem there `nftIds`\n    /// @param onBehalfOf who will receive the fragment tokens and the vault contribution quota.\n    ///                   (note. the NFTs of the msg.sender will be transfered)\n    ///                   if onBehalfOf == address(this),\n    ///                   it means msg.sender intends to swap the same quantity of NFTs from the vault as the `nftIds`\n    function fragmentNFTs(\n        CollectionKey key,\n        uint256[] memory nftIds,\n        address onBehalfOf\n    ) external;\n\n    /// @notice Kick expired safeboxes to the vault\n    function tidyExpiredNFTs(\n        address collectionId,\n        uint256[] memory nftIds\n    ) external;\n\n    /// @notice Randomly claim `claimCnt` NFTs from Floor Vault\n    ///         sender's wallet should have enough Fragment Tokens of the `collection`\n    ///         which will be burned to redeem the NFTs\n    /// @param maxCredit maximum credit can be costed in this operation,\n    ///        if real cost exceeds this limit, the tx will fail\n    /// @param receiver who will receive the NFTs.\n    ///                 note. - the msg.sender will pay the redemption cost.\n    ///                       - The Fragment Tokens of the msg.sender will be burned.\n    function claimRandomNFT(\n        address collectionId,\n        uint256 claimCnt,\n        uint256 maxCredit,\n        address receiver\n    ) external returns (uint256);\n\n    /// @notice Start auctions on specified `nftIds` with an initial bid price(`bidAmount`)\n    ///         This kind of auctions will be settled with Floor Credit Token\n    /// @param bidAmount initial bid price\n    function initAuctionOnExpiredSafeBoxes(\n        address collectionId,\n        uint256[] memory nftIds,\n        address bidToken,\n        uint256 bidAmount\n    ) external;\n\n    /// @notice Start auctions on specified `nftIds` index in the vault with an initial bid price(`bidAmount`)\n    ///         This kind of auctions will be settled with Fragment Token of the collection\n    /// @param bidAmount initial bid price\n    function initAuctionOnVault(\n        address collectionId,\n        uint256[] memory vaultIdx,\n        address bidToken,\n        uint96 bidAmount\n    ) external;\n\n    /// @notice Owner starts auctions on his locked Safeboxes\n    /// @param maxExpiry the latest nft's expiry, we need this to clear locking records\n    /// @param token which token should be used to settle auctions(bid, settle)\n    /// @param minimumBid minimum bid price when someone place a bid on the auction\n    function ownerInitAuctions(\n        address collectionId,\n        uint256[] memory nftIds,\n        uint256 maxExpiry,\n        address token,\n        uint256 minimumBid\n    ) external;\n\n    /// @notice Place a bid on specified `nftId`'s action\n    /// @param bidAmount bid price\n    /// @param bidOptionIdx which option used to extend auction expiry and bid price\n    function placeBidOnAuction(\n        address collectionId,\n        uint256 nftId,\n        uint256 bidAmount,\n        uint256 bidOptionIdx\n    ) external payable;\n\n    /// @notice Settle auctions of `nftIds`\n    function settleAuctions(\n        address collectionId,\n        uint256[] memory nftIds\n    ) external;\n\n    struct RaffleInitParam {\n        address collectionId;\n        uint256[] nftIds;\n        /// @notice which token used to buy and settle raffle\n        address ticketToken;\n        /// @notice price per ticket\n        uint96 ticketPrice;\n        /// @notice max tickets amount can be sold\n        uint32 maxTickets;\n        /// @notice durationIdx used to get how long does raffles last\n        uint256 duration;\n        /// @notice the largest epxiry of nfts, we need this to clear locking records\n        uint256 maxExpiry;\n    }\n\n    /// @notice Owner start raffles on locked `nftIds`\n    function ownerInitRaffles(RaffleInitParam memory param) external;\n\n    /// @notice Buy `nftId`'s raffle tickets\n    /// @param ticketCnt how many tickets should be bought in this operation\n    function buyRaffleTickets(\n        address collectionId,\n        uint256 nftId,\n        uint256 ticketCnt\n    ) external payable;\n\n    /// @notice Settle raffles of `nftIds`\n    function settleRaffles(\n        address collectionId,\n        uint256[] memory nftIds\n    ) external;\n\n    struct PrivateOfferInitParam {\n        address collectionId;\n        uint256[] nftIds;\n        /// @notice the largest epxiry of nfts, we need this to clear locking records\n        uint256 maxExpiry;\n        /// @notice who will receive the otc offers\n        address receiver;\n        /// @notice which token used to settle offers\n        address token;\n        /// @notice price of the offers\n        uint96 price;\n    }\n\n    /// @notice Owner start private offers(otc) on locked `nftIds`\n    function ownerInitPrivateOffers(\n        PrivateOfferInitParam memory param\n    ) external;\n\n    enum OfferOpType {\n        Cancel,\n        Decline,\n        ChangePrice\n    }\n\n    struct ChangeOfferPriceData {\n        uint96[] priceList;\n    }\n\n    /// @notice Owner or Receiver cancel the private offers of `nftIds`\n    function modifyOffers(\n        address collectionId,\n        uint256[] memory nftIds,\n        OfferOpType opTy,\n        bytes calldata data\n    ) external;\n\n    /// @notice Receiver accept the private offers of `nftIds`\n    function buyerAcceptPrivateOffers(\n        address collectionId,\n        uint256[] memory nftIds,\n        uint256 maxSafeboxExpiry\n    ) external payable;\n\n    /// @notice Clear expired or mismatching safeboxes of `nftIds` in user account\n    /// @param onBehalfOf whose account will be recalculated\n    /// @return credit amount has been released\n    function removeExpiredKeyAndRestoreCredit(\n        address collectionId,\n        uint256[] memory nftIds,\n        address onBehalfOf,\n        bool verifyLocking\n    ) external returns (uint256);\n\n    /// @notice Update user's staking credit status by iterating all active collections in user account\n    /// @param onBehalfOf whose account will be recalculated\n    /// @return availableCredit how many credit available to use after this opeartion\n    function recalculateAvailableCredit(\n        address onBehalfOf\n    ) external returns (uint256 availableCredit);\n\n    /// Util operations\n\n    /// @notice Called by external contracts to access granular pool state\n    /// @param slot Key of slot to sload\n    /// @return value The value of the slot as bytes32\n    function extsload(bytes32 slot) external view returns (bytes32 value);\n\n    /// @notice Called by external contracts to access granular pool state\n    /// @param slot Key of slot to start sloading from\n    /// @param nSlots Number of slots to load into return value\n    /// @return value The value of the sload-ed slots concatenated as dynamic bytes\n    function extsload(\n        bytes32 slot,\n        uint256 nSlots\n    ) external view returns (bytes memory value);\n\n    function creditToken() external view returns (address);\n}\n"
      },
      "src/interface/IFlooringEvent.sol": {
        "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.20;\n\nimport { AuctionType, Fees } from \"../logic/Structs.sol\";\n\ninterface IFlooringEvent {\n    event NewCollectionSupported(\n        address indexed collection,\n        address indexed floorToken\n    );\n    event UpdateTokenSupported(address indexed token, bool addOrRemove);\n    event ProxyCollectionChanged(\n        address indexed proxyCollection,\n        address indexed underlyingCollection\n    );\n\n    /// @notice `sender` deposit `token` into Flooring on behalf of `receiver`. `receiver`'s account will be updated.\n    event DepositToken(\n        address indexed sender,\n        address indexed receiver,\n        address indexed token,\n        uint256 amount\n    );\n    /// @notice `sender` withdraw `token` from Flooring and transfer it to `receiver`.\n    event WithdrawToken(\n        address indexed sender,\n        address indexed receiver,\n        address indexed token,\n        uint256 amount\n    );\n    /// @notice update the account maintain credit on behalfOf `onBehalfOf`\n    event UpdateMaintainCredit(\n        address indexed onBehalfOf,\n        uint256 minMaintCredit\n    );\n\n    /// @notice Lock NFTs\n    /// @param sender who send the tx and pay the NFTs\n    /// @param onBehalfOf who will hold the safeboxes and receive the Fragment Tokens\n    /// @param collection contract addr of the collection\n    /// @param tokenIds nft ids to lock\n    /// @param safeBoxKeys compacted safe box keys with same order of `tokenIds`\n    /// for each key, its format is: [167-160:vipLevel][159-96:keyId][95-0:lockedCredit]\n    /// @param safeBoxExpiryTs expiry timestamp of safeboxes\n    /// @param minMaintCredit `onBehalfOf`'s minMaintCredit after the lock\n    event LockNft(\n        address indexed sender,\n        address indexed onBehalfOf,\n        address indexed collection,\n        uint256[] tokenIds,\n        uint256[] safeBoxKeys,\n        uint256 safeBoxExpiryTs,\n        uint256 minMaintCredit,\n        address proxyCollection\n    );\n\n    /// @notice Extend keys\n    /// @param operator who extend the keys\n    /// @param collection contract addr of the collection\n    /// @param tokenIds nft ids to lock\n    /// @param safeBoxKeys compacted safe box keys with same order of `tokenIds`\n    /// for each key, its format is: [167-160:vipLevel][159-96:keyId][95-0:lockedCredit]\n    /// @param safeBoxExpiryTs expiry timestamp of safeboxes\n    /// @param minMaintCredit `operator`'s minMaintCredit after the lock\n    event ExtendKey(\n        address indexed operator,\n        address indexed collection,\n        uint256[] tokenIds,\n        uint256[] safeBoxKeys,\n        uint256 safeBoxExpiryTs,\n        uint256 minMaintCredit\n    );\n\n    /// @notice Unlock NFTs\n    /// @param operator who hold the safeboxes that will be unlocked\n    /// @param receiver who will receive the NFTs\n    event UnlockNft(\n        address indexed operator,\n        address indexed receiver,\n        address indexed collection,\n        uint256[] tokenIds,\n        address proxyCollection\n    );\n\n    /// @notice `operator` remove invalid keys on behalf of `onBehalfOf`.\n    /// `onBehalfOf`'s account will be updated.\n    event RemoveExpiredKey(\n        address indexed operator,\n        address indexed onBehalfOf,\n        address indexed collection,\n        uint256[] tokenIds,\n        uint256[] safeBoxKeys\n    );\n\n    /// @notice [Deprecated] Claim expired safeboxes that maintain NFTs\n    /// @param operator who will pay the redemption cost\n    /// @param receiver who will receive the NFTs\n    /// @param creditCost how many credit token cost in this claim\n    event ClaimExpiredNft(\n        address indexed operator,\n        address indexed receiver,\n        address indexed collection,\n        uint256[] tokenIds,\n        uint256 creditCost,\n        address proxyCollection\n    );\n\n    /// @notice Kick expired safeboxes to the vault\n    event ExpiredNftToVault(\n        address indexed operator,\n        address indexed collection,\n        uint256[] tokenIds\n    );\n\n    /// @notice Fragment NFTs to free pool\n    /// @param operator who will pay the NFTs\n    /// @param onBehalfOf who will receive the Fragment Tokens\n    event FragmentNft(\n        address indexed operator,\n        address indexed onBehalfOf,\n        address indexed collection,\n        uint256[] tokenIds\n    );\n\n    /// @notice Claim random NFTs from free pool\n    /// @param operator who will pay the redemption cost\n    /// @param receiver who will receive the NFTs\n    /// @param creditCost how many credit token cost in this claim\n    event ClaimRandomNft(\n        address indexed operator,\n        address indexed receiver,\n        address indexed collection,\n        uint256[] tokenIds,\n        uint256 creditCost\n    );\n\n    /// @notice [Deprecated]\n    event AuctionStarted(\n        address indexed trigger,\n        address indexed collection,\n        uint64[] activityIds,\n        uint256[] tokenIds,\n        address settleToken,\n        uint256 minimumBid,\n        uint256 feeRateBips,\n        uint256 auctionEndTime,\n        uint256 safeBoxExpiryTs,\n        bool selfTriggered,\n        uint256 adminFee\n    );\n\n    event AuctionStartedV2(\n        address indexed trigger,\n        address indexed collection,\n        uint64[] activityIds,\n        uint256[] tokenIds,\n        AuctionType typ,\n        Fees fees,\n        address settleToken,\n        uint256 minimumBid,\n        uint256 auctionEndTime,\n        uint256 safeBoxExpiryTs,\n        uint256 adminFee\n    );\n\n    event NewTopBidOnAuction(\n        address indexed bidder,\n        address indexed collection,\n        uint64 activityId,\n        uint256 tokenId,\n        uint256 bidAmount,\n        uint256 auctionEndTime,\n        uint256 safeBoxExpiryTs\n    );\n\n    event AuctionEnded(\n        address indexed winner,\n        address indexed collection,\n        uint64 activityId,\n        uint256 tokenId,\n        uint256 safeBoxKeyId,\n        uint256 collectedFunds\n    );\n\n    event RaffleStarted(\n        address indexed owner,\n        address indexed collection,\n        uint64[] activityIds,\n        uint256[] nftIds,\n        uint48 maxTickets,\n        address settleToken,\n        uint96 ticketPrice,\n        Fees fees,\n        uint48 raffleEndTime,\n        uint256 safeBoxExpiryTs,\n        uint256 adminFee\n    );\n\n    event RaffleTicketsSold(\n        address indexed buyer,\n        address indexed collection,\n        uint64 activityId,\n        uint256 nftId,\n        uint256 ticketsSold,\n        uint256 cost\n    );\n\n    event RaffleSettled(\n        address indexed winner,\n        address indexed collection,\n        uint64 activityId,\n        uint256 nftId,\n        uint256 safeBoxKeyId,\n        uint256 collectedFunds\n    );\n\n    event PrivateOfferStarted(\n        address indexed seller,\n        address indexed buyer,\n        address indexed collection,\n        uint64[] activityIds,\n        uint256[] nftIds,\n        address settleToken,\n        uint96 price,\n        uint256 offerEndTime,\n        uint256 safeBoxExpiryTs,\n        uint256 adminFee,\n        Fees fees\n    );\n\n    event PrivateOfferCanceled(\n        address indexed operator,\n        address indexed collection,\n        uint64[] activityIds,\n        uint256[] nftIds\n    );\n\n    event OfferMetaChanged(\n        address indexed operator,\n        address indexed collection,\n        uint64[] activityIds,\n        uint256[] nftIds,\n        uint96[] price,\n        address settleToken,\n        uint32 offerEndTime,\n        uint32 safeboxExpiryTs\n    );\n\n    event PrivateOfferAccepted(\n        address indexed buyer,\n        address indexed collection,\n        uint64[] activityIds,\n        uint256[] nftIds,\n        uint256[] safeBoxKeyIds,\n        uint32 safeboxExpiryTs\n    );\n}\n"
      },
      "src/interface/IFragmentToken.sol": {
        "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.20;\n\ninterface IFragmentToken {\n    error CallerIsNotTrustedContract();\n\n    function mint(address account, uint256 amount) external;\n\n    function burn(address account, uint256 amount) external;\n}\n"
      },
      "src/interface/IMulticall.sol": {
        "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.20;\n\n/// @title Multicall interface\n/// @notice Enables calling multiple methods in a single call to the contract\ninterface IMulticall {\n    /**\n     * @dev A call to an address target failed. The target may have reverted.\n     */\n    error FailedMulticall();\n\n    /// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed\n    /// @param data The encoded function data for each of the calls to make to this contract\n    /// @return results The results from each of the calls passed in via data\n    function multicall(\n        bytes[] calldata data\n    ) external returns (bytes[] memory results);\n}\n"
      },
      "src/library/Array.sol": {
        "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.20;\n\nlibrary Array {\n    /// @notice Compress `data` to [Length]:{[BytesLength][val...]}\n    /// eg. [0, 255, 256] will be convert to bytes series: 0x03 0x00 0x01 0xFF 0x02 0x00 0x01\n    /// 0x03 means there are 3 numbers\n    /// 0x00 means first number is 0\n    /// 0x01 means next number(255) has 1 byte to store the real value\n    /// 0xFF equals 255\n    /// 256 need 2 bytes(0x02) to store, and its value represented in hex is 0x0100\n    function encodeUints(\n        uint256[] memory data\n    ) internal pure returns (bytes memory res) {\n        uint256 dataLen = data.length;\n\n        require(dataLen <= type(uint8).max);\n\n        unchecked {\n            uint256 totalBytes;\n            for (uint256 i; i < dataLen; ++i) {\n                uint256 val = data[i];\n                while (val > 0) {\n                    val >>= 8;\n                    ++totalBytes;\n                }\n            }\n\n            res = new bytes(dataLen + totalBytes + 1);\n            assembly {\n                /// skip res's length, store data length\n                mstore8(add(res, 0x20), dataLen)\n            }\n\n            /// start from the second element idx\n            uint256 resIdx = 0x21;\n            for (uint256 i; i < dataLen; ++i) {\n                uint256 val = data[i];\n\n                uint256 byteLen;\n                while (val > 0) {\n                    val >>= 8;\n                    ++byteLen;\n                }\n\n                assembly {\n                    /// store bytes length of the `i`th element\n                    mstore8(add(res, resIdx), byteLen)\n                }\n                ++resIdx;\n\n                val = data[i];\n                for (uint256 j; j < byteLen; ++j) {\n                    assembly {\n                        mstore8(add(res, resIdx), val)\n                    }\n                    val >>= 8;\n                    ++resIdx;\n                }\n            }\n        }\n    }\n\n    function decodeUints(\n        bytes memory data\n    ) internal pure returns (uint256[] memory res) {\n        uint256 dataLen = data.length;\n        require(dataLen > 0);\n\n        res = new uint256[](uint8(data[0]));\n        uint256 k;\n\n        unchecked {\n            for (uint256 i = 1; i < dataLen; ++i) {\n                uint256 byteLen = uint8(data[i]);\n                /// if byteLen is zero, it means current element is zero, no need to update `res`, just increment `k`\n                if (byteLen > 0) {\n                    uint256 tmp;\n                    /// combine next `byteLen` bytes to `tmp`\n                    for (uint256 j; j < byteLen; ++j) {\n                        /// skip `byteLen`\n                        ++i;\n\n                        tmp |= ((uint256(uint8(data[i]))) << (j * 8));\n                    }\n                    res[k] = tmp;\n                }\n\n                ++k;\n            }\n        }\n    }\n\n    function slice(\n        uint256[] memory data,\n        uint256 start,\n        uint256 length\n    ) internal pure returns (uint256[] memory res) {\n        require(start + length <= data.length);\n\n        res = new uint256[](length);\n\n        unchecked {\n            start *= 0x20;\n            length *= 0x20;\n        }\n\n        for (uint256 i = 0x20; i <= length; ) {\n            assembly {\n                mstore(add(res, i), mload(add(data, add(i, start))))\n            }\n\n            unchecked {\n                i += 0x20;\n            }\n        }\n    }\n\n    function pushUint(\n        uint256[] storage data,\n        uint256[] calldata vals\n    ) internal {\n        uint256 len = vals.length;\n        for (uint256 i; i < len; ) {\n            data.push(vals[i]);\n\n            unchecked {\n                ++i;\n            }\n        }\n    }\n}\n"
      },
      "src/library/CurrencyTransfer.sol": {
        "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.20;\n\nlibrary CurrencyTransfer {\n    /// @notice Thrown when an ERC20 transfer fails\n    error ERC20TransferFailed();\n    /// @notice Thrown when an NATIVE transfer fails\n    error NativeTransferFailed();\n\n    address public constant NATIVE = address(0);\n\n    function safeTransfer(address token, address to, uint256 amount) internal {\n        // ref\n        // https://docs.soliditylang.org/en/latest/internals/layout_in_memory.html\n        // implementation from\n        // https://github.com/transmissions11/solmate/blob/v7/src/utils/SafeTransferLib.sol\n        // https://github.com/Uniswap/v4-core/blob/main/contracts/types/Currency.sol\n        bool success;\n\n        if (token == NATIVE) {\n            assembly {\n                // Transfer the ETH and store if it succeeded or not.\n                success := call(gas(), to, amount, 0, 0, 0, 0)\n            }\n\n            if (!success) revert NativeTransferFailed();\n        } else {\n            /// @solidity memory-safe-assembly\n            assembly {\n                // We'll write our calldata to this slot below, but restore it later.\n                let memPointer := mload(0x40)\n\n                // Write the abi-encoded calldata into memory, beginning with the function selector.\n                mstore(\n                    0,\n                    0xa9059cbb00000000000000000000000000000000000000000000000000000000\n                )\n                mstore(4, to) // Append the \"to\" argument.\n                mstore(36, amount) // Append the \"amount\" argument.\n\n                success := and(\n                    // Set success to whether the call reverted, if not we check it either\n                    // returned exactly 1 (can't just be non-zero data), or had no return data.\n                    or(\n                        and(eq(mload(0), 1), gt(returndatasize(), 31)),\n                        iszero(returndatasize())\n                    ),\n                    // We use 68 because that's the total length of our calldata (4 + 32 * 2)\n                    // Counterintuitively, this call() must be positioned after the or() in the\n                    // surrounding and() because and() evaluates its arguments from right to left.\n                    call(gas(), token, 0, 0, 68, 0, 32)\n                )\n\n                mstore(0x60, 0) // Restore the zero slot to zero.\n                mstore(0x40, memPointer) // Restore the memPointer.\n            }\n            if (!success) revert ERC20TransferFailed();\n        }\n    }\n\n    function safeTransferFrom(\n        address token,\n        address from,\n        address to,\n        uint256 amount\n    ) internal {\n        bool success;\n\n        /// @solidity memory-safe-assembly\n        assembly {\n            // Get a pointer to some free memory.\n            let memPointer := mload(0x40)\n\n            // Write the abi-encoded calldata into memory, beginning with the function selector.\n            mstore(\n                0,\n                0x23b872dd00000000000000000000000000000000000000000000000000000000\n            )\n            mstore(4, from) // Append and mask the \"from\" argument.\n            mstore(36, to) // Append and mask the \"to\" argument.\n            // Append the \"amount\" argument. Masking not required as it's a full 32 byte type.\n            mstore(68, amount)\n\n            success := and(\n                // Set success to whether the call reverted, if not we check it either\n                // returned exactly 1 (can't just be non-zero data), or had no return data.\n                or(\n                    and(eq(mload(0), 1), gt(returndatasize(), 31)),\n                    iszero(returndatasize())\n                ),\n                // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.\n                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.\n                // Counterintuitively, this call must be positioned second to the or() call in the\n                // surrounding and() call or else returndatasize() will be zero during the computation.\n                call(gas(), token, 0, 0, 100, 0, 32)\n            )\n\n            mstore(0x60, 0) // Restore the zero slot to zero.\n            mstore(0x40, memPointer) // Restore the memPointer.\n        }\n\n        if (!success) revert ERC20TransferFailed();\n    }\n}\n"
      },
      "src/library/ERC721Transfer.sol": {
        "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.20;\n\nlibrary ERC721Transfer {\n    /// @notice Thrown when an ERC721 transfer fails\n    error ERC721TransferFailed();\n\n    function safeTransferFrom(\n        address collection,\n        address from,\n        address to,\n        uint256 tokenId\n    ) internal {\n        bool success;\n\n        /// @solidity memory-safe-assembly\n        assembly {\n            // Get a pointer to some free memory.\n            let memPointer := mload(0x40)\n\n            // Write the abi-encoded calldata into memory, beginning with the function selector.\n            mstore(\n                0,\n                0x42842e0e00000000000000000000000000000000000000000000000000000000\n            )\n            mstore(4, from) // Append and mask the \"from\" argument.\n            mstore(36, to) // Append and mask the \"to\" argument.\n            // Append the \"tokenId\" argument. Masking not required as it's a full 32 byte type.\n            mstore(68, tokenId)\n\n            success := and(\n                // Set success to whether the call reverted, if not we check it either\n                // returned exactly 1 (can't just be non-zero data), or had no return data.\n                or(\n                    and(eq(mload(0), 1), gt(returndatasize(), 31)),\n                    iszero(returndatasize())\n                ),\n                // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.\n                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.\n                // Counterintuitively, this call must be positioned second to the or() call in the\n                // surrounding and() call or else returndatasize() will be zero during the computation.\n                call(gas(), collection, 0, 0, 100, 0, 32)\n            )\n\n            mstore(0x60, 0) // Restore the zero slot to zero.\n            mstore(0x40, memPointer) // Restore the memPointer.\n        }\n\n        if (!success) revert ERC721TransferFailed();\n    }\n\n    function safeBatchTransferFrom(\n        address collection,\n        address from,\n        address to,\n        uint256[] memory tokenIds\n    ) internal {\n        unchecked {\n            uint256 len = tokenIds.length;\n            for (uint256 i; i < len; ++i) {\n                safeTransferFrom(collection, from, to, tokenIds[i]);\n            }\n        }\n    }\n\n    function safeBatchTransferFromCalldata(\n        address collection,\n        address from,\n        address to,\n        uint256[] calldata tokenIds\n    ) internal {\n        unchecked {\n            uint256 len = tokenIds.length;\n            for (uint256 i; i < len; ++i) {\n                safeTransferFrom(collection, from, to, tokenIds[i]);\n            }\n        }\n    }\n}\n"
      },
      "src/library/OwnedUpgradeable.sol": {
        "content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\n/// @notice Simple single owner authorization mixin.\n/// @author modified from Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/auth/Owned.sol)\nabstract contract OwnedUpgradeable {\n    error Unauthorized();\n\n    event OwnerUpdated(address indexed user, address indexed newOwner);\n\n    address public owner;\n\n    modifier onlyOwner() virtual {\n        checkOwner();\n        _;\n    }\n\n    function checkOwner() internal view {\n        if (msg.sender != owner) revert Unauthorized();\n    }\n\n    function __Owned_init() internal {\n        owner = msg.sender;\n    }\n\n    function setOwner(address newOwner) public virtual onlyOwner {\n        owner = newOwner;\n\n        emit OwnerUpdated(msg.sender, newOwner);\n    }\n\n    uint256[49] private __gap;\n}\n"
      },
      "src/library/Pausable.sol": {
        "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.20;\n\n/// @dev Pause functions using a uint256 number\n///      If the lowest bit is flipped, then all functions with `whenNotPaused` are paused\n///      Other bits are used to pause each function with corresponding id as the bit index\nabstract contract Pausable {\n    error Paused(uint256 funcId);\n    error NotPaused(uint256 funcId);\n\n    event Pause(uint256 funcId);\n    event Unpause(uint256 funcId);\n\n    /// @dev Function only could be called when contract not paused\n    modifier whenNotPaused(uint256 funcId) {\n        _requireNotPaused(funcId);\n        _;\n    }\n\n    /// @dev Function only could be called when contract paused\n    modifier whenPaused(uint256 funcId) {\n        _requirePaused(funcId);\n        _;\n    }\n\n    /// @dev Only pauser can pause contract\n    modifier onlyPauser() {\n        _requirePauser();\n\n        _;\n    }\n\n    function isPaused(uint256 funcId) public view virtual returns (bool) {\n        uint256 funcIds = _funcIds();\n        return funcIds == 1 || (funcIds & (1 << funcId)) != 0;\n    }\n\n    function pause(\n        uint256 funcId\n    ) external virtual onlyPauser returns (uint256) {\n        uint256 funcIds = _funcIds() | (1 << funcId);\n\n        _setFuncIds(funcIds);\n\n        emit Pause(funcId);\n\n        return funcIds;\n    }\n\n    function unpause(\n        uint256 funcId\n    ) external virtual onlyPauser returns (uint256) {\n        uint256 funcIds = _funcIds() & (~(1 << funcId));\n\n        _setFuncIds(funcIds);\n\n        emit Unpause(funcId);\n\n        return funcIds;\n    }\n\n    // @dev Revert if the contract is paused.\n    function _requireNotPaused(uint256 funcId) internal view virtual {\n        if (isPaused(funcId)) revert Paused(funcId);\n    }\n\n    /// @dev Revert if the contract is not paused.\n    function _requirePaused(uint256 funcId) internal view virtual {\n        if (!isPaused(funcId)) revert NotPaused(funcId);\n    }\n\n    /// @dev Revert if the sender is not owner.\n    function _requirePauser() internal view {\n        if (msg.sender != _pauser()) revert();\n    }\n\n    /// @dev who can pause contract\n    function _pauser() internal view virtual returns (address);\n\n    function _funcIds() internal view virtual returns (uint256);\n\n    /// @dev update functions' status\n    function _setFuncIds(uint256 funcIds) internal virtual;\n}\n"
      },
      "src/library/RollingBuckets.sol": {
        "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.20;\n\nimport \"../Constants.sol\";\n\nlibrary RollingBuckets {\n    error BucketValueExceedsLimit();\n    error BucketLengthExceedsLimit();\n\n    /// @dev `MAX_BUCKET_SIZE` must be a multiple of `WORD_ELEMENT_SIZE`,\n    /// otherwise some words may be incomplete which may lead to incorrect bit positioning.\n    uint256 constant MAX_BUCKET_SIZE = Constants.MAX_LOCKING_BUCKET;\n    /// @dev each `ELEMENT_BIT_SIZE` bits stores an element\n    uint256 constant ELEMENT_BIT_SIZE = 24;\n    /// @dev `ELEMENT_BIT_SIZE` bits mask\n    uint256 constant MASK = 0xFFFFFF;\n    /// @dev one word(256 bits) can store (256 // ELEMENT_BIT_SIZE) elements\n    uint256 constant WORD_ELEMENT_SIZE = 10;\n\n    function position(\n        uint256 tick\n    ) private pure returns (uint256 wordPos, uint256 bitPos) {\n        unchecked {\n            wordPos = tick / WORD_ELEMENT_SIZE;\n            bitPos = tick % WORD_ELEMENT_SIZE;\n        }\n    }\n\n    function get(\n        mapping(uint256 => uint256) storage buckets,\n        uint256 bucketStamp\n    ) internal view returns (uint256) {\n        unchecked {\n            (uint256 wordPos, uint256 bitPos) = position(\n                bucketStamp % MAX_BUCKET_SIZE\n            );\n            return (buckets[wordPos] >> (bitPos * ELEMENT_BIT_SIZE)) & MASK;\n        }\n    }\n\n    /// [first, last)\n    function batchGet(\n        mapping(uint256 => uint256) storage buckets,\n        uint256 firstStamp,\n        uint256 lastStamp\n    ) internal view returns (uint256[] memory) {\n        if (firstStamp > lastStamp) revert BucketLengthExceedsLimit();\n\n        uint256 len;\n        unchecked {\n            len = lastStamp - firstStamp;\n        }\n\n        if (len > MAX_BUCKET_SIZE) {\n            revert BucketLengthExceedsLimit();\n        }\n\n        uint256[] memory result = new uint256[](len);\n        uint256 resultIndex;\n\n        unchecked {\n            (uint256 wordPos, uint256 bitPos) = position(\n                firstStamp % MAX_BUCKET_SIZE\n            );\n\n            uint256 wordVal = buckets[wordPos];\n            uint256 mask = MASK << (bitPos * ELEMENT_BIT_SIZE);\n\n            for (uint256 i = firstStamp; i < lastStamp; ) {\n                assembly {\n                    /// increase idx firstly to skip `array length`\n                    resultIndex := add(resultIndex, 0x20)\n                    /// wordVal store order starts from lowest bit\n                    /// result[i] = ((wordVal & mask) >> (bitPos * ELEMENT_BIT_SIZE))\n                    mstore(\n                        add(result, resultIndex),\n                        shr(mul(bitPos, ELEMENT_BIT_SIZE), and(wordVal, mask))\n                    )\n                    mask := shl(ELEMENT_BIT_SIZE, mask)\n                    bitPos := add(bitPos, 1)\n                    i := add(i, 1)\n                }\n\n                if (bitPos == WORD_ELEMENT_SIZE) {\n                    (wordPos, bitPos) = position(i % MAX_BUCKET_SIZE);\n\n                    wordVal = buckets[wordPos];\n                    mask = MASK;\n                }\n            }\n        }\n\n        return result;\n    }\n\n    function set(\n        mapping(uint256 => uint256) storage buckets,\n        uint256 bucketStamp,\n        uint256 value\n    ) internal {\n        if (value > MASK) revert BucketValueExceedsLimit();\n\n        unchecked {\n            (uint256 wordPos, uint256 bitPos) = position(\n                bucketStamp % MAX_BUCKET_SIZE\n            );\n\n            uint256 wordValue = buckets[wordPos];\n            uint256 newValue = value << (bitPos * ELEMENT_BIT_SIZE);\n\n            uint256 newWord = (wordValue &\n                ~(MASK << (bitPos * ELEMENT_BIT_SIZE))) | newValue;\n            buckets[wordPos] = newWord;\n        }\n    }\n\n    function batchSet(\n        mapping(uint256 => uint256) storage buckets,\n        uint256 firstStamp,\n        uint256[] memory values\n    ) internal {\n        uint256 valLength = values.length;\n        if (valLength > MAX_BUCKET_SIZE) revert BucketLengthExceedsLimit();\n        if (firstStamp > (type(uint256).max - valLength)) {\n            revert BucketLengthExceedsLimit();\n        }\n\n        unchecked {\n            (uint256 wordPos, uint256 bitPos) = position(\n                firstStamp % MAX_BUCKET_SIZE\n            );\n\n            uint256 wordValue = buckets[wordPos];\n            uint256 mask = ~(MASK << (bitPos * ELEMENT_BIT_SIZE));\n\n            /// reuse val length as End Postion\n            valLength = (valLength + 1) * 0x20;\n            /// start from first element offset\n            for (uint256 i = 0x20; i < valLength; i += 0x20) {\n                uint256 val;\n                assembly {\n                    val := mload(add(values, i))\n                }\n                if (val > MASK) revert BucketValueExceedsLimit();\n\n                assembly {\n                    /// newVal = val << (bitPos * BIT_SIZE)\n                    let newVal := shl(mul(bitPos, ELEMENT_BIT_SIZE), val)\n                    /// save newVal to wordVal, clear corresponding bits and set them as newVal\n                    /// wordValue = (wordVal & mask) | newVal\n                    wordValue := or(and(wordValue, mask), newVal)\n                    /// goto next number idx in current word\n                    bitPos := add(bitPos, 1)\n                    /// mask = ~(MASK << (bitPos, BIT_SIZE))\n                    mask := not(shl(mul(bitPos, ELEMENT_BIT_SIZE), MASK))\n                }\n\n                if (bitPos == WORD_ELEMENT_SIZE) {\n                    /// store hole word\n                    buckets[wordPos] = wordValue;\n\n                    /// get next word' position\n                    (wordPos, bitPos) = position(\n                        (firstStamp + (i / 0x20)) % MAX_BUCKET_SIZE\n                    );\n                    wordValue = buckets[wordPos];\n                    /// restore mask to make it start from lowest bits\n                    mask = ~MASK;\n                }\n            }\n            /// store last word which may incomplete\n            buckets[wordPos] = wordValue;\n        }\n    }\n}\n"
      },
      "src/library/TrustedUpgradeable.sol": {
        "content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\nimport { OwnedUpgradeable } from \"./OwnedUpgradeable.sol\";\n\nabstract contract TrustedUpgradeable is OwnedUpgradeable {\n    event TrustedUpdated(address trusted, bool setOrUnset);\n\n    mapping(address => uint256) public whitelist;\n\n    modifier onlyTrusted() virtual {\n        checkTrusted();\n        _;\n    }\n\n    function checkTrusted() internal view {\n        if (whitelist[msg.sender] == 0) revert Unauthorized();\n    }\n\n    function __Trusted_init() internal {\n        __Owned_init();\n        whitelist[owner] = 1;\n    }\n\n    function setTrusted(address trusted, bool trust) public virtual onlyOwner {\n        if (trust) whitelist[trusted] = 1;\n        else delete whitelist[trusted];\n        emit TrustedUpdated(trusted, trust);\n    }\n\n    uint256[49] private __gap;\n}\n"
      },
      "src/logic/Auction.sol": {
        "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.20;\n\nimport \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\n\nimport {\n    SafeBox,\n    CollectionState,\n    AuctionInfo,\n    FeeConfig,\n    Fees,\n    FeeRate,\n    AuctionType\n} from \"./Structs.sol\";\nimport \"./User.sol\";\nimport \"./Collection.sol\";\nimport \"./Helper.sol\";\nimport \"../Errors.sol\";\nimport \"../Constants.sol\";\nimport \"../interface/IFlooring.sol\";\nimport { SafeBoxLib } from \"./SafeBox.sol\";\nimport \"../library/RollingBuckets.sol\";\n\nlibrary AuctionLib {\n    using SafeCast for uint256;\n    using CollectionLib for CollectionState;\n    using SafeBoxLib for SafeBox;\n    using RollingBuckets for mapping(uint256 => uint256);\n    using UserLib for UserFloorAccount;\n    using UserLib for CollectionAccount;\n    using Helper for CollectionState;\n\n    event AuctionStartedV2(\n        address indexed trigger,\n        address indexed collection,\n        uint64[] activityIds,\n        uint256[] tokenIds,\n        AuctionType typ,\n        Fees fees,\n        address settleToken,\n        uint256 minimumBid,\n        uint256 auctionEndTime,\n        uint256 safeBoxExpiryTs,\n        uint256 adminFee\n    );\n\n    event NewTopBidOnAuction(\n        address indexed bidder,\n        address indexed collection,\n        uint64 activityId,\n        uint256 tokenId,\n        uint256 bidAmount,\n        uint256 auctionEndTime,\n        uint256 safeBoxExpiryTs\n    );\n\n    event AuctionEnded(\n        address indexed winner,\n        address indexed collection,\n        uint64 activityId,\n        uint256 tokenId,\n        uint256 safeBoxKeyId,\n        uint256 collectedFunds\n    );\n\n    function ownerInitAuctions(\n        CollectionState storage collection,\n        mapping(address => UserFloorAccount) storage userAccounts,\n        FeeConfig storage feeConf,\n        address creditToken,\n        address collectionId,\n        uint256[] memory nftIds,\n        uint256 maxExpiry,\n        address token,\n        uint256 minimumBid\n    ) public {\n        if (feeConf.safeboxFee.recipient == address(0))\n            revert Errors.TokenNotSupported();\n\n        UserFloorAccount storage userAccount = userAccounts[msg.sender];\n        uint256 adminFee = Constants.AUCTION_COST * nftIds.length;\n        /// transfer fee to contract account\n        userAccount.transferToken(\n            userAccounts[address(this)],\n            creditToken,\n            adminFee,\n            true\n        );\n\n        AuctionInfo memory auctionTemplate;\n        auctionTemplate.endTime = uint96(\n            block.timestamp + Constants.AUCTION_INITIAL_PERIODS\n        );\n        auctionTemplate.bidTokenAddress = token;\n        auctionTemplate.minimumBid = minimumBid.toUint96();\n        auctionTemplate.triggerAddress = msg.sender;\n        auctionTemplate.lastBidAmount = 0;\n        auctionTemplate.lastBidder = address(0);\n        auctionTemplate.typ = AuctionType.Owned;\n        auctionTemplate.fees = Fees({\n            royalty: FeeRate({ recipient: address(0), rateBips: 0 }),\n            protocol: FeeRate({\n                recipient: feeConf.safeboxFee.recipient,\n                rateBips: feeConf.safeboxFee.auctionOwned\n            })\n        });\n\n        (uint64[] memory activityIds, uint192 newExpiryTs) = _ownerInitAuctions(\n            collection,\n            userAccount.getByKey(collectionId),\n            nftIds,\n            maxExpiry,\n            auctionTemplate\n        );\n\n        emit AuctionStartedV2(\n            msg.sender,\n            collectionId,\n            activityIds,\n            nftIds,\n            auctionTemplate.typ,\n            auctionTemplate.fees,\n            token,\n            minimumBid,\n            auctionTemplate.endTime,\n            newExpiryTs,\n            adminFee\n        );\n    }\n\n    function _ownerInitAuctions(\n        CollectionState storage collectionState,\n        CollectionAccount storage userAccount,\n        uint256[] memory nftIds,\n        uint256 maxExpiry,\n        AuctionInfo memory auctionTemplate\n    ) private returns (uint64[] memory activityIds, uint32 newExpiryTs) {\n        newExpiryTs = uint32(\n            auctionTemplate.endTime + Constants.AUCTION_COMPLETE_GRACE_PERIODS\n        );\n\n        uint256 firstIdx = Helper.counterStamp(newExpiryTs) -\n            Helper.counterStamp(block.timestamp);\n\n        uint256[] memory toUpdateBucket;\n        /// if maxExpiryTs == 0,\n        /// it means all nftIds in this batch being locked infinitely that we don't need to update countingBuckets\n        if (maxExpiry > 0) {\n            toUpdateBucket = collectionState.countingBuckets.batchGet(\n                Helper.counterStamp(block.timestamp),\n                Math.min(\n                    Helper.counterStamp(maxExpiry),\n                    collectionState.lastUpdatedBucket\n                )\n            );\n        }\n\n        activityIds = new uint64[](nftIds.length);\n        for (uint256 i = 0; i < nftIds.length; ) {\n            if (collectionState.hasActiveActivities(nftIds[i]))\n                revert Errors.NftHasActiveActivities();\n\n            (SafeBox storage safeBox, ) = collectionState.useSafeBoxAndKey(\n                userAccount,\n                nftIds[i]\n            );\n\n            if (safeBox.isInfiniteSafeBox()) {\n                --collectionState.infiniteCnt;\n            } else {\n                uint256 oldExpiryTs = safeBox.expiryTs;\n                if (oldExpiryTs < newExpiryTs) {\n                    revert Errors.InvalidParam();\n                }\n                uint256 lastIdx = Helper.counterStamp(oldExpiryTs) -\n                    Helper.counterStamp(block.timestamp);\n                if (firstIdx > lastIdx || lastIdx > toUpdateBucket.length)\n                    revert Errors.InvalidParam();\n                for (uint256 k = firstIdx; k < lastIdx; ) {\n                    --toUpdateBucket[k];\n                    unchecked {\n                        ++k;\n                    }\n                }\n            }\n\n            safeBox.expiryTs = newExpiryTs;\n\n            activityIds[i] = collectionState.generateNextActivityId();\n\n            auctionTemplate.activityId = activityIds[i];\n            collectionState.activeAuctions[nftIds[i]] = auctionTemplate;\n\n            unchecked {\n                ++i;\n            }\n        }\n        if (toUpdateBucket.length > 0) {\n            collectionState.countingBuckets.batchSet(\n                Helper.counterStamp(block.timestamp),\n                toUpdateBucket\n            );\n        }\n    }\n\n    function initAuctionOnExpiredSafeBoxes(\n        CollectionState storage collection,\n        mapping(address => UserFloorAccount) storage userAccounts,\n        FeeConfig storage feeConf,\n        address creditToken,\n        address collectionId,\n        uint256[] memory nftIds,\n        address bidToken,\n        uint256 bidAmount\n    ) public {\n        if (feeConf.safeboxFee.recipient == address(0))\n            revert Errors.TokenNotSupported();\n        {\n            uint256 lockingRatio = Helper.calculateLockingRatio(collection, 0);\n            uint256 currentFee = Constants.getVaultRedemptionFee(\n                lockingRatio,\n                feeConf.vaultFee.redemptionBase\n            );\n            if (bidAmount < currentFee) revert Errors.AuctionInvalidBidAmount();\n        }\n\n        AuctionInfo memory auctionTemplate;\n        auctionTemplate.endTime = uint96(\n            block.timestamp + Constants.AUCTION_INITIAL_PERIODS\n        );\n        auctionTemplate.bidTokenAddress = bidToken;\n        auctionTemplate.minimumBid = bidAmount.toUint96();\n        auctionTemplate.triggerAddress = msg.sender;\n        auctionTemplate.lastBidAmount = bidAmount.toUint96();\n        auctionTemplate.lastBidder = msg.sender;\n        auctionTemplate.typ = AuctionType.Expired;\n        auctionTemplate.fees = Fees({\n            royalty: FeeRate({ recipient: address(0), rateBips: 0 }),\n            protocol: FeeRate({\n                recipient: feeConf.safeboxFee.recipient,\n                rateBips: feeConf.safeboxFee.auctionExpired\n            })\n        });\n\n        (\n            uint64[] memory activityIds,\n            uint192 newExpiry\n        ) = _initAuctionOnExpiredSafeBoxes(collection, nftIds, auctionTemplate);\n\n        uint256 adminFee = Constants.AUCTION_ON_EXPIRED_SAFEBOX_COST *\n            nftIds.length;\n        if (bidToken == creditToken) {\n            userAccounts[msg.sender].transferToken(\n                userAccounts[address(this)],\n                bidToken,\n                bidAmount * nftIds.length + adminFee,\n                true\n            );\n        } else {\n            userAccounts[msg.sender].transferToken(\n                userAccounts[address(this)],\n                bidToken,\n                bidAmount * nftIds.length,\n                false\n            );\n            if (adminFee > 0) {\n                userAccounts[msg.sender].transferToken(\n                    userAccounts[address(this)],\n                    creditToken,\n                    adminFee,\n                    true\n                );\n            }\n        }\n\n        emit AuctionStartedV2(\n            msg.sender,\n            collectionId,\n            activityIds,\n            nftIds,\n            auctionTemplate.typ,\n            auctionTemplate.fees,\n            bidToken,\n            bidAmount,\n            auctionTemplate.endTime,\n            newExpiry,\n            adminFee\n        );\n    }\n\n    function _initAuctionOnExpiredSafeBoxes(\n        CollectionState storage collectionState,\n        uint256[] memory nftIds,\n        AuctionInfo memory auctionTemplate\n    ) private returns (uint64[] memory activityIds, uint32 newExpiry) {\n        newExpiry = uint32(\n            auctionTemplate.endTime + Constants.AUCTION_COMPLETE_GRACE_PERIODS\n        );\n\n        activityIds = new uint64[](nftIds.length);\n        for (uint256 idx; idx < nftIds.length; ) {\n            uint256 nftId = nftIds[idx];\n            if (collectionState.hasActiveActivities(nftId))\n                revert Errors.NftHasActiveActivities();\n\n            SafeBox storage safeBox = collectionState.useSafeBox(nftId);\n            if (!safeBox.isSafeBoxExpired())\n                revert Errors.SafeBoxHasNotExpire();\n            if (Helper.isAuctionPeriodOver(safeBox))\n                revert Errors.SafeBoxAuctionWindowHasPassed();\n\n            activityIds[idx] = collectionState.generateNextActivityId();\n            auctionTemplate.activityId = activityIds[idx];\n            collectionState.activeAuctions[nftId] = auctionTemplate;\n\n            /// We keep the owner of safebox unchanged, and it will be used to distribute auction funds\n            safeBox.expiryTs = newExpiry;\n            safeBox.keyId = SafeBoxLib.SAFEBOX_KEY_NOTATION;\n\n            unchecked {\n                ++idx;\n            }\n        }\n\n        Helper.applyDiffToCounters(\n            collectionState,\n            Helper.counterStamp(block.timestamp),\n            Helper.counterStamp(newExpiry),\n            int256(nftIds.length)\n        );\n    }\n\n    function initAuctionOnVault(\n        CollectionState storage collection,\n        mapping(address => UserFloorAccount) storage userAccounts,\n        FeeConfig storage feeConf,\n        address creditToken,\n        address collectionId,\n        uint256[] memory vaultIdx,\n        address bidToken,\n        uint96 bidAmount\n    ) public {\n        if (vaultIdx.length != 1) revert Errors.InvalidParam();\n        if (feeConf.vaultFee.recipient == address(0))\n            revert Errors.TokenNotSupported();\n\n        {\n            /// check auction period and bid price\n            uint256 lockingRatio = Helper.calculateLockingRatio(collection, 0);\n            uint256 periodDuration = Constants.getVaultAuctionDurationAtLR(\n                lockingRatio\n            );\n            if (\n                block.timestamp - collection.lastVaultAuctionPeriodTs <=\n                periodDuration\n            ) {\n                revert Errors.PeriodQuotaExhausted();\n            }\n\n            uint256 currentFee = Constants.getVaultRedemptionFee(\n                lockingRatio,\n                feeConf.vaultFee.redemptionBase\n            );\n            if (bidAmount < currentFee) revert Errors.AuctionInvalidBidAmount();\n        }\n\n        AuctionInfo memory auctionTemplate;\n        auctionTemplate.endTime = uint96(\n            block.timestamp + Constants.AUCTION_INITIAL_PERIODS\n        );\n        auctionTemplate.bidTokenAddress = bidToken;\n        auctionTemplate.minimumBid = bidAmount;\n        auctionTemplate.triggerAddress = msg.sender;\n        auctionTemplate.lastBidAmount = bidAmount;\n        auctionTemplate.lastBidder = msg.sender;\n        auctionTemplate.typ = AuctionType.Vault;\n        auctionTemplate.fees = Fees({\n            royalty: FeeRate({\n                recipient: feeConf.royalty.recipient,\n                rateBips: feeConf.royalty.vault\n            }),\n            protocol: FeeRate({\n                recipient: feeConf.vaultFee.recipient,\n                rateBips: feeConf.vaultFee.vaultAuction\n            })\n        });\n\n        SafeBox memory safeboxTemplate = SafeBox({\n            keyId: SafeBoxLib.SAFEBOX_KEY_NOTATION,\n            expiryTs: uint32(\n                auctionTemplate.endTime +\n                    Constants.AUCTION_COMPLETE_GRACE_PERIODS\n            ),\n            owner: address(this)\n        });\n\n        uint64[] memory activityIds = new uint64[](vaultIdx.length);\n\n        /// vaultIdx keeps asc order\n        for (uint256 i = vaultIdx.length; i > 0; ) {\n            unchecked {\n                --i;\n            }\n\n            if (vaultIdx[i] >= collection.freeTokenIds.length)\n                revert Errors.InvalidParam();\n            uint256 nftId = collection.freeTokenIds[vaultIdx[i]];\n\n            collection.addSafeBox(nftId, safeboxTemplate);\n\n            auctionTemplate.activityId = collection.generateNextActivityId();\n            collection.activeAuctions[nftId] = auctionTemplate;\n            activityIds[i] = auctionTemplate.activityId;\n\n            collection.freeTokenIds[vaultIdx[i]] = collection.freeTokenIds[\n                collection.freeTokenIds.length - 1\n            ];\n            collection.freeTokenIds.pop();\n\n            /// reuse the array\n            vaultIdx[i] = nftId;\n        }\n\n        userAccounts[msg.sender].transferToken(\n            userAccounts[address(this)],\n            auctionTemplate.bidTokenAddress,\n            bidAmount * vaultIdx.length,\n            bidToken == creditToken\n        );\n\n        Helper.applyDiffToCounters(\n            collection,\n            Helper.counterStamp(block.timestamp),\n            Helper.counterStamp(safeboxTemplate.expiryTs),\n            int256(vaultIdx.length)\n        );\n\n        /// update auction timestamp\n        collection.lastVaultAuctionPeriodTs = uint32(block.timestamp);\n\n        emit AuctionStartedV2(\n            msg.sender,\n            collectionId,\n            activityIds,\n            vaultIdx,\n            auctionTemplate.typ,\n            auctionTemplate.fees,\n            auctionTemplate.bidTokenAddress,\n            bidAmount,\n            auctionTemplate.endTime,\n            safeboxTemplate.expiryTs,\n            0\n        );\n    }\n\n    struct BidParam {\n        uint256 nftId;\n        uint96 bidAmount;\n        address bidder;\n        uint256 extendDuration;\n        uint256 minIncrPct;\n    }\n\n    function placeBidOnAuction(\n        CollectionState storage collection,\n        mapping(address => UserFloorAccount) storage userAccounts,\n        address creditToken,\n        address collectionId,\n        uint256 nftId,\n        uint256 bidAmount,\n        uint256 bidOptionIdx\n    ) public {\n        uint256 prevBidAmount;\n        address prevBidder;\n        {\n            Constants.AuctionBidOption memory bidOption = Constants\n                .getBidOption(bidOptionIdx);\n            userAccounts[msg.sender].ensureVipCredit(\n                uint8(bidOption.vipLevel),\n                creditToken\n            );\n\n            (prevBidAmount, prevBidder) = _placeBidOnAuction(\n                collection,\n                BidParam(\n                    nftId,\n                    bidAmount.toUint96(),\n                    msg.sender,\n                    bidOption.extendDurationSecs,\n                    bidOption.minimumRaisePct\n                )\n            );\n        }\n\n        AuctionInfo memory auction = collection.activeAuctions[nftId];\n\n        address bidToken = auction.bidTokenAddress;\n        userAccounts[msg.sender].transferToken(\n            userAccounts[address(this)],\n            bidToken,\n            bidAmount,\n            bidToken == creditToken\n        );\n\n        if (prevBidAmount > 0) {\n            /// refund previous bid\n            /// contract account no need to check credit requirements\n            userAccounts[address(this)].transferToken(\n                userAccounts[prevBidder],\n                bidToken,\n                prevBidAmount,\n                false\n            );\n        }\n\n        SafeBox memory safebox = collection.safeBoxes[nftId];\n        emit NewTopBidOnAuction(\n            msg.sender,\n            collectionId,\n            auction.activityId,\n            nftId,\n            bidAmount,\n            auction.endTime,\n            safebox.expiryTs\n        );\n    }\n\n    function _placeBidOnAuction(\n        CollectionState storage collectionState,\n        BidParam memory param\n    ) private returns (uint128 prevBidAmount, address prevBidder) {\n        AuctionInfo storage auctionInfo = collectionState.activeAuctions[\n            param.nftId\n        ];\n\n        SafeBox storage safeBox = collectionState.useSafeBox(param.nftId);\n        uint256 endTime = auctionInfo.endTime;\n        {\n            (prevBidAmount, prevBidder) = (\n                auctionInfo.lastBidAmount,\n                auctionInfo.lastBidder\n            );\n            // param check\n            if (endTime == 0) revert Errors.AuctionNotExist();\n            if (endTime <= block.timestamp) revert Errors.AuctionHasExpire();\n            if (\n                prevBidAmount >= param.bidAmount ||\n                auctionInfo.minimumBid > param.bidAmount\n            ) {\n                revert Errors.AuctionBidIsNotHighEnough();\n            }\n            if (prevBidder == param.bidder) revert Errors.AuctionSelfBid();\n            // owner starts auction, can not bid by himself\n            if (\n                auctionInfo.typ == AuctionType.Owned &&\n                param.bidder == safeBox.owner\n            ) revert Errors.AuctionSelfBid();\n\n            if (\n                prevBidAmount > 0 &&\n                !isValidNewBid(param.bidAmount, prevBidAmount, param.minIncrPct)\n            ) {\n                revert Errors.AuctionInvalidBidAmount();\n            }\n        }\n\n        /// Changing safebox key id which means the corresponding safebox key doesn't hold the safebox now\n        safeBox.keyId = SafeBoxLib.SAFEBOX_KEY_NOTATION;\n\n        uint256 newAuctionEndTime = block.timestamp + param.extendDuration;\n        if (newAuctionEndTime > endTime) {\n            uint256 newSafeBoxExpiryTs = newAuctionEndTime +\n                Constants.AUCTION_COMPLETE_GRACE_PERIODS;\n            Helper.applyDiffToCounters(\n                collectionState,\n                Helper.counterStamp(safeBox.expiryTs),\n                Helper.counterStamp(newSafeBoxExpiryTs),\n                1\n            );\n\n            safeBox.expiryTs = uint32(newSafeBoxExpiryTs);\n            auctionInfo.endTime = uint96(newAuctionEndTime);\n        }\n\n        auctionInfo.lastBidAmount = param.bidAmount;\n        auctionInfo.lastBidder = param.bidder;\n    }\n\n    function settleAuctions(\n        CollectionState storage collection,\n        mapping(address => UserFloorAccount) storage userAccounts,\n        address collectionId,\n        uint256[] memory nftIds\n    ) public {\n        for (uint256 i; i < nftIds.length; ) {\n            uint256 nftId = nftIds[i];\n            SafeBox storage safebox = Helper.useSafeBox(collection, nftId);\n\n            if (safebox.isSafeBoxExpired()) revert Errors.SafeBoxHasExpire();\n\n            AuctionInfo memory auctionInfo = collection.activeAuctions[nftId];\n            if (auctionInfo.endTime == 0) revert Errors.AuctionNotExist();\n            if (auctionInfo.endTime > block.timestamp)\n                revert Errors.AuctionHasNotCompleted();\n            /// noone bid on the aciton, can not be settled\n            if (auctionInfo.lastBidder == address(0))\n                revert Errors.AuctionHasNotCompleted();\n\n            distributeFunds(userAccounts, auctionInfo, safebox.owner);\n\n            /// transfer safebox\n            address winner = auctionInfo.lastBidder;\n            SafeBoxKey memory key = SafeBoxKey({\n                keyId: collection.generateNextKeyId(),\n                lockingCredit: 0,\n                vipLevel: 0\n            });\n\n            safebox.keyId = key.keyId;\n            safebox.owner = winner;\n\n            UserFloorAccount storage account = userAccounts[winner];\n            CollectionAccount storage userCollectionAccount = account.getByKey(\n                collectionId\n            );\n            userCollectionAccount.addSafeboxKey(nftId, key);\n\n            delete collection.activeAuctions[nftId];\n\n            emit AuctionEnded(\n                winner,\n                collectionId,\n                auctionInfo.activityId,\n                nftId,\n                key.keyId,\n                auctionInfo.lastBidAmount\n            );\n\n            unchecked {\n                ++i;\n            }\n        }\n    }\n\n    function distributeFunds(\n        mapping(address => UserFloorAccount) storage accounts,\n        AuctionInfo memory auction,\n        address owner\n    ) private {\n        /// contract account no need to check credit requirements\n        address token = auction.bidTokenAddress;\n        Fees memory fees = auction.fees;\n        (uint256 priceWithoutFee, uint256 protocolFee, uint256 royalty) = Helper\n            .computeFees(auction.lastBidAmount, fees);\n\n        UserFloorAccount storage contractAccount = accounts[address(this)];\n\n        if (royalty > 0) {\n            contractAccount.transferToken(\n                accounts[fees.royalty.recipient],\n                token,\n                royalty,\n                false\n            );\n        }\n        if (protocolFee > 0) {\n            contractAccount.transferToken(\n                accounts[fees.protocol.recipient],\n                token,\n                protocolFee,\n                false\n            );\n        }\n        if (priceWithoutFee > 0) {\n            contractAccount.transferToken(\n                accounts[owner],\n                token,\n                priceWithoutFee,\n                false\n            );\n        }\n    }\n\n    function isValidNewBid(\n        uint256 newBid,\n        uint256 previousBid,\n        uint256 minRaisePct\n    ) private pure returns (bool) {\n        uint256 minIncrement = (previousBid * minRaisePct) / 100;\n        if (minIncrement < 1) {\n            minIncrement = 1;\n        }\n\n        if (newBid < previousBid + minIncrement) {\n            return false;\n        }\n        // think: always thought this should be previousBid....\n        uint256 newIncrementAmount = newBid / 100;\n        if (newIncrementAmount < 1) {\n            newIncrementAmount = 1;\n        }\n        return newBid % newIncrementAmount == 0;\n    }\n}\n"
      },
      "src/logic/Collection.sol": {
        "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.20;\n\nimport \"@openzeppelin/contracts/utils/math/Math.sol\";\nimport \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\nimport \"@openzeppelin/contracts/token/ERC721/IERC721.sol\";\n\nimport \"../library/RollingBuckets.sol\";\nimport \"../library/ERC721Transfer.sol\";\nimport \"../library/Array.sol\";\n\nimport \"../Errors.sol\";\nimport \"../Constants.sol\";\nimport \"./User.sol\";\nimport \"./Helper.sol\";\nimport \"./Structs.sol\";\nimport { SafeBoxLib } from \"./SafeBox.sol\";\n\nimport \"../interface/IFlooring.sol\";\n\nlibrary CollectionLib {\n    using SafeBoxLib for SafeBox;\n    using SafeCast for uint256;\n    using RollingBuckets for mapping(uint256 => uint256);\n    using UserLib for CollectionAccount;\n    using UserLib for UserFloorAccount;\n\n    event LockNft(\n        address indexed sender,\n        address indexed onBehalfOf,\n        address indexed collection,\n        uint256[] tokenIds,\n        uint256[] safeBoxKeys,\n        uint256 safeBoxExpiryTs,\n        uint256 minMaintCredit,\n        address proxyCollection\n    );\n    event ExtendKey(\n        address indexed operator,\n        address indexed collection,\n        uint256[] tokenIds,\n        uint256[] safeBoxKeys,\n        uint256 safeBoxExpiryTs,\n        uint256 minMaintCredit\n    );\n    event UnlockNft(\n        address indexed operator,\n        address indexed receiver,\n        address indexed collection,\n        uint256[] tokenIds,\n        address proxyCollection\n    );\n    event ExpiredNftToVault(\n        address indexed operator,\n        address indexed collection,\n        uint256[] tokenIds\n    );\n    event FragmentNft(\n        address indexed operator,\n        address indexed onBehalfOf,\n        address indexed collection,\n        uint256[] tokenIds\n    );\n    event ClaimRandomNft(\n        address indexed operator,\n        address indexed receiver,\n        address indexed collection,\n        uint256[] tokenIds,\n        uint256 creditCost\n    );\n\n    function fragmentNFTs(\n        CollectionState storage collectionState,\n        UserFloorAccount storage account,\n        address collectionId,\n        uint256[] memory nftIds,\n        address onBehalfOf\n    ) public {\n        uint256 nftLen = nftIds.length;\n\n        /// tricky logic: if `onBehalfOf` is the contract, shaffle the correspounding amount of NFTs\n        bool shuffle = onBehalfOf == address(this);\n\n        uint256[] memory pickedTokenIds;\n        if (shuffle) {\n            uint256 vaultCnt = collectionState.freeTokenIds.length;\n            /// no enough nft to shuffle\n            if (nftLen > vaultCnt) revert Errors.ClaimableNftInsufficient();\n\n            (pickedTokenIds, ) = pickFromVault(\n                collectionState,\n                nftLen,\n                0,\n                true\n            );\n        }\n\n        /// after shuffling, supply new NFTs to vault\n        uint32 contQuota;\n        for (uint256 i; i < nftLen; ) {\n            collectionState.freeTokenIds.push(nftIds[i]);\n\n            if (!shuffle) {\n                contQuota += Constants.getVaultContQuotaAtLR(\n                    Helper.calculateLockingRatio(collectionState, 0)\n                );\n            }\n\n            unchecked {\n                ++i;\n            }\n        }\n\n        if (!shuffle) {\n            /// if no shuffling, give back the Fragment Tokens\n            collectionState.floorToken.mint(\n                onBehalfOf,\n                Constants.FLOOR_TOKEN_AMOUNT * nftLen\n            );\n            Helper.checkAndUpdateVaultQuota(\n                account.getByKey(collectionId),\n                int32(contQuota)\n            );\n            ERC721Transfer.safeBatchTransferFrom(\n                collectionState._contractAddr,\n                msg.sender,\n                address(this),\n                nftIds\n            );\n        } else {\n            /// if shuffling, transfer user's NFTs first to avoid repetition.\n            ERC721Transfer.safeBatchTransferFrom(\n                collectionState._contractAddr,\n                msg.sender,\n                address(this),\n                nftIds\n            );\n            /// if shuffling, give back the picked NFTs\n            ERC721Transfer.safeBatchTransferFrom(\n                collectionState._contractAddr,\n                address(this),\n                msg.sender,\n                pickedTokenIds\n            );\n            /// tracking the out NFTs\n            emit ClaimRandomNft(\n                msg.sender,\n                msg.sender,\n                collectionId,\n                pickedTokenIds,\n                0\n            );\n        }\n\n        emit FragmentNft(msg.sender, onBehalfOf, collectionId, nftIds);\n    }\n\n    struct LockInfo {\n        bool isInfinite;\n        uint256 currentBucket;\n        uint256 newExpiryBucket;\n        uint256 selfLocked;\n        uint256 totalManaged;\n        uint256 newRequireLockCredit;\n        uint64 infiniteCnt;\n    }\n\n    function lockNfts(\n        CollectionState storage collection,\n        UserFloorAccount storage account,\n        LockParam memory param,\n        address onBehalfOf\n    ) public returns (uint256 totalCreditCost) {\n        if (onBehalfOf == address(this)) revert Errors.InvalidParam();\n        /// proxy collection only enabled when infinity lock\n        if (\n            param.collectionId != param.proxyCollectionId && param.expiryTs != 0\n        ) revert Errors.InvalidParam();\n\n        uint256 totalCredit = account.ensureVipCredit(\n            param.vipLevel,\n            param.creditToken\n        );\n        Helper.ensureMaxLocking(\n            collection,\n            param.vipLevel,\n            param.expiryTs,\n            param.nftIds.length,\n            false\n        );\n        {\n            uint8 maxVipLevel = Constants.getVipLevel(totalCredit);\n            uint256 newLocked = param.nftIds.length;\n            Helper.ensureProxyVipLevel(\n                maxVipLevel,\n                param.collectionId != param.proxyCollectionId\n            );\n            /// check period quota and global quota for the account\n            Helper.checkAndUpdateUserSafeboxQuota(\n                account,\n                maxVipLevel,\n                newLocked.toUint16()\n            );\n            /// don't try to add the collection account\n            Helper.checkCollectionSafeboxQuota(\n                account.getByKey(param.collectionId),\n                collection,\n                newLocked\n            );\n        }\n\n        /// cache value to avoid multi-reads\n        uint256 minMaintCredit = account.minMaintCredit;\n        uint256[] memory nftIds = param.nftIds;\n        uint256[] memory newKeys;\n        {\n            CollectionAccount storage userCollectionAccount = account\n                .getOrAddCollection(param.collectionId);\n\n            (totalCreditCost, newKeys) = _lockNfts(\n                collection,\n                userCollectionAccount,\n                nftIds,\n                param.expiryTs,\n                param.vipLevel\n            );\n\n            // compute max credit for locking cost\n            uint96 totalLockingCredit = userCollectionAccount\n                .totalLockingCredit;\n            {\n                uint256 creditBuffer;\n                unchecked {\n                    creditBuffer = totalCredit - totalLockingCredit;\n                }\n                if (\n                    totalCreditCost > creditBuffer ||\n                    totalCreditCost > param.maxCreditCost\n                ) {\n                    revert Errors.InsufficientCredit();\n                }\n            }\n\n            totalLockingCredit += totalCreditCost.toUint96();\n            userCollectionAccount.totalLockingCredit = totalLockingCredit;\n\n            if (totalLockingCredit > minMaintCredit) {\n                account.minMaintCredit = totalLockingCredit;\n                minMaintCredit = totalLockingCredit;\n            }\n        }\n\n        account.updateVipKeyCount(param.vipLevel, int256(nftIds.length));\n        /// mint for `onBehalfOf`, transfer from msg.sender\n        collection.floorToken.mint(\n            onBehalfOf,\n            Constants.FLOOR_TOKEN_AMOUNT * nftIds.length\n        );\n\n        /// @dev when using proxy (proxyCollection != collectionId), then the contract address of the collection\n        /// should be same as proxyCollectionId,\n        /// otherwise using underlying collection contract address\n        address contractAddr = param.proxyCollectionId != param.collectionId\n            ? param.proxyCollectionId\n            : collection._contractAddr;\n        ERC721Transfer.safeBatchTransferFrom(\n            contractAddr,\n            msg.sender,\n            address(this),\n            nftIds\n        );\n\n        emit LockNft(\n            msg.sender,\n            onBehalfOf,\n            param.collectionId,\n            nftIds,\n            newKeys,\n            param.expiryTs,\n            minMaintCredit,\n            param.proxyCollectionId\n        );\n    }\n\n    function _lockNfts(\n        CollectionState storage collectionState,\n        CollectionAccount storage account,\n        uint256[] memory nftIds,\n        uint256 expiryTs, // treat 0 as infinite lock.\n        uint8 vipLevel\n    ) private returns (uint256, uint256[] memory) {\n        LockInfo memory info = LockInfo({\n            isInfinite: expiryTs == 0,\n            currentBucket: Helper.counterStamp(block.timestamp),\n            newExpiryBucket: Helper.counterStamp(expiryTs),\n            selfLocked: account.keyCnt,\n            totalManaged: collectionState.activeSafeBoxCnt +\n                collectionState.freeTokenIds.length,\n            newRequireLockCredit: 0,\n            infiniteCnt: collectionState.infiniteCnt\n        });\n        if (info.isInfinite) {\n            /// if it is infinite lock, we need load all buckets to calculate the staking cost\n            info.newExpiryBucket = Helper.counterStamp(\n                block.timestamp + Constants.MAX_LOCKING_PERIOD\n            );\n        }\n\n        uint256[] memory buckets = Helper.prepareBucketUpdate(\n            collectionState,\n            info.currentBucket,\n            info.newExpiryBucket\n        );\n        /// @dev `keys` used to log info, we just compact its fields into one 256 bits number\n        uint256[] memory keys = new uint256[](nftIds.length);\n\n        for (uint256 idx; idx < nftIds.length; ) {\n            uint256 lockedCredit = updateCountersAndGetSafeboxCredit(\n                buckets,\n                info,\n                vipLevel\n            );\n\n            if (info.isInfinite) ++info.infiniteCnt;\n\n            SafeBoxKey memory key = SafeBoxKey({\n                keyId: Helper.generateNextKeyId(collectionState),\n                lockingCredit: lockedCredit.toUint96(),\n                vipLevel: vipLevel\n            });\n\n            account.addSafeboxKey(nftIds[idx], key);\n            addSafeBox(\n                collectionState,\n                nftIds[idx],\n                SafeBox({\n                    keyId: key.keyId,\n                    expiryTs: uint32(expiryTs),\n                    owner: msg.sender\n                })\n            );\n\n            keys[idx] = SafeBoxLib.encodeSafeBoxKey(key);\n\n            info.newRequireLockCredit += lockedCredit;\n            unchecked {\n                ++info.totalManaged;\n                ++info.selfLocked;\n                ++idx;\n            }\n        }\n\n        if (info.isInfinite) {\n            collectionState.infiniteCnt = info.infiniteCnt;\n        } else {\n            collectionState.countingBuckets.batchSet(\n                info.currentBucket,\n                buckets\n            );\n            if (info.newExpiryBucket > collectionState.lastUpdatedBucket) {\n                collectionState.lastUpdatedBucket = uint64(\n                    info.newExpiryBucket\n                );\n            }\n        }\n\n        return (info.newRequireLockCredit, keys);\n    }\n\n    function unlockNfts(\n        CollectionState storage collection,\n        UserFloorAccount storage userAccount,\n        address proxyCollectionId,\n        address collectionId,\n        uint256[] memory nftIds,\n        uint256 maxExpiryTs,\n        address receiver\n    ) public {\n        CollectionAccount storage userCollectionAccount = userAccount.getByKey(\n            collectionId\n        );\n        SafeBoxKey[] memory releasedKeys = _unlockNfts(\n            collection,\n            maxExpiryTs,\n            nftIds,\n            userCollectionAccount\n        );\n\n        for (uint256 i = 0; i < releasedKeys.length; ) {\n            userAccount.updateVipKeyCount(releasedKeys[i].vipLevel, -1);\n            unchecked {\n                ++i;\n            }\n        }\n\n        /// @dev if the receiver is the contract self, then unlock the safeboxes and dump the NFTs to the vault\n        if (receiver == address(this)) {\n            uint256 nftLen = nftIds.length;\n            uint32 contQuota;\n            for (uint256 i; i < nftLen; ) {\n                collection.freeTokenIds.push(nftIds[i]);\n                contQuota += Constants.getVaultContQuotaAtLR(\n                    Helper.calculateLockingRatio(collection, 0)\n                );\n                unchecked {\n                    ++i;\n                }\n            }\n            Helper.checkAndUpdateVaultQuota(\n                userAccount.getByKey(collectionId),\n                int32(contQuota)\n            );\n            emit FragmentNft(msg.sender, msg.sender, collectionId, nftIds);\n        } else {\n            collection.floorToken.burn(\n                msg.sender,\n                Constants.FLOOR_TOKEN_AMOUNT * nftIds.length\n            );\n\n            /// @dev when using proxy (proxyCollection != collectionId), then the contract address of the collection\n            /// should be same as proxyCollectionId,\n            /// otherwise using underlying collection contract address\n            address contractAddr = proxyCollectionId != collectionId\n                ? proxyCollectionId\n                : collection._contractAddr;\n            ERC721Transfer.safeBatchTransferFrom(\n                contractAddr,\n                address(this),\n                receiver,\n                nftIds\n            );\n        }\n\n        emit UnlockNft(\n            msg.sender,\n            receiver,\n            collectionId,\n            nftIds,\n            proxyCollectionId\n        );\n    }\n\n    function _unlockNfts(\n        CollectionState storage collectionState,\n        uint256 maxExpiryTs,\n        uint256[] memory nftIds,\n        CollectionAccount storage userCollectionAccount\n    ) private returns (SafeBoxKey[] memory) {\n        if (maxExpiryTs > 0 && maxExpiryTs < block.timestamp)\n            revert Errors.SafeBoxHasExpire();\n        SafeBoxKey[] memory expiredKeys = new SafeBoxKey[](nftIds.length);\n        uint256 currentBucketTime = Helper.counterStamp(block.timestamp);\n        uint256 creditToRelease = 0;\n        uint256[] memory buckets;\n\n        /// if maxExpiryTs == 0,\n        /// it means all nftIds in this batch being locked infinitely that we don't need to update countingBuckets\n        if (maxExpiryTs > 0) {\n            uint256 maxExpiryBucketTime = Math.min(\n                Helper.counterStamp(maxExpiryTs),\n                collectionState.lastUpdatedBucket\n            );\n            buckets = collectionState.countingBuckets.batchGet(\n                currentBucketTime,\n                maxExpiryBucketTime\n            );\n        }\n\n        for (uint256 i; i < nftIds.length; ) {\n            uint256 nftId = nftIds[i];\n\n            if (Helper.hasActiveActivities(collectionState, nftId))\n                revert Errors.NftHasActiveActivities();\n\n            (SafeBox storage safeBox, SafeBoxKey storage safeBoxKey) = Helper\n                .useSafeBoxAndKey(\n                    collectionState,\n                    userCollectionAccount,\n                    nftId\n                );\n\n            creditToRelease += safeBoxKey.lockingCredit;\n            if (safeBox.isInfiniteSafeBox()) {\n                --collectionState.infiniteCnt;\n            } else {\n                uint256 limit = Helper.counterStamp(safeBox.expiryTs) -\n                    currentBucketTime;\n                if (limit > buckets.length) revert Errors.InvalidParam();\n                for (uint256 idx; idx < limit; ) {\n                    --buckets[idx];\n                    unchecked {\n                        ++idx;\n                    }\n                }\n            }\n\n            expiredKeys[i] = safeBoxKey;\n\n            removeSafeBox(collectionState, nftId);\n            userCollectionAccount.removeSafeboxKey(nftId);\n\n            unchecked {\n                ++i;\n            }\n        }\n\n        userCollectionAccount.totalLockingCredit -= creditToRelease.toUint96();\n        if (buckets.length > 0) {\n            collectionState.countingBuckets.batchSet(\n                currentBucketTime,\n                buckets\n            );\n        }\n\n        return expiredKeys;\n    }\n\n    function extendLockingForKeys(\n        CollectionState storage collection,\n        UserFloorAccount storage userAccount,\n        LockParam memory param\n    ) public returns (uint256 totalCreditCost) {\n        uint8 newVipLevel = uint8(param.vipLevel);\n        uint256 totalCredit = userAccount.ensureVipCredit(\n            newVipLevel,\n            param.creditToken\n        );\n        Helper.ensureMaxLocking(\n            collection,\n            newVipLevel,\n            param.expiryTs,\n            param.nftIds.length,\n            true\n        );\n\n        uint256 minMaintCredit = userAccount.minMaintCredit;\n        uint256[] memory safeBoxKeys;\n        {\n            CollectionAccount storage collectionAccount = userAccount\n                .getOrAddCollection(param.collectionId);\n\n            // extend lock duration\n            int256[] memory vipLevelDiffs;\n            (\n                vipLevelDiffs,\n                totalCreditCost,\n                safeBoxKeys\n            ) = _extendLockingForKeys(\n                collection,\n                collectionAccount,\n                param.nftIds,\n                param.expiryTs,\n                uint8(newVipLevel)\n            );\n\n            // compute max credit for locking cost\n            uint96 totalLockingCredit = collectionAccount.totalLockingCredit;\n            {\n                uint256 creditBuffer;\n                unchecked {\n                    creditBuffer = totalCredit - totalLockingCredit;\n                }\n                if (\n                    totalCreditCost > creditBuffer ||\n                    totalCreditCost > param.maxCreditCost\n                ) {\n                    revert Errors.InsufficientCredit();\n                }\n            }\n\n            // update user vip key counts\n            for (uint256 vipLevel = 0; vipLevel < vipLevelDiffs.length; ) {\n                userAccount.updateVipKeyCount(\n                    uint8(vipLevel),\n                    vipLevelDiffs[vipLevel]\n                );\n                unchecked {\n                    ++vipLevel;\n                }\n            }\n\n            totalLockingCredit += totalCreditCost.toUint96();\n            collectionAccount.totalLockingCredit = totalLockingCredit;\n            if (totalLockingCredit > minMaintCredit) {\n                userAccount.minMaintCredit = totalLockingCredit;\n                minMaintCredit = totalLockingCredit;\n            }\n        }\n\n        emit ExtendKey(\n            msg.sender,\n            param.collectionId,\n            param.nftIds,\n            safeBoxKeys,\n            param.expiryTs,\n            minMaintCredit\n        );\n    }\n\n    function _extendLockingForKeys(\n        CollectionState storage collectionState,\n        CollectionAccount storage userCollectionAccount,\n        uint256[] memory nftIds,\n        uint256 newExpiryTs, // expiryTs of 0 is infinite.\n        uint8 newVipLevel\n    ) private returns (int256[] memory, uint256, uint256[] memory) {\n        LockInfo memory info = LockInfo({\n            isInfinite: newExpiryTs == 0,\n            currentBucket: Helper.counterStamp(block.timestamp),\n            newExpiryBucket: Helper.counterStamp(newExpiryTs),\n            selfLocked: userCollectionAccount.keyCnt,\n            totalManaged: collectionState.activeSafeBoxCnt +\n                collectionState.freeTokenIds.length,\n            newRequireLockCredit: 0,\n            infiniteCnt: collectionState.infiniteCnt\n        });\n        if (info.isInfinite) {\n            info.newExpiryBucket = Helper.counterStamp(\n                block.timestamp + Constants.MAX_LOCKING_PERIOD\n            );\n        }\n\n        uint256[] memory buckets = Helper.prepareBucketUpdate(\n            collectionState,\n            info.currentBucket,\n            info.newExpiryBucket\n        );\n        int256[] memory vipLevelDiffs = new int256[](Constants.VIP_LEVEL_COUNT);\n        /// @dev `keys` used to log info, we just compact its fields into one 256 bits number\n        uint256[] memory keys = new uint256[](nftIds.length);\n\n        for (uint256 idx; idx < nftIds.length; ) {\n            if (\n                Helper.hasActiveActivities(collectionState, nftIds[idx]) &&\n                /// listing safebox can be extended\n                !Helper.hasActiveListOffer(collectionState, nftIds[idx])\n            ) {\n                revert Errors.NftHasActiveActivities();\n            }\n\n            (SafeBox storage safeBox, SafeBoxKey storage safeBoxKey) = Helper\n                .useSafeBoxAndKey(\n                    collectionState,\n                    userCollectionAccount,\n                    nftIds[idx]\n                );\n\n            {\n                uint256 extendOffset = Helper.counterStamp(safeBox.expiryTs) -\n                    info.currentBucket;\n                unchecked {\n                    for (uint256 i; i < extendOffset; ++i) {\n                        if (buckets[i] == 0) revert Errors.InvalidParam();\n                        --buckets[i];\n                    }\n                }\n            }\n\n            uint256 safeboxQuote = updateCountersAndGetSafeboxCredit(\n                buckets,\n                info,\n                newVipLevel\n            );\n\n            if (safeboxQuote > safeBoxKey.lockingCredit) {\n                info.newRequireLockCredit += (safeboxQuote -\n                    safeBoxKey.lockingCredit);\n                safeBoxKey.lockingCredit = safeboxQuote.toUint96();\n            }\n\n            uint8 oldVipLevel = safeBoxKey.vipLevel;\n            if (newVipLevel > oldVipLevel) {\n                safeBoxKey.vipLevel = newVipLevel;\n                --vipLevelDiffs[oldVipLevel];\n                ++vipLevelDiffs[newVipLevel];\n            }\n\n            if (info.isInfinite) {\n                safeBox.expiryTs = 0;\n                ++info.infiniteCnt;\n            } else {\n                safeBox.expiryTs = uint32(newExpiryTs);\n            }\n\n            keys[idx] = SafeBoxLib.encodeSafeBoxKey(safeBoxKey);\n\n            unchecked {\n                ++idx;\n            }\n        }\n\n        if (info.isInfinite) {\n            collectionState.infiniteCnt = info.infiniteCnt;\n        } else {\n            collectionState.countingBuckets.batchSet(\n                info.currentBucket,\n                buckets\n            );\n            if (info.newExpiryBucket > collectionState.lastUpdatedBucket) {\n                collectionState.lastUpdatedBucket = uint64(\n                    info.newExpiryBucket\n                );\n            }\n        }\n        return (vipLevelDiffs, info.newRequireLockCredit, keys);\n    }\n\n    function updateCountersAndGetSafeboxCredit(\n        uint256[] memory counters,\n        LockInfo memory lockInfo,\n        uint8 vipLevel\n    ) private pure returns (uint256 result) {\n        unchecked {\n            uint256 infiniteCnt = lockInfo.infiniteCnt;\n            uint256 totalManaged = lockInfo.totalManaged;\n\n            uint256 counterOffsetEnd = (counters.length + 1) * 0x20;\n            uint256 tmpCount;\n            if (lockInfo.isInfinite) {\n                for (uint256 i = 0x20; i < counterOffsetEnd; i += 0x20) {\n                    assembly {\n                        tmpCount := mload(add(counters, i))\n                    }\n                    result += Constants.getRequiredStakingForLockRatio(\n                        infiniteCnt + tmpCount,\n                        totalManaged\n                    );\n                }\n            } else {\n                for (uint256 i = 0x20; i < counterOffsetEnd; i += 0x20) {\n                    assembly {\n                        tmpCount := mload(add(counters, i))\n                    }\n                    result += Constants.getRequiredStakingForLockRatio(\n                        infiniteCnt + tmpCount,\n                        totalManaged\n                    );\n                    assembly {\n                        /// increase counters[i]\n                        mstore(add(counters, i), add(tmpCount, 1))\n                    }\n                }\n                result = Constants.getVipRequiredStakingWithDiscount(\n                    result,\n                    vipLevel\n                );\n            }\n            result = Constants.getRequiredStakingWithSelfRatio(\n                result,\n                Helper.calculateSelfLockingRatio(\n                    lockInfo.selfLocked,\n                    totalManaged\n                )\n            );\n        }\n    }\n\n    function tidyExpiredNFTs(\n        CollectionState storage collection,\n        uint256[] memory nftIds,\n        address collectionId\n    ) public {\n        uint256 nftLen = nftIds.length;\n\n        for (uint256 i; i < nftLen; ) {\n            uint256 nftId = nftIds[i];\n            SafeBox storage safeBox = Helper.useSafeBox(collection, nftId);\n            if (!safeBox.isSafeBoxExpired())\n                revert Errors.SafeBoxHasNotExpire();\n            if (!Helper.isAuctionPeriodOver(safeBox))\n                revert Errors.AuctionHasNotCompleted();\n\n            /// remove expired safebox, and dump it to vault\n            removeSafeBox(collection, nftId);\n            collection.freeTokenIds.push(nftId);\n\n            unchecked {\n                ++i;\n            }\n        }\n\n        emit ExpiredNftToVault(msg.sender, collectionId, nftIds);\n    }\n\n    function claimRandomNFT(\n        CollectionState storage collection,\n        mapping(address => UserFloorAccount) storage userAccounts,\n        mapping(address => FeeConfig) storage feeConfs,\n        address collectionId,\n        uint256 claimCnt,\n        uint256 maxCreditCost,\n        address receiver\n    ) public returns (uint256 totalCreditCost) {\n        if (claimCnt == 0 || collection.freeTokenIds.length < claimCnt)\n            revert Errors.ClaimableNftInsufficient();\n\n        {\n            uint256 freeAmount = collection.freeTokenIds.length;\n            uint256 totalManaged = collection.activeSafeBoxCnt + freeAmount;\n            /// when locking ratio greater than xx%, stop redemption\n            if (\n                Helper.calculateLockingRatioRaw(\n                    freeAmount - claimCnt,\n                    totalManaged - claimCnt\n                ) > Constants.VAULT_REDEMPTION_MAX_LOKING_RATIO\n            ) {\n                revert Errors.ClaimableNftInsufficient();\n            }\n        }\n\n        /// quota represented with 32 bits\n        /// fragment token fee with 10^18 decimals\n        bool useQuotaTx = (maxCreditCost >> 32) == 0;\n\n        uint256[] memory selectedTokenIds;\n        (selectedTokenIds, totalCreditCost) = pickFromVault(\n            collection,\n            claimCnt,\n            feeConfs[address(collection.floorToken)].vaultFee.redemptionBase,\n            useQuotaTx\n        );\n\n        if (totalCreditCost > maxCreditCost) {\n            revert Errors.InsufficientCredit();\n        }\n\n        if (useQuotaTx) {\n            UserFloorAccount storage userAccount = userAccounts[msg.sender];\n            Helper.checkAndUpdateVaultQuota(\n                userAccount.getByKey(collectionId),\n                -int32(uint32(totalCreditCost))\n            );\n        } else {\n            distributeRedemptionFunds(\n                userAccounts,\n                feeConfs,\n                address(collection.floorToken),\n                claimCnt,\n                totalCreditCost\n            );\n        }\n\n        collection.floorToken.burn(\n            msg.sender,\n            Constants.FLOOR_TOKEN_AMOUNT * selectedTokenIds.length\n        );\n        ERC721Transfer.safeBatchTransferFrom(\n            collection._contractAddr,\n            address(this),\n            receiver,\n            selectedTokenIds\n        );\n\n        emit ClaimRandomNft(\n            msg.sender,\n            receiver,\n            collectionId,\n            selectedTokenIds,\n            totalCreditCost\n        );\n    }\n\n    function pickFromVault(\n        CollectionState storage collection,\n        uint256 pickCnt,\n        uint16 baseFeeRate,\n        bool payWithQuota\n    ) private returns (uint256[] memory pickedTokenIds, uint256 totalCost) {\n        pickedTokenIds = new uint256[](pickCnt);\n        uint256 freeCnt = collection.freeTokenIds.length;\n        uint256 totalManaged = collection.activeSafeBoxCnt + freeCnt;\n        while (pickCnt > 0) {\n            uint256 lockingRatio = Helper.calculateLockingRatioRaw(\n                freeCnt,\n                totalManaged\n            );\n            if (payWithQuota) {\n                totalCost += Constants.getVaultQuotaFeeAtLR(lockingRatio);\n            } else {\n                totalCost += Constants.getVaultRedemptionFee(\n                    lockingRatio,\n                    baseFeeRate\n                );\n            }\n\n            /// just compute a deterministic random number\n            uint256 chosenNftIdx = uint256(\n                keccak256(\n                    abi.encodePacked(\n                        block.timestamp,\n                        block.prevrandao,\n                        totalManaged\n                    )\n                )\n            ) % freeCnt;\n\n            unchecked {\n                --pickCnt;\n                --totalManaged;\n                --freeCnt;\n            }\n\n            pickedTokenIds[pickCnt] = collection.freeTokenIds[chosenNftIdx];\n\n            collection.freeTokenIds[chosenNftIdx] = collection.freeTokenIds[\n                freeCnt\n            ];\n            collection.freeTokenIds.pop();\n        }\n    }\n\n    function distributeRedemptionFunds(\n        mapping(address => UserFloorAccount) storage accounts,\n        mapping(address => FeeConfig) storage feeConfs,\n        address token,\n        uint256 redeemCnt,\n        uint256 totalPrice\n    ) private {\n        address protocolRecipient = feeConfs[token].vaultFee.recipient;\n        address royaltyRecipient = feeConfs[token].royalty.recipient;\n\n        /// a bit difference,\n        /// for vault redemption with fee, we treat royaltyRate as bips of the Fragment Token amount of 1 NFT\n        /// so the dust will be the incoming to the protocol\n        uint256 royaltyRate = feeConfs[token].royalty.vault;\n        uint256 royalty = redeemCnt *\n            ((Constants.FLOOR_TOKEN_AMOUNT / 10000) * royaltyRate);\n        if (totalPrice < royalty) {\n            royalty = totalPrice;\n        }\n\n        UserFloorAccount storage userAccount = accounts[msg.sender];\n        userAccount.transferToken(\n            accounts[protocolRecipient],\n            token,\n            totalPrice - royalty,\n            false\n        );\n        userAccount.transferToken(\n            accounts[royaltyRecipient],\n            token,\n            royalty,\n            false\n        );\n    }\n\n    function getLockingBuckets(\n        CollectionState storage collection,\n        uint256 startTimestamp,\n        uint256 endTimestamp\n    ) public view returns (uint256[] memory) {\n        return\n            Helper.prepareBucketUpdate(\n                collection,\n                Helper.counterStamp(startTimestamp),\n                Math.min(\n                    collection.lastUpdatedBucket,\n                    Helper.counterStamp(endTimestamp)\n                )\n            );\n    }\n\n    function addSafeBox(\n        CollectionState storage collectionState,\n        uint256 nftId,\n        SafeBox memory safebox\n    ) internal {\n        if (collectionState.safeBoxes[nftId].keyId > 0)\n            revert Errors.SafeBoxAlreadyExist();\n        collectionState.safeBoxes[nftId] = safebox;\n        ++collectionState.activeSafeBoxCnt;\n    }\n\n    function removeSafeBox(\n        CollectionState storage collectionState,\n        uint256 nftId\n    ) internal {\n        delete collectionState.safeBoxes[nftId];\n        --collectionState.activeSafeBoxCnt;\n    }\n}\n"
      },
      "src/logic/CollectionKey.sol": {
        "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.20;\n\n/// @notice The key used to uniquely identify a collection, where the range includes both left and right boundaries.\n///         As some contracts may have multiple collections within one contract based on token ranges.\n///         Currently, it consists of three components:\n///         - The lowest 20 bytes represent the contract address of the collection.\n///         - [optional] The next 6 bytes indicate the starting value of the range.\n///         - [optional] The following 6 bytes represent the ending value of the range.\ntype CollectionKey is bytes32;\n\nlibrary CollectionKeyLib {\n    /// @notice Converts a CollectionKey to an address identifier.\n    /// @param key The CollectionKey to be converted.\n    /// @return An address identifier derived from the CollectionKey.\n    function id(CollectionKey key) internal pure returns (address) {\n        // Unwrap the CollectionKey to extract its data\n        uint256 data = uint256(CollectionKey.unwrap(key));\n\n        // Check if the contract address can be directly obtained from the data\n        if ((data >> 160) == 0) {\n            // If the upper 96 bits are zero, directly convert to address\n            return address(uint160(data));\n        } else {\n            // If the upper 96 bits are non-zero, derive the address using keccak256\n            return\n                address(\n                    uint160(\n                        uint256(\n                            keccak256(\n                                abi.encodePacked(\n                                    uint160(data),\n                                    uint48(data >> 160),\n                                    uint48(data >> 208)\n                                )\n                            )\n                        )\n                    )\n                );\n        }\n    }\n\n    function contractAddr(CollectionKey key) internal pure returns (address) {\n        return address(uint160(uint256(CollectionKey.unwrap(key))));\n    }\n\n    /// @notice Checks if a given range of tokenIds is valid for the specified CollectionKey.\n    /// @param key The CollectionKey to validate against.\n    /// @param first The starting tokenId of the range to be checked.\n    /// @param last The ending tokenId of the range to be checked.\n    /// @return A boolean indicating whether the specified range of tokenIds is valid.\n    function isValidTokenList(\n        CollectionKey key,\n        uint256 first,\n        uint256 last\n    ) internal pure returns (bool) {\n        uint256 data = uint256(CollectionKey.unwrap(key));\n\n        // Check if the range of tokenIds falls within the specified CollectionKey\n        return\n            (data >> 160) == 0 ||\n            (first <= last &&\n                uint48(data >> 160) <= first &&\n                last <= uint48(data >> 208));\n    }\n\n    /// @notice Converts a collection contract address to a CollectionKey.\n    /// @param collectionContract The address of the collection contract to be converted.\n    /// @return A CollectionKey representing the given collection contract address.\n    function toKey(\n        address collectionContract\n    ) internal pure returns (CollectionKey) {\n        return\n            CollectionKey.wrap(bytes32(uint256(uint160(collectionContract))));\n    }\n\n    function toKey(\n        address collectionContract,\n        uint48 left,\n        uint48 right\n    ) internal pure returns (CollectionKey) {\n        require(left < right);\n        uint256 data = uint160(collectionContract) |\n            (uint256(left) << 160) |\n            (uint256(right) << 208);\n        return CollectionKey.wrap(bytes32(data));\n    }\n}\n"
      },
      "src/logic/Helper.sol": {
        "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.20;\n\nimport \"../Constants.sol\";\nimport \"../Errors.sol\";\nimport \"./SafeBox.sol\";\nimport \"./User.sol\";\nimport {\n    SafeBox,\n    CollectionState,\n    AuctionInfo,\n    CollectionAccount,\n    SafeBoxKey,\n    FeeRate,\n    PrivateOffer,\n    Fees\n} from \"./Structs.sol\";\nimport \"../library/RollingBuckets.sol\";\n\nlibrary Helper {\n    using SafeBoxLib for SafeBox;\n    using UserLib for CollectionAccount;\n    using RollingBuckets for mapping(uint256 => uint256);\n\n    function counterStamp(uint256 timestamp) internal pure returns (uint96) {\n        unchecked {\n            return\n                uint96(\n                    (timestamp + Constants.BUCKET_SPAN_1) /\n                        Constants.BUCKET_SPAN\n                );\n        }\n    }\n\n    function checkCollectionSafeboxQuota(\n        CollectionAccount storage account,\n        CollectionState storage collection,\n        uint256 newLocked\n    ) internal view {\n        uint256 selfRatio = calculateSelfLockingRatio(\n            account.keyCnt + newLocked,\n            collection.activeSafeBoxCnt +\n                collection.freeTokenIds.length +\n                newLocked\n        );\n        if (selfRatio > Constants.USER_COLLECTION_LOCKED_BOUND_PCT)\n            revert Errors.UserQuotaExhausted();\n    }\n\n    function checkAndUpdateUserSafeboxQuota(\n        UserFloorAccount storage account,\n        uint8 vipLevel,\n        uint16 newLocked\n    ) internal {\n        uint16 used = updateUserSafeboxQuota(account);\n        uint16 totalQuota = Constants.getSafeboxPeriodQuota(vipLevel);\n\n        uint16 nextUsed = used + newLocked;\n        if (nextUsed > totalQuota) revert Errors.PeriodQuotaExhausted();\n\n        account.safeboxQuotaUsed = nextUsed;\n\n        checkSafeboxQuotaGlobal(account, vipLevel, newLocked);\n    }\n\n    function checkSafeboxQuotaGlobal(\n        UserFloorAccount storage account,\n        uint8 vipLevel,\n        uint256 newLocked\n    ) internal view {\n        uint256 totalQuota = Constants.getSafeboxUserQuota(vipLevel);\n        if (totalQuota < newLocked) {\n            revert Errors.UserQuotaExhausted();\n        } else {\n            unchecked {\n                totalQuota -= newLocked;\n            }\n        }\n\n        (, uint256[] memory keyCnts) = UserLib.getMinLevelAndVipKeyCounts(\n            account.vipInfo\n        );\n        for (uint256 i; i < Constants.VIP_LEVEL_COUNT; ) {\n            if (totalQuota >= keyCnts[i]) {\n                unchecked {\n                    totalQuota -= keyCnts[i];\n                }\n            } else {\n                revert Errors.UserQuotaExhausted();\n            }\n            unchecked {\n                ++i;\n            }\n        }\n    }\n\n    function ensureProxyVipLevel(uint8 vipLevel, bool proxy) internal pure {\n        if (proxy && vipLevel < Constants.PROXY_COLLECTION_VIP_THRESHOLD) {\n            revert Errors.InvalidParam();\n        }\n    }\n\n    function ensureMaxLocking(\n        CollectionState storage collection,\n        uint8 vipLevel,\n        uint256 requireExpiryTs,\n        uint256 requireLockCnt,\n        bool extend\n    ) internal view {\n        /// vip level 0 can not use safebox utilities.\n        if (vipLevel >= Constants.VIP_LEVEL_COUNT || vipLevel == 0) {\n            revert Errors.InvalidParam();\n        }\n\n        uint256 lockingRatio = calculateLockingRatio(\n            collection,\n            requireLockCnt\n        );\n        uint256 restrictRatio;\n        if (extend) {\n            /// try to extend exist safebox\n            /// only restrict infinity locking, normal safebox with expiry should be skipped\n            restrictRatio = requireExpiryTs == 0\n                ? Constants.getLockingRatioForInfinite(vipLevel)\n                : 100;\n        } else {\n            /// try to lock(create new safebox)\n            /// restrict maximum locking ratio to use safebox\n            restrictRatio = Constants.getLockingRatioForSafebox(vipLevel);\n            if (requireExpiryTs == 0) {\n                uint256 extraRatio = Constants.getLockingRatioForInfinite(\n                    vipLevel\n                );\n                if (restrictRatio > extraRatio) restrictRatio = extraRatio;\n            }\n        }\n\n        if (lockingRatio > restrictRatio) revert Errors.InvalidParam();\n\n        /// only check when it is not infinite lock\n        if (requireExpiryTs > 0) {\n            uint256 deltaBucket;\n            unchecked {\n                deltaBucket =\n                    counterStamp(requireExpiryTs) -\n                    counterStamp(block.timestamp);\n            }\n            if (\n                deltaBucket == 0 ||\n                deltaBucket > Constants.getVipLockingBuckets(vipLevel)\n            ) {\n                revert Errors.InvalidParam();\n            }\n        }\n    }\n\n    function useSafeBoxAndKey(\n        CollectionState storage collection,\n        CollectionAccount storage userAccount,\n        uint256 nftId\n    ) internal view returns (SafeBox storage safeBox, SafeBoxKey storage key) {\n        safeBox = collection.safeBoxes[nftId];\n        if (safeBox.keyId == 0) revert Errors.SafeBoxNotExist();\n        if (safeBox.isSafeBoxExpired()) revert Errors.SafeBoxHasExpire();\n\n        key = userAccount.getByKey(nftId);\n        if (!safeBox.isKeyMatchingSafeBox(key))\n            revert Errors.NoMatchingSafeBoxKey();\n    }\n\n    function useSafeBox(\n        CollectionState storage collection,\n        uint256 nftId\n    ) internal view returns (SafeBox storage safeBox) {\n        safeBox = collection.safeBoxes[nftId];\n        if (safeBox.keyId == 0) revert Errors.SafeBoxNotExist();\n    }\n\n    function generateNextKeyId(\n        CollectionState storage collectionState\n    ) internal returns (uint64 nextKeyId) {\n        nextKeyId = collectionState.nextKeyId;\n        ++collectionState.nextKeyId;\n    }\n\n    function generateNextActivityId(\n        CollectionState storage collection\n    ) internal returns (uint64 nextActivityId) {\n        nextActivityId = collection.nextActivityId;\n        ++collection.nextActivityId;\n    }\n\n    function isAuctionPeriodOver(\n        SafeBox storage safeBox\n    ) internal view returns (bool) {\n        return\n            safeBox.expiryTs + Constants.FREE_AUCTION_PERIOD < block.timestamp;\n    }\n\n    function hasActiveActivities(\n        CollectionState storage collection,\n        uint256 nftId\n    ) internal view returns (bool) {\n        return\n            hasActiveAuction(collection, nftId) ||\n            hasActiveRaffle(collection, nftId) ||\n            hasActivePrivateOffer(collection, nftId) ||\n            hasActiveListOffer(collection, nftId);\n    }\n\n    function hasActiveAuction(\n        CollectionState storage collection,\n        uint256 nftId\n    ) internal view returns (bool) {\n        return collection.activeAuctions[nftId].endTime >= block.timestamp;\n    }\n\n    function hasActiveRaffle(\n        CollectionState storage collection,\n        uint256 nftId\n    ) internal view returns (bool) {\n        return collection.activeRaffles[nftId].endTime >= block.timestamp;\n    }\n\n    function hasActivePrivateOffer(\n        CollectionState storage collection,\n        uint256 nftId\n    ) internal view returns (bool) {\n        return collection.activePrivateOffers[nftId].endTime >= block.timestamp;\n    }\n\n    function hasActiveListOffer(\n        CollectionState storage collection,\n        uint256 nftId\n    ) internal view returns (bool) {\n        PrivateOffer storage offer = collection.activePrivateOffers[nftId];\n        return\n            offer.activityId > 0 &&\n            offer.buyer == address(0) &&\n            !useSafeBox(collection, nftId).isSafeBoxExpired();\n    }\n\n    function calculateActivityFee(\n        uint256 settleAmount,\n        uint256 feeRateBips\n    ) internal pure returns (uint256 afterFee, uint256 fee) {\n        fee = (settleAmount * feeRateBips) / 10000;\n        unchecked {\n            afterFee = settleAmount - fee;\n        }\n    }\n\n    function prepareBucketUpdate(\n        CollectionState storage collection,\n        uint256 startBucket,\n        uint256 endBucket\n    ) internal view returns (uint256[] memory buckets) {\n        uint256 validEnd = collection.lastUpdatedBucket;\n        uint256 padding;\n        if (endBucket < validEnd) {\n            validEnd = endBucket;\n        } else {\n            unchecked {\n                padding = endBucket - validEnd;\n            }\n        }\n\n        if (startBucket < validEnd) {\n            if (padding == 0) {\n                buckets = collection.countingBuckets.batchGet(\n                    startBucket,\n                    validEnd\n                );\n            } else {\n                uint256 validLen;\n                unchecked {\n                    validLen = validEnd - startBucket;\n                }\n                buckets = new uint256[](validLen + padding);\n                uint256[] memory tmp = collection.countingBuckets.batchGet(\n                    startBucket,\n                    validEnd\n                );\n                for (uint256 i; i < validLen; ) {\n                    buckets[i] = tmp[i];\n                    unchecked {\n                        ++i;\n                    }\n                }\n            }\n        } else {\n            buckets = new uint256[](endBucket - startBucket);\n        }\n    }\n\n    function getActiveSafeBoxes(\n        CollectionState storage collectionState,\n        uint256 timestamp\n    ) internal view returns (uint256) {\n        uint256 bucketStamp = counterStamp(timestamp);\n        if (collectionState.lastUpdatedBucket < bucketStamp) {\n            return 0;\n        }\n        return collectionState.countingBuckets.get(bucketStamp);\n    }\n\n    function calculateLockingRatio(\n        CollectionState storage collection,\n        uint256 newLocked\n    ) internal view returns (uint256) {\n        uint256 freeAmount = collection.freeTokenIds.length;\n        uint256 totalManaged = newLocked +\n            collection.activeSafeBoxCnt +\n            freeAmount;\n        return calculateLockingRatioRaw(freeAmount, totalManaged);\n    }\n\n    function calculateLockingRatioRaw(\n        uint256 freeAmount,\n        uint256 totalManaged\n    ) internal pure returns (uint256) {\n        if (totalManaged == 0) {\n            return 0;\n        } else {\n            unchecked {\n                return (100 - (freeAmount * 100) / totalManaged);\n            }\n        }\n    }\n\n    function calculateSelfLockingRatio(\n        uint256 selfLocked,\n        uint256 collectionTatalManaged\n    ) internal pure returns (uint256) {\n        if (collectionTatalManaged == 0) {\n            return 0;\n        } else {\n            unchecked {\n                return ((selfLocked * 100) / collectionTatalManaged);\n            }\n        }\n    }\n\n    function updateUserSafeboxQuota(\n        UserFloorAccount storage account\n    ) internal returns (uint16) {\n        if (\n            block.timestamp - account.lastQuotaPeriodTs <=\n            Constants.USER_SAFEBOX_QUOTA_REFRESH_DURATION\n        ) {\n            return account.safeboxQuotaUsed;\n        } else {\n            unchecked {\n                account.lastQuotaPeriodTs = uint32(\n                    (block.timestamp /\n                        Constants.USER_SAFEBOX_QUOTA_REFRESH_DURATION) *\n                        Constants.USER_SAFEBOX_QUOTA_REFRESH_DURATION\n                );\n            }\n            account.safeboxQuotaUsed = 0;\n            return 0;\n        }\n    }\n\n    function applyDiffToCounters(\n        CollectionState storage collectionState,\n        uint256 startBucket,\n        uint256 endBucket,\n        int256 diff\n    ) internal {\n        if (startBucket == endBucket) return;\n        uint256[] memory buckets = Helper.prepareBucketUpdate(\n            collectionState,\n            startBucket,\n            endBucket\n        );\n        unchecked {\n            uint256 bucketLen = buckets.length;\n            if (diff > 0) {\n                uint256 tmp = uint256(diff);\n                for (uint256 i; i < bucketLen; ++i) {\n                    buckets[i] += tmp;\n                }\n            } else {\n                uint256 tmp = uint256(-diff);\n                for (uint256 i; i < bucketLen; ++i) {\n                    buckets[i] -= tmp;\n                }\n            }\n        }\n        collectionState.countingBuckets.batchSet(startBucket, buckets);\n        if (endBucket > collectionState.lastUpdatedBucket) {\n            collectionState.lastUpdatedBucket = uint64(endBucket);\n        }\n    }\n\n    function computeFees(\n        uint256 price,\n        Fees memory fees\n    )\n        internal\n        pure\n        returns (uint256 priceWithoutFee, uint256 protocolFee, uint256 royalty)\n    {\n        protocolFee = (price * fees.protocol.rateBips) / 10_000;\n        royalty = (price * fees.royalty.rateBips) / 10_000;\n        unchecked {\n            priceWithoutFee = price - protocolFee - royalty;\n        }\n    }\n\n    function checkAndUpdateVaultQuota(\n        CollectionAccount storage account,\n        int32 diff\n    ) internal {\n        if (\n            block.timestamp - account.lastVaultActiveTs >\n            Constants.VAULT_QUOTA_RESET_PERIOD\n        ) {\n            // exceeds the reset period\n            if (diff < 0) revert Errors.UserQuotaExhausted();\n            else account.vaultContQuota = uint32(diff);\n        } else {\n            int32 res = int32(account.vaultContQuota) + diff;\n            if (res < 0) revert Errors.UserQuotaExhausted();\n            else account.vaultContQuota = uint32(res);\n        }\n\n        if (diff > 0) {\n            /// only refresh when increasing quota\n            account.lastVaultActiveTs = uint32(block.timestamp);\n        }\n    }\n}\n"
      },
      "src/logic/PrivateOffer.sol": {
        "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.20;\n\nimport \"@openzeppelin/contracts/utils/math/Math.sol\";\n\nimport \"../Errors.sol\";\nimport \"../interface/IFlooring.sol\";\nimport \"../library/RollingBuckets.sol\";\nimport {\n    SafeBox,\n    CollectionState,\n    PrivateOffer,\n    Fees,\n    FeeRate,\n    FeeConfig\n} from \"./Structs.sol\";\nimport { SafeBoxLib } from \"./SafeBox.sol\";\nimport \"./User.sol\";\nimport \"./Helper.sol\";\n\nlibrary PrivateOfferLib {\n    using SafeBoxLib for SafeBox;\n    using RollingBuckets for mapping(uint256 => uint256);\n    using UserLib for UserFloorAccount;\n    using UserLib for CollectionAccount;\n    using Helper for CollectionState;\n\n    // todo: event should be moved to Interface as far as Solidity 0.8.22 ready.\n    // https://github.com/ethereum/solidity/pull/14274\n    // https://github.com/ethereum/solidity/issues/14430\n    event PrivateOfferStarted(\n        address indexed seller,\n        address indexed buyer,\n        address indexed collection,\n        uint64[] activityIds,\n        uint256[] nftIds,\n        address settleToken,\n        uint96 price,\n        uint256 offerEndTime,\n        uint256 safeBoxExpiryTs,\n        uint256 adminFee,\n        Fees fees\n    );\n\n    event PrivateOfferCanceled(\n        address indexed operator,\n        address indexed collection,\n        uint64[] activityIds,\n        uint256[] nftIds\n    );\n\n    event OfferMetaChanged(\n        address indexed operator,\n        address indexed collection,\n        uint64[] activityIds,\n        uint256[] nftIds,\n        uint96[] price,\n        address settleToken,\n        uint32 offerEndTime,\n        uint32 safeboxExpiryTs\n    );\n\n    event PrivateOfferAccepted(\n        address indexed buyer,\n        address indexed collection,\n        uint64[] activityIds,\n        uint256[] nftIds,\n        uint256[] safeBoxKeyIds,\n        uint32 safeboxExpiryTs\n    );\n\n    function ownerInitPrivateOffers(\n        CollectionState storage collection,\n        mapping(address => UserFloorAccount) storage userAccounts,\n        FeeConfig storage feeConf,\n        address creditToken,\n        IFlooring.PrivateOfferInitParam memory param\n    ) public {\n        if (param.receiver == msg.sender) revert Errors.InvalidParam();\n        /// if receiver is none, means list and anyone can buy it\n        if (param.receiver == address(0)) {\n            return startListOffer(collection, userAccounts, feeConf, param);\n        }\n\n        UserFloorAccount storage userAccount = userAccounts[msg.sender];\n\n        PrivateOffer memory offerTemplate = PrivateOffer({\n            endTime: uint96(block.timestamp + Constants.PRIVATE_OFFER_DURATION),\n            activityId: 0,\n            token: param.token,\n            price: param.price,\n            owner: msg.sender,\n            buyer: param.receiver,\n            fees: Fees(FeeRate(address(0), 0), FeeRate(address(0), 0))\n        });\n\n        (\n            uint64[] memory offerActivityIds,\n            uint192 safeBoxExpiryTs\n        ) = _ownerInitPrivateOffers(\n                collection,\n                userAccount.getByKey(param.collectionId),\n                param.nftIds,\n                offerTemplate,\n                param.maxExpiry\n            );\n\n        uint256 totalFeeCost = param.nftIds.length *\n            Constants.PRIVATE_OFFER_COST;\n        userAccount.transferToken(\n            userAccounts[address(this)],\n            creditToken,\n            totalFeeCost,\n            true\n        );\n\n        emit PrivateOfferStarted(\n            msg.sender,\n            param.receiver,\n            param.collectionId,\n            offerActivityIds,\n            param.nftIds,\n            param.token,\n            param.price,\n            offerTemplate.endTime,\n            safeBoxExpiryTs,\n            totalFeeCost,\n            offerTemplate.fees\n        );\n    }\n\n    function _ownerInitPrivateOffers(\n        CollectionState storage collection,\n        CollectionAccount storage userAccount,\n        uint256[] memory nftIds,\n        PrivateOffer memory offerTemplate,\n        uint256 maxExpiry\n    )\n        private\n        returns (uint64[] memory offerActivityIds, uint32 safeBoxExpiryTs)\n    {\n        safeBoxExpiryTs = uint32(\n            offerTemplate.endTime +\n                Constants.PRIVATE_OFFER_COMPLETE_GRACE_DURATION\n        );\n        uint256 nowBucketCnt = Helper.counterStamp(block.timestamp);\n\n        uint256[] memory toUpdateBucket;\n        if (maxExpiry > 0) {\n            toUpdateBucket = collection.countingBuckets.batchGet(\n                nowBucketCnt,\n                Math.min(\n                    collection.lastUpdatedBucket,\n                    Helper.counterStamp(maxExpiry)\n                )\n            );\n        }\n\n        offerActivityIds = new uint64[](nftIds.length);\n        uint256 firstIdx = Helper.counterStamp(safeBoxExpiryTs) - nowBucketCnt;\n        for (uint256 i; i < nftIds.length; ) {\n            uint256 nftId = nftIds[i];\n            if (collection.hasActiveActivities(nftId))\n                revert Errors.NftHasActiveActivities();\n\n            (SafeBox storage safeBox, ) = collection.useSafeBoxAndKey(\n                userAccount,\n                nftId\n            );\n\n            if (safeBox.isInfiniteSafeBox()) {\n                --collection.infiniteCnt;\n            } else {\n                uint256 oldExpiryTs = safeBox.expiryTs;\n                if (oldExpiryTs < safeBoxExpiryTs) {\n                    revert Errors.InvalidParam();\n                }\n                uint256 lastIdx = Helper.counterStamp(oldExpiryTs) -\n                    nowBucketCnt;\n                if (firstIdx > lastIdx || lastIdx > toUpdateBucket.length)\n                    revert Errors.InvalidParam();\n                for (uint256 k = firstIdx; k < lastIdx; ) {\n                    --toUpdateBucket[k];\n                    unchecked {\n                        ++k;\n                    }\n                }\n            }\n\n            safeBox.expiryTs = safeBoxExpiryTs;\n            offerTemplate.activityId = collection.generateNextActivityId();\n            collection.activePrivateOffers[nftId] = offerTemplate;\n            offerActivityIds[i] = offerTemplate.activityId;\n\n            unchecked {\n                ++i;\n            }\n        }\n        if (toUpdateBucket.length > 0) {\n            collection.countingBuckets.batchSet(nowBucketCnt, toUpdateBucket);\n        }\n    }\n\n    function modifyOffers(\n        CollectionState storage collection,\n        mapping(address => UserFloorAccount) storage accounts,\n        address collectionId,\n        uint256[] memory nftIds,\n        IFlooring.OfferOpType opTy,\n        bytes calldata data\n    ) public {\n        if (\n            opTy == IFlooring.OfferOpType.Cancel ||\n            opTy == IFlooring.OfferOpType.Decline\n        ) {\n            removePrivateOffers(collection, collectionId, nftIds);\n        } else if (opTy == IFlooring.OfferOpType.ChangePrice) {\n            IFlooring.ChangeOfferPriceData memory priceData = abi.decode(\n                data,\n                (IFlooring.ChangeOfferPriceData)\n            );\n            modifyOfferPrice(\n                collection,\n                accounts[msg.sender],\n                collectionId,\n                nftIds,\n                priceData.priceList\n            );\n        } else {\n            revert Errors.InvalidParam();\n        }\n    }\n\n    function removePrivateOffers(\n        CollectionState storage collection,\n        address collectionId,\n        uint256[] memory nftIds\n    ) internal {\n        uint64[] memory offerActivityIds = new uint64[](nftIds.length);\n        for (uint256 i; i < nftIds.length; ) {\n            uint256 nftId = nftIds[i];\n            PrivateOffer storage offer = collection.activePrivateOffers[nftId];\n            if (offer.owner != msg.sender && offer.buyer != msg.sender)\n                revert Errors.NoPrivilege();\n\n            offerActivityIds[i] = offer.activityId;\n            delete collection.activePrivateOffers[nftId];\n\n            unchecked {\n                ++i;\n            }\n        }\n\n        emit PrivateOfferCanceled(\n            msg.sender,\n            collectionId,\n            offerActivityIds,\n            nftIds\n        );\n    }\n\n    function buyerAcceptPrivateOffers(\n        CollectionState storage collection,\n        mapping(address => UserFloorAccount) storage userAccounts,\n        address collectionId,\n        address creditToken,\n        uint256[] memory nftIds,\n        uint256 maxExpiry\n    ) public {\n        uint256[] memory safeBoxKeyIds = new uint256[](nftIds.length);\n        uint64[] memory activityIds = new uint64[](nftIds.length);\n\n        uint32 newExpiryTs = uint32(\n            block.timestamp + Constants.PRIVATE_OFFER_COMPLETE_GRACE_DURATION\n        );\n        uint256[] memory toUpdateBucket;\n        if (maxExpiry > 0) {\n            toUpdateBucket = Helper.prepareBucketUpdate(\n                collection,\n                Helper.counterStamp(block.timestamp),\n                Math.max(\n                    Helper.counterStamp(maxExpiry),\n                    Helper.counterStamp(newExpiryTs)\n                )\n            );\n        }\n\n        for (uint256 i; i < nftIds.length; ) {\n            uint256 nftId = nftIds[i];\n            if (\n                !Helper.hasActiveListOffer(collection, nftId) &&\n                !Helper.hasActivePrivateOffer(collection, nftId)\n            ) {\n                revert Errors.ActivityNotExist();\n            }\n\n            {\n                PrivateOffer memory offer = collection.activePrivateOffers[\n                    nftId\n                ];\n                if (offer.endTime > 0 && offer.endTime <= block.timestamp)\n                    revert Errors.ActivityHasExpired();\n                if (offer.buyer != address(0) && offer.buyer != msg.sender)\n                    revert Errors.NoPrivilege();\n                if (offer.owner == msg.sender) revert Errors.NoPrivilege();\n\n                activityIds[i] = offer.activityId;\n\n                distributeFunds(userAccounts, offer, creditToken);\n            }\n\n            SafeBoxKey memory newKey = SafeBoxKey({\n                keyId: collection.generateNextKeyId(),\n                vipLevel: 0,\n                lockingCredit: 0\n            });\n            safeBoxKeyIds[i] = newKey.keyId;\n\n            {\n                /// transfer safebox key\n                CollectionAccount storage buyerCollectionAccount = userAccounts[\n                    msg.sender\n                ].getByKey(collectionId);\n                buyerCollectionAccount.addSafeboxKey(nftId, newKey);\n            }\n\n            /// update safebox\n            SafeBox storage safeBox = collection.useSafeBox(nftId);\n            /// this revert couldn't happen but just leaving it (we have checked offer'EndTime before)\n            if (safeBox.isSafeBoxExpired()) revert Errors.SafeBoxHasExpire();\n            safeBox.keyId = newKey.keyId;\n            safeBox.owner = msg.sender;\n            if (safeBox.isInfiniteSafeBox()) {\n                --collection.infiniteCnt;\n                /// infinite locked safebox should be shorten to the newExpiryTs\n                safeBox.expiryTs = uint32(block.timestamp);\n            }\n            updateSafeboxBuckets(safeBox, toUpdateBucket, newExpiryTs);\n\n            delete collection.activePrivateOffers[nftId];\n\n            unchecked {\n                ++i;\n            }\n        }\n\n        if (toUpdateBucket.length > 0) {\n            collection.countingBuckets.batchSet(\n                Helper.counterStamp(block.timestamp),\n                toUpdateBucket\n            );\n            uint96 newBucket = Helper.counterStamp(newExpiryTs);\n            if (collection.lastUpdatedBucket < newBucket) {\n                collection.lastUpdatedBucket = uint64(newBucket);\n            }\n        }\n\n        emit PrivateOfferAccepted(\n            msg.sender,\n            collectionId,\n            activityIds,\n            nftIds,\n            safeBoxKeyIds,\n            newExpiryTs\n        );\n    }\n\n    function modifyOfferPrice(\n        CollectionState storage collection,\n        UserFloorAccount storage ownerAccount,\n        address collectionId,\n        uint256[] memory nftIds,\n        uint96[] memory newPriceList\n    ) internal {\n        if (nftIds.length != newPriceList.length) revert Errors.InvalidParam();\n\n        CollectionAccount storage ownerCollection = ownerAccount.getByKey(\n            collectionId\n        );\n\n        uint64[] memory activityIds = new uint64[](nftIds.length);\n        for (uint256 i; i < nftIds.length; ) {\n            uint256 nftId = nftIds[i];\n            PrivateOffer storage offer = collection.activePrivateOffers[nftId];\n            if (offer.owner != msg.sender) revert Errors.NoPrivilege();\n            if (offer.endTime > 0 && offer.endTime <= block.timestamp)\n                revert Errors.ActivityHasExpired();\n\n            /// dummy check ownership\n            /// when offer.endTime is zero, we check the safebox expiry\n            collection.useSafeBoxAndKey(ownerCollection, nftIds[i]);\n\n            offer.price = newPriceList[i];\n            activityIds[i] = offer.activityId;\n\n            unchecked {\n                ++i;\n            }\n        }\n\n        /// time no change\n        emit OfferMetaChanged(\n            msg.sender,\n            collectionId,\n            activityIds,\n            nftIds,\n            newPriceList,\n            address(0),\n            0,\n            0\n        );\n    }\n\n    function startListOffer(\n        CollectionState storage collection,\n        mapping(address => UserFloorAccount) storage userAccounts,\n        FeeConfig storage feeConf,\n        IFlooring.PrivateOfferInitParam memory param\n    ) internal {\n        if (feeConf.safeboxFee.recipient == address(0))\n            revert Errors.TokenNotSupported();\n\n        PrivateOffer memory template = PrivateOffer({\n            /// leave endTime empty as it is same as safebox expiry\n            endTime: 0,\n            activityId: 0,\n            token: param.token,\n            price: param.price,\n            owner: msg.sender,\n            buyer: address(0),\n            fees: Fees({\n                royalty: FeeRate({\n                    recipient: feeConf.royalty.recipient,\n                    rateBips: feeConf.royalty.marketlist\n                }),\n                protocol: FeeRate({\n                    recipient: feeConf.safeboxFee.recipient,\n                    rateBips: feeConf.safeboxFee.marketlist\n                })\n            })\n        });\n        CollectionAccount storage ownerCollection = userAccounts[msg.sender]\n            .getByKey(param.collectionId);\n\n        uint64[] memory activityIds = new uint64[](param.nftIds.length);\n        for (uint256 i; i < param.nftIds.length; ) {\n            uint256 nftId = param.nftIds[i];\n            if (collection.hasActiveActivities(nftId))\n                revert Errors.NftHasActiveActivities();\n\n            /// dummy check\n            collection.useSafeBoxAndKey(ownerCollection, nftId);\n\n            template.activityId = Helper.generateNextActivityId(collection);\n            collection.activePrivateOffers[nftId] = template;\n\n            activityIds[i] = template.activityId;\n            unchecked {\n                ++i;\n            }\n        }\n\n        emit PrivateOfferStarted(\n            msg.sender,\n            address(0), // buyer no restrictions\n            param.collectionId,\n            activityIds,\n            param.nftIds,\n            param.token,\n            param.price,\n            0, // same with safebox expiry each other\n            0, // same with safebox expiry\n            0, // no admin fee\n            template.fees\n        );\n    }\n\n    function updateSafeboxBuckets(\n        SafeBox storage safebox,\n        uint256[] memory buckets,\n        uint32 newExpireTs\n    ) private {\n        uint256 offset = Helper.counterStamp(block.timestamp);\n\n        /// the safebox isn't expired, so that the bucket should be greater or equeal than `offset`\n        uint256 safeboxExpiryBucketIdx = Helper.counterStamp(safebox.expiryTs) -\n            offset;\n        uint256 expectBucketIdx = Helper.counterStamp(newExpireTs) - offset;\n\n        if (safeboxExpiryBucketIdx < expectBucketIdx) {\n            /// expend the key,\n            if (expectBucketIdx > buckets.length) revert Errors.InvalidParam();\n            for (uint256 i = safeboxExpiryBucketIdx; i < expectBucketIdx; ) {\n                ++buckets[i];\n                unchecked {\n                    ++i;\n                }\n            }\n        } else if (safeboxExpiryBucketIdx > expectBucketIdx) {\n            /// shorten the key,\n            if (safeboxExpiryBucketIdx > buckets.length)\n                revert Errors.InvalidParam();\n            for (uint256 i = expectBucketIdx; i < safeboxExpiryBucketIdx; ) {\n                --buckets[i];\n                unchecked {\n                    ++i;\n                }\n            }\n        }\n        safebox.expiryTs = newExpireTs;\n    }\n\n    function distributeFunds(\n        mapping(address => UserFloorAccount) storage accounts,\n        PrivateOffer memory offer,\n        address creditToken\n    ) private {\n        if (offer.price > 0) {\n            UserFloorAccount storage buyerAccount = accounts[msg.sender];\n            address token = offer.token;\n            (\n                uint256 priceWithoutFee,\n                uint256 protocolFee,\n                uint256 royalty\n            ) = Helper.computeFees(offer.price, offer.fees);\n\n            {\n                /// calculate owner vip tier discounts\n                uint8 ownerVipLevel = Constants.getVipLevel(\n                    accounts[offer.owner].tokenBalance(creditToken)\n                );\n                uint256 protocolFeeAfterDiscount = Constants\n                    .getListingProtocolFeeWithDiscount(\n                        protocolFee,\n                        ownerVipLevel\n                    );\n                priceWithoutFee += (protocolFee - protocolFeeAfterDiscount);\n                protocolFee = protocolFeeAfterDiscount;\n            }\n\n            buyerAccount.transferToken(\n                accounts[offer.owner],\n                token,\n                priceWithoutFee,\n                token == creditToken\n            );\n            buyerAccount.transferToken(\n                accounts[offer.fees.protocol.recipient],\n                offer.token,\n                protocolFee,\n                token == creditToken\n            );\n            buyerAccount.transferToken(\n                accounts[offer.fees.royalty.recipient],\n                offer.token,\n                royalty,\n                token == creditToken\n            );\n        }\n    }\n}\n"
      },
      "src/logic/Raffle.sol": {
        "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.20;\n\nimport {\n    SafeBox,\n    CollectionState,\n    RaffleInfo,\n    TicketRecord,\n    FeeConfig,\n    Fees,\n    FeeRate\n} from \"./Structs.sol\";\nimport \"./User.sol\";\nimport \"./Collection.sol\";\nimport \"./Helper.sol\";\nimport \"../Errors.sol\";\nimport \"../library/RollingBuckets.sol\";\nimport \"../library/Array.sol\";\n\nlibrary RaffleLib {\n    using CollectionLib for CollectionState;\n    using SafeBoxLib for SafeBox;\n    using RollingBuckets for mapping(uint256 => uint256);\n    using UserLib for UserFloorAccount;\n    using UserLib for CollectionAccount;\n    using Helper for CollectionState;\n\n    event RaffleStarted(\n        address indexed owner,\n        address indexed collection,\n        uint64[] activityIds,\n        uint256[] nftIds,\n        uint48 maxTickets,\n        address settleToken,\n        uint96 ticketPrice,\n        Fees fees,\n        uint48 raffleEndTime,\n        uint256 safeBoxExpiryTs,\n        uint256 adminFee\n    );\n\n    event RaffleTicketsSold(\n        address indexed buyer,\n        address indexed collection,\n        uint64 activityId,\n        uint256 nftId,\n        uint256 ticketsSold,\n        uint256 cost\n    );\n\n    event RaffleSettled(\n        address indexed winner,\n        address indexed collection,\n        uint64 activityId,\n        uint256 nftId,\n        uint256 safeBoxKeyId,\n        uint256 collectedFunds\n    );\n\n    function ownerInitRaffles(\n        CollectionState storage collection,\n        mapping(address => UserFloorAccount) storage userAccounts,\n        FeeConfig storage feeConf,\n        IFlooring.RaffleInitParam memory param,\n        address creditToken\n    ) public {\n        if (feeConf.safeboxFee.recipient == address(0))\n            revert Errors.TokenNotSupported();\n\n        UserFloorAccount storage userAccount = userAccounts[msg.sender];\n        {\n            if (\n                uint256(param.maxTickets) * param.ticketPrice > type(uint96).max\n            ) revert Errors.InvalidParam();\n\n            (uint256 vipLevel, uint256 duration) = Constants.raffleDurations(\n                param.duration\n            );\n            userAccount.ensureVipCredit(uint8(vipLevel), creditToken);\n            param.duration = duration;\n        }\n\n        uint256 adminFee = Constants.RAFFLE_COST * param.nftIds.length;\n        userAccount.transferToken(\n            userAccounts[address(this)],\n            creditToken,\n            adminFee,\n            true\n        );\n\n        Fees memory fees = Fees({\n            royalty: FeeRate({\n                recipient: feeConf.royalty.recipient,\n                rateBips: feeConf.royalty.raffle\n            }),\n            protocol: FeeRate({\n                recipient: feeConf.safeboxFee.recipient,\n                rateBips: feeConf.safeboxFee.raffle\n            })\n        });\n        (\n            uint64[] memory raffleActivityIds,\n            uint48 raffleEndTime,\n            uint192 safeBoxExpiryTs\n        ) = _ownerInitRaffles(\n                collection,\n                userAccount.getByKey(param.collectionId),\n                param,\n                fees\n            );\n\n        emit RaffleStarted(\n            msg.sender,\n            param.collectionId,\n            raffleActivityIds,\n            param.nftIds,\n            param.maxTickets,\n            param.ticketToken,\n            param.ticketPrice,\n            fees,\n            raffleEndTime,\n            safeBoxExpiryTs,\n            adminFee\n        );\n    }\n\n    function _ownerInitRaffles(\n        CollectionState storage collection,\n        CollectionAccount storage userAccount,\n        IFlooring.RaffleInitParam memory param,\n        Fees memory fees\n    )\n        private\n        returns (\n            uint64[] memory raffleActivityIds,\n            uint48 raffleEndTime,\n            uint32 safeBoxExpiryTs\n        )\n    {\n        raffleEndTime = uint48(block.timestamp + param.duration);\n        safeBoxExpiryTs = uint32(\n            raffleEndTime + Constants.RAFFLE_COMPLETE_GRACE_PERIODS\n        );\n\n        uint256 startBucket = Helper.counterStamp(block.timestamp);\n\n        uint256[] memory toUpdateBucket;\n        if (param.maxExpiry > 0) {\n            toUpdateBucket = collection.countingBuckets.batchGet(\n                startBucket,\n                Math.min(\n                    Helper.counterStamp(param.maxExpiry),\n                    collection.lastUpdatedBucket\n                )\n            );\n        }\n\n        raffleActivityIds = new uint64[](param.nftIds.length);\n        uint256 firstIdx = Helper.counterStamp(safeBoxExpiryTs) - startBucket;\n        for (uint256 i; i < param.nftIds.length; ) {\n            uint256 nftId = param.nftIds[i];\n\n            if (collection.hasActiveActivities(nftId))\n                revert Errors.NftHasActiveActivities();\n\n            (SafeBox storage safeBox, ) = collection.useSafeBoxAndKey(\n                userAccount,\n                nftId\n            );\n\n            if (safeBox.isInfiniteSafeBox()) {\n                --collection.infiniteCnt;\n            } else {\n                uint256 oldExpiryTs = safeBox.expiryTs;\n                if (oldExpiryTs < safeBoxExpiryTs) {\n                    revert Errors.InvalidParam();\n                }\n                uint256 lastIdx = Helper.counterStamp(oldExpiryTs) -\n                    startBucket;\n                if (firstIdx > lastIdx || lastIdx > toUpdateBucket.length)\n                    revert Errors.InvalidParam();\n                for (uint256 k = firstIdx; k < lastIdx; ) {\n                    --toUpdateBucket[k];\n                    unchecked {\n                        ++k;\n                    }\n                }\n            }\n\n            safeBox.expiryTs = safeBoxExpiryTs;\n            raffleActivityIds[i] = collection.generateNextActivityId();\n\n            RaffleInfo storage newRaffle = collection.activeRaffles[nftId];\n            newRaffle.endTime = raffleEndTime;\n            newRaffle.token = param.ticketToken;\n            newRaffle.ticketPrice = param.ticketPrice;\n            newRaffle.maxTickets = param.maxTickets;\n            newRaffle.owner = msg.sender;\n            newRaffle.activityId = raffleActivityIds[i];\n            newRaffle.fees = fees;\n\n            unchecked {\n                ++i;\n            }\n        }\n\n        if (toUpdateBucket.length > 0) {\n            collection.countingBuckets.batchSet(startBucket, toUpdateBucket);\n        }\n    }\n\n    function buyRaffleTickets(\n        CollectionState storage collection,\n        mapping(address => UserFloorAccount) storage accounts,\n        address creditToken,\n        address collectionId,\n        uint256 nftId,\n        uint256 ticketCnt\n    ) public {\n        RaffleInfo storage raffle = collection.activeRaffles[nftId];\n        if (raffle.owner == address(0) || raffle.owner == msg.sender)\n            revert Errors.NoPrivilege();\n        if (raffle.endTime < block.timestamp)\n            revert Errors.ActivityHasExpired();\n        if (raffle.maxTickets < raffle.ticketSold + ticketCnt)\n            revert Errors.InvalidParam();\n\n        SafeBox storage safeBox = collection.useSafeBox(nftId);\n        safeBox.keyId = SafeBoxLib.SAFEBOX_KEY_NOTATION;\n\n        // buyer buy tickets idx in [startIdx, endIdx)\n        raffle.tickets.push(\n            TicketRecord({\n                buyer: msg.sender,\n                startIdx: uint48(raffle.ticketSold),\n                endIdx: uint48(raffle.ticketSold + ticketCnt)\n            })\n        );\n\n        uint256 cost = raffle.ticketPrice * ticketCnt;\n        raffle.ticketSold += uint48(ticketCnt);\n        raffle.collectedFund += uint96(cost);\n\n        address token = raffle.token;\n        accounts[msg.sender].transferToken(\n            accounts[address(this)],\n            token,\n            cost,\n            token == creditToken\n        );\n\n        emit RaffleTicketsSold(\n            msg.sender,\n            collectionId,\n            raffle.activityId,\n            nftId,\n            ticketCnt,\n            cost\n        );\n    }\n\n    function prepareSettleRaffles(\n        CollectionState storage collection,\n        uint256[] calldata nftIds\n    ) public returns (bytes memory compactedNftIds, uint256 nftLen) {\n        nftLen = nftIds.length;\n        uint256[] memory tmpNftIds = new uint256[](nftLen);\n        uint256 cnt;\n        for (uint256 i; i < nftLen; ++i) {\n            uint256 nftId = nftIds[i];\n            RaffleInfo storage raffle = collection.activeRaffles[nftId];\n\n            if (raffle.endTime >= block.timestamp)\n                revert Errors.ActivityHasNotCompleted();\n            if (raffle.isSettling) revert Errors.InvalidParam();\n\n            if (raffle.ticketSold == 0) {\n                continue;\n            }\n\n            SafeBox storage safeBox = collection.useSafeBox(nftId);\n            // raffle must be settled before safebox expired\n            // otherwise it maybe conflict with auction\n            if (safeBox.isSafeBoxExpired()) revert Errors.SafeBoxHasExpire();\n\n            tmpNftIds[cnt] = nftId;\n            raffle.isSettling = true;\n\n            unchecked {\n                ++cnt;\n            }\n        }\n\n        /// only allow up to 8 raffles can be settled\n        if (cnt > 8) revert Errors.InvalidParam();\n\n        if (cnt == nftLen) {\n            compactedNftIds = Array.encodeUints(tmpNftIds);\n        } else {\n            nftLen = cnt;\n            compactedNftIds = Array.encodeUints(Array.slice(tmpNftIds, 0, cnt));\n        }\n    }\n\n    function settleRaffles(\n        CollectionState storage collection,\n        mapping(address => UserFloorAccount) storage userAccounts,\n        address collectionId,\n        bytes memory compactedNftIds,\n        uint256[] memory randoms\n    ) public {\n        uint256[] memory nftIds = Array.decodeUints(compactedNftIds);\n\n        for (uint256 i; i < nftIds.length; ) {\n            uint256 nftId = nftIds[i];\n            RaffleInfo storage raffle = collection.activeRaffles[nftId];\n\n            TicketRecord memory winTicket = getWinTicket(\n                raffle.tickets,\n                uint48(randoms[i] % raffle.ticketSold)\n            );\n\n            SafeBoxKey memory key = SafeBoxKey({\n                keyId: collection.generateNextKeyId(),\n                vipLevel: 0,\n                lockingCredit: 0\n            });\n\n            {\n                /// we don't check whether the safebox is exist, it had done in the `prepareSettleRaffles`\n                SafeBox storage safeBox = collection.safeBoxes[nftId];\n                safeBox.keyId = key.keyId;\n                safeBox.owner = winTicket.buyer;\n            }\n\n            {\n                /// transfer safebox key to winner account\n                CollectionAccount\n                    storage winnerCollectionAccount = userAccounts[\n                        winTicket.buyer\n                    ].getByKey(collectionId);\n                winnerCollectionAccount.addSafeboxKey(nftId, key);\n            }\n\n            distributeFunds(\n                userAccounts,\n                raffle.fees,\n                raffle.owner,\n                raffle.token,\n                raffle.collectedFund\n            );\n\n            emit RaffleSettled(\n                winTicket.buyer,\n                collectionId,\n                raffle.activityId,\n                nftId,\n                key.keyId,\n                raffle.collectedFund\n            );\n\n            delete collection.activeRaffles[nftId];\n\n            unchecked {\n                ++i;\n            }\n        }\n    }\n\n    function getWinTicket(\n        TicketRecord[] storage tickets,\n        uint48 idx\n    ) private view returns (TicketRecord memory ticket) {\n        uint256 low;\n        uint256 high = tickets.length;\n\n        unchecked {\n            while (low <= high) {\n                // Note that mid will always be strictly less than high (i.e. it will be a valid array index)\n                // because Math.average rounds down (it does integer division with truncation).\n                uint256 mid = Math.average(low, high);\n\n                ticket = tickets[mid];\n                if (ticket.startIdx <= idx && idx < ticket.endIdx) {\n                    return ticket;\n                }\n\n                if (ticket.startIdx < idx) {\n                    high = mid;\n                } else {\n                    low = mid + 1;\n                }\n            }\n        }\n    }\n\n    function distributeFunds(\n        mapping(address => UserFloorAccount) storage accounts,\n        Fees memory fees,\n        address owner,\n        address token,\n        uint256 price\n    ) private {\n        /// contract account no need to check credit requirements\n        (uint256 priceWithoutFee, uint256 protocolFee, uint256 royalty) = Helper\n            .computeFees(price, fees);\n\n        UserFloorAccount storage contractAccount = accounts[address(this)];\n\n        if (royalty > 0) {\n            contractAccount.transferToken(\n                accounts[fees.royalty.recipient],\n                token,\n                royalty,\n                false\n            );\n        }\n        if (protocolFee > 0) {\n            contractAccount.transferToken(\n                accounts[fees.protocol.recipient],\n                token,\n                protocolFee,\n                false\n            );\n        }\n        if (priceWithoutFee > 0) {\n            contractAccount.transferToken(\n                accounts[owner],\n                token,\n                priceWithoutFee,\n                false\n            );\n        }\n    }\n}\n"
      },
      "src/logic/SafeBox.sol": {
        "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.20;\n\nimport { SafeBox, SafeBoxKey } from \"./Structs.sol\";\n\nlibrary SafeBoxLib {\n    uint64 public constant SAFEBOX_KEY_NOTATION = type(uint64).max;\n\n    function isInfiniteSafeBox(\n        SafeBox storage safeBox\n    ) internal view returns (bool) {\n        return safeBox.expiryTs == 0;\n    }\n\n    function isSafeBoxExpired(\n        SafeBox storage safeBox\n    ) internal view returns (bool) {\n        return safeBox.expiryTs != 0 && safeBox.expiryTs < block.timestamp;\n    }\n\n    function _isSafeBoxExpired(\n        SafeBox memory safeBox\n    ) internal view returns (bool) {\n        return safeBox.expiryTs != 0 && safeBox.expiryTs < block.timestamp;\n    }\n\n    function isKeyMatchingSafeBox(\n        SafeBox storage safeBox,\n        SafeBoxKey storage safeBoxKey\n    ) internal view returns (bool) {\n        return safeBox.keyId == safeBoxKey.keyId;\n    }\n\n    function _isKeyMatchingSafeBox(\n        SafeBox memory safeBox,\n        SafeBoxKey memory safeBoxKey\n    ) internal pure returns (bool) {\n        return safeBox.keyId == safeBoxKey.keyId;\n    }\n\n    function encodeSafeBoxKey(\n        SafeBoxKey memory key\n    ) internal pure returns (uint256) {\n        uint256 val = key.lockingCredit;\n        val |= (uint256(key.keyId) << 96);\n        val |= (uint256(key.vipLevel) << 160);\n        return val;\n    }\n}\n"
      },
      "src/logic/Structs.sol": {
        "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.20;\n\nimport \"../interface/IFragmentToken.sol\";\n\nstruct SafeBox {\n    /// Either matching a key OR Constants.SAFEBOX_KEY_NOTATION meaning temporarily\n    /// held by a bidder in auction.\n    uint64 keyId;\n    /// The timestamp that the safe box expires.\n    uint32 expiryTs;\n    /// The owner of the safebox. It maybe outdated due to expiry\n    address owner;\n}\n\nstruct PrivateOffer {\n    /// private offer end time\n    uint96 endTime;\n    /// which token used to accpet the offer\n    address token;\n    /// price of the offer\n    uint96 price;\n    address owner;\n    /// who should receive the offer\n    address buyer;\n    uint64 activityId;\n    Fees fees;\n}\n\nenum AuctionType {\n    Owned,\n    Expired,\n    Vault\n}\n\nstruct AuctionInfo {\n    /// The end time for the auction.\n    uint96 endTime;\n    /// Bid token address.\n    address bidTokenAddress;\n    /// Minimum Bid.\n    uint96 minimumBid;\n    /// The person who trigger the auction at the beginning.\n    address triggerAddress;\n    uint96 lastBidAmount;\n    address lastBidder;\n    /// [Deprecated] Whether the auction is triggered by the NFT owner itself？\n    /// Note. Don't remove it directly as we need keep mainnet contract layout\n    bool isSelfTriggered;\n    uint64 activityId;\n    /// [Deprecated] fee config\n    /// Note. Don't remove it directly as we need keep mainnet contract layout\n    uint32 oldFeeRateBips;\n    AuctionType typ;\n    Fees fees;\n}\n\nstruct TicketRecord {\n    /// who buy the tickets\n    address buyer;\n    /// Start index of tickets\n    /// [startIdx, endIdx)\n    uint48 startIdx;\n    /// End index of tickets\n    uint48 endIdx;\n}\n\nstruct RaffleInfo {\n    /// raffle end time\n    uint48 endTime;\n    /// max tickets amount the raffle can sell\n    uint48 maxTickets;\n    /// which token used to buy the raffle tickets\n    address token;\n    /// owner of raffle\n    address owner;\n    /// price per ticket\n    uint96 ticketPrice;\n    uint64 activityId;\n    /// total funds collected by selling tickets\n    uint96 collectedFund;\n    /// total sold tickets amount\n    uint48 ticketSold;\n    /// whether the raffle is being settling\n    bool isSettling;\n    /// tickets sold records\n    TicketRecord[] tickets;\n    Fees fees;\n}\n\nstruct CollectionState {\n    /// The address of the Floor Token cooresponding to the NFTs.\n    IFragmentToken floorToken;\n    /// Records the active safe box in each time bucket.\n    mapping(uint256 => uint256) countingBuckets;\n    /// Stores all of the NFTs that has been fragmented but *without* locked up limit.\n    uint256[] freeTokenIds;\n    /// Huge map for all the `SafeBox`es in one collection.\n    mapping(uint256 => SafeBox) safeBoxes;\n    /// Stores all the ongoing auctions: nftId => `AuctionInfo`.\n    mapping(uint256 => AuctionInfo) activeAuctions;\n    /// Stores all the ongoing raffles: nftId => `RaffleInfo`.\n    mapping(uint256 => RaffleInfo) activeRaffles;\n    /// Stores all the ongoing private offers: nftId => `PrivateOffer`.\n    mapping(uint256 => PrivateOffer) activePrivateOffers;\n    /// The last bucket time the `countingBuckets` is updated.\n    uint64 lastUpdatedBucket;\n    /// Next Key Id. This should start from 1, we treat key id `SafeboxLib.SAFEBOX_KEY_NOTATION` as temporarily\n    /// being used for activities(auction/raffle).\n    uint64 nextKeyId;\n    /// Active Safe Box Count.\n    uint64 activeSafeBoxCnt;\n    /// The number of infinite lock count.\n    uint64 infiniteCnt;\n    /// Next Activity Id. This should start from 1\n    uint64 nextActivityId;\n    uint32 lastVaultAuctionPeriodTs;\n    /// The contract address of the collection, used to transfer\n    /// it could be different from the collection id\n    address _contractAddr;\n}\n\nstruct UserFloorAccount {\n    /// @notice it should be maximum of the `totalLockingCredit` across all collections\n    uint96 minMaintCredit;\n    /// @notice used to iterate collection accounts\n    /// packed with `minMaintCredit` to reduce storage slot access\n    address firstCollection;\n    /// @notice user vip level related info\n    /// 0 - 239 bits: store SafeBoxKey Count per vip level, per level using 24 bits\n    /// 240 - 247 bits: store minMaintVipLevel\n    /// 248 - 255 bits: remaining\n    uint256 vipInfo;\n    /// @notice Locked Credit amount which cannot be withdrawn and will be released as time goes.\n    uint256 lockedCredit;\n    mapping(address => CollectionAccount) accounts;\n    mapping(address => uint256) tokenAmounts;\n    /// Each account has safebox quota to use per period\n    uint32 lastQuotaPeriodTs;\n    uint16 safeboxQuotaUsed;\n    /// [Deprecated] Each account has vault redemption waiver per period\n    uint32 lastWaiverPeriodTs;\n    uint96 creditWaiverUsed;\n}\n\nstruct SafeBoxKey {\n    /// locked credit amount of this safebox\n    uint96 lockingCredit;\n    /// corresponding key id of the safebox\n    uint64 keyId;\n    /// which vip level the safebox locked\n    uint8 vipLevel;\n}\n\nstruct CollectionAccount {\n    mapping(uint256 => SafeBoxKey) keys;\n    /// total locking credit of all `keys` in this collection\n    uint96 totalLockingCredit;\n    /// track next collection as linked list\n    address next;\n    /// tracking total locked of the collection\n    uint32 keyCnt;\n    /// Depositing to vault gets quota, redepmtion consumes quota\n    uint32 vaultContQuota;\n    /// Used to track and clear vault contribution quota when the quota is inactive for a certain duration\n    uint32 lastVaultActiveTs;\n}\n\nstruct Fees {\n    FeeRate royalty;\n    FeeRate protocol;\n}\n\nstruct FeeConfig {\n    RoyaltyFeeRate royalty;\n    SafeboxFeeRate safeboxFee;\n    VaultFeeRate vaultFee;\n}\n\nstruct RoyaltyFeeRate {\n    address recipient;\n    uint16 marketlist;\n    uint16 vault;\n    uint16 raffle;\n}\n\nstruct VaultFeeRate {\n    address recipient;\n    uint16 vaultAuction;\n    uint16 redemptionBase;\n}\n\nstruct SafeboxFeeRate {\n    address recipient;\n    uint16 auctionOwned;\n    uint16 auctionExpired;\n    uint16 raffle;\n    uint16 marketlist;\n}\n\nstruct FeeRate {\n    address recipient;\n    uint16 rateBips;\n}\n\n/// Internal Structure\nstruct LockParam {\n    address proxyCollectionId;\n    address collectionId;\n    uint256[] nftIds;\n    uint256 expiryTs;\n    uint8 vipLevel;\n    uint256 maxCreditCost;\n    address creditToken;\n}\n"
      },
      "src/logic/User.sol": {
        "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.20;\n\nimport \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\n\nimport \"../library/CurrencyTransfer.sol\";\nimport \"../Constants.sol\";\nimport \"../Errors.sol\";\nimport \"./SafeBox.sol\";\nimport \"../library/Array.sol\";\nimport {\n    UserFloorAccount,\n    CollectionAccount,\n    SafeBoxKey,\n    CollectionState,\n    SafeBox\n} from \"./Structs.sol\";\n\nlibrary UserLib {\n    using SafeCast for uint256;\n    using SafeBoxLib for SafeBox;\n\n    /// @notice `sender` deposit `token` into Flooring on behalf of `receiver`. `receiver`'s account will be updated.\n    event DepositToken(\n        address indexed sender,\n        address indexed receiver,\n        address indexed token,\n        uint256 amount\n    );\n    /// @notice `sender` withdraw `token` from Flooring and transfer it to `receiver`.\n    event WithdrawToken(\n        address indexed sender,\n        address indexed receiver,\n        address indexed token,\n        uint256 amount\n    );\n    /// @notice update the account maintain credit on behalfOf `onBehalfOf`\n    event UpdateMaintainCredit(\n        address indexed onBehalfOf,\n        uint256 minMaintCredit\n    );\n    event RemoveExpiredKey(\n        address indexed operator,\n        address indexed onBehalfOf,\n        address indexed collection,\n        uint256[] tokenIds,\n        uint256[] safeBoxKeys\n    );\n\n    address internal constant LIST_GUARD = address(1);\n\n    function ensureVipCredit(\n        UserFloorAccount storage account,\n        uint8 requireVipLevel,\n        address creditToken\n    ) internal view returns (uint256) {\n        uint256 totalCredit = tokenBalance(account, creditToken);\n        if (\n            Constants.getVipBalanceRequirements(requireVipLevel) > totalCredit\n        ) {\n            revert Errors.InsufficientBalanceForVipLevel();\n        }\n        return totalCredit;\n    }\n\n    function getMinMaintVipLevel(\n        UserFloorAccount storage account\n    ) internal view returns (uint8) {\n        unchecked {\n            return uint8(account.vipInfo >> 240);\n        }\n    }\n\n    function getMinLevelAndVipKeyCounts(\n        uint256 vipInfo\n    ) internal pure returns (uint8 minLevel, uint256[] memory counts) {\n        unchecked {\n            counts = new uint256[](Constants.VIP_LEVEL_COUNT);\n            minLevel = uint8(vipInfo >> 240);\n            for (uint256 i; i < Constants.VIP_LEVEL_COUNT; ++i) {\n                counts[i] = (vipInfo >> (i * 24)) & 0xFFFFFF;\n            }\n        }\n    }\n\n    function storeMinLevelAndVipKeyCounts(\n        UserFloorAccount storage account,\n        uint8 minMaintVipLevel,\n        uint256[] memory keyCounts\n    ) internal {\n        unchecked {\n            uint256 _data = (uint256(minMaintVipLevel) << 240);\n            for (uint256 i; i < Constants.VIP_LEVEL_COUNT; ++i) {\n                _data |= ((keyCounts[i] & 0xFFFFFF) << (i * 24));\n            }\n            account.vipInfo = _data;\n        }\n    }\n\n    function getOrAddCollection(\n        UserFloorAccount storage user,\n        address collection\n    ) internal returns (CollectionAccount storage) {\n        CollectionAccount storage entry = user.accounts[collection];\n        if (entry.next == address(0)) {\n            if (user.firstCollection == address(0)) {\n                user.firstCollection = collection;\n                entry.next = LIST_GUARD;\n            } else {\n                entry.next = user.firstCollection;\n                user.firstCollection = collection;\n            }\n        }\n        return entry;\n    }\n\n    function removeCollection(\n        UserFloorAccount storage userAccount,\n        address collection,\n        address prev\n    ) internal {\n        CollectionAccount storage cur = userAccount.accounts[collection];\n        if (cur.next == address(0)) revert Errors.InvalidParam();\n\n        if (collection == userAccount.firstCollection) {\n            if (cur.next == LIST_GUARD) {\n                userAccount.firstCollection = address(0);\n            } else {\n                userAccount.firstCollection = cur.next;\n            }\n        } else {\n            CollectionAccount storage prevAccount = userAccount.accounts[prev];\n            if (prevAccount.next != collection) revert Errors.InvalidParam();\n            prevAccount.next = cur.next;\n        }\n\n        delete userAccount.accounts[collection];\n    }\n\n    function getByKey(\n        UserFloorAccount storage userAccount,\n        address collection\n    ) internal view returns (CollectionAccount storage) {\n        return userAccount.accounts[collection];\n    }\n\n    function addSafeboxKey(\n        CollectionAccount storage account,\n        uint256 nftId,\n        SafeBoxKey memory key\n    ) internal {\n        if (account.keys[nftId].keyId > 0) {\n            revert Errors.SafeBoxKeyAlreadyExist();\n        }\n\n        ++account.keyCnt;\n        account.keys[nftId] = key;\n    }\n\n    function removeSafeboxKey(\n        CollectionAccount storage account,\n        uint256 nftId\n    ) internal {\n        --account.keyCnt;\n        delete account.keys[nftId];\n    }\n\n    function getByKey(\n        CollectionAccount storage account,\n        uint256 nftId\n    ) internal view returns (SafeBoxKey storage) {\n        return account.keys[nftId];\n    }\n\n    function tokenBalance(\n        UserFloorAccount storage account,\n        address token\n    ) internal view returns (uint256) {\n        return account.tokenAmounts[token];\n    }\n\n    function lockCredit(\n        UserFloorAccount storage account,\n        uint256 amount\n    ) internal {\n        unchecked {\n            account.lockedCredit += amount;\n        }\n    }\n\n    function unlockCredit(\n        UserFloorAccount storage account,\n        uint256 amount\n    ) internal {\n        account.lockedCredit -= amount;\n    }\n\n    function deposit(\n        UserFloorAccount storage account,\n        address onBehalfOf,\n        address token,\n        uint256 amount,\n        bool isLockCredit\n    ) public {\n        depositToken(account, token, amount);\n        if (isLockCredit) lockCredit(account, amount);\n\n        if (token == CurrencyTransfer.NATIVE) {\n            require(amount == msg.value);\n        } else {\n            CurrencyTransfer.safeTransferFrom(\n                token,\n                msg.sender,\n                address(this),\n                amount\n            );\n        }\n        emit DepositToken(msg.sender, onBehalfOf, token, amount);\n    }\n\n    function withdraw(\n        UserFloorAccount storage account,\n        address receiver,\n        address token,\n        uint256 amount,\n        bool isCredit\n    ) public {\n        withdrawToken(account, token, amount, isCredit);\n        CurrencyTransfer.safeTransfer(token, receiver, amount);\n        emit WithdrawToken(msg.sender, receiver, token, amount);\n    }\n\n    function depositToken(\n        UserFloorAccount storage account,\n        address token,\n        uint256 amount\n    ) internal {\n        account.tokenAmounts[token] += amount;\n    }\n\n    function withdrawToken(\n        UserFloorAccount storage account,\n        address token,\n        uint256 amount,\n        bool isCreditToken\n    ) internal {\n        uint256 balance = account.tokenAmounts[token];\n        if (balance < amount) {\n            revert Errors.InsufficientCredit();\n        }\n\n        if (isCreditToken) {\n            uint256 avaiableBuf;\n            unchecked {\n                avaiableBuf = balance - amount;\n            }\n            if (\n                avaiableBuf <\n                Constants.getVipBalanceRequirements(\n                    getMinMaintVipLevel(account)\n                ) ||\n                avaiableBuf < account.minMaintCredit ||\n                avaiableBuf < account.lockedCredit\n            ) {\n                revert Errors.InsufficientCredit();\n            }\n\n            account.tokenAmounts[token] = avaiableBuf;\n        } else {\n            unchecked {\n                account.tokenAmounts[token] = balance - amount;\n            }\n        }\n    }\n\n    function transferToken(\n        UserFloorAccount storage from,\n        UserFloorAccount storage to,\n        address token,\n        uint256 amount,\n        bool isCreditToken\n    ) internal {\n        if (amount > 0) {\n            withdrawToken(from, token, amount, isCreditToken);\n            depositToken(to, token, amount);\n        }\n    }\n\n    function updateVipKeyCount(\n        UserFloorAccount storage account,\n        uint8 vipLevel,\n        int256 diff\n    ) internal {\n        if (vipLevel > 0 && diff != 0) {\n            (\n                uint8 minMaintVipLevel,\n                uint256[] memory keyCounts\n            ) = getMinLevelAndVipKeyCounts(account.vipInfo);\n\n            if (diff < 0) {\n                keyCounts[vipLevel] -= uint256(-diff);\n                if (vipLevel == minMaintVipLevel && keyCounts[vipLevel] == 0) {\n                    uint8 newVipLevel = vipLevel;\n                    do {\n                        unchecked {\n                            --newVipLevel;\n                        }\n                    } while (newVipLevel > 0 && keyCounts[newVipLevel] == 0);\n\n                    minMaintVipLevel = newVipLevel;\n                }\n            } else {\n                keyCounts[vipLevel] += uint256(diff);\n                if (vipLevel > minMaintVipLevel) {\n                    minMaintVipLevel = vipLevel;\n                }\n            }\n            storeMinLevelAndVipKeyCounts(account, minMaintVipLevel, keyCounts);\n        }\n    }\n\n    function recalculateMinMaintCredit(\n        UserFloorAccount storage account,\n        address onBehalfOf\n    ) public returns (uint256 maxLocking) {\n        address prev = account.firstCollection;\n        for (\n            address collection = account.firstCollection;\n            collection != LIST_GUARD && collection != address(0);\n\n        ) {\n            (uint256 locking, address next) = (\n                getByKey(account, collection).totalLockingCredit,\n                getByKey(account, collection).next\n            );\n            if (locking == 0) {\n                removeCollection(account, collection, prev);\n                collection = next;\n            } else {\n                if (locking > maxLocking) {\n                    maxLocking = locking;\n                }\n                prev = collection;\n                collection = next;\n            }\n        }\n\n        account.minMaintCredit = uint96(maxLocking);\n\n        emit UpdateMaintainCredit(onBehalfOf, maxLocking);\n    }\n\n    function removeExpiredKeysAndRestoreCredits(\n        UserFloorAccount storage account,\n        CollectionState storage collectionState,\n        address collectionId,\n        uint256[] memory nftIds,\n        address onBehalfOf,\n        bool verifyLocking\n    ) public returns (uint256 releasedCredit) {\n        CollectionAccount storage collectionAccount = getByKey(\n            account,\n            collectionId\n        );\n        if (verifyLocking) {\n            /// tricky logic,\n            /// when verifying, we assume the keyCnt maybe incorrect and prevent it from underflow by increasing it\n            collectionAccount.keyCnt = uint32(nftIds.length);\n        }\n\n        uint256 lockingCredit;\n        uint256 removedCnt;\n        uint256[] memory removedKeys = new uint256[](nftIds.length);\n\n        for (uint256 i; i < nftIds.length; ) {\n            SafeBoxKey memory safeBoxKey = getByKey(\n                collectionAccount,\n                nftIds[i]\n            );\n            SafeBox memory safeBox = collectionState.safeBoxes[nftIds[i]];\n\n            if (safeBoxKey.keyId == 0) {\n                revert Errors.InvalidParam();\n            }\n\n            if (\n                safeBox._isSafeBoxExpired() ||\n                !safeBox._isKeyMatchingSafeBox(safeBoxKey)\n            ) {\n                /// reuse the array\n                nftIds[removedCnt] = nftIds[i];\n                removedKeys[removedCnt] = SafeBoxLib.encodeSafeBoxKey(\n                    safeBoxKey\n                );\n\n                unchecked {\n                    ++removedCnt;\n                    releasedCredit += safeBoxKey.lockingCredit;\n                }\n\n                updateVipKeyCount(account, safeBoxKey.vipLevel, -1);\n                removeSafeboxKey(collectionAccount, nftIds[i]);\n            } else if (verifyLocking) {\n                unchecked {\n                    lockingCredit += safeBoxKey.lockingCredit;\n                }\n            }\n\n            unchecked {\n                ++i;\n            }\n        }\n\n        if (releasedCredit > 0) {\n            collectionAccount.totalLockingCredit -= releasedCredit.toUint96();\n        }\n\n        if (verifyLocking) {\n            if (collectionAccount.totalLockingCredit != lockingCredit)\n                revert Errors.InvalidParam();\n            /// when locking credit are matching, the amount of keys with credit locked can be computed directly\n            /// the keys without credit locked can be ignored temporarily\n            collectionAccount.keyCnt = (nftIds.length - removedCnt).toUint32();\n        }\n\n        emit RemoveExpiredKey(\n            msg.sender,\n            onBehalfOf,\n            collectionId,\n            removedCnt == nftIds.length\n                ? nftIds\n                : Array.slice(nftIds, 0, removedCnt),\n            removedCnt == nftIds.length\n                ? removedKeys\n                : Array.slice(removedKeys, 0, removedCnt)\n        );\n    }\n}\n"
      }
    },
    "settings": {
      "metadata": {
        "bytecodeHash": "none",
        "useLiteralContent": true
      },
      "optimizer": {
        "enabled": true,
        "runs": 800
      },
      "outputSelection": {
        "*": {
          "*": [
            "evm.bytecode",
            "evm.deployedBytecode",
            "devdoc",
            "userdoc",
            "metadata",
            "abi"
          ]
        }
      },
      "libraries": {
        "src/logic/Auction.sol": {
          "AuctionLib": "0x4309483f46aad4a2c95d5cc5af3f0be2dc5243f7"
        },
        "src/logic/Collection.sol": {
          "CollectionLib": "0x11943d370259dfe4c94f59dab2e52b6339597c61"
        },
        "src/logic/PrivateOffer.sol": {
          "PrivateOfferLib": "0xe43da036d82e8d667ab2e8414a7017f6aa728d34"
        },
        "src/logic/Raffle.sol": {
          "RaffleLib": "0x6afb8d582e306da5646adaa568a5f04281e08632"
        },
        "src/logic/User.sol": {
          "UserLib": "0x45d08c50915ff84f41735cfe71c64d060cf458e7"
        }
      }
    }
  }}