resolc 1.1.0

Solidity frontend for the revive compiler
{
  "language": "Solidity",
  "sources": {
    "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (access/Ownable2Step.sol)\n\npragma solidity ^0.8.20;\n\nimport {OwnableUpgradeable} from \"./OwnableUpgradeable.sol\";\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module which provides access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * This extension of the {Ownable} contract includes a two-step mechanism to transfer\n * ownership, where the new owner must call {acceptOwnership} in order to replace the\n * old one. This can help prevent common mistakes, such as transfers of ownership to\n * incorrect accounts, or to contracts that are unable to interact with the\n * permission system.\n *\n * The initial owner is specified at deployment time in the constructor for `Ownable`. This\n * can later be changed with {transferOwnership} and {acceptOwnership}.\n *\n * This module is used through inheritance. It will make available all functions\n * from parent (Ownable).\n */\nabstract contract Ownable2StepUpgradeable is Initializable, OwnableUpgradeable {\n    /// @custom:storage-location erc7201:openzeppelin.storage.Ownable2Step\n    struct Ownable2StepStorage {\n        address _pendingOwner;\n    }\n\n    // keccak256(abi.encode(uint256(keccak256(\"openzeppelin.storage.Ownable2Step\")) - 1)) & ~bytes32(uint256(0xff))\n    bytes32 private constant Ownable2StepStorageLocation = 0x237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c00;\n\n    function _getOwnable2StepStorage() private pure returns (Ownable2StepStorage storage $) {\n        assembly {\n            $.slot := Ownable2StepStorageLocation\n        }\n    }\n\n    event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);\n\n    function __Ownable2Step_init() internal onlyInitializing {\n    }\n\n    function __Ownable2Step_init_unchained() internal onlyInitializing {\n    }\n    /**\n     * @dev Returns the address of the pending owner.\n     */\n    function pendingOwner() public view virtual returns (address) {\n        Ownable2StepStorage storage $ = _getOwnable2StepStorage();\n        return $._pendingOwner;\n    }\n\n    /**\n     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.\n     * Can only be called by the current owner.\n     *\n     * Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer.\n     */\n    function transferOwnership(address newOwner) public virtual override onlyOwner {\n        Ownable2StepStorage storage $ = _getOwnable2StepStorage();\n        $._pendingOwner = newOwner;\n        emit OwnershipTransferStarted(owner(), newOwner);\n    }\n\n    /**\n     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.\n     * Internal function without access restriction.\n     */\n    function _transferOwnership(address newOwner) internal virtual override {\n        Ownable2StepStorage storage $ = _getOwnable2StepStorage();\n        delete $._pendingOwner;\n        super._transferOwnership(newOwner);\n    }\n\n    /**\n     * @dev The new owner accepts the ownership transfer.\n     */\n    function acceptOwnership() public virtual {\n        address sender = _msgSender();\n        if (pendingOwner() != sender) {\n            revert OwnableUnauthorizedAccount(sender);\n        }\n        _transferOwnership(sender);\n    }\n}\n"
    },
    "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\n\npragma solidity ^0.8.20;\n\nimport {ContextUpgradeable} from \"../utils/ContextUpgradeable.sol\";\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * The initial owner is set to the address provided by the deployer. This can\n * later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {\n    /// @custom:storage-location erc7201:openzeppelin.storage.Ownable\n    struct OwnableStorage {\n        address _owner;\n    }\n\n    // keccak256(abi.encode(uint256(keccak256(\"openzeppelin.storage.Ownable\")) - 1)) & ~bytes32(uint256(0xff))\n    bytes32 private constant OwnableStorageLocation = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300;\n\n    function _getOwnableStorage() private pure returns (OwnableStorage storage $) {\n        assembly {\n            $.slot := OwnableStorageLocation\n        }\n    }\n\n    /**\n     * @dev The caller account is not authorized to perform an operation.\n     */\n    error OwnableUnauthorizedAccount(address account);\n\n    /**\n     * @dev The owner is not a valid owner account. (eg. `address(0)`)\n     */\n    error OwnableInvalidOwner(address owner);\n\n    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n    /**\n     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\n     */\n    function __Ownable_init(address initialOwner) internal onlyInitializing {\n        __Ownable_init_unchained(initialOwner);\n    }\n\n    function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {\n        if (initialOwner == address(0)) {\n            revert OwnableInvalidOwner(address(0));\n        }\n        _transferOwnership(initialOwner);\n    }\n\n    /**\n     * @dev Throws if called by any account other than the owner.\n     */\n    modifier onlyOwner() {\n        _checkOwner();\n        _;\n    }\n\n    /**\n     * @dev Returns the address of the current owner.\n     */\n    function owner() public view virtual returns (address) {\n        OwnableStorage storage $ = _getOwnableStorage();\n        return $._owner;\n    }\n\n    /**\n     * @dev Throws if the sender is not the owner.\n     */\n    function _checkOwner() internal view virtual {\n        if (owner() != _msgSender()) {\n            revert OwnableUnauthorizedAccount(_msgSender());\n        }\n    }\n\n    /**\n     * @dev Leaves the contract without owner. It will not be possible to call\n     * `onlyOwner` functions. Can only be called by the current owner.\n     *\n     * NOTE: Renouncing ownership will leave the contract without an owner,\n     * thereby disabling any functionality that is only available to the owner.\n     */\n    function renounceOwnership() public virtual onlyOwner {\n        _transferOwnership(address(0));\n    }\n\n    /**\n     * @dev Transfers ownership of the contract to a new account (`newOwner`).\n     * Can only be called by the current owner.\n     */\n    function transferOwnership(address newOwner) public virtual onlyOwner {\n        if (newOwner == address(0)) {\n            revert OwnableInvalidOwner(address(0));\n        }\n        _transferOwnership(newOwner);\n    }\n\n    /**\n     * @dev Transfers ownership of the contract to a new account (`newOwner`).\n     * Internal function without access restriction.\n     */\n    function _transferOwnership(address newOwner) internal virtual {\n        OwnableStorage storage $ = _getOwnableStorage();\n        address oldOwner = $._owner;\n        $._owner = newOwner;\n        emit OwnershipTransferred(oldOwner, newOwner);\n    }\n}\n"
    },
    "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol": {
      "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.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 reinitialization) 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 Pointer to storage slot. Allows integrators to override it with a custom storage location.\n     *\n     * NOTE: Consider following the ERC-7201 formula to derive storage locations.\n     */\n    function _initializableStorageSlot() internal pure virtual returns (bytes32) {\n        return INITIALIZABLE_STORAGE;\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        bytes32 slot = _initializableStorageSlot();\n        assembly {\n            $.slot := slot\n        }\n    }\n}\n"
    },
    "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\n\npragma solidity ^0.8.20;\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract ContextUpgradeable is Initializable {\n    function __Context_init() internal onlyInitializing {\n    }\n\n    function __Context_init_unchained() internal onlyInitializing {\n    }\n    function _msgSender() internal view virtual returns (address) {\n        return msg.sender;\n    }\n\n    function _msgData() internal view virtual returns (bytes calldata) {\n        return msg.data;\n    }\n\n    function _contextSuffixLength() internal view virtual returns (uint256) {\n        return 0;\n    }\n}\n"
    },
    "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol)\n\npragma solidity ^0.8.20;\n\nimport {ContextUpgradeable} from \"../utils/ContextUpgradeable.sol\";\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module which allows children to implement an emergency stop\n * mechanism that can be triggered by an authorized account.\n *\n * This module is used through inheritance. It will make available the\n * modifiers `whenNotPaused` and `whenPaused`, which can be applied to\n * the functions of your contract. Note that they will not be pausable by\n * simply including this module, only once the modifiers are put in place.\n */\nabstract contract PausableUpgradeable is Initializable, ContextUpgradeable {\n    /// @custom:storage-location erc7201:openzeppelin.storage.Pausable\n    struct PausableStorage {\n        bool _paused;\n    }\n\n    // keccak256(abi.encode(uint256(keccak256(\"openzeppelin.storage.Pausable\")) - 1)) & ~bytes32(uint256(0xff))\n    bytes32 private constant PausableStorageLocation = 0xcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300;\n\n    function _getPausableStorage() private pure returns (PausableStorage storage $) {\n        assembly {\n            $.slot := PausableStorageLocation\n        }\n    }\n\n    /**\n     * @dev Emitted when the pause is triggered by `account`.\n     */\n    event Paused(address account);\n\n    /**\n     * @dev Emitted when the pause is lifted by `account`.\n     */\n    event Unpaused(address account);\n\n    /**\n     * @dev The operation failed because the contract is paused.\n     */\n    error EnforcedPause();\n\n    /**\n     * @dev The operation failed because the contract is not paused.\n     */\n    error ExpectedPause();\n\n    /**\n     * @dev Modifier to make a function callable only when the contract is not paused.\n     *\n     * Requirements:\n     *\n     * - The contract must not be paused.\n     */\n    modifier whenNotPaused() {\n        _requireNotPaused();\n        _;\n    }\n\n    /**\n     * @dev Modifier to make a function callable only when the contract is paused.\n     *\n     * Requirements:\n     *\n     * - The contract must be paused.\n     */\n    modifier whenPaused() {\n        _requirePaused();\n        _;\n    }\n\n    function __Pausable_init() internal onlyInitializing {\n    }\n\n    function __Pausable_init_unchained() internal onlyInitializing {\n    }\n    /**\n     * @dev Returns true if the contract is paused, and false otherwise.\n     */\n    function paused() public view virtual returns (bool) {\n        PausableStorage storage $ = _getPausableStorage();\n        return $._paused;\n    }\n\n    /**\n     * @dev Throws if the contract is paused.\n     */\n    function _requireNotPaused() internal view virtual {\n        if (paused()) {\n            revert EnforcedPause();\n        }\n    }\n\n    /**\n     * @dev Throws if the contract is not paused.\n     */\n    function _requirePaused() internal view virtual {\n        if (!paused()) {\n            revert ExpectedPause();\n        }\n    }\n\n    /**\n     * @dev Triggers stopped state.\n     *\n     * Requirements:\n     *\n     * - The contract must not be paused.\n     */\n    function _pause() internal virtual whenNotPaused {\n        PausableStorage storage $ = _getPausableStorage();\n        $._paused = true;\n        emit Paused(_msgSender());\n    }\n\n    /**\n     * @dev Returns to normal state.\n     *\n     * Requirements:\n     *\n     * - The contract must be paused.\n     */\n    function _unpause() internal virtual whenPaused {\n        PausableStorage storage $ = _getPausableStorage();\n        $._paused = false;\n        emit Unpaused(_msgSender());\n    }\n}\n"
    },
    "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)\n\npragma solidity ^0.8.20;\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module that helps prevent reentrant calls to a function.\n *\n * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier\n * available, which can be applied to functions to make sure there are no nested\n * (reentrant) calls to them.\n *\n * Note that because there is a single `nonReentrant` guard, functions marked as\n * `nonReentrant` may not call one another. This can be worked around by making\n * those functions `private`, and then adding `external` `nonReentrant` entry\n * points to them.\n *\n * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,\n * consider using {ReentrancyGuardTransient} instead.\n *\n * TIP: If you would like to learn more about reentrancy and alternative ways\n * to protect against it, check out our blog post\n * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].\n */\nabstract contract ReentrancyGuardUpgradeable is Initializable {\n    // Booleans are more expensive than uint256 or any type that takes up a full\n    // word because each write operation emits an extra SLOAD to first read the\n    // slot's contents, replace the bits taken up by the boolean, and then write\n    // back. This is the compiler's defense against contract upgrades and\n    // pointer aliasing, and it cannot be disabled.\n\n    // The values being non-zero value makes deployment a bit more expensive,\n    // but in exchange the refund on every call to nonReentrant will be lower in\n    // amount. Since refunds are capped to a percentage of the total\n    // transaction's gas, it is best to keep them low in cases like this one, to\n    // increase the likelihood of the full refund coming into effect.\n    uint256 private constant NOT_ENTERED = 1;\n    uint256 private constant ENTERED = 2;\n\n    /// @custom:storage-location erc7201:openzeppelin.storage.ReentrancyGuard\n    struct ReentrancyGuardStorage {\n        uint256 _status;\n    }\n\n    // keccak256(abi.encode(uint256(keccak256(\"openzeppelin.storage.ReentrancyGuard\")) - 1)) & ~bytes32(uint256(0xff))\n    bytes32 private constant ReentrancyGuardStorageLocation = 0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;\n\n    function _getReentrancyGuardStorage() private pure returns (ReentrancyGuardStorage storage $) {\n        assembly {\n            $.slot := ReentrancyGuardStorageLocation\n        }\n    }\n\n    /**\n     * @dev Unauthorized reentrant call.\n     */\n    error ReentrancyGuardReentrantCall();\n\n    function __ReentrancyGuard_init() internal onlyInitializing {\n        __ReentrancyGuard_init_unchained();\n    }\n\n    function __ReentrancyGuard_init_unchained() internal onlyInitializing {\n        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();\n        $._status = NOT_ENTERED;\n    }\n\n    /**\n     * @dev Prevents a contract from calling itself, directly or indirectly.\n     * Calling a `nonReentrant` function from another `nonReentrant`\n     * function is not supported. It is possible to prevent this from happening\n     * by making the `nonReentrant` function external, and making it call a\n     * `private` function that does the actual work.\n     */\n    modifier nonReentrant() {\n        _nonReentrantBefore();\n        _;\n        _nonReentrantAfter();\n    }\n\n    function _nonReentrantBefore() private {\n        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();\n        // On the first call to nonReentrant, _status will be NOT_ENTERED\n        if ($._status == ENTERED) {\n            revert ReentrancyGuardReentrantCall();\n        }\n\n        // Any calls to nonReentrant after this point will fail\n        $._status = ENTERED;\n    }\n\n    function _nonReentrantAfter() private {\n        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();\n        // By storing the original value once again, a refund is triggered (see\n        // https://eips.ethereum.org/EIPS/eip-2200)\n        $._status = NOT_ENTERED;\n    }\n\n    /**\n     * @dev Returns true if the reentrancy guard is currently set to \"entered\", which indicates there is a\n     * `nonReentrant` function in the call stack.\n     */\n    function _reentrancyGuardEntered() internal view returns (bool) {\n        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();\n        return $._status == ENTERED;\n    }\n}\n"
    },
    "@openzeppelin/contracts/access/Ownable.sol": {
      "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\n\npragma solidity ^0.8.20;\n\nimport {Context} from \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * The initial owner is set to the address provided by the deployer. This can\n * later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n    address private _owner;\n\n    /**\n     * @dev The caller account is not authorized to perform an operation.\n     */\n    error OwnableUnauthorizedAccount(address account);\n\n    /**\n     * @dev The owner is not a valid owner account. (eg. `address(0)`)\n     */\n    error OwnableInvalidOwner(address owner);\n\n    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n    /**\n     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\n     */\n    constructor(address initialOwner) {\n        if (initialOwner == address(0)) {\n            revert OwnableInvalidOwner(address(0));\n        }\n        _transferOwnership(initialOwner);\n    }\n\n    /**\n     * @dev Throws if called by any account other than the owner.\n     */\n    modifier onlyOwner() {\n        _checkOwner();\n        _;\n    }\n\n    /**\n     * @dev Returns the address of the current owner.\n     */\n    function owner() public view virtual returns (address) {\n        return _owner;\n    }\n\n    /**\n     * @dev Throws if the sender is not the owner.\n     */\n    function _checkOwner() internal view virtual {\n        if (owner() != _msgSender()) {\n            revert OwnableUnauthorizedAccount(_msgSender());\n        }\n    }\n\n    /**\n     * @dev Leaves the contract without owner. It will not be possible to call\n     * `onlyOwner` functions. Can only be called by the current owner.\n     *\n     * NOTE: Renouncing ownership will leave the contract without an owner,\n     * thereby disabling any functionality that is only available to the owner.\n     */\n    function renounceOwnership() public virtual onlyOwner {\n        _transferOwnership(address(0));\n    }\n\n    /**\n     * @dev Transfers ownership of the contract to a new account (`newOwner`).\n     * Can only be called by the current owner.\n     */\n    function transferOwnership(address newOwner) public virtual onlyOwner {\n        if (newOwner == address(0)) {\n            revert OwnableInvalidOwner(address(0));\n        }\n        _transferOwnership(newOwner);\n    }\n\n    /**\n     * @dev Transfers ownership of the contract to a new account (`newOwner`).\n     * Internal function without access restriction.\n     */\n    function _transferOwnership(address newOwner) internal virtual {\n        address oldOwner = _owner;\n        _owner = newOwner;\n        emit OwnershipTransferred(oldOwner, newOwner);\n    }\n}\n"
    },
    "@openzeppelin/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)\n\npragma solidity >=0.4.16;\n\n/**\n * @dev Interface of the ERC-20 standard as defined in the ERC.\n */\ninterface IERC20 {\n    /**\n     * @dev Emitted when `value` tokens are moved from one account (`from`) to\n     * another (`to`).\n     *\n     * Note that `value` may be zero.\n     */\n    event Transfer(address indexed from, address indexed to, uint256 value);\n\n    /**\n     * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n     * a call to {approve}. `value` is the new allowance.\n     */\n    event Approval(address indexed owner, address indexed spender, uint256 value);\n\n    /**\n     * @dev Returns the value of tokens in existence.\n     */\n    function totalSupply() external view returns (uint256);\n\n    /**\n     * @dev Returns the value of tokens owned by `account`.\n     */\n    function balanceOf(address account) external view returns (uint256);\n\n    /**\n     * @dev Moves a `value` amount of tokens from the caller's account to `to`.\n     *\n     * Returns a boolean value indicating whether the operation succeeded.\n     *\n     * Emits a {Transfer} event.\n     */\n    function transfer(address to, uint256 value) external returns (bool);\n\n    /**\n     * @dev Returns the remaining number of tokens that `spender` will be\n     * allowed to spend on behalf of `owner` through {transferFrom}. This is\n     * zero by default.\n     *\n     * This value changes when {approve} or {transferFrom} are called.\n     */\n    function allowance(address owner, address spender) external view returns (uint256);\n\n    /**\n     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n     * caller's tokens.\n     *\n     * Returns a boolean value indicating whether the operation succeeded.\n     *\n     * IMPORTANT: Beware that changing an allowance with this method brings the risk\n     * that someone may use both the old and the new allowance by unfortunate\n     * transaction ordering. One possible solution to mitigate this race\n     * condition is to first reduce the spender's allowance to 0 and set the\n     * desired value afterwards:\n     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n     *\n     * Emits an {Approval} event.\n     */\n    function approve(address spender, uint256 value) external returns (bool);\n\n    /**\n     * @dev Moves a `value` amount of tokens from `from` to `to` using the\n     * allowance mechanism. `value` is then deducted from the caller's\n     * allowance.\n     *\n     * Returns a boolean value indicating whether the operation succeeded.\n     *\n     * Emits a {Transfer} event.\n     */\n    function transferFrom(address from, address to, uint256 value) external returns (bool);\n}\n"
    },
    "@openzeppelin/contracts/utils/Context.sol": {
      "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n    function _msgSender() internal view virtual returns (address) {\n        return msg.sender;\n    }\n\n    function _msgData() internal view virtual returns (bytes calldata) {\n        return msg.data;\n    }\n\n    function _contextSuffixLength() internal view virtual returns (uint256) {\n        return 0;\n    }\n}\n"
    },
    "contracts/Marketplace.sol": {
      "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.24;\n\nimport {Initializable} from \"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\";\nimport {Ownable2StepUpgradeable} from \"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol\";\nimport {ReentrancyGuardUpgradeable} from \"@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol\";\nimport {PausableUpgradeable} from \"@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {IMockMobRule} from \"./interfaces/IMockMobRule.sol\";\n\n/**\n * @title Marketplace\n * @notice Decentralized marketplace for digital and physical goods on Asset Hub EVM\n * @dev Upgradeable contract with escrow, encrypted key storage, and multi-asset support\n */\ncontract Marketplace is Initializable, Ownable2StepUpgradeable, ReentrancyGuardUpgradeable, PausableUpgradeable {\n    /// @notice Version of the contract\n    string public constant VERSION = \"1.0.0\";\n\n    /// @notice Dispute period duration (48 hours)\n    uint256 public constant DISPUTE_PERIOD = 48 hours;\n\n    /// @notice Delivery timeout duration (14 days)\n    uint256 public constant DELIVERY_TIMEOUT = 14 days;\n\n    /// @notice Post-delivery dispute window (2 days)\n    uint256 public constant POST_DELIVERY_TIMEOUT = 2 days;\n\n    /// @notice Native asset address (0x0)\n    address public constant NATIVE_ASSET = address(0);\n\n    /**\n     * Note: enums here have corresponding types in packages/types/src/item.ts\n     * keep them in sync\n     */\n\n    enum ItemType {\n        DIGITAL,\n        PHYSICAL\n    }\n\n    enum PurchaseStatus {\n        PENDING,\n        COMPLETED,\n        DISPUTED,\n        REFUNDED,\n        SHIPPED,\n        DELIVERED\n    }\n\n    struct Item {\n        uint256 id;\n        address seller;\n        ItemType itemType;\n        string metadataIPFSHash; // JSON metadata with description, images, etc.\n        uint256 price;\n        address paymentAsset; // address(0) for native token, ERC20 address otherwise\n        bool active;\n        uint256 createdAt;\n        string name; // Item title (max 200 chars)\n        string category; // Lowercase category tag (max 50 chars)\n    }\n\n    struct Purchase {\n        uint256 id;\n        uint256 itemId;\n        address buyer;\n        address seller;\n        uint256 amount;\n        address paymentAsset;\n        uint256 purchaseTime;\n        uint256 releaseTime;\n        PurchaseStatus status;\n        // Shipping fields\n        bytes32 deliveryCode; // Hash of delivery confirmation code (0x0 if not set)\n        uint256 deliveryTimeout; // Block timestamp when delivery should be complete\n        uint256 postDeliveryTimeout; // Block timestamp when post-delivery disputes end\n        bool deliveryConfirmed; // True if buyer confirmed delivery\n        bool disputePaused; // True if dispute pauses seller's ability to claim\n        uint256 shippingTimestamp; // Block timestamp when delivery code was set (0 if not shipped)\n        uint256 deliveryTimestamp; // Block timestamp when delivery was confirmed (0 if not confirmed)\n        // Dispute fields\n        uint256 disputeCaseId; // Case ID from MockMobRule (0 = no dispute, >0 = active dispute)\n        string disputeMetadataCID; // IPFS CID from Bulletin Chain containing initiator's evidence (empty if no dispute)\n        string counterEvidenceCID; // IPFS CID from Bulletin Chain containing counter-evidence from other party (empty if not submitted)\n        address disputeInitiator; // Who raised the dispute (address(0) if no dispute, buyer/seller if dispute exists)\n        bytes encryptedShippingAddress; // Encrypted shipping address (ECDH with seller's public key)\n        string buyerPublicKey; // Buyer's X25519 public key (hex string) for decryption\n        bytes encryptedTrackingInfo; // Encrypted tracking info in format \"CARRIER;TrackingNumber\" (ECDH with buyer's public key)\n    }\n\n    /// @notice Counter for item IDs\n    uint256 private _nextItemId;\n\n    /// @notice Counter for purchase IDs\n    uint256 private _nextPurchaseId;\n\n    /// @notice Mapping of item ID to Item\n    mapping(uint256 => Item) public items;\n\n    /// @notice Mapping of purchase ID to Purchase\n    mapping(uint256 => Purchase) public purchases;\n\n    /// @notice Mapping of item ID => buyer => purchased\n    mapping(uint256 => mapping(address => bool)) public hasPurchased;\n\n    /// @notice Mapping of allowed payment assets\n    mapping(address => bool) public allowedAssets;\n\n    /// @notice Address of MockMobRule contract for dispute resolution\n    IMockMobRule public mockMobRule;\n\n    // Events\n    event ItemCreated(\n        uint256 indexed itemId,\n        address indexed seller,\n        ItemType itemType,\n        string name,\n        string category,\n        uint256 price,\n        address paymentAsset\n    );\n\n    event ItemUpdated(uint256 indexed itemId, string name, string category, string metadataIPFSHash, uint256 price);\n\n    event ItemDeactivated(uint256 indexed itemId);\n\n    event ItemPurchased(\n        uint256 indexed purchaseId,\n        uint256 indexed itemId,\n        address indexed buyer,\n        address seller,\n        uint256 amount\n    );\n\n    event FundsReleased(uint256 indexed purchaseId, address indexed seller, uint256 amount);\n\n    event DisputeRaised(\n        uint256 indexed purchaseId,\n        uint256 indexed caseId,\n        address indexed initiator,\n        string disputeMetadataCID\n    );\n\n    event DisputeEvidenceAdded(uint256 indexed purchaseId, address indexed submitter, string counterEvidenceCID);\n\n    event DeliveryCodeSet(uint256 indexed purchaseId, address indexed seller);\n\n    event TrackingInfoSet(uint256 indexed purchaseId, address indexed seller);\n\n    event DeliveryConfirmed(uint256 indexed purchaseId, address indexed buyer);\n\n    event PaymentAssetAdded(address indexed asset);\n\n    event PaymentAssetRemoved(address indexed asset);\n\n    event MockMobRuleUpdated(address indexed newAddress, address indexed oldAddress);\n\n    struct Shop {\n        address owner;\n        string name;\n        string metadataCID;\n        string publicKey;\n        uint256 registeredAt;\n        bool active;\n    }\n\n    mapping(address => Shop) private shops;\n\n    event ShopRegistered(address indexed owner, string name, string metadataCID, string publicKey);\n    event ShopUpdated(address indexed owner, string name, string metadataCID, string publicKey);\n\n    /// @notice Category tracking for autocomplete/discovery\n    mapping(string => uint256) public categoryUsageCount;\n    string[] private _allCategories;\n    mapping(string => bool) private _categoryExists;\n\n    /// @custom:oz-upgrades-unsafe-allow constructor\n    constructor() {\n        _disableInitializers();\n    }\n\n    /**\n     * @notice Initialize the contract\n     * @param owner_ Initial owner address\n     */\n    function initialize(address owner_) public initializer {\n        __Ownable_init(owner_);\n        __ReentrancyGuard_init();\n        __Pausable_init();\n\n        // Native asset is always allowed\n        allowedAssets[NATIVE_ASSET] = true;\n\n        _nextItemId = 1;\n        _nextPurchaseId = 1;\n    }\n\n    /**\n     * @dev Normalize category string to lowercase for consistent matching.\n     *\n     * ASCII Table Reference:\n     *   - Uppercase letters A-Z have codes 65-90  (0x41-0x5A)\n     *   - Lowercase letters a-z have codes 97-122 (0x61-0x7A)\n     *   - The difference between cases is exactly 32 (0x20)\n     *\n     * Example: 'A' (65) + 32 = 'a' (97)\n     *\n     * This ensures \"Electronics\", \"ELECTRONICS\", and \"electronics\" all map\n     * to \"electronics\" for consistent category filtering and discovery.\n     *\n     * @param category_ Input category string (any case)\n     * @return Lowercase category string\n     */\n    function _normalizeCategory(string memory category_) private pure returns (string memory) {\n        bytes memory categoryBytes = bytes(category_);\n        bytes memory lowerBytes = new bytes(categoryBytes.length);\n\n        for (uint256 i = 0; i < categoryBytes.length; i++) {\n            bytes1 char = categoryBytes[i];\n            // Check if character is uppercase A-Z (ASCII 65-90 / 0x41-0x5A)\n            // If so, add 32 to convert to lowercase a-z (ASCII 97-122 / 0x61-0x7A)\n            if (char >= 0x41 && char <= 0x5A) {\n                lowerBytes[i] = bytes1(uint8(char) + 32);\n            } else {\n                lowerBytes[i] = char;\n            }\n        }\n\n        return string(lowerBytes);\n    }\n\n    /**\n     * @dev Track category usage for discovery\n     * @param category_ Normalized category string\n     */\n    function _trackCategory(string memory category_) private {\n        if (!_categoryExists[category_]) {\n            _allCategories.push(category_);\n            _categoryExists[category_] = true;\n        }\n        categoryUsageCount[category_]++;\n    }\n\n    /**\n     * @notice Create a new item listing\n     * @param itemType_ Type of item (DIGITAL or PHYSICAL)\n     * @param name_ Item title (max 200 characters)\n     * @param category_ Category tag (will be normalized to lowercase, max 50 chars)\n     * @param metadataIPFSHash_ IPFS hash of item metadata JSON\n     * @param price_ Price in payment asset\n     * @param paymentAsset_ Payment asset address (0x0 for native)\n     * @return itemId The ID of the created item\n     */\n    function createItem(\n        ItemType itemType_,\n        string memory name_,\n        string memory category_,\n        string memory metadataIPFSHash_,\n        uint256 price_,\n        address paymentAsset_\n    ) external whenNotPaused returns (uint256) {\n        require(bytes(name_).length > 0 && bytes(name_).length <= 200, \"Name must be 1-200 chars\");\n        require(bytes(category_).length > 0 && bytes(category_).length <= 50, \"Category must be 1-50 chars\");\n        require(this.isShopRegistered(msg.sender), \"Shop not registered\");\n        require(bytes(metadataIPFSHash_).length > 0, \"Invalid metadata hash\");\n        require(price_ > 0, \"Price must be greater than 0\");\n        require(allowedAssets[paymentAsset_], \"Payment asset not allowed\");\n\n        // Normalize category to lowercase\n        string memory normalizedCategory = _normalizeCategory(category_);\n\n        uint256 itemId = _nextItemId++;\n\n        items[itemId] = Item({\n            id: itemId,\n            seller: msg.sender,\n            itemType: itemType_,\n            name: name_,\n            category: normalizedCategory,\n            metadataIPFSHash: metadataIPFSHash_,\n            price: price_,\n            paymentAsset: paymentAsset_,\n            active: true,\n            createdAt: block.timestamp\n        });\n\n        // Track category for discovery\n        _trackCategory(normalizedCategory);\n\n        emit ItemCreated(itemId, msg.sender, itemType_, name_, normalizedCategory, price_, paymentAsset_);\n\n        return itemId;\n    }\n\n    /**\n     * @notice Update an existing item\n     * @param itemId_ Item ID to update\n     * @param name_ New item title\n     * @param category_ New category tag\n     * @param metadataIPFSHash_ New IPFS hash of metadata\n     * @param price_ New price\n     */\n    function updateItem(\n        uint256 itemId_,\n        string memory name_,\n        string memory category_,\n        string memory metadataIPFSHash_,\n        uint256 price_\n    ) external {\n        Item storage item = items[itemId_];\n        require(item.seller == msg.sender, \"Not item owner\");\n        require(item.active, \"Item not active\");\n        require(bytes(name_).length > 0 && bytes(name_).length <= 200, \"Name must be 1-200 chars\");\n        require(bytes(category_).length > 0 && bytes(category_).length <= 50, \"Category must be 1-50 chars\");\n        require(bytes(metadataIPFSHash_).length > 0, \"Invalid metadata hash\");\n        require(price_ > 0, \"Price must be greater than 0\");\n\n        // Normalize new category\n        string memory normalizedCategory = _normalizeCategory(category_);\n\n        // Update category tracking if category changed\n        if (keccak256(bytes(item.category)) != keccak256(bytes(normalizedCategory))) {\n            // Decrement old category count\n            if (categoryUsageCount[item.category] > 0) {\n                categoryUsageCount[item.category]--;\n            }\n            // Track new category\n            _trackCategory(normalizedCategory);\n        }\n\n        item.name = name_;\n        item.category = normalizedCategory;\n        item.metadataIPFSHash = metadataIPFSHash_;\n        item.price = price_;\n\n        emit ItemUpdated(itemId_, name_, normalizedCategory, metadataIPFSHash_, price_);\n    }\n\n    /**\n     * @notice Deactivate an item\n     * @param itemId_ Item ID to deactivate\n     */\n    function deactivateItem(uint256 itemId_) external {\n        Item storage item = items[itemId_];\n        require(item.seller == msg.sender, \"Not item owner\");\n        require(item.active, \"Item already inactive\");\n\n        item.active = false;\n\n        emit ItemDeactivated(itemId_);\n    }\n\n    /**\n     * @notice Purchase an item\n     * @dev Digital goods: payment sent directly to seller (instant delivery)\n     * @dev Physical goods: payment held in escrow until buyer confirms delivery\n     * @param itemId_ Item ID to purchase\n     * @param encryptedShippingAddress_ Encrypted shipping address (only for physical items, empty for digital)\n     * @param buyerPublicKey_ Buyer's X25519 public key (hex string) for decryption (only for physical items, not implemented yet for digital)\n     * @return purchaseId The ID of the purchase\n     */\n    function purchaseItem(\n        uint256 itemId_,\n        bytes calldata encryptedShippingAddress_,\n        string calldata buyerPublicKey_\n    ) external payable nonReentrant whenNotPaused returns (uint256) {\n        Item memory item = items[itemId_];\n        require(item.active, \"Item not active\");\n        require(item.seller != msg.sender, \"Cannot buy own item\");\n\n        // Handle payment\n        if (item.paymentAsset == NATIVE_ASSET) {\n            require(msg.value == item.price, \"Incorrect payment amount\");\n        } else {\n            require(msg.value == 0, \"No native payment needed\");\n            require(\n                IERC20(item.paymentAsset).transferFrom(msg.sender, address(this), item.price),\n                \"Payment transfer failed\"\n            );\n        }\n\n        uint256 purchaseId = _nextPurchaseId++;\n\n        // @TODO update this once digital item encryption is implemented\n        // Digital goods: Temporarily instant payment to seller (no escrow needed), immediately completed\n        // Physical goods: escrow until buyer confirms delivery\n        bool isDigital = item.itemType == ItemType.DIGITAL;\n        PurchaseStatus initialStatus = isDigital ? PurchaseStatus.COMPLETED : PurchaseStatus.PENDING;\n\n        // Validate shipping address: required for physical items, must be empty for digital items\n        if (isDigital) {\n            require(encryptedShippingAddress_.length == 0, \"Shipping address not needed for digital items\");\n            // for future item decryption (not implemented yet) ?\n            // require(bytes(buyerPublicKey_).length > 0, \"Buyer public key required for digital items\");\n        } else {\n            require(encryptedShippingAddress_.length > 0, \"Shipping address required for physical items\");\n            require(bytes(buyerPublicKey_).length > 0, \"Buyer public key required for physical items\");\n        }\n\n        purchases[purchaseId] = Purchase({\n            id: purchaseId,\n            itemId: itemId_,\n            buyer: msg.sender,\n            seller: item.seller,\n            amount: item.price,\n            paymentAsset: item.paymentAsset,\n            purchaseTime: block.timestamp,\n            releaseTime: isDigital ? 0 : block.timestamp + DISPUTE_PERIOD,\n            status: initialStatus,\n            // Initialize shipping fields\n            deliveryCode: bytes32(0),\n            deliveryTimeout: 0,\n            postDeliveryTimeout: 0,\n            deliveryConfirmed: isDigital, // Digital goods are immediately delivered\n            disputePaused: false,\n            shippingTimestamp: 0,\n            deliveryTimestamp: 0,\n            // Initialize dispute fields\n            disputeCaseId: 0,\n            disputeMetadataCID: \"\",\n            counterEvidenceCID: \"\",\n            disputeInitiator: address(0),\n            encryptedShippingAddress: encryptedShippingAddress_, // Store encrypted shipping address\n            buyerPublicKey: buyerPublicKey_, // Store buyer's public key for decryption\n            encryptedTrackingInfo: \"\" // Initialize empty, will be set when seller ships\n        });\n\n        // Record purchase\n        hasPurchased[itemId_][msg.sender] = true;\n\n        emit ItemPurchased(purchaseId, itemId_, msg.sender, item.seller, item.price);\n\n        // For digital goods, send payment immediately to seller\n        if (isDigital) {\n            _transferFunds(item.seller, item.price, item.paymentAsset);\n            emit FundsReleased(purchaseId, item.seller, item.price);\n        }\n\n        // For physical goods, the funds are held in escrow until the buyer confirms delivery\n        // the escrow logic is implicit\n\n        return purchaseId;\n    }\n\n    /**\n     * @notice Release funds to seller after delivery timeout (if buyer didn't confirm)\n     * @param purchaseId_ Purchase ID\n     * @dev Only for physical goods - digital goods release funds immediately on purchase\n     * @dev Can only be called if:\n     *      - Item has been shipped (status SHIPPED)\n     *      - Delivery timeout has passed\n     *      - Buyer hasn't confirmed delivery\n     *      - No active dispute\n     */\n    function releaseFunds(uint256 purchaseId_) external nonReentrant {\n        Purchase storage purchase = purchases[purchaseId_];\n        require(purchase.status == PurchaseStatus.SHIPPED, \"Item not shipped\");\n        require(block.timestamp >= purchase.deliveryTimeout, \"Delivery timeout not reached\");\n        require(!purchase.deliveryConfirmed, \"Delivery already confirmed\");\n        require(!purchase.disputePaused, \"Dispute active - cannot release\");\n\n        purchase.status = PurchaseStatus.COMPLETED;\n\n        // Transfer funds to seller\n        _transferFunds(purchase.seller, purchase.amount, purchase.paymentAsset);\n\n        emit FundsReleased(purchaseId_, purchase.seller, purchase.amount);\n    }\n\n    /**\n     * @notice Get item details\n     * @param itemId_ Item ID\n     * @return Item struct\n     */\n    function getItem(uint256 itemId_) external view returns (Item memory) {\n        return items[itemId_];\n    }\n\n    /**\n     * @notice Get purchase details\n     * @param purchaseId_ Purchase ID\n     * @return Purchase struct\n     */\n    function getPurchase(uint256 purchaseId_) external view returns (Purchase memory) {\n        return purchases[purchaseId_];\n    }\n\n    /**\n     * @notice Seller sets delivery confirmation code after shipping\n     * @param purchaseId_ Purchase ID\n     * @param deliveryCode_ Delivery confirmation code (will be hashed on-chain)\n     * @dev Only seller can call, only for physical goods, only once\n     */\n    function setDeliveryCode(uint256 purchaseId_, string memory deliveryCode_) external {\n        Purchase storage purchase = purchases[purchaseId_];\n        require(purchase.seller == msg.sender, \"Only seller can set code\");\n        require(purchase.status == PurchaseStatus.PENDING, \"Purchase not pending\");\n\n        Item memory item = items[purchase.itemId];\n        require(item.itemType == ItemType.PHYSICAL, \"Only for physical goods\");\n        require(purchase.deliveryCode == bytes32(0), \"Code already set\");\n\n        // Hash the code (keccak256) for storage\n        purchase.deliveryCode = keccak256(abi.encodePacked(deliveryCode_));\n        purchase.status = PurchaseStatus.SHIPPED;\n        purchase.deliveryTimeout = block.timestamp + DELIVERY_TIMEOUT;\n        purchase.shippingTimestamp = block.timestamp;\n\n        emit DeliveryCodeSet(purchaseId_, purchase.seller);\n    }\n\n    /**\n     * @notice Seller sets delivery code and tracking info in one transaction\n     * @param purchaseId_ Purchase ID\n     * @param deliveryCode_ Delivery confirmation code\n     * @param encryptedTrackingInfo_ Encrypted tracking info in format \"CARRIER;TrackingNumber\"\n     * @dev Only seller can call, only for physical goods\n     * @dev This is a convenience function that combines setDeliveryCode and setTrackingInfo\n     */\n    function setDeliveryCodeAndTrackingInfo(\n        uint256 purchaseId_,\n        string memory deliveryCode_,\n        bytes memory encryptedTrackingInfo_\n    ) external {\n        Purchase storage purchase = purchases[purchaseId_];\n        require(purchase.seller == msg.sender, \"Only seller can set code and tracking info\");\n        require(purchase.status == PurchaseStatus.PENDING, \"Purchase not pending\");\n\n        Item memory item = items[purchase.itemId];\n        require(item.itemType == ItemType.PHYSICAL, \"Only for physical goods\");\n        require(purchase.deliveryCode == bytes32(0), \"Code already set\");\n        require(encryptedTrackingInfo_.length > 0, \"Tracking info cannot be empty\");\n        // not sure if we should allow overwriting the tracking info\n        require(purchase.encryptedTrackingInfo.length == 0, \"Tracking info already set\");\n\n        // Hash the code (keccak256) for storage\n        purchase.deliveryCode = keccak256(abi.encodePacked(deliveryCode_));\n        purchase.status = PurchaseStatus.SHIPPED;\n        purchase.deliveryTimeout = block.timestamp + DELIVERY_TIMEOUT;\n        purchase.shippingTimestamp = block.timestamp;\n\n        // Set encrypted tracking info\n        purchase.encryptedTrackingInfo = encryptedTrackingInfo_;\n\n        emit DeliveryCodeSet(purchaseId_, purchase.seller);\n        emit TrackingInfoSet(purchaseId_, purchase.seller);\n    }\n\n    /**\n     * @notice Buyer confirms delivery with confirmation code\n     * @param purchaseId_ Purchase ID\n     * @param deliveryCode_ Delivery confirmation code\n     * @dev Releases funds immediately to seller\n     * @dev Only for physical goods - digital goods release funds immediately on purchase\n     */\n    function confirmDelivery(uint256 purchaseId_, string memory deliveryCode_) external nonReentrant {\n        Purchase storage purchase = purchases[purchaseId_];\n        require(msg.sender == purchase.buyer, \"Only buyer can confirm\");\n        require(purchase.status == PurchaseStatus.SHIPPED, \"Item not shipped\");\n        require(!purchase.deliveryConfirmed, \"Already confirmed\");\n        // can the buyer confirm delivery after the delivery timeout?\n        require(block.timestamp <= purchase.deliveryTimeout, \"Delivery timeout passed\");\n\n        // Verify code matches\n        bytes32 codeHash = keccak256(abi.encodePacked(deliveryCode_));\n        require(purchase.deliveryCode == codeHash, \"Invalid delivery code\");\n\n        purchase.deliveryConfirmed = true;\n        purchase.status = PurchaseStatus.COMPLETED;\n        // Set post-delivery timeout window (2 days after delivery confirmation for disputes)\n        purchase.postDeliveryTimeout = block.timestamp + POST_DELIVERY_TIMEOUT;\n        purchase.deliveryTimestamp = block.timestamp;\n\n        // Release funds\n        _transferFunds(purchase.seller, purchase.amount, purchase.paymentAsset);\n\n        emit DeliveryConfirmed(purchaseId_, purchase.buyer);\n        emit FundsReleased(purchaseId_, purchase.seller, purchase.amount);\n    }\n\n    /**\n     * @notice Set MockMobRule contract address for dispute resolution\n     * @param mockMobRule_ Address of MockMobRule contract\n     * @dev Validates that address is a contract and implements IMockMobRule interface\n     */\n    function setMockMobRule(address mockMobRule_) external onlyOwner {\n        require(mockMobRule_ != address(0), \"Invalid address\");\n        require(mockMobRule_.code.length > 0, \"Not a contract\");\n\n        // Verify interface by calling a view function\n        // This will revert if interface doesn't match\n        IMockMobRule(mockMobRule_).getTotalCases();\n\n        address oldAddress = address(mockMobRule);\n        mockMobRule = IMockMobRule(mockMobRule_);\n        emit MockMobRuleUpdated(mockMobRule_, oldAddress);\n    }\n\n    /**\n     * @notice Raise a dispute for a purchase\n     * @param purchaseId_ The purchase ID to dispute\n     * @param disputeMetadataCID_ IPFS CID from Bulletin Chain containing full dispute metadata\n     * @return caseId The case ID from MockMobRule\n     * @dev Only buyer or seller can raise dispute. First one to call wins.\n     * @dev Purchase must not be finalized (COMPLETED or REFUNDED)\n     * @dev MockMobRule must be set before calling this function\n     */\n    function raiseDispute(\n        uint256 purchaseId_,\n        string memory disputeMetadataCID_\n    ) external nonReentrant returns (uint256 caseId) {\n        // Get purchase\n        Purchase storage purchase = purchases[purchaseId_];\n        require(purchase.id != 0, \"Purchase does not exist\");\n\n        // Check no existing dispute\n        require(purchase.disputeCaseId == 0, \"Dispute already exists\");\n\n        // Check caller is buyer or seller\n        require(msg.sender == purchase.buyer || msg.sender == purchase.seller, \"Not buyer or seller\");\n\n        // Check MockMobRule is set\n        require(address(mockMobRule) != address(0), \"MockMobRule not set\");\n\n        // Verify MockMobRule is still valid (address hasn't changed)\n        // This ensures connection is still valid\n        try mockMobRule.getTotalCases() returns (uint256) {\n            // Connection is valid, continue\n        } catch {\n            revert(\"MockMobRule connection failed\");\n        }\n\n        // Check CID not empty\n        require(bytes(disputeMetadataCID_).length > 0, \"CID required\");\n\n        // Check purchase not finalized\n        require(\n            purchase.status != PurchaseStatus.COMPLETED && purchase.status != PurchaseStatus.REFUNDED,\n            \"Purchase finalized\"\n        );\n\n        // Call MockMobRule.createDispute()\n        caseId = mockMobRule.createDispute(\n            purchaseId_,\n            purchase.buyer,\n            purchase.seller,\n            msg.sender, // initiator\n            disputeMetadataCID_\n        );\n\n        // Update purchase\n        purchase.disputeCaseId = caseId;\n        purchase.disputeMetadataCID = disputeMetadataCID_;\n        purchase.disputeInitiator = msg.sender;\n        purchase.status = PurchaseStatus.DISPUTED;\n\n        // Emit event\n        emit DisputeRaised(purchaseId_, caseId, msg.sender, disputeMetadataCID_);\n\n        return caseId;\n    }\n\n    /**\n     * @notice Add counter-evidence to an existing dispute (for the other party)\n     * @param purchaseId_ The purchase ID with an active dispute\n     * @param counterEvidenceCID_ IPFS CID from Bulletin Chain containing counter-evidence metadata\n     * @dev Only the non-initiator party (buyer or seller) can add counter-evidence\n     * @dev Dispute must exist and not be resolved\n     */\n    function addDisputeEvidence(uint256 purchaseId_, string memory counterEvidenceCID_) external nonReentrant {\n        Purchase storage purchase = purchases[purchaseId_];\n        require(purchase.id != 0, \"Purchase does not exist\");\n        require(purchase.disputeCaseId != 0, \"No active dispute\");\n        require(purchase.status == PurchaseStatus.DISPUTED, \"Dispute not active\");\n        require(bytes(counterEvidenceCID_).length > 0, \"CID required\");\n\n        // Check caller is buyer or seller\n        require(msg.sender == purchase.buyer || msg.sender == purchase.seller, \"Not buyer or seller\");\n\n        // Check caller is NOT the initiator (only the other party can add counter-evidence)\n        require(msg.sender != purchase.disputeInitiator, \"Initiator cannot add counter-evidence\");\n\n        // Check MockMobRule is set\n        require(address(mockMobRule) != address(0), \"MockMobRule not set\");\n\n        // Call MockMobRule.addCounterEvidence() to store counter-evidence\n        mockMobRule.addCounterEvidence(purchase.disputeCaseId, counterEvidenceCID_);\n\n        // Also store in Purchase for quick access (optional, can be removed if not needed)\n        purchase.counterEvidenceCID = counterEvidenceCID_;\n\n        // Emit event\n        emit DisputeEvidenceAdded(purchaseId_, msg.sender, counterEvidenceCID_);\n    }\n\n    /**\n     * @notice Get dispute status for a purchase\n     * @param purchaseId_ The purchase ID\n     * @return hasDispute True if purchase has an active dispute\n     * @return caseId The case ID (0 if no dispute)\n     * @return verdict The verdict (0 = Pending, 1 = InitiatorWins, 2 = InitiatorLoses)\n     * @return isResolved True if dispute has been resolved (verdict != Pending)\n     * @return initiator The address that initiated the dispute\n     * @return winner The address of the winner (address(0) if pending or no dispute)\n     */\n    function getDisputeStatus(\n        uint256 purchaseId_\n    )\n        external\n        view\n        returns (bool hasDispute, uint256 caseId, uint8 verdict, bool isResolved, address initiator, address winner)\n    {\n        Purchase memory purchase = purchases[purchaseId_];\n        require(purchase.id != 0, \"Purchase does not exist\");\n\n        hasDispute = purchase.disputeCaseId != 0;\n\n        if (!hasDispute) {\n            return (false, 0, 0, false, address(0), address(0));\n        }\n\n        caseId = purchase.disputeCaseId;\n        initiator = purchase.disputeInitiator;\n\n        // Query MockMobRule for verdict\n        require(address(mockMobRule) != address(0), \"MockMobRule not set\");\n\n        try mockMobRule.getVerdict(caseId) returns (IMockMobRule.Verdict verdict_) {\n            verdict = uint8(verdict_);\n            isResolved = verdict_ != IMockMobRule.Verdict.Pending;\n\n            // Determine winner based on verdict\n            if (isResolved) {\n                if (verdict_ == IMockMobRule.Verdict.InitiatorWins) {\n                    winner = initiator;\n                } else {\n                    // InitiatorLoses\n                    // Other party wins\n                    winner = (initiator == purchase.buyer) ? purchase.seller : purchase.buyer;\n                }\n            } else {\n                winner = address(0); // Pending, no winner yet\n            }\n        } catch {\n            // If query fails, assume pending\n            verdict = 0; // Pending\n            isResolved = false;\n            winner = address(0);\n        }\n\n        return (hasDispute, caseId, verdict, isResolved, initiator, winner);\n    }\n\n    /**\n     * @notice Claim dispute resolution funds (only winner can call)\n     * @param purchaseId_ The purchase ID\n     * @dev Transfers funds to winner based on verdict:\n     *      - If buyer wins: refund buyer (status = REFUNDED)\n     *      - If seller wins: release to seller (status = COMPLETED)\n     */\n    function claimDisputeResolution(uint256 purchaseId_) external nonReentrant {\n        Purchase storage purchase = purchases[purchaseId_];\n        require(purchase.id != 0, \"Purchase does not exist\");\n        require(purchase.disputeCaseId != 0, \"No dispute\");\n        require(purchase.status == PurchaseStatus.DISPUTED, \"Dispute not active\");\n\n        (bool hasDispute, , , bool isResolved, , address winner) = this.getDisputeStatus(purchaseId_);\n\n        require(hasDispute, \"No dispute\");\n        require(isResolved, \"Dispute not resolved\");\n        require(winner != address(0), \"No winner determined\");\n        require(msg.sender == winner, \"Not winner\");\n\n        if (winner == purchase.buyer) {\n            purchase.status = PurchaseStatus.REFUNDED;\n        } else {\n            purchase.status = PurchaseStatus.COMPLETED;\n        }\n\n        _transferFunds(winner, purchase.amount, purchase.paymentAsset);\n\n        emit FundsReleased(purchaseId_, winner, purchase.amount);\n    }\n\n    /**\n     * @notice Get total number of items created\n     * @return Total item count\n     */\n    function getTotalItems() external view returns (uint256) {\n        return _nextItemId > 0 ? _nextItemId - 1 : 0;\n    }\n\n    /**\n     * @notice Get all active items (paginated)\n     * @param offset_ Starting index\n     * @param limit_ Maximum number of items to return\n     * @return Array of active items\n     */\n    function getActiveItems(uint256 offset_, uint256 limit_) external view returns (Item[] memory) {\n        require(limit_ > 0 && limit_ <= 100, \"Limit must be between 1 and 100\");\n\n        // Count active items\n        uint256 activeCount = 0;\n        for (uint256 i = 1; i < _nextItemId; i++) {\n            if (items[i].active) {\n                activeCount++;\n            }\n        }\n\n        // Calculate result size\n        uint256 start = offset_ < activeCount ? offset_ : activeCount;\n        uint256 end = start + limit_ < activeCount ? start + limit_ : activeCount;\n        uint256 resultSize = end > start ? end - start : 0;\n\n        Item[] memory result = new Item[](resultSize);\n        if (resultSize == 0) return result;\n\n        // Collect active items\n        uint256 currentIndex = 0;\n        uint256 resultIndex = 0;\n\n        for (uint256 i = 1; i < _nextItemId && resultIndex < resultSize; i++) {\n            if (items[i].active) {\n                if (currentIndex >= start) {\n                    result[resultIndex] = items[i];\n                    resultIndex++;\n                }\n                currentIndex++;\n            }\n        }\n\n        return result;\n    }\n\n    /**\n     * @notice Get items by seller (paginated)\n     * @param seller_ Seller address\n     * @param offset_ Starting index\n     * @param limit_ Maximum number of items to return\n     * @return Array of items created by seller\n     */\n    function getItemsBySeller(address seller_, uint256 offset_, uint256 limit_) external view returns (Item[] memory) {\n        require(limit_ > 0 && limit_ <= 100, \"Limit must be between 1 and 100\");\n\n        // Count seller's items\n        uint256 sellerCount = 0;\n        for (uint256 i = 1; i < _nextItemId; i++) {\n            if (items[i].seller == seller_) {\n                sellerCount++;\n            }\n        }\n\n        // Calculate result size\n        uint256 start = offset_ < sellerCount ? offset_ : sellerCount;\n        uint256 end = start + limit_ < sellerCount ? start + limit_ : sellerCount;\n        uint256 resultSize = end > start ? end - start : 0;\n\n        Item[] memory result = new Item[](resultSize);\n        if (resultSize == 0) return result;\n\n        // Collect seller's items\n        uint256 currentIndex = 0;\n        uint256 resultIndex = 0;\n\n        for (uint256 i = 1; i < _nextItemId && resultIndex < resultSize; i++) {\n            if (items[i].seller == seller_) {\n                if (currentIndex >= start) {\n                    result[resultIndex] = items[i];\n                    resultIndex++;\n                }\n                currentIndex++;\n            }\n        }\n\n        return result;\n    }\n\n    /**\n     * @notice Get items by category (paginated)\n     * @param category_ Category to filter by (case-insensitive)\n     * @param offset_ Starting index\n     * @param limit_ Maximum number of items to return\n     * @return Array of items in the specified category\n     */\n    function getItemsByCategory(\n        string memory category_,\n        uint256 offset_,\n        uint256 limit_\n    ) external view returns (Item[] memory) {\n        require(limit_ > 0 && limit_ <= 100, \"Limit must be between 1 and 100\");\n\n        // Normalize category for comparison\n        string memory normalizedCategory = _normalizeCategory(category_);\n\n        // Count items in category\n        uint256 categoryCount = 0;\n        for (uint256 i = 1; i < _nextItemId; i++) {\n            if (items[i].active && keccak256(bytes(items[i].category)) == keccak256(bytes(normalizedCategory))) {\n                categoryCount++;\n            }\n        }\n\n        // Calculate result size\n        uint256 start = offset_ < categoryCount ? offset_ : categoryCount;\n        uint256 end = start + limit_ < categoryCount ? start + limit_ : categoryCount;\n        uint256 resultSize = end > start ? end - start : 0;\n\n        Item[] memory result = new Item[](resultSize);\n        if (resultSize == 0) return result;\n\n        // Collect items in category\n        uint256 currentIndex = 0;\n        uint256 resultIndex = 0;\n\n        for (uint256 i = 1; i < _nextItemId && resultIndex < resultSize; i++) {\n            if (items[i].active && keccak256(bytes(items[i].category)) == keccak256(bytes(normalizedCategory))) {\n                if (currentIndex >= start) {\n                    result[resultIndex] = items[i];\n                    resultIndex++;\n                }\n                currentIndex++;\n            }\n        }\n\n        return result;\n    }\n\n    /**\n     * @notice Get all categories that have been used\n     * @return Array of all category strings\n     */\n    function getAllCategories() external view returns (string[] memory) {\n        return _allCategories;\n    }\n\n    /**\n     * @notice Get popular categories (with minimum usage threshold)\n     * @param minUsage_ Minimum number of items in category\n     * @return Array of popular category strings\n     */\n    function getPopularCategories(uint256 minUsage_) external view returns (string[] memory) {\n        // Count categories that meet threshold\n        uint256 popularCount = 0;\n        for (uint256 i = 0; i < _allCategories.length; i++) {\n            if (categoryUsageCount[_allCategories[i]] >= minUsage_) {\n                popularCount++;\n            }\n        }\n\n        // Collect popular categories\n        string[] memory result = new string[](popularCount);\n        uint256 resultIndex = 0;\n\n        for (uint256 i = 0; i < _allCategories.length; i++) {\n            if (categoryUsageCount[_allCategories[i]] >= minUsage_) {\n                result[resultIndex] = _allCategories[i];\n                resultIndex++;\n            }\n        }\n\n        return result;\n    }\n\n    /**\n     * @notice Add a payment asset\n     * @param asset_ Asset address to add\n     */\n    function addPaymentAsset(address asset_) external onlyOwner {\n        require(!allowedAssets[asset_], \"Asset already allowed\");\n        allowedAssets[asset_] = true;\n        emit PaymentAssetAdded(asset_);\n    }\n\n    /**\n     * @notice Remove a payment asset\n     * @param asset_ Asset address to remove\n     */\n    function removePaymentAsset(address asset_) external onlyOwner {\n        require(asset_ != NATIVE_ASSET, \"Cannot remove native asset\");\n        require(allowedAssets[asset_], \"Asset not allowed\");\n        allowedAssets[asset_] = false;\n        emit PaymentAssetRemoved(asset_);\n    }\n\n    /**\n     * @notice Pause the contract (emergency)\n     */\n    function pause() external onlyOwner {\n        _pause();\n    }\n\n    /**\n     * @notice Unpause the contract\n     */\n    function unpause() external onlyOwner {\n        _unpause();\n    }\n\n    /**\n     * @dev Internal function to transfer funds\n     */\n    function _transferFunds(address to_, uint256 amount_, address asset_) private {\n        if (asset_ == NATIVE_ASSET) {\n            (bool success, ) = payable(to_).call{value: amount_}(\"\");\n            require(success, \"Transfer failed\");\n        } else {\n            require(IERC20(asset_).transfer(to_, amount_), \"Transfer failed\");\n        }\n    }\n\n    /**\n     * @notice Register or update a shop for the caller\n     * @param name_ Human readable shop name\n     * @param metadataCID_ CID that points to the shop metadata JSON (logo, description, links, etc.)\n     * @param publicKey_ X25519 public encryption key for ECDH (hex string)\n     */\n    function registerShop(string calldata name_, string calldata metadataCID_, string calldata publicKey_) external {\n        require(bytes(name_).length > 0, \"Name required\");\n        require(bytes(metadataCID_).length > 0, \"Invalid metadata CID\");\n        require(bytes(publicKey_).length > 0, \"Public key required\");\n\n        Shop storage shop = shops[msg.sender];\n        bool isNewShop = shop.owner == address(0) || !shop.active;\n\n        shop.owner = msg.sender;\n        shop.name = name_;\n        shop.metadataCID = metadataCID_;\n        shop.publicKey = publicKey_;\n        shop.registeredAt = block.timestamp;\n        shop.active = true;\n\n        if (isNewShop) {\n            emit ShopRegistered(msg.sender, name_, metadataCID_, publicKey_);\n        } else {\n            emit ShopUpdated(msg.sender, name_, metadataCID_, publicKey_);\n        }\n    }\n\n    /**\n     * @notice Returns the shop struct for an owner\n     */\n    function getShop(address owner_) external view returns (Shop memory) {\n        return shops[owner_];\n    }\n\n    /**\n     * @notice Returns true if the owner has an active shop\n     */\n    function isShopRegistered(address owner_) external view returns (bool) {\n        Shop memory shop = shops[owner_];\n        return shop.owner != address(0) && shop.active;\n    }\n}\n"
    },
    "contracts/MockMobRule.sol": {
      "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.24;\n\nimport {Ownable} from \"@openzeppelin/contracts/access/Ownable.sol\";\n\n/**\n * @title MockMobRule\n * @notice Mock dispute resolution contract simulating MobRule pallet for Phase 2\n * @dev Admin-resolved disputes - will be replaced with real MobRule in Phase 3\n * @author mark3t Team\n */\ncontract MockMobRule is Ownable {\n    /// @notice Verdict types\n    /// @dev Pending = not yet resolved, InitiatorWins = initiator's claim is valid, InitiatorLoses = initiator's claim rejected\n    enum Verdict {\n        Pending,\n        InitiatorWins,\n        InitiatorLoses\n    }\n\n    /// @notice Dispute case structure\n    struct Case {\n        uint256 caseId;\n        uint256 purchaseId;\n        address buyer;\n        address seller;\n        address initiator; // Who raised the dispute (buyer or seller)\n        string disputeMetadataCID; // IPFS CID from Bulletin Chain containing full dispute metadata (title, description, evidence)\n        string counterEvidenceCID; // IPFS CID from Bulletin Chain containing counter-evidence from the other party (empty if not submitted)\n        Verdict verdict; // Pending until resolved\n        uint256 createdAt;\n    }\n\n    /// @notice Counter for case IDs\n    uint256 private _nextCaseId;\n\n    /// @notice Mapping of case ID to Case\n    mapping(uint256 => Case) public cases;\n\n    /// @notice Mapping of purchase ID to case ID (one dispute per purchase)\n    mapping(uint256 => uint256) public purchaseToCase;\n\n    /// @notice Event emitted when a new dispute case is created\n    /// @param caseId The case ID\n    /// @param purchaseId The purchase ID\n    /// @param buyer Buyer address\n    /// @param seller Seller address\n    /// @param initiator Who raised the dispute (buyer or seller)\n    /// @param disputeMetadataCID IPFS CID from Bulletin Chain containing full dispute metadata\n    event CaseCreated(\n        uint256 indexed caseId,\n        uint256 indexed purchaseId,\n        address indexed buyer,\n        address seller,\n        address initiator,\n        string disputeMetadataCID\n    );\n\n    /// @notice Event emitted when counter-evidence is added to a case\n    /// @param caseId The case ID\n    /// @param submitter The address that submitted counter-evidence\n    /// @param counterEvidenceCID IPFS CID from Bulletin Chain containing counter-evidence metadata\n    event CounterEvidenceAdded(uint256 indexed caseId, address indexed submitter, string counterEvidenceCID);\n\n    /// @notice Event emitted when a case is resolved\n    /// @param caseId The case ID\n    /// @param verdict The verdict (1 = InitiatorWins, 2 = InitiatorLoses)\n    /// @dev Admin dashboard can listen to this event for instant updates\n    event CaseResolved(uint256 indexed caseId, Verdict indexed verdict);\n\n    /**\n     * @notice Initialize MockMobRule contract\n     * @param initialOwner Address that will own the contract (admin)\n     */\n    constructor(address initialOwner) Ownable(initialOwner) {\n        _nextCaseId = 1;\n    }\n\n    /**\n     * @notice Create a new dispute case\n     * @param purchaseId_ The purchase ID from Marketplace\n     * @param buyer_ Buyer address\n     * @param seller_ Seller address\n     * @param initiator_ Who is raising the dispute (must be either buyer_ or seller_)\n     * @param disputeMetadataCID_ IPFS CID from Bulletin Chain containing full dispute metadata (title, description, evidence)\n     * @return caseId The ID of the created case\n     */\n    function createDispute(\n        uint256 purchaseId_,\n        address buyer_,\n        address seller_,\n        address initiator_,\n        string memory disputeMetadataCID_\n    ) external returns (uint256) {\n        require(buyer_ != address(0), \"Invalid buyer\");\n        require(seller_ != address(0), \"Invalid seller\");\n        require(initiator_ != address(0), \"Invalid initiator\");\n        require(initiator_ == buyer_ || initiator_ == seller_, \"Initiator must be buyer or seller\");\n        require(bytes(disputeMetadataCID_).length > 0, \"CID required\");\n        require(purchaseToCase[purchaseId_] == 0, \"Dispute exists\");\n\n        uint256 caseId = _nextCaseId;\n        ++_nextCaseId;\n        cases[caseId] = Case({\n            caseId: caseId,\n            purchaseId: purchaseId_,\n            buyer: buyer_,\n            seller: seller_,\n            initiator: initiator_,\n            disputeMetadataCID: disputeMetadataCID_,\n            counterEvidenceCID: \"\",\n            verdict: Verdict.Pending,\n            createdAt: block.timestamp\n        });\n\n        purchaseToCase[purchaseId_] = caseId;\n\n        emit CaseCreated(caseId, purchaseId_, buyer_, seller_, initiator_, disputeMetadataCID_);\n\n        return caseId;\n    }\n\n    /**\n     * @notice Add counter-evidence to an existing dispute case\n     * @param caseId_ The case ID\n     * @param counterEvidenceCID_ IPFS CID from Bulletin Chain containing counter-evidence metadata\n     * @dev Only the non-initiator party (buyer or seller) can add counter-evidence\n     * @dev Can be called by Marketplace contract on behalf of users\n     */\n    function addCounterEvidence(uint256 caseId_, string memory counterEvidenceCID_) external {\n        Case storage case_ = cases[caseId_];\n        require(case_.caseId != 0, \"Case does not exist\");\n        require(case_.verdict == Verdict.Pending, \"Case already resolved\");\n        require(bytes(counterEvidenceCID_).length > 0, \"CID required\");\n        require(bytes(case_.counterEvidenceCID).length == 0, \"Counter-evidence already submitted\");\n\n        // Note: Marketplace contract verifies caller is buyer/seller and not initiator\n        // We trust Marketplace's verification here\n\n        case_.counterEvidenceCID = counterEvidenceCID_;\n\n        // Emit event with Marketplace's msg.sender (the actual user)\n        // Marketplace will pass the user address in the event\n        emit CounterEvidenceAdded(caseId_, msg.sender, counterEvidenceCID_);\n    }\n\n    /**\n     * @notice Resolve a dispute case (admin only)\n     * @param caseId_ The case ID to resolve\n     * @param verdict_ The verdict (1 = InitiatorWins, 2 = InitiatorLoses)\n     * @dev Cannot set verdict back to Pending\n     */\n    function resolveCase(uint256 caseId_, Verdict verdict_) external onlyOwner {\n        Case storage case_ = cases[caseId_];\n        require(case_.caseId != 0, \"Case does not exist\");\n        require(case_.verdict == Verdict.Pending, \"Case already resolved\");\n        require(verdict_ != Verdict.Pending, \"Cannot set Pending\");\n\n        case_.verdict = verdict_;\n\n        emit CaseResolved(caseId_, verdict_);\n    }\n\n    /**\n     * @notice Get verdict for a case\n     * @param caseId_ The case ID\n     * @return verdict The verdict (0 = Pending, 1 = InitiatorWins, 2 = InitiatorLoses)\n     * @dev Frontend polls this function to check dispute status\n     * @dev If verdict == Pending, case is still being resolved\n     * @dev If verdict == InitiatorWins, initiator can claim funds\n     * @dev If verdict == InitiatorLoses, non-initiator can claim funds\n     */\n    function getVerdict(uint256 caseId_) external view returns (Verdict verdict) {\n        Case memory case_ = cases[caseId_];\n        require(case_.caseId != 0, \"Case does not exist\");\n\n        return case_.verdict;\n    }\n\n    /**\n     * @notice Get full case information\n     * @param caseId_ The case ID\n     * @return case_ The full Case struct\n     */\n    function getCase(uint256 caseId_) external view returns (Case memory) {\n        Case memory case_ = cases[caseId_];\n        require(case_.caseId != 0, \"Case does not exist\");\n        return case_;\n    }\n\n    /**\n     * @notice Get case ID for a purchase\n     * @param purchaseId_ The purchase ID\n     * @return caseId The case ID (0 if no dispute exists)\n     */\n    function getCaseIdByPurchase(uint256 purchaseId_) external view returns (uint256) {\n        return purchaseToCase[purchaseId_];\n    }\n\n    /**\n     * @notice Get total number of cases\n     * @return total The total number of cases created\n     */\n    function getTotalCases() external view returns (uint256) {\n        return _nextCaseId > 0 ? _nextCaseId - 1 : 0;\n    }\n}\n"
    },
    "contracts/interfaces/IMockMobRule.sol": {
      "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.24;\n\n/**\n * @title IMockMobRule\n * @notice Interface for MockMobRule contract\n * @dev Used by Marketplace to interact with MockMobRule without direct dependency\n */\ninterface IMockMobRule {\n    /// @notice Verdict types\n    enum Verdict {\n        Pending,\n        InitiatorWins,\n        InitiatorLoses\n    }\n\n    /// @notice Dispute case structure\n    struct Case {\n        uint256 caseId;\n        uint256 purchaseId;\n        address buyer;\n        address seller;\n        address initiator;\n        string disputeMetadataCID;\n        string counterEvidenceCID;\n        Verdict verdict;\n        uint256 createdAt;\n    }\n\n    /**\n     * @notice Create a new dispute case\n     * @param purchaseId_ The purchase ID from Marketplace\n     * @param buyer_ Buyer address\n     * @param seller_ Seller address\n     * @param initiator_ Who is raising the dispute (must be either buyer_ or seller_)\n     * @param disputeMetadataCID_ IPFS CID from Bulletin Chain containing full dispute metadata\n     * @return caseId The ID of the created case\n     */\n    function createDispute(\n        uint256 purchaseId_,\n        address buyer_,\n        address seller_,\n        address initiator_,\n        string memory disputeMetadataCID_\n    ) external returns (uint256);\n\n    /**\n     * @notice Add counter-evidence to an existing dispute case\n     * @param caseId_ The case ID\n     * @param counterEvidenceCID_ IPFS CID from Bulletin Chain containing counter-evidence metadata\n     */\n    function addCounterEvidence(uint256 caseId_, string memory counterEvidenceCID_) external;\n\n    /**\n     * @notice Resolve a dispute case (admin only)\n     * @param caseId_ The case ID to resolve\n     * @param verdict_ The verdict (1 = InitiatorWins, 2 = InitiatorLoses)\n     */\n    function resolveCase(uint256 caseId_, Verdict verdict_) external;\n\n    /**\n     * @notice Get verdict for a case\n     * @param caseId_ The case ID\n     * @return verdict The verdict (0 = Pending, 1 = InitiatorWins, 2 = InitiatorLoses)\n     */\n    function getVerdict(uint256 caseId_) external view returns (Verdict verdict);\n\n    /**\n     * @notice Get full case information\n     * @param caseId_ The case ID\n     * @return case_ The full Case struct\n     */\n    function getCase(uint256 caseId_) external view returns (Case memory);\n\n    /**\n     * @notice Get case ID for a purchase\n     * @param purchaseId_ The purchase ID\n     * @return caseId The case ID (0 if no dispute exists)\n     */\n    function getCaseIdByPurchase(uint256 purchaseId_) external view returns (uint256);\n\n    /**\n     * @notice Get total number of cases\n     * @return total The total number of cases created\n     */\n    function getTotalCases() external view returns (uint256);\n}\n"
    }
  },
  "settings": {
    "outputSelection": {
      "*": {
        "": [
          "ast"
        ],
        "*": [
          "evm.bytecode",
          "abi",
          "evm.methodIdentifiers",
          "evm.deployedBytecode",
          "storageLayout",
          "metadata"
        ]
      }
    },
    "optimizer": {
      "enabled": true,
      "details": {}
    },
    "metadata": {}
  }
}