ETH Price: $2,079.30 (+1.30%)
 

Overview

ETH Balance

162 wei

Eth Value

Less Than $0.01 (@ $2,079.30/ETH)

Token Holdings

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Auto Fallback Or...244598292026-02-15 4:31:5917 mins ago1771129919IN
0xcf7D049C...dA75a5932
0 ETH0.000010170.03351052
Auto Fallback Or...244597192026-02-15 4:09:5939 mins ago1771128599IN
0xcf7D049C...dA75a5932
0 ETH0.000009920.03065886
Create Order244596172026-02-15 3:49:231 hr ago1771127363IN
0xcf7D049C...dA75a5932
0.0001 ETH0.000009230.02830404
Create Order244595222026-02-15 3:30:231 hr ago1771126223IN
0xcf7D049C...dA75a5932
0.0088 ETH0.000011730.03630176
Fulfill Own Orde...244565942026-02-14 17:41:5911 hrs ago1771090919IN
0xcf7D049C...dA75a5932
0 ETH0.000337591.05425082
Fulfill Own Orde...244565582026-02-14 17:34:4711 hrs ago1771090487IN
0xcf7D049C...dA75a5932
0 ETH0.000320641.05667008
Fulfill Orders W...244565222026-02-14 17:27:3511 hrs ago1771090055IN
0xcf7D049C...dA75a5932
0 ETH0.000021120.05901593
Create Order244565192026-02-14 17:26:5911 hrs ago1771090019IN
0xcf7D049C...dA75a5932
0 ETH0.000338391.05912679
Create Order244564572026-02-14 17:14:3511 hrs ago1771089275IN
0xcf7D049C...dA75a5932
0.005 ETH0.000340881.05444199
Fulfill Orders W...244543032026-02-14 10:02:3518 hrs ago1771063355IN
0xcf7D049C...dA75a5932
0 ETH0.00000350.05743032
Cancel Order244542972026-02-14 10:01:2318 hrs ago1771063283IN
0xcf7D049C...dA75a5932
0 ETH0.000009450.13005298
Create Order244542922026-02-14 10:00:2318 hrs ago1771063223IN
0xcf7D049C...dA75a5932
0.00047186 ETH0.000043090.13326224
Fulfill Orders W...244542752026-02-14 9:56:5918 hrs ago1771063019IN
0xcf7D049C...dA75a5932
0 ETH0.00002460.05649992
Create Order244542742026-02-14 9:56:4718 hrs ago1771063007IN
0xcf7D049C...dA75a5932
0.05152108 ETH0.000043410.13423633
Fulfill Orders W...244537102026-02-14 8:03:4720 hrs ago1771056227IN
0xcf7D049C...dA75a5932
0 ETH0.000017280.04522361
Create Order244537092026-02-14 8:03:3520 hrs ago1771056215IN
0xcf7D049C...dA75a5932
0 ETH0.000327341.04019954
Fulfill Orders W...244535402026-02-14 7:29:4721 hrs ago1771054187IN
0xcf7D049C...dA75a5932
0 ETH0.000015780.04131122
Create Order244535372026-02-14 7:29:1121 hrs ago1771054151IN
0xcf7D049C...dA75a5932
0 ETH0.000328071.04256551
Fulfill Orders W...244522802026-02-14 3:16:3525 hrs ago1771038995IN
0xcf7D049C...dA75a5932
0 ETH0.000018320.04380811
Create Order244522762026-02-14 3:15:4725 hrs ago1771038947IN
0xcf7D049C...dA75a5932
0.01926258 ETH0.000081580.25021804
Fulfill Orders W...244517822026-02-14 1:36:3527 hrs ago1771032995IN
0xcf7D049C...dA75a5932
0 ETH0.000018660.04462218
Create Order244517812026-02-14 1:36:2327 hrs ago1771032983IN
0xcf7D049C...dA75a5932
0.01035288 ETH0.000338331.04619937
Fulfill Orders W...244507562026-02-13 22:10:4730 hrs ago1771020647IN
0xcf7D049C...dA75a5932
0 ETH0.000021890.05233232
Create Order244507552026-02-13 22:10:3530 hrs ago1771020635IN
0xcf7D049C...dA75a5932
0.41986979 ETH0.000041670.12888415
Fulfill Orders W...244503372026-02-13 20:46:3532 hrs ago1771015595IN
0xcf7D049C...dA75a5932
0 ETH0.000019720.05111867
View all transactions

Latest 25 internal transactions (View All)

Advanced mode:
Parent Transaction Hash Method Block
From
To
Execute Trade244598292026-02-15 4:31:5917 mins ago1771129919
0xcf7D049C...dA75a5932
0.00871199 ETH
Execute Trade244597192026-02-15 4:09:5939 mins ago1771128599
0xcf7D049C...dA75a5932
0.00009899 ETH
Transfer244596172026-02-15 3:49:231 hr ago1771127363
0xcf7D049C...dA75a5932
0.000001 ETH
Transfer244595222026-02-15 3:30:231 hr ago1771126223
0xcf7D049C...dA75a5932
0.000088 ETH
Transfer244565942026-02-14 17:41:5911 hrs ago1771090919
0xcf7D049C...dA75a5932
0.17097473 ETH
Collect Eth Fee244565942026-02-14 17:41:5911 hrs ago1771090919
0xcf7D049C...dA75a5932
0.00172701 ETH
Transfer244565942026-02-14 17:41:5911 hrs ago1771090919
0xcf7D049C...dA75a5932
0.17270175 ETH
Execute Trade244565582026-02-14 17:34:4711 hrs ago1771090487
0xcf7D049C...dA75a5932
0.0049456 ETH
Transfer244565222026-02-14 17:27:3511 hrs ago1771090055
0xcf7D049C...dA75a5932
0.28201819 ETH
Distribute Marke...244565222026-02-14 17:27:3511 hrs ago1771090055
0xcf7D049C...dA75a5932
0.00284866 ETH
Transfer244565222026-02-14 17:27:3511 hrs ago1771090055
0xcf7D049C...dA75a5932
0.28486686 ETH
Transfer244564572026-02-14 17:14:3511 hrs ago1771089275
0xcf7D049C...dA75a5932
0.00005439 ETH
Transfer244542972026-02-14 10:01:2318 hrs ago1771063283
0xcf7D049C...dA75a5932
0.00046714 ETH
Transfer244542922026-02-14 10:00:2318 hrs ago1771063223
0xcf7D049C...dA75a5932
0.00000471 ETH
Execute Buy Orde...244542752026-02-14 9:56:5918 hrs ago1771063019
0xcf7D049C...dA75a5932
0.05100581 ETH
Transfer244542742026-02-14 9:56:4718 hrs ago1771063007
0xcf7D049C...dA75a5932
0.00051526 ETH
Transfer244537102026-02-14 8:03:4720 hrs ago1771056227
0xcf7D049C...dA75a5932
0.46438594 ETH
Distribute Marke...244537102026-02-14 8:03:4720 hrs ago1771056227
0xcf7D049C...dA75a5932
0.00469076 ETH
Transfer244537102026-02-14 8:03:4720 hrs ago1771056227
0xcf7D049C...dA75a5932
0.4690767 ETH
Transfer244535402026-02-14 7:29:4721 hrs ago1771054187
0xcf7D049C...dA75a5932
0.17620129 ETH
Distribute Marke...244535402026-02-14 7:29:4721 hrs ago1771054187
0xcf7D049C...dA75a5932
0.00177981 ETH
Transfer244535402026-02-14 7:29:4721 hrs ago1771054187
0xcf7D049C...dA75a5932
0.1779811 ETH
Execute Buy Orde...244522802026-02-14 3:16:3525 hrs ago1771038995
0xcf7D049C...dA75a5932
0.01906997 ETH
Transfer244522762026-02-14 3:15:4725 hrs ago1771038947
0xcf7D049C...dA75a5932
0.0001926 ETH
Execute Buy Orde...244517822026-02-14 1:36:3527 hrs ago1771032995
0xcf7D049C...dA75a5932
0.01024936 ETH
View All Internal Transactions
Loading...
Loading
Loading...
Loading
Cross-Chain Transactions

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
GradientOrderbook

Compiler Version
v0.8.26+commit.8a97fa7a

Optimization Enabled:
Yes with 1 runs

Other Settings:
paris EvmVersion
File 1 of 29 : GradientOrderbook.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IGradientRegistry} from "./interfaces/IGradientRegistry.sol";
import {IGradientMarketMakerPoolV3} from "./interfaces/IGradientMarketMakerPoolV3.sol";
import {IGradientFeeManager} from "./interfaces/IGradientFeeManager.sol";
import {IUniswapV2Router02} from "./interfaces/IUniswapV2Router.sol";
import {IFallbackExecutor} from "./interfaces/IFallbackExecutor.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {IUniswapV2Factory} from "./interfaces/IUniswapV2Factory.sol";
import {IUniswapV2Pair} from "./interfaces/IUniswapV2Pair.sol";
import {IUniswapV3Factory} from "./interfaces/IUniswapV3Factory.sol";
import {IUniswapV3Pool} from "./interfaces/IUniswapV3Pool.sol";
import {IUniswapV3PriceHelper} from "./interfaces/IUniswapV3PriceHelper.sol";
import {GradientMarketMakerFactory} from "./GradientMarketMakerFactory.sol";

/**
 * @title GradientOrderbook
 * @author Gradient Protocol
 * @notice A decentralized orderbook for trading ERC20 tokens against ETH
 * @dev This contract implements a traditional orderbook with limit and market orders.
 */
contract GradientOrderbook is Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20;

    /// @notice Registry contract for accessing other protocol contracts
    IGradientRegistry public gradientRegistry;

    /// @notice Fee manager contract for handling fee distribution
    IGradientFeeManager public feeManager;

    /// @notice Types of orders that can be placed
    enum OrderType {
        Buy,
        Sell
    }

    /// @notice Types of order execution
    enum OrderExecutionType {
        Limit,
        Market
    }

    /// @notice Possible states of an order
    enum OrderStatus {
        Active,
        Filled,
        Cancelled,
        Expired
    }

    /// @notice Structure containing all information about an order
    /// @dev All amounts use the decimal precision of their respective tokens
    struct Order {
        uint256 orderId; // Unique identifier for the order
        address owner; // Address that created the order
        OrderType orderType; // Whether this is a buy or sell order
        OrderExecutionType executionType; // Whether this is a limit or market order
        address token; // Token being traded
        uint256 amount; // Total amount of tokens to trade
        uint256 price; // For limit orders: exact price, For market orders: max price (buy) or min price (sell)
        uint256 ethAmount; // Amount of ETH committed for buy orders
        uint256 ethSpent; // Actual ETH spent so far (for buy market orders)
        uint256 filledAmount; // Amount of tokens that have been filled
        uint256 expirationTime; // Timestamp when the order expires
        OrderStatus status; // Current status of the order
    }

    /// @notice Parameters for matching orders
    struct OrderMatch {
        uint256 buyOrderId; // ID of the buy order
        uint256 sellOrderId; // ID of the sell order
        uint256 fillAmount; // Amount of tokens to exchange
    }

    /// @notice Counter for generating unique order IDs
    uint256 private _orderIdCounter;

    /// @notice Default fee percentage charged on all trades (in basis points, 1 = 0.01%)
    uint256 public defaultFeePercentage;

    /// @notice Token-specific fee percentages (in basis points, 1 = 0.01%)
    /// @dev Default is 100 basis points (1%) for all tokens
    mapping(address => uint256) public tokenSpecificFeePercentage;

    /// @notice Maximum fee percentage that can be set (in basis points)
    uint256 public constant MAX_FEE_PERCENTAGE = 500; // 5%

    /// @notice Minimum fee percentage that can be set (in basis points)
    uint256 public constant MIN_FEE_PERCENTAGE = 50; // 0.5%

    /// @notice Maximum token-specific fee percentage (in basis points)
    uint256 public constant MAX_TOKEN_SPECIFIC_FEE_PERCENTAGE = 300; // 3%

    /// @notice Mapping from order ID to Order struct
    mapping(uint256 => Order) public orders;

    /// @notice Mapping from token pair + order type + execution type hash to array of order IDs
    /// @dev Key is keccak256(abi.encodePacked(token, orderType, executionType))
    mapping(bytes32 => uint256) public totalOrderCount;
    mapping(bytes32 => uint256) public headOrder;
    mapping(bytes32 => uint256) public tailOrder;
    struct LinkedOrder {
        uint256 prev;
        uint256 next;
        bool exists;
    }
    mapping(bytes32 => mapping(uint256 => LinkedOrder)) public linkedOrders;

    /// @notice Mapping from order ID to its position in the queue
    /// @dev Used for efficient removal of orders from queues
    mapping(uint256 => uint256) private orderQueuePositions;

    /// @notice Divisor used for fee calculations (10000 = 100%)
    uint256 public constant DIVISOR = 10000;

    uint256 public minOrderSize;
    uint256 public maxOrderSize;
    uint256 public maxOrderTtl;

    /// @notice Maximum allowed price deviation from market price (in basis points, 1 = 0.01%)
    uint256 public maxPriceDeviation = 500; // 5% default

    /// @notice Dust tolerance for automatic order fulfillment (in basis points, 1 = 0.01%)
    uint256 public dustTolerance = 100; // 1% default

    /// @notice Uniswap V3 Factory address for accessing V3 pools
    address public uniswapV3Factory;

    /// @notice Uniswap V3 Price Helper contract for price calculations
    IUniswapV3PriceHelper public uniswapV3PriceHelper;

    /// @notice Common fee tiers for Uniswap V3 (500 = 0.05%, 3000 = 0.3%, 10000 = 1%)
    uint24[] public v3FeeTiers = [500, 3000, 10000];

    /// @notice Emitted when a new order is created
    event OrderCreated(
        uint256 indexed orderId,
        address indexed owner,
        OrderType orderType,
        OrderExecutionType executionType,
        address token,
        uint256 amount,
        uint256 price,
        uint256 expirationTime,
        uint256 totalCost,
        string objectId,
        bool isAutofallback
    );

    /// @notice Emitted when an order is cancelled by its owner
    event OrderCancelled(uint256 indexed orderId);

    /// @notice Emitted when an order expires
    event OrderExpired(uint256 indexed orderId);

    /// @notice Emitted when an order is completely filled
    event OrderFulfilled(
        uint256 indexed orderId,
        uint256 amount,
        uint256 totalFilledAmount,
        uint256 executionPrice
    );

    /// @notice Emitted when an order is partially filled
    event OrderPartiallyFulfilled(
        uint256 indexed orderId,
        uint256 amount,
        uint256 remaining,
        uint256 totalFilledAmount,
        uint256 executionPrice
    );

    /// @notice Emitted when default fee percentage is updated
    event DefaultFeePercentageUpdated(
        uint256 oldFeePercentage,
        uint256 newFeePercentage
    );

    /// @notice Emitted when token-specific fee percentage is updated
    event TokenSpecificFeePercentageUpdated(
        address indexed token,
        uint256 oldFeePercentage,
        uint256 newFeePercentage
    );

    event OrderSizeLimitsUpdated(uint256 minSize, uint256 maxSize);
    event MaxTTLUpdated(uint256 newMaxTTL);
    event RateLimitUpdated(uint256 newInterval);

    /// @notice Emitted when an order is fulfilled through matching
    event OrderFulfilledByMatching(
        uint256 indexed orderId,
        uint256 indexed matchedOrderId,
        uint256 amount,
        uint256 price
    );

    /// @notice Emitted when an order is fulfilled through market maker
    event OrderFulfilledByMarketMaker(
        uint256 indexed orderId,
        address indexed marketMakerPool,
        uint256 amount,
        uint256 price
    );

    /// @notice Emitted when fees are distributed to market maker pool
    event FeeDistributedToPool(
        address indexed marketMakerPool,
        address indexed token,
        uint256 amount,
        uint256 totalFee
    );

    /// @notice Emitted when max price deviation is updated
    event MaxPriceDeviationUpdated(uint256 oldDeviation, uint256 newDeviation);

    /// @notice Emitted when dust tolerance is updated
    event DustToleranceUpdated(uint256 oldTolerance, uint256 newTolerance);

    /// @notice Emitted when Uniswap V3 Factory is updated
    event UniswapV3FactoryUpdated(
        address indexed oldFactory,
        address indexed newFactory
    );

    /// @notice Emitted when Uniswap V3 Price Helper is updated
    event UniswapV3PriceHelperUpdated(address indexed priceHelper);

    /// @notice Emitted when V3 fee tiers are updated
    event V3FeeTiersUpdated(uint24[] feeTiers);

    // Modifiers
    modifier onlyAuthorizedFulfiller() {
        require(
            gradientRegistry.isAuthorizedFulfiller(msg.sender),
            "Caller is not authorized"
        );
        _;
    }

    modifier orderExists(uint256 orderId) {
        require(orders[orderId].owner != address(0), "Order does not exist");
        _;
    }

    modifier onlyOrderOwner(uint256 orderId) {
        require(orders[orderId].owner == msg.sender, "Not order owner");
        _;
    }

    modifier validToken(address token) {
        require(token != address(0), "Invalid token");
        require(token.code.length > 0, "Not a contract");
        // Check if token is blocked
        require(!gradientRegistry.blockedTokens(token), "Token is blocked");
        _;
    }

    modifier validateMarketOrderPrice(uint256 orderId, uint256 executionPrice) {
        Order memory order = orders[orderId];

        if (order.executionType == OrderExecutionType.Market) {
            if (order.orderType == OrderType.Buy) {
                require(
                    executionPrice <= order.price,
                    "Execution price exceeds buyer's max price"
                );
            } else {
                require(
                    executionPrice >= order.price,
                    "Execution price below seller's min price"
                );
            }
        }

        if (order.executionType == OrderExecutionType.Limit) {
            require(
                executionPrice == order.price,
                "Execution price not matched with order price."
            );
        }
        _;
    }

    constructor(IGradientRegistry _gradientRegistry) Ownable(msg.sender) {
        gradientRegistry = _gradientRegistry;
        // feeManager will be set via setGradientRegistry after deployment
        defaultFeePercentage = 100; // 1% default fee for all trades

        minOrderSize = 1000000000000; // 0.000001 ETH
        maxOrderSize = 1000 ether;
        maxOrderTtl = 30 days;
    }

    receive() external payable {}

    fallback() external payable {}

    /// @notice Internal function to calculate and collect ETH fees
    /// @param amount Amount in ETH to calculate fee from
    /// @param token Token address for potential token-specific fee
    /// @return uint256 Fee amount collected
    function _collectEthFee(
        uint256 amount,
        address token
    ) internal returns (uint256) {
        // Use token-specific fee if set, otherwise use default fee
        uint256 feePercentage = getCurrentFeePercentage(token);

        uint256 feeAmount = (amount * feePercentage) / DIVISOR;
        if (feeAmount > 0) {
            // Transfer ETH to feeManager and track
            feeManager.collectEthFee{value: feeAmount}(feeAmount, token);
        }
        return feeAmount;
    }

    /// @notice Internal function to calculate and collect token fees
    /// @param amount Amount in tokens to calculate fee from
    /// @param token Token address
    /// @return uint256 Fee amount collected
    function _collectTokenFee(
        uint256 amount,
        address token
    ) internal returns (uint256) {
        // Use token-specific fee if set, otherwise use default fee
        uint256 feePercentage = getCurrentFeePercentage(token);

        uint256 feeAmount = (amount * feePercentage) / DIVISOR;
        if (feeAmount > 0) {
            // Transfer tokens to feeManager
            IERC20(token).safeTransfer(address(feeManager), feeAmount);
            // Track the fee in feeManager
            feeManager.collectTokenFee(feeAmount, token);
        }
        return feeAmount;
    }

    /// @notice Internal function to distribute market maker fees according to new split logic
    /// @param totalFee Total fee amount to distribute
    /// @param token Token address for partner token check
    /// @param marketMakerPool Market maker pool address for distribution
    function _distributeMarketMakerTokenFees(
        uint256 totalFee,
        address token,
        address marketMakerPool
    ) internal {
        feeManager.distributeMarketMakerTokenFees(
            totalFee,
            token,
            marketMakerPool
        );
    }

    /// @notice Internal function to distribute market maker ETH fees according to new split logic
    /// @param totalFee Total ETH fee amount to distribute
    /// @param token Token address for partner token check
    /// @param marketMakerPool Market maker pool address for distribution
    function _distributeMarketMakerEthFees(
        uint256 totalFee,
        address token,
        address marketMakerPool
    ) internal {
        feeManager.distributeMarketMakerEthFees{value: totalFee}(
            totalFee,
            token,
            marketMakerPool
        );
    }

    /// @notice Adds an order to its appropriate queue
    /// @param orderId The ID of the order to add
    /// @param token The token address
    /// @param orderType The type of order (Buy/Sell)
    /// @param executionType The type of execution (Limit/Market)
    function _addOrderToQueue(
        uint256 orderId,
        address token,
        OrderType orderType,
        OrderExecutionType executionType
    ) internal {
        bytes32 queueKey = _getQueueKey(token, orderType, executionType);

        linkedOrders[queueKey][orderId] = LinkedOrder({
            prev: tailOrder[queueKey],
            next: 0,
            exists: true
        });

        if (tailOrder[queueKey] != 0) {
            linkedOrders[queueKey][tailOrder[queueKey]].next = orderId;
        } else {
            headOrder[queueKey] = orderId;
        }

        tailOrder[queueKey] = orderId;

        // Store the position of the order in the queue
        orderQueuePositions[orderId] = totalOrderCount[queueKey];
        totalOrderCount[queueKey] += 1;
    }

    function _removeOrderFromLinkedQueue(
        bytes32 queueKey,
        uint256 orderId
    ) internal {
        LinkedOrder storage node = linkedOrders[queueKey][orderId];
        require(node.exists, "Order not in queue");

        if (node.prev != 0) {
            linkedOrders[queueKey][node.prev].next = node.next;
        } else {
            headOrder[queueKey] = node.next;
        }

        if (node.next != 0) {
            linkedOrders[queueKey][node.next].prev = node.prev;
        } else {
            tailOrder[queueKey] = node.prev;
        }

        delete linkedOrders[queueKey][orderId];
    }

    /// @notice Creates a new order in the orderbook
    /// @param orderType Type of order (Buy/Sell)
    /// @param executionType Type of execution (Limit/Market)
    /// @param token Address of the token to trade
    /// @param amount Amount of tokens to trade
    /// @param price For limit orders: exact price, For market orders: max price (buy) or min price (sell)
    /// @param ttl Time-to-live in seconds for the order
    /// @param objectId Optional object ID for tracking
    /// @param isAutofallback Whether this is an autofallback order
    /// @dev For buy orders, requires ETH to be sent with the transaction
    /// @dev For sell orders, requires token approval
    /// @return uint256 ID of the created order
    function createOrder(
        OrderType orderType,
        OrderExecutionType executionType,
        address token,
        uint256 amount,
        uint256 price,
        uint256 ttl,
        string memory objectId,
        bool isAutofallback
    ) external payable validToken(token) nonReentrant returns (uint256) {
        require(amount > 0, "Amount must be greater than 0");
        require(price > 0, "Invalid price range");
        require(ttl > 0, "TTL must be greater than 0");
        require(ttl <= maxOrderTtl, "TTL too long");

        // Normalize token amount to 18 decimals for consistent price calculations
        uint256 normalizedAmount = normalizeTo18Decimals(amount, token);

        require(
            normalizedAmount <= type(uint256).max / price,
            "Price calculation would overflow"
        );
        uint256 totalCost = (normalizedAmount * price) / 1e18;
        require(totalCost >= minOrderSize, "Order too small");
        require(totalCost <= maxOrderSize, "Order too large");

        if (orderType == OrderType.Buy) {
            require(msg.value >= totalCost, "Insufficient ETH sent");
        } else {
            IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
        }

        uint256 orderId = _orderIdCounter++;
        uint256 expirationTime = block.timestamp + ttl;

        orders[orderId] = Order({
            orderId: orderId,
            owner: msg.sender,
            orderType: orderType,
            executionType: executionType,
            token: token,
            amount: normalizedAmount, // Store normalized amount for calculations
            price: price,
            ethAmount: (orderType == OrderType.Buy) ? totalCost : 0,
            ethSpent: 0,
            filledAmount: 0,
            expirationTime: expirationTime,
            status: OrderStatus.Active
        });

        _addOrderToQueue(orderId, token, orderType, executionType);

        emit OrderCreated(
            orderId,
            msg.sender,
            orderType,
            executionType,
            token,
            amount, // Emit original amount for transparency
            price,
            expirationTime,
            totalCost,
            objectId,
            isAutofallback
        );

        if (orderType == OrderType.Buy && msg.value > totalCost) {
            (bool success, ) = msg.sender.call{value: msg.value - totalCost}(
                ""
            );
            require(success, "ETH return failed");
        }

        return orderId;
    }

    /// @notice Cancels an active order
    /// @param orderId ID of the order to cancel
    /// @dev Only the order owner can cancel their order
    /// @dev Refunds ETH for buy orders and tokens for sell orders
    function cancelOrder(
        uint256 orderId
    ) external nonReentrant orderExists(orderId) onlyOrderOwner(orderId) {
        Order storage order = orders[orderId];
        require(order.status == OrderStatus.Active, "Order not active");
        require(!isOrderExpired(orderId), "Order expired");

        order.status = OrderStatus.Cancelled;
        if (order.orderType == OrderType.Buy) {
            uint256 refundAmount;
            if (order.executionType == OrderExecutionType.Market) {
                refundAmount = order.ethAmount > order.ethSpent
                    ? (order.ethAmount - order.ethSpent)
                    : 0;
            } else {
                uint256 remainingAmount = order.amount > order.filledAmount
                    ? (order.amount - order.filledAmount)
                    : 0;
                refundAmount = (remainingAmount * order.price) / 1e18;
            }
            if (refundAmount > 0) {
                require(
                    address(this).balance >= refundAmount,
                    "Insufficient ETH in contract"
                );
                (bool success, ) = order.owner.call{value: refundAmount}("");
                require(success, "ETH refund failed");
            }
        } else {
            uint256 remainingAmount = order.amount > order.filledAmount
                ? (order.amount - order.filledAmount)
                : 0;
            if (remainingAmount > 0) {
                uint256 actualRemainingAmount = denormalizeFrom18Decimals(
                    remainingAmount,
                    order.token
                );
                IERC20(order.token).safeTransfer(
                    order.owner,
                    actualRemainingAmount
                );
            }
        }

        bytes32 queueKey = _getQueueKey(
            order.token,
            order.orderType,
            order.executionType
        );
        _removeOrderFromLinkedQueue(queueKey, orderId);

        emit OrderCancelled(orderId);
    }

    /// @notice Marks multiple expired orders as expired and handles refunds
    /// @param orderIds Array of IDs of expired orders to clean up
    /// @dev Anyone can call this function for expired orders
    /// @dev Refunds tokens for unfilled sell orders and ETH for unfilled buy orders
    /// @dev More gas efficient than calling cleanupExpiredOrder multiple times
    function cleanupExpiredOrders(
        uint256[] memory orderIds
    ) external nonReentrant {
        require(orderIds.length > 0, "No orders to clean up");
        require(orderIds.length <= 100, "Too many orders to clean up at once");

        for (uint256 i = 0; i < orderIds.length; i++) {
            uint256 orderId = orderIds[i];

            // Check if order exists
            require(
                orders[orderId].owner != address(0),
                "Order does not exist"
            );

            Order storage order = orders[orderId];
            require(order.status == OrderStatus.Active, "Order not active");
            require(isOrderExpired(orderId), "Order not expired");

            order.status = OrderStatus.Expired;

            if (order.orderType == OrderType.Sell) {
                uint256 remainingAmount = order.amount > order.filledAmount
                    ? (order.amount - order.filledAmount)
                    : 0;
                if (remainingAmount > 0) {
                    // Denormalize the remaining amount back to token decimals
                    uint256 actualRemainingAmount = denormalizeFrom18Decimals(
                        remainingAmount,
                        order.token
                    );
                    IERC20(order.token).safeTransfer(
                        order.owner,
                        actualRemainingAmount
                    );
                }
            }

            if (order.orderType == OrderType.Buy) {
                uint256 refundAmount;
                if (order.executionType == OrderExecutionType.Market) {
                    refundAmount = order.ethAmount > order.ethSpent
                        ? (order.ethAmount - order.ethSpent)
                        : 0;
                } else {
                    uint256 remainingAmount = order.amount > order.filledAmount
                        ? (order.amount - order.filledAmount)
                        : 0;
                    refundAmount = (remainingAmount * order.price) / 1e18;
                }
                if (refundAmount > 0) {
                    require(
                        address(this).balance >= refundAmount,
                        "Insufficient ETH in contract"
                    );
                    (bool success, ) = payable(order.owner).call{
                        value: refundAmount
                    }("");
                    require(success, "ETH refund failed");
                }
            }

            bytes32 queueKey = _getQueueKey(
                order.token,
                order.orderType,
                order.executionType
            );
            _removeOrderFromLinkedQueue(queueKey, orderId);

            emit OrderExpired(orderId);
        }
    }

    /// @notice Fulfills multiple matched limit orders
    /// @param matches Array of OrderMatch structs containing match details
    /// @dev Only whitelisted fulfillers can call this function
    /// @dev All orders in matches must be limit orders
    /// @dev This function matches buy and sell orders against each other
    function fulfillLimitOrders(
        OrderMatch[] calldata matches
    ) external nonReentrant onlyAuthorizedFulfiller {
        require(matches.length > 0, "No order matches to fulfill");

        for (uint256 i = 0; i < matches.length; i++) {
            _fulfillLimitOrders(matches[i]);
        }
    }

    /// @notice Fulfills multiple matched market orders through order matching
    /// @param matches Array of OrderMatch structs containing match details
    /// @param executionPrices Array of execution prices for each match
    /// @dev Only whitelisted fulfillers can call this function
    /// @dev All orders in matches must be market orders
    /// @dev This function matches buy and sell orders against each other
    function fulfillMarketOrders(
        OrderMatch[] calldata matches,
        uint256[] calldata executionPrices
    ) external nonReentrant onlyAuthorizedFulfiller {
        require(matches.length > 0, "No order matches to fulfill");
        require(
            matches.length == executionPrices.length,
            "Mismatched arrays length"
        );

        for (uint256 i = 0; i < matches.length; i++) {
            _fulfillMarketOrders(matches[i], executionPrices[i]);
        }
    }

    /// @notice Internal function to fulfill a matched pair of limit orders
    /// @param _match OrderMatch struct containing the match details
    /// @dev Handles the transfer of ETH and tokens between parties
    /// @dev Allows partial fills of either order
    function _fulfillLimitOrders(OrderMatch memory _match) internal {
        Order storage buyOrder = orders[_match.buyOrderId];
        Order storage sellOrder = orders[_match.sellOrderId];

        // Validate orders
        require(
            buyOrder.status == OrderStatus.Active &&
                sellOrder.status == OrderStatus.Active,
            "Orders must be active"
        );
        require(
            !isOrderExpired(_match.buyOrderId) &&
                !isOrderExpired(_match.sellOrderId),
            "1 of the orders expired"
        );
        require(
            buyOrder.orderType == OrderType.Buy &&
                sellOrder.orderType == OrderType.Sell,
            "Invalid order types"
        );
        require(buyOrder.token == sellOrder.token, "Token mismatch");
        require(
            buyOrder.owner != sellOrder.owner,
            "Seller and buyer cannot be the same"
        );
        require(
            buyOrder.executionType == OrderExecutionType.Limit &&
                sellOrder.executionType == OrderExecutionType.Limit,
            "Not limit orders"
        );

        // Handle different fulfillment types
        _fulfillLimitOrdersMatching(_match);
    }

    /// @notice Internal function to fulfill limit orders through matching
    /// @param _match OrderMatch struct containing the match details
    function _fulfillLimitOrdersMatching(OrderMatch memory _match) internal {
        Order storage buyOrder = orders[_match.buyOrderId];
        Order storage sellOrder = orders[_match.sellOrderId];

        require(
            buyOrder.price >= sellOrder.price,
            "Price mismatch for limit orders"
        );

        uint256 buyRemaining = buyOrder.amount > buyOrder.filledAmount
            ? (buyOrder.amount - buyOrder.filledAmount)
            : 0;
        uint256 sellRemaining = sellOrder.amount > sellOrder.filledAmount
            ? (sellOrder.amount - sellOrder.filledAmount)
            : 0;
        uint256 actualFillAmount = _match.fillAmount;

        if (actualFillAmount > buyRemaining) {
            actualFillAmount = buyRemaining;
        }
        if (actualFillAmount > sellRemaining) {
            actualFillAmount = sellRemaining;
        }

        require(actualFillAmount > 0, "No amount to fill");

        uint256 tokenAmount = actualFillAmount;
        uint256 paymentAmount = (actualFillAmount * sellOrder.price) / 1e18; // Use sell price for limit orders

        // Calculate fees from receiving amounts
        uint256 buyerFee = _collectEthFee(paymentAmount, sellOrder.token); // ETH fee from buyer's payment
        uint256 sellerFee = _collectTokenFee(tokenAmount, sellOrder.token); // Token fee from seller's tokens

        // Transfer ETH to seller (buyer pays ETH fee)
        uint256 sellerPayment = paymentAmount - buyerFee;
        (bool success, ) = sellOrder.owner.call{value: sellerPayment}("");
        require(success, "ETH transfer to seller failed");

        // Transfer tokens to buyer (seller pays token fee)
        {
            uint256 actualTokenAmount = denormalizeFrom18Decimals(
                tokenAmount,
                sellOrder.token
            );
            uint256 actualTokenFee = denormalizeFrom18Decimals(
                sellerFee,
                sellOrder.token
            );
            IERC20(sellOrder.token).safeTransfer(
                buyOrder.owner,
                actualTokenAmount - actualTokenFee
            );
        }

        buyOrder.filledAmount += actualFillAmount;
        sellOrder.filledAmount += actualFillAmount;

        if (buyOrder.price > sellOrder.price) {
            uint256 savedAmount = (actualFillAmount *
                (buyOrder.price - sellOrder.price)) / 1e18;
            (success, ) = buyOrder.owner.call{value: savedAmount}("");
            require(success, "ETH savings return failed");
        }

        _updateOrderStatus(
            _match.buyOrderId,
            actualFillAmount,
            sellOrder.price
        );
        _updateOrderStatus(
            _match.sellOrderId,
            actualFillAmount,
            buyOrder.price
        );
    }

    /// @notice Internal function to fulfill a matched pair of market orders
    /// @param _match OrderMatch struct containing the match details
    /// @param executionPrice The price at which the orders will be executed
    /// @dev Handles the transfer of ETH and tokens between parties
    /// @dev Allows partial fills of either order
    function _fulfillMarketOrders(
        OrderMatch memory _match,
        uint256 executionPrice
    ) internal {
        Order storage buyOrder = orders[_match.buyOrderId];
        Order storage sellOrder = orders[_match.sellOrderId];

        // Validate orders
        require(
            buyOrder.status == OrderStatus.Active &&
                sellOrder.status == OrderStatus.Active,
            "Orders must be active"
        );
        require(
            !isOrderExpired(_match.buyOrderId) &&
                !isOrderExpired(_match.sellOrderId),
            "Orders expired"
        );
        require(
            buyOrder.orderType == OrderType.Buy &&
                sellOrder.orderType == OrderType.Sell,
            "Invalid order types"
        );
        require(buyOrder.token == sellOrder.token, "Token mismatch");
        require(
            (buyOrder.executionType == OrderExecutionType.Market ||
                sellOrder.executionType == OrderExecutionType.Market),
            "Not market orders"
        );

        _fulfillMarketOrdersMatching(_match, executionPrice);
    }

    /// @notice Internal function to fulfill market orders through matching
    /// @param _match OrderMatch struct containing the match details
    /// @param executionPrice The price at which the orders will be executed
    function _fulfillMarketOrdersMatching(
        OrderMatch memory _match,
        uint256 executionPrice
    ) internal {
        Order storage buyOrder = orders[_match.buyOrderId];
        Order storage sellOrder = orders[_match.sellOrderId];

        // Validate execution price against market price
        require(
            validateExecutionPrice(buyOrder.token, executionPrice),
            "Execution price deviates too much from market price"
        );

        if (buyOrder.executionType == OrderExecutionType.Market) {
            require(
                executionPrice <= buyOrder.price,
                "Execution price exceeds buyer's max price"
            );
        }
        if (sellOrder.executionType == OrderExecutionType.Market) {
            require(
                executionPrice >= sellOrder.price,
                "Execution price below seller's min price"
            );
        }

        uint256 buyRemaining = getBuyOrderRemainingAmount(
            buyOrder,
            executionPrice
        );
        uint256 sellRemaining = sellOrder.amount > sellOrder.filledAmount
            ? (sellOrder.amount - sellOrder.filledAmount)
            : 0;

        uint256 actualFillAmount = _match.fillAmount;
        if (actualFillAmount > buyRemaining) {
            actualFillAmount = buyRemaining;
        }
        if (actualFillAmount > sellRemaining) {
            actualFillAmount = sellRemaining;
        }

        require(actualFillAmount > 0, "No amount to fill");

        uint256 tokenAmount = actualFillAmount;
        uint256 paymentAmount = (actualFillAmount * executionPrice) / 1e18;

        // Calculate fees from receiving amounts
        uint256 buyerFee = _collectEthFee(paymentAmount, sellOrder.token); // ETH fee from buyer's payment
        uint256 sellerFee = _collectTokenFee(tokenAmount, sellOrder.token); // Token fee from seller's tokens

        // Transfer ETH to seller (buyer pays ETH fee)
        uint256 sellerPayment = paymentAmount - buyerFee;

        (bool success, ) = sellOrder.owner.call{value: sellerPayment}("");
        require(success, "ETH transfer to seller failed");

        {
            uint256 actualTokenAmount = denormalizeFrom18Decimals(
                tokenAmount,
                sellOrder.token
            );
            uint256 actualTokenFee = denormalizeFrom18Decimals(
                sellerFee,
                sellOrder.token
            );
            IERC20(sellOrder.token).safeTransfer(
                buyOrder.owner,
                actualTokenAmount - actualTokenFee
            );
        }

        buyOrder.filledAmount += actualFillAmount;
        // Track actual ETH spent for buy market orders
        if (
            buyOrder.orderType == OrderType.Buy &&
            buyOrder.executionType == OrderExecutionType.Market
        ) {
            buyOrder.ethSpent += paymentAmount;
        }
        sellOrder.filledAmount += actualFillAmount;

        _updateOrderStatus(_match.buyOrderId, actualFillAmount, executionPrice);
        _updateOrderStatus(
            _match.sellOrderId,
            actualFillAmount,
            executionPrice
        );
    }

    /// @notice Fulfills multiple orders through the market maker pool
    /// @param orderIds Array of order IDs to fulfill
    /// @param fillAmounts Array of fill amounts for each order
    /// @param executionPrices Array of execution prices for each order
    /// @param merkleRoot The merkle root to use for position updates
    /// @dev Only whitelisted fulfillers can call this function
    function fulfillOrdersWithMarketMaker(
        uint256[] calldata orderIds,
        uint256[] calldata fillAmounts,
        uint256[] calldata executionPrices,
        bytes32 merkleRoot
    ) external nonReentrant onlyAuthorizedFulfiller {
        require(orderIds.length > 0, "No orders to fulfill");
        require(
            orderIds.length == fillAmounts.length &&
                orderIds.length == executionPrices.length,
            "Mismatched arrays length"
        );

        for (uint256 i = 0; i < orderIds.length; i++) {
            require(fillAmounts[i] > 0, "Fill amount must be greater than 0");
            require(
                executionPrices[i] > 0,
                "Execution price must be greater than 0"
            );
            _fulfillOrderWithMarketMaker(
                orderIds[i],
                fillAmounts[i],
                executionPrices[i],
                merkleRoot
            );
        }
    }

    /// @notice Internal function to fulfill a single order through the market maker pool
    /// @param orderId ID of the order to fulfill
    /// @param fillAmount Amount of tokens to fill
    /// @param executionPrice The price at which the order will be executed
    /// @param merkleRoot The merkle root to use for position updates
    function _fulfillOrderWithMarketMaker(
        uint256 orderId,
        uint256 fillAmount,
        uint256 executionPrice,
        bytes32 merkleRoot
    ) internal validateMarketOrderPrice(orderId, executionPrice) {
        Order storage order = orders[orderId];

        // Validate order
        require(order.status == OrderStatus.Active, "Order not active");
        require(!isOrderExpired(orderId), "Order expired");

        // Validate execution price against market price
        require(
            validateExecutionPrice(order.token, executionPrice),
            "Execution price deviates too much from market price"
        );

        address marketMakerFactory = gradientRegistry.marketMakerFactory();
        require(
            marketMakerFactory != address(0),
            "Market maker factory not set"
        );

        address marketMakerPool = GradientMarketMakerFactory(marketMakerFactory)
            .getPool(order.token);
        require(
            marketMakerPool != address(0),
            "Market maker pool not found for token"
        );

        // Calculate actual fill amount based on remaining amount
        uint256 remainingAmount;
        if (
            order.orderType == OrderType.Buy &&
            order.executionType == OrderExecutionType.Market
        ) {
            remainingAmount = getBuyOrderRemainingAmount(order, executionPrice);
        } else {
            remainingAmount = order.amount > order.filledAmount
                ? (order.amount - order.filledAmount)
                : 0;
        }
        uint256 actualFillAmount = fillAmount > remainingAmount
            ? remainingAmount
            : fillAmount;

        require(actualFillAmount > 0, "No amount to fill");

        // Calculate payment amount and fees
        uint256 paymentAmount = (actualFillAmount * executionPrice) / 1e18;

        if (order.orderType == OrderType.Buy) {
            // Buy order from order to get tokens from market maker pool
            uint256 actualTokenAmount = denormalizeFrom18Decimals(
                actualFillAmount,
                order.token
            );

            // For buy orders: orderbook sends ETH to market maker, receives tokens
            IGradientMarketMakerPoolV3(marketMakerPool).executeBuyOrder{
                value: paymentAmount
            }(paymentAmount, actualTokenAmount, merkleRoot);

            // Calculate fee from received tokens and deduct from user
            uint256 tokenFee = (actualTokenAmount *
                (getCurrentFeePercentage(order.token))) / DIVISOR;
            uint256 netTokenAmount = actualTokenAmount - tokenFee;

            // Distribute fees using new market maker split logic (50% to market makers, 50% to teams)
            if (tokenFee > 0) {
                // Transfer fee tokens to feeManager
                IERC20(order.token).safeTransfer(address(feeManager), tokenFee);

                // Distribute according to the split
                _distributeMarketMakerTokenFees(
                    tokenFee,
                    order.token,
                    marketMakerPool
                );
            }

            IERC20(order.token).safeTransfer(order.owner, netTokenAmount);
        } else {
            // Denormalize the amount for token approval
            uint256 actualTokenAmount = denormalizeFrom18Decimals(
                actualFillAmount,
                order.token
            );

            // For sell orders: orderbook sends full tokens to market maker, receives ETH
            IERC20(order.token).approve(marketMakerPool, actualTokenAmount);

            // Execute sell order - Orderbook sends tokens, receives ETH
            IGradientMarketMakerPoolV3(marketMakerPool).executeSellOrder(
                paymentAmount,
                actualTokenAmount,
                merkleRoot
            );

            // Calculate fee from received ETH and deduct from user
            uint256 ethFee = (paymentAmount *
                (getCurrentFeePercentage(order.token))) / DIVISOR;
            uint256 netEthAmount = paymentAmount - ethFee;

            // Distribute fees using new market maker split logic (50% to market makers, 50% to teams)
            if (ethFee > 0) {
                // Distribute according to the split
                _distributeMarketMakerEthFees(
                    ethFee,
                    order.token,
                    marketMakerPool
                );
            }

            // Transfer ETH to seller (minus fee)
            (bool success, ) = order.owner.call{value: netEthAmount}("");
            require(success, "ETH transfer to seller failed");
        }

        // Update order state
        order.filledAmount += actualFillAmount;
        // Track actual ETH spent for buy market orders
        if (
            order.orderType == OrderType.Buy &&
            order.executionType == OrderExecutionType.Market
        ) {
            order.ethSpent += paymentAmount;
        }

        // Update order status
        _updateOrderStatus(orderId, actualFillAmount, executionPrice);

        emit OrderFulfilledByMarketMaker(
            orderId,
            marketMakerPool,
            actualFillAmount,
            executionPrice
        );
    }

    /// @notice Internal function to fulfill an order via AMM
    /// @param orderId ID of the order to fulfill
    /// @param fillAmount Amount of tokens to fill
    /// @param minAmountOut Minimum amount to receive (slippage protection)
    /// @dev Uses FallbackExecutor to find the best DEX and execute the trade
    function _fulfillOrderWithAMM(
        uint256 orderId,
        uint256 fillAmount,
        uint256 minAmountOut
    ) internal {
        require(fillAmount > 0, "Fill amount must be greater than 0");

        Order storage order = orders[orderId];
        require(order.status == OrderStatus.Active, "Order not active");

        // Calculate actual fill amount based on remaining amount
        uint256 remainingAmount;
        if (
            order.orderType == OrderType.Buy &&
            order.executionType == OrderExecutionType.Market
        ) {
            remainingAmount = getBuyOrderRemainingAmount(order, order.price);
        } else {
            remainingAmount = order.amount > order.filledAmount
                ? (order.amount - order.filledAmount)
                : 0;
        }
        uint256 actualFillAmount = fillAmount > remainingAmount
            ? remainingAmount
            : fillAmount;
        require(actualFillAmount > 0, "No amount to fill");

        // Get FallbackExecutor from registry
        address fallbackExecutor = gradientRegistry.fallbackExecutor();
        require(fallbackExecutor != address(0), "FallbackExecutor not set");

        // For buy orders, calculate how much ETH to send based on order type
        uint256 ethToSend;
        uint256 effectiveExecutionPrice;

        if (order.orderType == OrderType.Buy) {
            if (order.executionType == OrderExecutionType.Market) {
                // For market orders, send the remaining ETH (up to the fill amount)
                uint256 ethRemaining = order.ethAmount > order.ethSpent
                    ? (order.ethAmount - order.ethSpent)
                    : 0;
                ethToSend = ethRemaining;
            } else {
                // For limit orders, calculate based on order price
                ethToSend = (actualFillAmount * order.price) / 1e18;
            }

            // Execute the buy trade directly through FallbackExecutor with full ETH amount
            uint256 tokensReceived = IFallbackExecutor(fallbackExecutor)
                .executeTrade{value: ethToSend}(
                order.token,
                ethToSend,
                minAmountOut,
                true // isBuy = true
            );

            // Calculate fee from received tokens and deduct from user (buy order receives tokens)
            uint256 tokenFee = _collectTokenFee(tokensReceived, order.token);
            uint256 netTokenAmount = tokensReceived - tokenFee;

            // Transfer tokens to the buyer (minus fee)
            IERC20(order.token).safeTransfer(order.owner, netTokenAmount);

            // Calculate effective execution price for buy orders
            if (tokensReceived > 0) {
                effectiveExecutionPrice = (ethToSend * 1e18) / tokensReceived;
            } else {
                effectiveExecutionPrice = order.price; // Fallback to order price
            }
        } else {
            // Denormalize the amount for token approval and transfer
            uint256 actualTokenAmount = denormalizeFrom18Decimals(
                actualFillAmount,
                order.token
            );

            // Approve tokens to FallbackExecutor (approve the full amount)
            IERC20(order.token).approve(fallbackExecutor, actualTokenAmount);

            // Execute the sell trade with full amount
            uint256 ethReceived = IFallbackExecutor(fallbackExecutor)
                .executeTrade(
                    order.token,
                    actualTokenAmount,
                    minAmountOut,
                    false // isBuy = false
                );

            // Calculate fee from received ETH and deduct from user (sell order receives ETH)
            uint256 ethFee = _collectEthFee(ethReceived, order.token);
            uint256 netEthAmount = ethReceived - ethFee;
            (bool success, ) = order.owner.call{value: netEthAmount}("");
            require(success, "ETH transfer to seller failed");

            // Calculate effective execution price for sell orders
            if (actualFillAmount > 0) {
                effectiveExecutionPrice =
                    (ethReceived * 1e18) /
                    actualFillAmount;
            } else {
                effectiveExecutionPrice = order.price; // Fallback to order price
            }
        }
        order.filledAmount += actualFillAmount;
        // Track actual ETH spent for buy market orders
        if (
            order.orderType == OrderType.Buy &&
            order.executionType == OrderExecutionType.Market
        ) {
            order.ethSpent += ethToSend;
        }

        // Update order status
        _updateOrderStatus(orderId, actualFillAmount, effectiveExecutionPrice);
    }

    /// @notice Allows authorized fulfillers to automatically fulfill orders via AMM
    /// @param orderId ID of the order to fulfill
    /// @param fillAmount Amount of tokens to fill
    /// @param minAmountOut Minimum amount to receive (slippage protection)
    /// @dev Only authorized fulfillers can call this function
    /// @dev Uses FallbackExecutor to find the best DEX and execute the trade
    /// @dev This enables automatic order fulfillment so users don't need to manually fulfill their orders
    function autoFallbackOrderWithAMM(
        uint256 orderId,
        uint256 fillAmount,
        uint256 minAmountOut
    ) external nonReentrant orderExists(orderId) onlyAuthorizedFulfiller {
        _fulfillOrderWithAMM(orderId, fillAmount, minAmountOut);
    }

    /// @notice Allows users to fulfill their own order via AMM
    /// @param orderId ID of the order to fulfill
    /// @param fillAmount Amount of tokens to fill
    /// @param minAmountOut Minimum amount to receive (slippage protection)
    /// @dev Only the order owner can call this function
    /// @dev Uses FallbackExecutor to find the best DEX and execute the trade
    function fulfillOwnOrderWithAMM(
        uint256 orderId,
        uint256 fillAmount,
        uint256 minAmountOut
    ) external nonReentrant orderExists(orderId) onlyOrderOwner(orderId) {
        _fulfillOrderWithAMM(orderId, fillAmount, minAmountOut);
    }

    /// @notice Internal function to update order status
    /// @param orderId ID of the order to update
    /// @param actualFillAmount Amount of tokens/ETH that was filled
    /// @param executionPrice The price at which the order was executed
    function _updateOrderStatus(
        uint256 orderId,
        uint256 actualFillAmount,
        uint256 executionPrice
    ) internal {
        Order storage order = orders[orderId];
        // For buy market orders, check if all ETH is spent
        if (
            order.orderType == OrderType.Buy &&
            order.executionType == OrderExecutionType.Market
        ) {
            uint256 remainingEth = order.ethAmount > order.ethSpent
                ? (order.ethAmount - order.ethSpent)
                : 0;

            // Check if remaining ETH is below dust tolerance
            bool isDustRemaining = remainingEth > 0 &&
                (remainingEth * 10000) / order.ethAmount <= dustTolerance;

            if (order.ethSpent >= order.ethAmount || isDustRemaining) {
                order.status = OrderStatus.Filled;
                // If dust remaining, mark it as spent to prevent refund issues
                if (isDustRemaining) {
                    order.ethSpent = order.ethAmount;
                }
                bytes32 queueKey = _getQueueKey(
                    order.token,
                    order.orderType,
                    order.executionType
                );
                _removeOrderFromLinkedQueue(queueKey, orderId);
                emit OrderFulfilled(
                    orderId,
                    actualFillAmount,
                    order.ethSpent,
                    executionPrice
                );
            } else {
                emit OrderPartiallyFulfilled(
                    orderId,
                    actualFillAmount,
                    remainingEth,
                    order.ethSpent,
                    executionPrice
                );
            }
        } else {
            uint256 remainingAmount = order.amount > order.filledAmount
                ? (order.amount - order.filledAmount)
                : 0;

            // Check if remaining tokens are below dust tolerance
            bool isDustRemaining = remainingAmount > 0 &&
                (remainingAmount * 10000) / order.amount <= dustTolerance;

            if (order.filledAmount == order.amount || isDustRemaining) {
                order.status = OrderStatus.Filled;
                // If dust remaining, mark it as filled to prevent refund issues
                if (isDustRemaining) {
                    order.filledAmount = order.amount;
                }
                bytes32 queueKey = _getQueueKey(
                    order.token,
                    order.orderType,
                    order.executionType
                );
                _removeOrderFromLinkedQueue(queueKey, orderId);
                emit OrderFulfilled(
                    orderId,
                    actualFillAmount,
                    order.filledAmount,
                    executionPrice
                );
            } else {
                emit OrderPartiallyFulfilled(
                    orderId,
                    actualFillAmount,
                    remainingAmount,
                    order.filledAmount,
                    executionPrice
                );
            }
        }
    }

    // ========================== View Functions ==========================

    /// @notice Returns the current fee percentage for a token
    /// @param token Token address
    /// @return uint256 Fee percentage
    function getCurrentFeePercentage(
        address token
    ) public view returns (uint256) {
        return
            tokenSpecificFeePercentage[token] > 0
                ? tokenSpecificFeePercentage[token]
                : defaultFeePercentage;
    }

    /// @notice Helper function to get token decimals
    /// @param token The token address
    /// @return uint8 The number of decimals for the token
    function getTokenDecimals(address token) internal view returns (uint8) {
        return IERC20Metadata(token).decimals();
    }

    /// @notice Helper function to normalize token amount to 18 decimals
    /// @param amount The token amount in its native decimals
    /// @param token The token address
    /// @return uint256 The normalized amount in 18 decimals
    function normalizeTo18Decimals(
        uint256 amount,
        address token
    ) internal view returns (uint256) {
        uint8 decimals = getTokenDecimals(token);
        if (decimals == 18) {
            return amount;
        } else if (decimals < 18) {
            return amount * (10 ** (18 - decimals));
        } else {
            return amount / (10 ** (decimals - 18));
        }
    }

    /// @notice Helper function to denormalize from 18 decimals to token decimals
    /// @param amount The amount in 18 decimals
    /// @param token The token address
    /// @return uint256 The denormalized amount in token decimals
    function denormalizeFrom18Decimals(
        uint256 amount,
        address token
    ) internal view returns (uint256) {
        uint8 decimals = getTokenDecimals(token);
        if (decimals == 18) {
            return amount;
        } else if (decimals < 18) {
            return amount / (10 ** (18 - decimals));
        } else {
            return amount * (10 ** (decimals - 18));
        }
    }

    /// @notice Gets the count of active orders for a given queue
    /// @param queueKey The queue key to count active orders for
    /// @return uint256 Number of active orders in the queue
    function getActiveOrdersCount(
        bytes32 queueKey
    ) public view returns (uint256) {
        uint256 activeCount = 0;
        uint256 currentOrderId = headOrder[queueKey];

        while (currentOrderId != 0) {
            Order storage order = orders[currentOrderId];
            if (
                order.status == OrderStatus.Active &&
                !isOrderExpired(currentOrderId)
            ) {
                activeCount++;
            }

            currentOrderId = linkedOrders[queueKey][currentOrderId].next;
        }

        return activeCount;
    }

    /// @notice Retrieves all active orders for a given token, order type, and execution type
    /// @param token Address of the token
    /// @param orderType Type of orders to retrieve (Buy/Sell)
    /// @param executionType Type of execution (Limit/Market)
    /// @return uint256[] Array of order IDs that are active and not expired
    function getActiveOrders(
        address token,
        OrderType orderType,
        OrderExecutionType executionType
    ) external view returns (uint256[] memory) {
        bytes32 queueKey = _getQueueKey(token, orderType, executionType);

        uint256 activeCount = getActiveOrdersCount(queueKey);

        // Create array of active orders
        uint256[] memory activeOrders = new uint256[](activeCount);
        uint256 currentOrderId = headOrder[queueKey];
        uint256 currentIndex = 0;

        while (currentOrderId != 0 && currentIndex < activeCount) {
            Order storage order = orders[currentOrderId];
            if (
                order.status == OrderStatus.Active &&
                !isOrderExpired(currentOrderId)
            ) {
                activeOrders[currentIndex] = currentOrderId;
                currentIndex++;
            }

            currentOrderId = linkedOrders[queueKey][currentOrderId].next;
        }

        return activeOrders;
    }

    /// @notice Generates a unique key for order queues based on token, order type, and execution type
    /// @param token The token address
    /// @param orderType The type of order (Buy/Sell)
    /// @param executionType The type of execution (Limit/Market)
    /// @return bytes32 A unique key for the order queue
    function _getQueueKey(
        address token,
        OrderType orderType,
        OrderExecutionType executionType
    ) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(token, orderType, executionType));
    }

    /// @notice Checks if an order has expired
    /// @param orderId ID of the order to check
    /// @return bool True if the order has expired, false otherwise
    function isOrderExpired(
        uint256 orderId
    ) public view orderExists(orderId) returns (bool) {
        return block.timestamp > orders[orderId].expirationTime;
    }

    /// @notice Retrieves detailed information about an order
    /// @param orderId ID of the order to query
    /// @return Order struct containing all order details
    function getOrder(
        uint256 orderId
    ) external view orderExists(orderId) returns (Order memory) {
        return orders[orderId];
    }

    /// @notice Gets the unfilled amount for an order
    /// @param orderId ID of the order to query
    /// @return uint256 Amount of tokens/ETH remaining to be filled
    function getRemainingAmount(
        uint256 orderId
    ) external view orderExists(orderId) returns (uint256) {
        Order storage order = orders[orderId];
        if (
            order.orderType == OrderType.Buy &&
            order.executionType == OrderExecutionType.Market
        ) {
            // For market buy orders, return remaining ETH
            return
                order.ethAmount > order.ethSpent
                    ? (order.ethAmount - order.ethSpent)
                    : 0;
        } else {
            // For other orders, return remaining tokens
            return
                order.amount > order.filledAmount
                    ? (order.amount - order.filledAmount)
                    : 0;
        }
    }

    // ========================== Internal Helper Functions ==========================
    /// @notice Calculates the remaining buyable token amount for a buy order
    /// @param order The order struct (storage pointer)
    /// @param executionPrice The price at which the order is being filled
    /// @return uint256 The remaining amount of tokens that can be bought
    function getBuyOrderRemainingAmount(
        Order storage order,
        uint256 executionPrice
    ) internal view returns (uint256) {
        if (
            order.executionType == OrderExecutionType.Market &&
            order.orderType == OrderType.Buy
        ) {
            uint256 ethRemaining = order.ethAmount > order.ethSpent
                ? (order.ethAmount - order.ethSpent)
                : 0;
            return (ethRemaining * 1e18) / executionPrice;
        } else {
            return
                order.amount > order.filledAmount
                    ? (order.amount - order.filledAmount)
                    : 0;
        }
    }

    // ========================== Admin Functions ==========================

    /// @notice Sets the default fee percentage for all trades
    /// @param newFeePercentage New fee percentage in basis points (1 = 0.01%)
    /// @dev Only callable by contract owner
    function setDefaultFeePercentage(
        uint256 newFeePercentage
    ) external onlyOwner {
        require(
            newFeePercentage <= MAX_FEE_PERCENTAGE,
            "Fee percentage too high"
        );
        uint256 oldFeePercentage = defaultFeePercentage;
        defaultFeePercentage = newFeePercentage;
        emit DefaultFeePercentageUpdated(oldFeePercentage, newFeePercentage);
    }

    /// @notice Sets the token-specific fee percentage for a given token
    /// @param token Token address to set fee for
    /// @param newFeePercentage New fee percentage in basis points (1 = 0.01%)
    /// @dev Only callable by contract owner. Fee must be between 0.5% and 3%
    function setTokenSpecificFeePercentage(
        address token,
        uint256 newFeePercentage
    ) external onlyOwner {
        require(token != address(0), "Invalid token address");
        require(
            newFeePercentage >= MIN_FEE_PERCENTAGE,
            "Fee percentage too low"
        );
        require(
            newFeePercentage <= MAX_TOKEN_SPECIFIC_FEE_PERCENTAGE,
            "Fee percentage too high"
        );
        uint256 oldFeePercentage = tokenSpecificFeePercentage[token];
        tokenSpecificFeePercentage[token] = newFeePercentage;
        emit TokenSpecificFeePercentageUpdated(
            token,
            oldFeePercentage,
            newFeePercentage
        );
    }

    /// @notice Removes token-specific fee percentage for a given token (reverts to default)
    /// @param token Token address to remove specific fee for
    /// @dev Only callable by contract owner. Sets token-specific fee to 0, so it uses default
    function removeTokenSpecificFeePercentage(
        address token
    ) external onlyOwner {
        require(token != address(0), "Invalid token address");
        uint256 oldFeePercentage = tokenSpecificFeePercentage[token];
        require(oldFeePercentage > 0, "No specific fee set for this token");
        tokenSpecificFeePercentage[token] = 0;
        emit TokenSpecificFeePercentageUpdated(token, oldFeePercentage, 0);
    }

    /**
     * @notice Sets the gradient registry address
     * @param _gradientRegistry New gradient registry address
     * @dev Only callable by the contract owner
     */
    function setGradientRegistry(
        IGradientRegistry _gradientRegistry
    ) external onlyOwner {
        require(
            address(_gradientRegistry) != address(0),
            "Invalid gradient registry"
        );
        gradientRegistry = _gradientRegistry;
        feeManager = IGradientFeeManager(_gradientRegistry.feeManager());
    }

    /**
     * @notice Sets the fee manager address
     * @param _feeManager New fee manager address
     * @dev Only callable by the contract owner
     */
    function setFeeManager(IGradientFeeManager _feeManager) external onlyOwner {
        require(address(_feeManager) != address(0), "Invalid fee manager");
        feeManager = _feeManager;
    }

    /// @notice Sets the minimum and maximum order size limits
    /// @param _minOrderSize New minimum order size in ETH (wei)
    /// @param _maxOrderSize New maximum order size in ETH (wei)
    /// @dev Only callable by contract owner
    function setOrderSizeLimits(
        uint256 _minOrderSize,
        uint256 _maxOrderSize
    ) external onlyOwner {
        require(
            _minOrderSize < _maxOrderSize,
            "Min size must be less than max size"
        );
        minOrderSize = _minOrderSize;
        maxOrderSize = _maxOrderSize;
        emit OrderSizeLimitsUpdated(_minOrderSize, _maxOrderSize);
    }

    /// @notice Sets the maximum time-to-live for orders
    /// @param _maxOrderTtl New maximum TTL in seconds
    /// @dev Only callable by contract owner
    function setMaxOrderTtl(uint256 _maxOrderTtl) external onlyOwner {
        require(_maxOrderTtl > 0, "TTL must be greater than 0");
        maxOrderTtl = _maxOrderTtl;
        emit MaxTTLUpdated(_maxOrderTtl);
    }

    /// @notice Updates the dust tolerance
    /// @param newDustTolerance New dust tolerance in basis points (1 = 0.01%)
    /// @dev Only callable by contract owner
    function updateDustTolerance(uint256 newDustTolerance) external onlyOwner {
        require(newDustTolerance <= 10000, "Dust tolerance too high");
        uint256 oldDustTolerance = dustTolerance;
        dustTolerance = newDustTolerance;
        emit DustToleranceUpdated(oldDustTolerance, newDustTolerance);
    }

    /// @notice Gets the current market price from Uniswap for a token (checks V3 first, then V2)
    /// @param token Address of the token
    /// @return marketPrice Current market price in ETH (18 decimals)
    function getCurrentMarketPrice(
        address token
    ) public view returns (uint256 marketPrice) {
        // Try V3 first if price helper is set
        if (address(uniswapV3PriceHelper) != address(0)) {
            address routerAddress = gradientRegistry.router();
            if (routerAddress != address(0)) {
                uint8 decimals = IERC20Metadata(token).decimals();
                uint256 v3Price = uniswapV3PriceHelper.getPriceFromV3(
                    token,
                    IUniswapV2Router02(routerAddress).WETH(),
                    decimals
                );
                if (v3Price > 0) {
                    return v3Price;
                }
            }
        }

        // Fall back to V2
        uint256 v2Price = getPriceFromV2(token);
        require(v2Price > 0, "No liquidity available in V2 or V3");
        return v2Price;
    }

    /// @notice Validates execution price against current market price
    /// @param token Address of the token
    /// @param executionPrice Execution price to validate
    /// @return bool True if price is within acceptable deviation
    function validateExecutionPrice(
        address token,
        uint256 executionPrice
    ) public view returns (bool) {
        // Skip validation for v3 pairs
        if (address(uniswapV3PriceHelper) != address(0)) {
            address routerAddress = gradientRegistry.router();
            if (routerAddress != address(0)) {
                try
                    uniswapV3PriceHelper.getV3PoolAddress(
                        token,
                        IUniswapV2Router02(routerAddress).WETH()
                    )
                returns (address v3Pool) {
                    if (v3Pool != address(0)) {
                        return true; // v3 pair exists - bypass validation
                    }
                } catch {}
            }
        }

        uint256 marketPrice = getCurrentMarketPrice(token);

        // Handle edge cases
        if (marketPrice == 0) {
            return false; // Cannot validate against zero market price
        }

        if (executionPrice == marketPrice) {
            return true; // Exact match is always valid
        }

        // Calculate price deviation with improved precision
        uint256 deviation;
        if (executionPrice > marketPrice) {
            // Calculate percentage above market price
            // deviation = ((executionPrice - marketPrice) * 10000) / marketPrice
            uint256 priceDifference = executionPrice - marketPrice;

            // Check for overflow in multiplication
            require(
                priceDifference <= type(uint256).max / 10000,
                "Price difference too large"
            );

            deviation = (priceDifference * 10000) / marketPrice;
        } else {
            // Calculate percentage below market price
            // deviation = ((marketPrice - executionPrice) * 10000) / marketPrice
            uint256 priceDifference = marketPrice - executionPrice;

            // Check for overflow in multiplication
            require(
                priceDifference <= type(uint256).max / 10000,
                "Price difference too large"
            );

            deviation = (priceDifference * 10000) / marketPrice;
        }

        return deviation <= maxPriceDeviation;
    }

    /// @notice Gets the reserves for a token pair from Uniswap V2
    /// @param token Address of the token
    /// @return reserveETH ETH reserve amount
    /// @return reserveToken Token reserve amount
    function getReserves(
        address token
    ) public view returns (uint256 reserveETH, uint256 reserveToken) {
        address routerAddress = gradientRegistry.router();
        require(routerAddress != address(0), "Router not set");

        IUniswapV2Router02 router = IUniswapV2Router02(routerAddress);
        address factory = router.factory();
        address weth = router.WETH();

        // Get pair address
        address pairAddress = IUniswapV2Factory(factory).getPair(token, weth);
        require(pairAddress != address(0), "Pair does not exist");

        // Get reserves
        (uint112 reserve0, uint112 reserve1, ) = IUniswapV2Pair(pairAddress)
            .getReserves();
        address token0 = IUniswapV2Pair(pairAddress).token0();

        (reserveETH, reserveToken) = token0 == token
            ? (reserve1, reserve0)
            : (reserve0, reserve1);
    }

    /// @notice Gets price from Uniswap V2 pool
    /// @param token Address of the token
    /// @return price Price in ETH per token (18 decimals), or 0 if pool doesn't exist
    function getPriceFromV2(
        address token
    ) internal view returns (uint256 price) {
        try this.getReserves(token) returns (
            uint256 reserveETH,
            uint256 reserveToken
        ) {
            if (reserveETH == 0 || reserveToken == 0) {
                return 0;
            }

            // Calculate price: ETH per token (18 decimals)
            uint8 decimals = IERC20Metadata(token).decimals();

            if (decimals == 18) {
                price = (reserveETH * 1e18) / reserveToken;
            } else if (decimals < 18) {
                uint256 scalingFactor = 10 ** (18 - decimals);
                uint256 scaledReserveETH = reserveETH / scalingFactor;
                price = (scaledReserveETH * 1e18) / reserveToken;
            } else {
                uint256 scalingFactor = 10 ** (decimals - 18);
                uint256 scaledReserveToken = reserveToken / scalingFactor;
                price = (reserveETH * 1e18) / scaledReserveToken;
            }
        } catch {
            return 0;
        }
    }

    /// @notice Updates the maximum allowed price deviation from market price
    /// @param newDeviation New maximum price deviation in basis points (1 = 0.01%)
    /// @dev Only callable by contract owner
    function updateMaxPriceDeviation(uint256 newDeviation) external onlyOwner {
        require(newDeviation <= 10000, "Deviation too high");
        uint256 oldDeviation = maxPriceDeviation;
        maxPriceDeviation = newDeviation;
        emit MaxPriceDeviationUpdated(oldDeviation, newDeviation);
    }

    /// @notice Sets the Uniswap V3 Factory address
    /// @param _uniswapV3Factory Address of the Uniswap V3 Factory
    /// @dev Only callable by contract owner
    function setUniswapV3Factory(address _uniswapV3Factory) external onlyOwner {
        require(_uniswapV3Factory != address(0), "Invalid factory address");
        address oldFactory = uniswapV3Factory;
        uniswapV3Factory = _uniswapV3Factory;
        emit UniswapV3FactoryUpdated(oldFactory, _uniswapV3Factory);
    }

    /// @notice Sets the Uniswap V3 Price Helper address
    /// @param _uniswapV3PriceHelper Address of the Uniswap V3 Price Helper
    /// @dev Only callable by contract owner
    function setUniswapV3PriceHelper(
        IUniswapV3PriceHelper _uniswapV3PriceHelper
    ) external onlyOwner {
        require(
            address(_uniswapV3PriceHelper) != address(0),
            "Invalid price helper address"
        );
        uniswapV3PriceHelper = _uniswapV3PriceHelper;
        emit UniswapV3PriceHelperUpdated(address(_uniswapV3PriceHelper));
    }

    /// @notice Sets the V3 fee tiers to check
    /// @param _feeTiers Array of fee tiers (e.g., [500, 3000, 10000] for 0.05%, 0.3%, 1%)
    /// @dev Only callable by contract owner
    function setV3FeeTiers(uint24[] calldata _feeTiers) external onlyOwner {
        require(_feeTiers.length > 0, "Fee tiers array cannot be empty");
        require(_feeTiers.length <= 10, "Too many fee tiers");
        v3FeeTiers = _feeTiers;
        emit V3FeeTiersUpdated(_feeTiers);
    }

    // =============================== EMERGENCY FUNCTIONS ===============================

    /**
     * @notice Emergency function to withdraw ETH from the contract
     * @param recipient Address to receive the ETH
     * @param amount Amount of ETH to withdraw (0 = withdraw all)
     * @dev Only callable by contract owner in emergency situations
     */
    function emergencyWithdrawETH(
        address payable recipient,
        uint256 amount
    ) external onlyOwner {
        require(recipient != address(0), "Invalid recipient");
        require(address(this).balance > 0, "No ETH to withdraw");

        uint256 withdrawAmount = amount == 0 ? address(this).balance : amount;
        require(
            withdrawAmount <= address(this).balance,
            "Insufficient ETH balance"
        );

        (bool success, ) = recipient.call{value: withdrawAmount}("");
        require(success, "ETH withdrawal failed");

        emit EmergencyWithdrawETH(recipient, withdrawAmount);
    }

    /**
     * @notice Emergency function to withdraw ERC20 tokens from the contract
     * @param token Address of the token to withdraw
     * @param recipient Address to receive the tokens
     * @param amount Amount of tokens to withdraw (0 = withdraw all)
     * @dev Only callable by contract owner in emergency situations
     */
    function emergencyWithdrawToken(
        address token,
        address recipient,
        uint256 amount
    ) external onlyOwner {
        require(token != address(0), "Invalid token address");
        require(recipient != address(0), "Invalid recipient");

        uint256 balance = IERC20(token).balanceOf(address(this));
        require(balance > 0, "No tokens to withdraw");

        uint256 withdrawAmount = amount == 0 ? balance : amount;
        require(withdrawAmount <= balance, "Insufficient token balance");

        IERC20(token).safeTransfer(recipient, withdrawAmount);

        emit EmergencyWithdrawToken(token, recipient, withdrawAmount);
    }

    /**
     * @notice Emergency function to withdraw multiple tokens at once
     * @param tokens Array of token addresses to withdraw
     * @param recipient Address to receive all tokens
     * @dev Only callable by contract owner in emergency situations
     * @dev More gas efficient than calling emergencyWithdrawToken multiple times
     */
    function emergencyWithdrawMultipleTokens(
        address[] calldata tokens,
        address recipient
    ) external onlyOwner {
        require(recipient != address(0), "Invalid recipient");
        require(tokens.length > 0, "No tokens specified");
        require(tokens.length <= 20, "Too many tokens to withdraw at once");

        for (uint256 i = 0; i < tokens.length; i++) {
            address token = tokens[i];
            require(token != address(0), "Invalid token address");

            uint256 balance = IERC20(token).balanceOf(address(this));
            if (balance > 0) {
                IERC20(token).safeTransfer(recipient, balance);
                emit EmergencyWithdrawToken(token, recipient, balance);
            }
        }
    }

    // Events for emergency withdrawals
    event EmergencyWithdrawETH(address indexed recipient, uint256 amount);
    event EmergencyWithdrawToken(
        address indexed token,
        address indexed recipient,
        uint256 amount
    );
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol)

pragma solidity ^0.8.20;

import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";

/**
 * @title IERC1363
 * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
 *
 * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
 * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
 */
interface IERC1363 is IERC20, IERC165 {
    /*
     * Note: the ERC-165 identifier for this interface is 0xb0202a11.
     * 0xb0202a11 ===
     *   bytes4(keccak256('transferAndCall(address,uint256)')) ^
     *   bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
     */

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @param data Additional data with no specified format, sent in call to `spender`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}

File 4 of 29 : IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../utils/introspection/IERC165.sol";

File 5 of 29 : IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../token/ERC20/IERC20.sol";

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC-20 standard.
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC-20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    /**
     * @dev An operation with an ERC-20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
        return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
        return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     *
     * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
     * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
     * set here.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            safeTransfer(token, to, value);
        } else if (!token.transferAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
     * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferFromAndCallRelaxed(
        IERC1363 token,
        address from,
        address to,
        uint256 value,
        bytes memory data
    ) internal {
        if (to.code.length == 0) {
            safeTransferFrom(token, from, to, value);
        } else if (!token.transferFromAndCall(from, to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
     * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
     * once without retrying, and relies on the returned value to be true.
     *
     * Reverts if the returned value is other than `true`.
     */
    function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            forceApprove(token, to, value);
        } else if (!token.approveAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            // bubble errors
            if iszero(success) {
                let ptr := mload(0x40)
                returndatacopy(ptr, 0, returndatasize())
                revert(ptr, returndatasize())
            }
            returnSize := returndatasize()
            returnValue := mload(0)
        }

        if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0)
        }
        return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Create2.sol)

pragma solidity ^0.8.20;

import {Errors} from "./Errors.sol";

/**
 * @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
 * `CREATE2` can be used to compute in advance the address where a smart
 * contract will be deployed, which allows for interesting new mechanisms known
 * as 'counterfactual interactions'.
 *
 * See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more
 * information.
 */
library Create2 {
    /**
     * @dev There's no code to deploy.
     */
    error Create2EmptyBytecode();

    /**
     * @dev Deploys a contract using `CREATE2`. The address where the contract
     * will be deployed can be known in advance via {computeAddress}.
     *
     * The bytecode for a contract can be obtained from Solidity with
     * `type(contractName).creationCode`.
     *
     * Requirements:
     *
     * - `bytecode` must not be empty.
     * - `salt` must have not been used for `bytecode` already.
     * - the factory must have a balance of at least `amount`.
     * - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
     */
    function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) {
        if (address(this).balance < amount) {
            revert Errors.InsufficientBalance(address(this).balance, amount);
        }
        if (bytecode.length == 0) {
            revert Create2EmptyBytecode();
        }
        assembly ("memory-safe") {
            addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
            // if no address was created, and returndata is not empty, bubble revert
            if and(iszero(addr), not(iszero(returndatasize()))) {
                let p := mload(0x40)
                returndatacopy(p, 0, returndatasize())
                revert(p, returndatasize())
            }
        }
        if (addr == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
     * `bytecodeHash` or `salt` will result in a new destination address.
     */
    function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
        return computeAddress(salt, bytecodeHash, address(this));
    }

    /**
     * @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
     * `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
     */
    function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) {
        assembly ("memory-safe") {
            let ptr := mload(0x40) // Get free memory pointer

            // |                   | ↓ ptr ...  ↓ ptr + 0x0B (start) ...  ↓ ptr + 0x20 ...  ↓ ptr + 0x40 ...   |
            // |-------------------|---------------------------------------------------------------------------|
            // | bytecodeHash      |                                                        CCCCCCCCCCCCC...CC |
            // | salt              |                                      BBBBBBBBBBBBB...BB                   |
            // | deployer          | 000000...0000AAAAAAAAAAAAAAAAAAA...AA                                     |
            // | 0xFF              |            FF                                                             |
            // |-------------------|---------------------------------------------------------------------------|
            // | memory            | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC |
            // | keccak(start, 85) |            ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ |

            mstore(add(ptr, 0x40), bytecodeHash)
            mstore(add(ptr, 0x20), salt)
            mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes
            let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff
            mstore8(start, 0xff)
            addr := and(keccak256(start, 85), 0xffffffffffffffffffffffffffffffffffffffff)
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/cryptography/Hashes.sol)

pragma solidity ^0.8.20;

/**
 * @dev Library of standard hash functions.
 *
 * _Available since v5.1._
 */
library Hashes {
    /**
     * @dev Commutative Keccak256 hash of a sorted pair of bytes32. Frequently used when working with merkle proofs.
     *
     * NOTE: Equivalent to the `standardNodeHash` in our https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
     */
    function commutativeKeccak256(bytes32 a, bytes32 b) internal pure returns (bytes32) {
        return a < b ? efficientKeccak256(a, b) : efficientKeccak256(b, a);
    }

    /**
     * @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory.
     */
    function efficientKeccak256(bytes32 a, bytes32 b) internal pure returns (bytes32 value) {
        assembly ("memory-safe") {
            mstore(0x00, a)
            mstore(0x20, b)
            value := keccak256(0x00, 0x40)
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/MerkleProof.sol)
// This file was procedurally generated from scripts/generate/templates/MerkleProof.js.

pragma solidity ^0.8.20;

import {Hashes} from "./Hashes.sol";

/**
 * @dev These functions deal with verification of Merkle Tree proofs.
 *
 * The tree and the proofs can be generated using our
 * https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
 * You will find a quickstart guide in the readme.
 *
 * WARNING: You should avoid using leaf values that are 64 bytes long prior to
 * hashing, or use a hash function other than keccak256 for hashing leaves.
 * This is because the concatenation of a sorted pair of internal nodes in
 * the Merkle tree could be reinterpreted as a leaf value.
 * OpenZeppelin's JavaScript library generates Merkle trees that are safe
 * against this attack out of the box.
 *
 * IMPORTANT: Consider memory side-effects when using custom hashing functions
 * that access memory in an unsafe way.
 *
 * NOTE: This library supports proof verification for merkle trees built using
 * custom _commutative_ hashing functions (i.e. `H(a, b) == H(b, a)`). Proving
 * leaf inclusion in trees built using non-commutative hashing functions requires
 * additional logic that is not supported by this library.
 */
library MerkleProof {
    /**
     *@dev The multiproof provided is not valid.
     */
    error MerkleProofInvalidMultiproof();

    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     *
     * This version handles proofs in memory with the default hashing function.
     */
    function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        return processProof(proof, leaf) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leaves & pre-images are assumed to be sorted.
     *
     * This version handles proofs in memory with the default hashing function.
     */
    function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     *
     * This version handles proofs in memory with a custom hashing function.
     */
    function verify(
        bytes32[] memory proof,
        bytes32 root,
        bytes32 leaf,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bool) {
        return processProof(proof, leaf, hasher) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leaves & pre-images are assumed to be sorted.
     *
     * This version handles proofs in memory with a custom hashing function.
     */
    function processProof(
        bytes32[] memory proof,
        bytes32 leaf,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = hasher(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     *
     * This version handles proofs in calldata with the default hashing function.
     */
    function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        return processProofCalldata(proof, leaf) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leaves & pre-images are assumed to be sorted.
     *
     * This version handles proofs in calldata with the default hashing function.
     */
    function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     *
     * This version handles proofs in calldata with a custom hashing function.
     */
    function verifyCalldata(
        bytes32[] calldata proof,
        bytes32 root,
        bytes32 leaf,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bool) {
        return processProofCalldata(proof, leaf, hasher) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leaves & pre-images are assumed to be sorted.
     *
     * This version handles proofs in calldata with a custom hashing function.
     */
    function processProofCalldata(
        bytes32[] calldata proof,
        bytes32 leaf,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = hasher(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
     * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
     *
     * This version handles multiproofs in memory with the default hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
     * The `leaves` must be validated independently. See {processMultiProof}.
     */
    function multiProofVerify(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProof(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
     * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
     * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
     * respectively.
     *
     * This version handles multiproofs in memory with the default hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
     * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
     * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
     *
     * NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
     * and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
     * validating the leaves elsewhere.
     */
    function processMultiProof(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32[] memory leaves
    ) internal pure returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the Merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofFlagsLen = proofFlags.length;

        // Check proof validity.
        if (leavesLen + proof.length != proofFlagsLen + 1) {
            revert MerkleProofInvalidMultiproof();
        }

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](proofFlagsLen);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < proofFlagsLen; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = Hashes.commutativeKeccak256(a, b);
        }

        if (proofFlagsLen > 0) {
            if (proofPos != proof.length) {
                revert MerkleProofInvalidMultiproof();
            }
            unchecked {
                return hashes[proofFlagsLen - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    /**
     * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
     * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
     *
     * This version handles multiproofs in memory with a custom hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
     * The `leaves` must be validated independently. See {processMultiProof}.
     */
    function multiProofVerify(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32 root,
        bytes32[] memory leaves,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bool) {
        return processMultiProof(proof, proofFlags, leaves, hasher) == root;
    }

    /**
     * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
     * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
     * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
     * respectively.
     *
     * This version handles multiproofs in memory with a custom hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
     * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
     * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
     *
     * NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
     * and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
     * validating the leaves elsewhere.
     */
    function processMultiProof(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32[] memory leaves,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the Merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofFlagsLen = proofFlags.length;

        // Check proof validity.
        if (leavesLen + proof.length != proofFlagsLen + 1) {
            revert MerkleProofInvalidMultiproof();
        }

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](proofFlagsLen);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < proofFlagsLen; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = hasher(a, b);
        }

        if (proofFlagsLen > 0) {
            if (proofPos != proof.length) {
                revert MerkleProofInvalidMultiproof();
            }
            unchecked {
                return hashes[proofFlagsLen - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    /**
     * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
     * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
     *
     * This version handles multiproofs in calldata with the default hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
     * The `leaves` must be validated independently. See {processMultiProofCalldata}.
     */
    function multiProofVerifyCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProofCalldata(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
     * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
     * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
     * respectively.
     *
     * This version handles multiproofs in calldata with the default hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
     * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
     * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
     *
     * NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
     * and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
     * validating the leaves elsewhere.
     */
    function processMultiProofCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32[] memory leaves
    ) internal pure returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the Merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofFlagsLen = proofFlags.length;

        // Check proof validity.
        if (leavesLen + proof.length != proofFlagsLen + 1) {
            revert MerkleProofInvalidMultiproof();
        }

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](proofFlagsLen);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < proofFlagsLen; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = Hashes.commutativeKeccak256(a, b);
        }

        if (proofFlagsLen > 0) {
            if (proofPos != proof.length) {
                revert MerkleProofInvalidMultiproof();
            }
            unchecked {
                return hashes[proofFlagsLen - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    /**
     * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
     * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
     *
     * This version handles multiproofs in calldata with a custom hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
     * The `leaves` must be validated independently. See {processMultiProofCalldata}.
     */
    function multiProofVerifyCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32 root,
        bytes32[] memory leaves,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bool) {
        return processMultiProofCalldata(proof, proofFlags, leaves, hasher) == root;
    }

    /**
     * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
     * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
     * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
     * respectively.
     *
     * This version handles multiproofs in calldata with a custom hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
     * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
     * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
     *
     * NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
     * and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
     * validating the leaves elsewhere.
     */
    function processMultiProofCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32[] memory leaves,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the Merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofFlagsLen = proofFlags.length;

        // Check proof validity.
        if (leavesLen + proof.length != proofFlagsLen + 1) {
            revert MerkleProofInvalidMultiproof();
        }

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](proofFlagsLen);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < proofFlagsLen; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = hasher(a, b);
        }

        if (proofFlagsLen > 0) {
            if (proofPos != proof.length) {
                revert MerkleProofInvalidMultiproof();
            }
            unchecked {
                return hashes[proofFlagsLen - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }
}

File 13 of 29 : Errors.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of common custom errors used in multiple contracts
 *
 * IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
 * It is recommended to avoid relying on the error API for critical functionality.
 *
 * _Available since v5.1._
 */
library Errors {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error InsufficientBalance(uint256 balance, uint256 needed);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedCall();

    /**
     * @dev The deployment failed.
     */
    error FailedDeployment();

    /**
     * @dev A necessary precompile is missing.
     */
    error MissingPrecompile(address);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[ERC].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    uint256 private _status;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    constructor() {
        _status = NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        if (_status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        // Any calls to nonReentrant after this point will fail
        _status = ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == ENTERED;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
import {IGradientRegistry} from "./interfaces/IGradientRegistry.sol";
import {GradientMarketMakerPoolV3} from "./GradientMarketMakerPoolV3.sol";
import {IEventAggregator} from "./interfaces/IEventAggregator.sol";
import {IGradientMarketMakerPoolV3} from "./interfaces/IGradientMarketMakerPoolV3.sol";

// Custom errors to save gas and reduce contract size
error InvalidRegistry();
error InvalidEventAggregator();
error InvalidTokenAddress();
error PoolAlreadyExists();
error TokenBlocked();
error EthAmountMismatch();
error TokenAmountZero();
error InvalidUniv3Helper();

/**
 * @title GradientMarketMakerFactory
 * @notice Factory contract for deploying individual token market maker pools
 * @dev Similar to Uniswap V2 Factory pattern - one pool per token
 */
contract GradientMarketMakerFactory is Ownable {
    using SafeERC20 for IERC20;
    IGradientRegistry public immutable gradientRegistry;
    IEventAggregator public eventAggregator;
    address public univ3Helper;

    // Mapping from token address to pool address
    mapping(address => address) public getPool;

    // Reverse mapping from pool address to token address
    mapping(address => address) public getToken;

    // Array of all pools
    address[] public allPools;

    // Events
    event PoolCreated(
        address indexed token,
        address indexed pool,
        uint256 poolIndex
    );
    event EventAggregatorUpdated(
        address indexed oldEventAggregator,
        address indexed newEventAggregator
    );
    event Univ3HelperUpdated(
        address indexed oldUniv3Helper,
        address indexed newUniv3Helper
    );

    constructor(
        IGradientRegistry _gradientRegistry,
        IEventAggregator _eventAggregator
    ) Ownable(msg.sender) {
        if (address(_gradientRegistry) == address(0)) revert InvalidRegistry();
        gradientRegistry = _gradientRegistry;

        if (address(_eventAggregator) != address(0)) {
            if (address(_eventAggregator).code.length == 0)
                revert InvalidEventAggregator();
        }

        eventAggregator = _eventAggregator;
    }

    /**
     * @notice Set the EventAggregator address
     * @param _eventAggregator New EventAggregator address
     */
    function setEventAggregator(
        IEventAggregator _eventAggregator
    ) external onlyOwner {
        if (
            address(_eventAggregator) == address(0) ||
            address(_eventAggregator).code.length == 0
        ) revert InvalidEventAggregator();
        address oldEventAggregator = address(eventAggregator);
        eventAggregator = _eventAggregator;
        emit EventAggregatorUpdated(
            oldEventAggregator,
            address(_eventAggregator)
        );
    }

    /**
     * @notice Set the UniswapV3PriceHelper address
     * @param _univ3Helper New UniswapV3PriceHelper address
     */
    function setUniv3Helper(address _univ3Helper) external onlyOwner {
        if (_univ3Helper != address(0) && _univ3Helper.code.length == 0)
            revert InvalidUniv3Helper();
        address oldUniv3Helper = univ3Helper;
        univ3Helper = _univ3Helper;
        emit Univ3HelperUpdated(oldUniv3Helper, _univ3Helper);
    }

    /**
     * @notice Calculate the salt for CREATE2 deployment
     * @param token Address of the token
     * @return salt The calculated salt
     */
    function _calculateSalt(
        address token
    ) internal pure returns (bytes32 salt) {
        return keccak256(abi.encodePacked(token));
    }

    /**
     * @notice Get the bytecode for GradientMarketMakerPool with constructor arguments
     * @param token Address of the token
     * @return bytecode The complete bytecode for deployment
     */
    function _getPoolBytecode(
        address token
    ) internal view returns (bytes memory bytecode) {
        bytecode = abi.encodePacked(
            type(GradientMarketMakerPoolV3).creationCode,
            abi.encode(IERC20(token), address(this))
        );
    }

    /**
     * @notice Predict the address where a pool will be deployed for a given token
     * @param token Address of the token
     * @return predictedAddress The predicted pool address
     */
    function predictPoolAddress(
        address token
    ) external view returns (address predictedAddress) {
        bytes32 salt = _calculateSalt(token);
        bytes memory bytecode = _getPoolBytecode(token);
        predictedAddress = Create2.computeAddress(salt, keccak256(bytecode));
    }

    /**
     * @notice Get the number of pools created
     * @return Number of pools
     */
    function allPoolsLength() external view returns (uint256) {
        return allPools.length;
    }

    /**
     * @notice Create a new market maker pool for a token using CREATE2
     * @param token Address of the token
     * @return pool Address of the created pool
     */
    function createPool(address token) external returns (address pool) {
        if (token == address(0)) revert InvalidTokenAddress();
        if (token.code.length == 0) revert InvalidTokenAddress();
        if (getPool[token] != address(0)) revert PoolAlreadyExists();
        if (gradientRegistry.blockedTokens(token)) revert TokenBlocked();

        // Calculate salt and bytecode for CREATE2
        bytes32 salt = _calculateSalt(token);
        bytes memory bytecode = _getPoolBytecode(token);

        // Deploy pool using CREATE2
        pool = Create2.deploy(0, salt, bytecode);

        // Store pool address
        getPool[token] = pool;
        getToken[pool] = token;
        allPools.push(pool);

        try eventAggregator.emitPoolCreated(token, pool) {
            // Success - EventAggregator call completed
        } catch {
            // EventAggregator call failed - continue execution
        }
    }

    /**
     * @notice Create a new market maker pool for a token with initial liquidity
     * @param token Address of the token
     * @param initialEthAmount Amount of ETH to add as initial liquidity
     * @param initialTokenAmount Amount of tokens to add as initial liquidity
     * @param minPrice Minimum price for liquidity range
     * @param maxPrice Maximum price for liquidity range
     * @return pool Address of the created pool
     */
    function createPoolWithLiquidity(
        address token,
        uint256 initialEthAmount,
        uint256 initialTokenAmount,
        uint256 minPrice,
        uint256 maxPrice
    ) external payable returns (address pool) {
        if (token == address(0)) revert InvalidTokenAddress();
        if (token.code.length == 0) revert InvalidTokenAddress();
        if (getPool[token] != address(0)) revert PoolAlreadyExists();
        if (gradientRegistry.blockedTokens(token)) revert TokenBlocked();
        if (msg.value != initialEthAmount) revert EthAmountMismatch();

        // Create the pool using CREATE2
        bytes32 salt = _calculateSalt(token);
        bytes memory bytecode = _getPoolBytecode(token);
        pool = Create2.deploy(0, salt, bytecode);

        // Store pool address
        getPool[token] = pool;
        getToken[pool] = token;
        allPools.push(pool);

        // Add initial liquidity for the specified user
        if (initialEthAmount > 0) {
            IGradientMarketMakerPoolV3(pool).addETHLiquidityForUser{
                value: initialEthAmount
            }(msg.sender, minPrice, maxPrice);
        }

        if (initialTokenAmount > 0) {
            // Transfer tokens from caller to factory first
            IERC20(token).safeTransferFrom(
                msg.sender,
                address(this),
                initialTokenAmount
            );
            // Approve pool to spend tokens
            IERC20(token).forceApprove(pool, initialTokenAmount);
            // Add token liquidity for the specified user
            IGradientMarketMakerPoolV3(pool).addTokenLiquidityForUser(
                msg.sender,
                initialTokenAmount,
                minPrice,
                maxPrice
            );
            // Reset allowance to zero to prevent future unexpected pulls
            IERC20(token).forceApprove(pool, 0);
        }

        try eventAggregator.emitPoolCreated(token, pool) {
            // Success - EventAggregator call completed
        } catch {
            // EventAggregator call failed - continue execution
        }
    }

    /**
     * @notice Check if a pool exists for a token
     * @param token Address of the token
     * @return exists True if pool exists
     */
    function poolExists(address token) external view returns (bool exists) {
        return getPool[token] != address(0);
    }

    /**
     * @notice Get all deployed pools
     * @return allPoolAddresses Array of all pool addresses
     */
    function getAllPools()
        external
        view
        returns (address[] memory allPoolAddresses)
    {
        return allPools;
    }

    /**
     * @notice Check if a given address is a valid pool
     * @param poolAddress Address to check
     * @return isValid True if the address is a valid pool
     */
    function isValidPool(
        address poolAddress
    ) external view returns (bool isValid) {
        return getToken[poolAddress] != address(0);
    }

    /**
     * @notice Get the registry address
     * @return registryAddress Address of the GradientRegistry
     */
    function getRegistry() external view returns (address) {
        return address(gradientRegistry);
    }

    /**
     * @notice Get the event aggregator address
     * @return eventAggregatorAddress Address of the EventAggregator
     */
    function getEventAggregator() external view returns (address) {
        return address(eventAggregator);
    }

    // =============================== EMERGENCY FUNCTIONS ===============================

    /**
     * @notice Emergency function to withdraw ETH from the contract
     * @param recipient Address to receive the ETH
     * @param amount Amount of ETH to withdraw (0 = withdraw all)
     * @dev Only callable by contract owner in emergency situations
     */
    function emergencyWithdrawETH(
        address payable recipient,
        uint256 amount
    ) external onlyOwner {
        require(recipient != address(0), "Invalid recipient");
        require(address(this).balance > 0, "No ETH to withdraw");

        uint256 withdrawAmount = amount == 0 ? address(this).balance : amount;
        require(
            withdrawAmount <= address(this).balance,
            "Insufficient ETH balance"
        );

        (bool success, ) = recipient.call{value: withdrawAmount}("");
        require(success, "ETH withdrawal failed");

        emit EmergencyWithdrawETH(recipient, withdrawAmount);
    }

    /**
     * @notice Emergency function to withdraw ERC20 tokens from the contract
     * @param token Address of the token to withdraw
     * @param recipient Address to receive the tokens
     * @param amount Amount of tokens to withdraw (0 = withdraw all)
     * @dev Only callable by contract owner in emergency situations
     */
    function emergencyWithdrawToken(
        address token,
        address recipient,
        uint256 amount
    ) external onlyOwner {
        require(token != address(0), "Invalid token address");
        require(recipient != address(0), "Invalid recipient");

        uint256 balance = IERC20(token).balanceOf(address(this));
        require(balance > 0, "No tokens to withdraw");

        uint256 withdrawAmount = amount == 0 ? balance : amount;
        require(withdrawAmount <= balance, "Insufficient token balance");

        IERC20(token).safeTransfer(recipient, withdrawAmount);

        emit EmergencyWithdrawToken(token, recipient, withdrawAmount);
    }

    /**
     * @notice Emergency function to withdraw multiple tokens at once
     * @param tokens Array of token addresses to withdraw
     * @param recipient Address to receive all tokens
     * @dev Only callable by contract owner in emergency situations
     * @dev More gas efficient than calling emergencyWithdrawToken multiple times
     */
    function emergencyWithdrawMultipleTokens(
        address[] calldata tokens,
        address recipient
    ) external onlyOwner {
        require(recipient != address(0), "Invalid recipient");
        require(tokens.length > 0, "No tokens specified");
        require(tokens.length <= 20, "Too many tokens to withdraw at once");

        for (uint256 i = 0; i < tokens.length; i++) {
            address token = tokens[i];
            require(token != address(0), "Invalid token address");

            uint256 balance = IERC20(token).balanceOf(address(this));
            if (balance > 0) {
                IERC20(token).safeTransfer(recipient, balance);
                emit EmergencyWithdrawToken(token, recipient, balance);
            }
        }
    }

    // Events for emergency withdrawals
    event EmergencyWithdrawETH(address indexed recipient, uint256 amount);
    event EmergencyWithdrawToken(
        address indexed token,
        address indexed recipient,
        uint256 amount
    );
}

File 17 of 29 : GradientMarketMakerPoolV3.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import {IGradientRegistry} from "./interfaces/IGradientRegistry.sol";
import {IGradientMarketMakerFactory} from "./interfaces/IGradientMarketMakerFactory.sol";
import {IUniswapV2Pair} from "./interfaces/IUniswapV2Pair.sol";
import {IUniswapV2Router02} from "./interfaces/IUniswapV2Router.sol";
import {IUniswapV2Factory} from "./interfaces/IUniswapV2Factory.sol";
import {IUniswapV3PriceHelper} from "./interfaces/IUniswapV3PriceHelper.sol";
import {IEventAggregator} from "./interfaces/IEventAggregator.sol";
import {IGradientMarketMakerPoolV3} from "./interfaces/IGradientMarketMakerPoolV3.sol";

// Custom errors
error InvalidTokenAddress();
error TokenBlocked();
error OnlyRewardDistributor();
error OnlyOrderbook();
error OnlyFactory();
error OnlyOwner();
error UnsupportedTokenDecimals();
error AmountZero();
error InsufficientShares();
error InsufficientPoolBalance();
error InsufficientWithdrawal();
error ETHTransferFailed();
error ETHTransferToOrderbookFailed();
error VersionAlreadyProcessed();
error VersionNotAvailable();
error InvalidMerkleProof();
error NoMerkleRootForUpdates();
error InvalidSharesPercentage();
error NoLiquidity();
error NoLiquidityToWithdraw();
error NoSharesToBurn();
error InsufficientSharesToBurn();
error NoRewards();
error NoETHLiquidityOrRewards();
error NoTokenLiquidityOrRewards();
error NoTokenProviderRewards();
error InvalidRecipient();
error InsufficientETHBalance();
error InsufficientTokenBalance();
error ETHWithdrawalFailed();
error TokenWithdrawalFailed();
error RouterNotSet();
error PairDoesNotExist();
error OverflowInETHRewardCalculation();
error OverflowInTokenProviderRewardCalculation();
error OverflowInTokenRewardCalculation();
error ETHAmountMismatch();
error InsufficientTokenLiquidity();
error InsufficientETHLiquidity();
error ETHAmountBelowMinimum();
error TokenAmountBelowMinimum();
error NoETHSent();
error NoLiquidityOrRewards();
error InvalidMinLiquidity();
error InvalidMinTokenLiquidity();
error InvalidPriceRange();
error PriceOutOfRange();
error InvalidPriceOrder();
error OverlappingPriceRange();

/**
 * @title GradientMarketMakerPoolV3
 * @notice Individual pool contract for a single token with price range functionality
 * @dev Each token gets its own pool contract with concentrated liquidity price ranges
 * @dev Users can specify min/max price ranges when adding liquidity
 * @dev Enhanced version with better gas optimization and security
 */
contract GradientMarketMakerPoolV3 is ReentrancyGuard {
    using SafeERC20 for IERC20;

    // Price range struct for concentrated liquidity
    struct PriceRange {
        uint256 minPrice; // Minimum price (in wei per token)
        uint256 maxPrice; // Maximum price (in wei per token)
        bool isActive; // Whether this range is active
    }

    // Provider position with price range support
    struct ProviderPosition {
        uint256 position;
        uint256 rewardDebt;
        uint256 pendingRewards;
        uint256 lastUpdateVersion;
    }

    // Pool metrics with price range support
    struct PoolMetrics {
        uint256 position; // Total ETH or tokens
        uint256 accRewardPerShare; // Accumulated rewards per share
        uint256 rewardBalance; // Available reward balance
        uint256 accountedPosition; // Accounted position for calculations
    }

    // Events
    event LiquidityDeposited(
        address indexed user,
        address indexed token,
        uint256 amount,
        uint256 positionMinted,
        bool isETH,
        uint256 minPrice,
        uint256 maxPrice
    );

    event LiquidityWithdrawn(
        address indexed user,
        address indexed token,
        uint256 amount,
        uint256 positionBurned,
        bool isETH,
        uint256 minPrice,
        uint256 maxPrice
    );

    event PoolFeeDistributed(
        address indexed from,
        uint256 amount,
        address indexed token,
        bool isETH
    );

    event FeeClaimed(
        address indexed user,
        uint256 amount,
        address indexed token,
        bool isETH
    );

    event FeeRefunded(address indexed recipient, uint256 amount, bool isETH);

    event PoolBalanceUpdated(
        address indexed token,
        uint256 newTotalETH,
        uint256 newTotalTokens,
        uint256 newETHLPShares,
        uint256 newTokenLPShares
    );

    event PriceRangeUpdated(
        address indexed user,
        uint256 oldMinPrice,
        uint256 oldMaxPrice,
        uint256 newMinPrice,
        uint256 newMaxPrice,
        bool isETH
    );

    event MerkleRootUpdated(uint256 indexed version, bytes32 merkleRoot);

    event UserPositionUpdated(
        address indexed user,
        uint256 indexed version,
        uint256 newETHPositions,
        uint256 newTokenPositions
    );

    event MinLiquidityUpdated(uint256 newMinLiquidity, bool isETH);

    event EmergencyWithdraw(
        address indexed token,
        address indexed recipient,
        uint256 amount,
        bool isETH
    );

    // Position events
    event PositionCreated(
        uint256 indexed positionId,
        address indexed owner,
        uint256 amount,
        uint256 minPrice,
        uint256 maxPrice,
        bool isETH
    );

    event PositionUpdated(
        uint256 indexed positionId,
        address indexed owner,
        uint256 newAmount,
        uint256 newMinPrice,
        uint256 newMaxPrice
    );

    event PositionClosed(
        uint256 indexed positionId,
        address indexed owner,
        uint256 amount,
        uint256 rewards
    );

    // Immutable token address - this pool is dedicated to one token
    IERC20 public immutable tokenContract;
    IGradientMarketMakerFactory public immutable factoryContract;

    // Pool state with price range support
    uint256 public totalETH;
    uint256 public totalTokens;

    // Single position per user (like V2 but with price range)
    mapping(address => ProviderPosition) public ethProviders;
    mapping(address => ProviderPosition) public tokenProviders;

    // User price ranges (shared between ETH and token positions)
    mapping(address => PriceRange) public userPriceRanges;

    // Reward tracking - separate ETH pools for each provider type (same as V2)
    uint256 public accRewardPerShare; // For ETH providers (ETH rewards)
    uint256 public accTokenRewardPerShare; // For token providers (token rewards)
    uint256 public rewardBalance; // ETH rewards for ETH providers
    uint256 public tokenProviderRewardBalance; // ETH rewards for token providers

    uint256 public constant SCALE = 1e18;

    // Maximum supported token decimals to prevent overflow
    uint8 public constant MAX_TOKEN_DECIMALS = 24;

    uint8 public tokenDecimals;

    // Configurable minimum liquidity requirements
    uint256 public minLiquidity;
    uint256 public minTokenLiquidity;

    // Track totals for this specific token pool
    uint256 public totalEthAdded;
    uint256 public totalEthRemoved;
    uint256 public totalTokensAdded;
    uint256 public totalTokensRemoved; // Total tokens removed from this pool
    uint256 public totalTokenRewardsDistributed; // Total token rewards distributed

    // Uniswap pair address
    address public uniswapPair;

    // Merkle root for LP share updates
    bytes32 public merkleRoot;
    uint256 public currentVersion;
    mapping(uint256 => bytes32) public versionMerkleRoots;

    // Position limits

    // Events are inherited from interface

    modifier isNotBlocked() {
        if (getRegistry().blockedTokens(address(tokenContract)))
            revert TokenBlocked();
        _;
    }

    modifier onlyRewardDistributor() {
        if (!getRegistry().isRewardDistributor(msg.sender))
            revert OnlyRewardDistributor();
        _;
    }

    modifier onlyOrderbook() {
        if (msg.sender != getRegistry().orderbook()) revert OnlyOrderbook();
        _;
    }

    modifier onlyFactory() {
        if (msg.sender != address(factoryContract)) revert OnlyFactory();
        _;
    }

    modifier onlyOwner() {
        if (msg.sender != factoryContract.owner()) revert OnlyOwner();
        _;
    }

    constructor(IERC20 _token, address _factory) {
        if (address(_token) == address(0)) revert InvalidTokenAddress();
        if (_factory == address(0)) revert InvalidRecipient();

        tokenContract = _token;
        factoryContract = IGradientMarketMakerFactory(_factory);

        tokenDecimals = IERC20Metadata(address(_token)).decimals();

        // Validate token decimals to prevent overflow
        if (tokenDecimals > MAX_TOKEN_DECIMALS) {
            revert UnsupportedTokenDecimals();
        }

        minLiquidity = 1000000000000; // 0.000001 ETH minimum (default)
        minTokenLiquidity = 2 * (10 ** tokenDecimals); // Set minimum token liquidity to 2 tokens
    }

    /**
     * @notice Receive ETH for reward distribution
     */
    receive() external payable {}

    /**
     * @notice Get the current owner (factory owner)
     * @return The current owner of the factory
     */
    function owner() public view returns (address) {
        return factoryContract.owner();
    }

    // =============================== INTERNAL FUNCTIONS ===============================

    /**
     * @notice Get the registry address from the factory
     * @return registryAddress Address of the GradientRegistry
     */
    function getRegistry() public view returns (IGradientRegistry) {
        return IGradientRegistry(factoryContract.getRegistry());
    }

    /**
     * @notice Get the event aggregator address from the factory
     * @return eventAggregatorAddress Address of the EventAggregator
     */
    function getEventAggregator() public view returns (IEventAggregator) {
        return IEventAggregator(factoryContract.getEventAggregator());
    }

    /**
     * @notice Get the Uniswap V3 helper address from the factory
     * @return univ3HelperAddress Address of the UniswapV3PriceHelper
     */
    function getUniv3Helper() public view returns (IUniswapV3PriceHelper) {
        address helperAddress = factoryContract.univ3Helper();
        if (helperAddress == address(0)) {
            return IUniswapV3PriceHelper(address(0));
        }
        return IUniswapV3PriceHelper(helperAddress);
    }

    /**
     * @notice Validate price range parameters
     * @param minPrice Minimum price
     * @param maxPrice Maximum price
     */
    function _validatePriceRange(
        uint256 minPrice,
        uint256 maxPrice
    ) internal pure {
        if (minPrice == 0 || maxPrice == 0) revert InvalidPriceRange();
        if (minPrice >= maxPrice) revert InvalidPriceOrder();
    }

    /**
     * @notice Check if current price is within a given range
     * @param price Current price to check
     * @param minPrice Minimum price
     * @param maxPrice Maximum price
     * @return isWithinRange True if price is within range
     */
    function _isPriceWithinRange(
        uint256 price,
        uint256 minPrice,
        uint256 maxPrice
    ) internal pure returns (bool isWithinRange) {
        return price >= minPrice && price <= maxPrice;
    }

    /**
     * @notice Normalize token amount to 18 decimals for consistent calculations
     * @param amount Amount in token decimals
     * @return uint256 Amount normalized to 18 decimals
     */
    function _normalizeTo18Decimals(
        uint256 amount
    ) internal view returns (uint256) {
        if (tokenDecimals == 18) {
            return amount;
        } else if (tokenDecimals < 18) {
            return amount * (10 ** (18 - tokenDecimals));
        } else {
            return amount / (10 ** (tokenDecimals - 18));
        }
    }

    /**
     * @notice Denormalize from 18 decimals to token decimals
     * @param amount Amount in 18 decimals
     * @return uint256 Amount in token decimals
     */
    function _denormalizeFrom18Decimals(
        uint256 amount
    ) internal view returns (uint256) {
        if (tokenDecimals == 18) {
            return amount;
        } else if (tokenDecimals < 18) {
            return amount / (10 ** (18 - tokenDecimals));
        } else {
            return amount * (10 ** (tokenDecimals - 18));
        }
    }

    /**
     * @notice Update merkle root after trade execution
     * @param newMerkleRoot New merkle root to set
     */
    function _updateMerkleRootAfterTrade(bytes32 newMerkleRoot) internal {
        if (newMerkleRoot != bytes32(0)) {
            currentVersion++;
            merkleRoot = newMerkleRoot;
            versionMerkleRoots[currentVersion] = newMerkleRoot;

            try
                getEventAggregator().emitMerkleRootUpdated(
                    currentVersion,
                    newMerkleRoot
                )
            {
                // Success - EventAggregator call completed
            } catch {
                // EventAggregator call failed - continue execution
            }

            emit MerkleRootUpdated(currentVersion, newMerkleRoot);
        }
    }

    /**
     * @notice Verify merkle proof for user position update
     * @param user User address
     * @param proof Merkle proof
     * @param newETHPosition New ETH position value
     * @param newTokenPosition New token position value
     * @param ethRewardsToAdd ETH rewards to add
     * @param tokenRewardsToAdd Token rewards to add
     * @return isValid Whether the proof is valid
     */
    function _verifyMerkleProof(
        address user,
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) internal view returns (bool isValid) {
        bytes32 leaf = keccak256(
            abi.encodePacked(
                user,
                newETHPosition,
                newTokenPosition,
                ethRewardsToAdd,
                tokenRewardsToAdd
            )
        );

        // Special handling for single provider case
        if (proof.length == 0) {
            // For single provider, verify that the leaf equals the merkle root
            return leaf == merkleRoot;
        }

        // Use standard merkle proof verification
        return MerkleProof.verify(proof, merkleRoot, leaf);
    }

    /**
     * @notice Check if user needs position update for either asset type
     * @param user User address to check
     * @return needsUpdate True if user needs position update
     */
    function _needsPositionUpdate(address user) internal view returns (bool) {
        // Check ETH position
        if (
            ethProviders[user].position > 0 &&
            ethProviders[user].lastUpdateVersion < currentVersion
        ) {
            return true;
        }
        if (
            ethProviders[user].pendingRewards > 0 &&
            ethProviders[user].lastUpdateVersion < currentVersion
        ) {
            return true;
        }

        // Check token position
        if (
            tokenProviders[user].position > 0 &&
            tokenProviders[user].lastUpdateVersion < currentVersion
        ) {
            return true;
        }
        if (
            tokenProviders[user].pendingRewards > 0 &&
            tokenProviders[user].lastUpdateVersion < currentVersion
        ) {
            return true;
        }

        return false;
    }

    // =============================== PUBLIC FUNCTIONS ===============================

    /**
     * @notice Add ETH liquidity with price range and optional position update
     * @param minPrice Minimum price for this liquidity position
     * @param maxPrice Maximum price for this liquidity position
     * @param proof Merkle proof for position update (required if user has pending updates)
     * @param newETHPosition New ETH position value (required if proof provided)
     * @param newTokenPosition New token position value (required if proof provided)
     * @param ethRewardsToAdd ETH rewards to add
     * @param tokenRewardsToAdd Token rewards to add
     */
    function addETHLiquidity(
        uint256 minPrice,
        uint256 maxPrice,
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) external payable isNotBlocked nonReentrant {
        if (msg.value < minLiquidity) revert ETHAmountBelowMinimum();
        _validatePriceRange(minPrice, maxPrice);

        // Check if user needs position update - only if they have existing liquidity
        if (_needsPositionUpdate(msg.sender)) {
            // Proof is required - validate parameters
            if (merkleRoot == bytes32(0)) revert NoMerkleRootForUpdates();

            // Verify merkle proof
            if (
                !_verifyMerkleProof(
                    msg.sender,
                    proof,
                    newETHPosition,
                    newTokenPosition,
                    ethRewardsToAdd,
                    tokenRewardsToAdd
                )
            ) revert InvalidMerkleProof();

            // Update user position first
            _updateUserPosition(
                msg.sender,
                newETHPosition,
                newTokenPosition,
                ethRewardsToAdd,
                tokenRewardsToAdd
            );
        }

        // Then add ETH liquidity with price range
        _addETHLiquidity(msg.sender, msg.value, minPrice, maxPrice);
    }

    /**
     * @notice Add token liquidity with price range and optional position update
     * @param tokenAmount Amount of tokens to deposit
     * @param minPrice Minimum price for this liquidity position
     * @param maxPrice Maximum price for this liquidity position
     * @param proof Merkle proof for position update (required if user has pending updates)
     * @param newETHPosition New ETH position value (required if proof provided)
     * @param newTokenPosition New token position value (required if proof provided)
     * @param ethRewardsToAdd ETH rewards to add
     * @param tokenRewardsToAdd Token rewards to add
     */
    function addTokenLiquidity(
        uint256 tokenAmount,
        uint256 minPrice,
        uint256 maxPrice,
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) external isNotBlocked nonReentrant {
        if (tokenAmount < minTokenLiquidity) revert TokenAmountBelowMinimum();
        _validatePriceRange(minPrice, maxPrice);

        // Check if user needs position update - only if they have existing liquidity
        if (_needsPositionUpdate(msg.sender)) {
            // Proof is required - validate parameters
            if (merkleRoot == bytes32(0)) revert NoMerkleRootForUpdates();

            // Verify merkle proof
            if (
                !_verifyMerkleProof(
                    msg.sender,
                    proof,
                    newETHPosition,
                    newTokenPosition,
                    ethRewardsToAdd,
                    tokenRewardsToAdd
                )
            ) revert InvalidMerkleProof();

            // Update user position first
            _updateUserPosition(
                msg.sender,
                newETHPosition,
                newTokenPosition,
                ethRewardsToAdd,
                tokenRewardsToAdd
            );
        }

        // Then add token liquidity with price range
        _addTokenLiquidity(msg.sender, tokenAmount, minPrice, maxPrice);
    }

    /**
     * @notice Add both ETH and token liquidity with price range and optional position update
     * @param tokenAmount Amount of tokens to deposit
     * @param minPrice Minimum price for liquidity position (applies to both ETH and token positions)
     * @param maxPrice Maximum price for liquidity position (applies to both ETH and token positions)
     * @param proof Merkle proof for position update (required if user has pending updates)
     * @param newETHPosition New ETH position value (required if proof provided)
     * @param newTokenPosition New token position value (required if proof provided)
     * @param ethRewardsToAdd ETH rewards to add
     * @param tokenRewardsToAdd Token rewards to add
     */
    function addLiquidity(
        uint256 tokenAmount,
        uint256 minPrice,
        uint256 maxPrice,
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) external payable isNotBlocked nonReentrant {
        if (msg.value < minLiquidity) revert ETHAmountBelowMinimum();
        if (tokenAmount < minTokenLiquidity) revert TokenAmountBelowMinimum();
        _validatePriceRange(minPrice, maxPrice);

        // Check if user needs position update for either asset type - only if they have existing liquidity
        bool needsUpdate = _needsPositionUpdate(msg.sender);

        if (needsUpdate) {
            // Proof is required - validate parameters
            if (merkleRoot == bytes32(0)) revert NoMerkleRootForUpdates();

            // Verify merkle proof
            if (
                !_verifyMerkleProof(
                    msg.sender,
                    proof,
                    newETHPosition,
                    newTokenPosition,
                    ethRewardsToAdd,
                    tokenRewardsToAdd
                )
            ) revert InvalidMerkleProof();

            // Update user position first
            _updateUserPosition(
                msg.sender,
                newETHPosition,
                newTokenPosition,
                ethRewardsToAdd,
                tokenRewardsToAdd
            );
        }

        // Then add liquidity with price ranges
        _addETHLiquidity(msg.sender, msg.value, minPrice, maxPrice);
        _addTokenLiquidity(msg.sender, tokenAmount, minPrice, maxPrice);
    }

    /**
     * @notice Add ETH liquidity to the pool with price range (internal function)
     * @param user User address to add liquidity for
     * @param ethAmount Amount of ETH to deposit
     * @param minPrice Minimum price for this liquidity position
     * @param maxPrice Maximum price for this liquidity position
     */
    function _addETHLiquidity(
        address user,
        uint256 ethAmount,
        uint256 minPrice,
        uint256 maxPrice
    ) internal {
        // Initialize Uniswap pair if not set
        if (uniswapPair == address(0)) {
            uniswapPair = getPairAddress();
        }
        if (uniswapPair == address(0)) revert PairDoesNotExist();

        // Calculate pending rewards for existing position
        if (ethProviders[user].position > 0) {
            uint256 pendingReward = (ethProviders[user].position *
                accRewardPerShare) /
                SCALE -
                ethProviders[user].rewardDebt;
            ethProviders[user].pendingRewards += pendingReward;
        }

        // Add to existing position or create new one
        if (ethProviders[user].position > 0) {
            // User already has ETH position, add to it
            ethProviders[user].position += ethAmount;
        } else {
            // Create new ETH position
            ethProviders[user] = ProviderPosition({
                position: ethAmount,
                rewardDebt: 0,
                pendingRewards: 0,
                lastUpdateVersion: currentVersion
            });
        }

        // Set or update user price range (allow updates when inactive)
        if (!userPriceRanges[user].isActive) {
            userPriceRanges[user] = PriceRange({
                minPrice: minPrice,
                maxPrice: maxPrice,
                isActive: true
            });
        }

        // Update reward debt for the position
        ethProviders[user].rewardDebt =
            (ethProviders[user].position * accRewardPerShare) /
            SCALE;

        totalETH += ethAmount;
        totalEthAdded += ethAmount;

        uint256 eventMinPrice = userPriceRanges[user].isActive
            ? userPriceRanges[user].minPrice
            : minPrice;
        uint256 eventMaxPrice = userPriceRanges[user].isActive
            ? userPriceRanges[user].maxPrice
            : maxPrice;

        try
            getEventAggregator().emitLiquidityEvent(
                user,
                address(tokenContract),
                0, // ETH_ADD
                ethAmount,
                eventMinPrice,
                eventMaxPrice
            )
        {
            // Success - EventAggregator call completed
        } catch {
            // EventAggregator call failed - continue execution
        }

        // Emit local event
        emit LiquidityDeposited(
            user,
            address(tokenContract),
            ethAmount,
            ethAmount,
            true,
            eventMinPrice,
            eventMaxPrice
        );
    }

    /**
     * @notice Add token liquidity to the pool with price range (internal function)
     * @param user User address to add liquidity for
     * @param tokenAmount Amount of tokens to deposit
     * @param minPrice Minimum price for this liquidity position
     * @param maxPrice Maximum price for this liquidity position
     */
    function _addTokenLiquidity(
        address user,
        uint256 tokenAmount,
        uint256 minPrice,
        uint256 maxPrice
    ) internal {
        // Initialize Uniswap pair if not set
        if (uniswapPair == address(0)) {
            uniswapPair = getPairAddress();
        }
        if (uniswapPair == address(0)) revert PairDoesNotExist();

        // Record balance before transfer to handle fee-on-transfer tokens
        uint256 balanceBefore = tokenContract.balanceOf(address(this));

        // Transfer tokens from user
        tokenContract.safeTransferFrom(msg.sender, address(this), tokenAmount);

        // Calculate actual received amount (handles fee-on-transfer and rebasing tokens)
        uint256 actualReceived = tokenContract.balanceOf(address(this)) -
            balanceBefore;

        // Calculate pending rewards for existing position
        if (tokenProviders[user].position > 0) {
            uint256 pendingReward = (tokenProviders[user].position *
                accTokenRewardPerShare) /
                SCALE -
                tokenProviders[user].rewardDebt;
            tokenProviders[user].pendingRewards += pendingReward;
        }

        // Add to existing position or create new one
        if (tokenProviders[user].position > 0) {
            // User already has token position, add to it
            tokenProviders[user].position += actualReceived;
        } else {
            // Create new token position
            tokenProviders[user] = ProviderPosition({
                position: actualReceived,
                rewardDebt: 0,
                pendingRewards: 0,
                lastUpdateVersion: currentVersion
            });
        }

        // Set or update user price range
        if (!userPriceRanges[user].isActive) {
            userPriceRanges[user] = PriceRange({
                minPrice: minPrice,
                maxPrice: maxPrice,
                isActive: true
            });
        }

        // Update reward debt for the position
        tokenProviders[user].rewardDebt =
            (tokenProviders[user].position * accTokenRewardPerShare) /
            SCALE;

        totalTokens += actualReceived;
        totalTokensAdded += actualReceived;

        uint256 eventMinPrice = userPriceRanges[user].isActive
            ? userPriceRanges[user].minPrice
            : minPrice;
        uint256 eventMaxPrice = userPriceRanges[user].isActive
            ? userPriceRanges[user].maxPrice
            : maxPrice;

        try
            getEventAggregator().emitLiquidityEvent(
                user,
                address(tokenContract),
                2, // TOKEN_ADD
                actualReceived,
                eventMinPrice,
                eventMaxPrice
            )
        {
            // Success - EventAggregator call completed
        } catch {
            // EventAggregator call failed - continue execution
        }

        // Emit local event
        emit LiquidityDeposited(
            user,
            address(tokenContract),
            actualReceived,
            actualReceived,
            false,
            eventMinPrice,
            eventMaxPrice
        );
    }

    /**
     * @notice Get the Uniswap pool/pair address for this token (checks V3 first, then V2)
     * @return poolAddress Address of the Uniswap V3 pool or V2 pair (returns address(0) if neither exists)
     * @dev Returns V3 pool address if available, otherwise V2 pair address
     */
    function getPairAddress() public view returns (address poolAddress) {
        address routerAddress = getRegistry().router();
        if (routerAddress == address(0)) revert RouterNotSet();

        IUniswapV2Router02 router = IUniswapV2Router02(routerAddress);
        address weth = router.WETH();

        // Try V3 first
        IUniswapV3PriceHelper helper = getUniv3Helper();
        if (address(helper) != address(0)) {
            address v3Pool = helper.getV3PoolAddress(
                address(tokenContract),
                weth
            );
            if (v3Pool != address(0)) {
                return v3Pool;
            }
        }

        // Fall back to V2
        address factoryAddress = router.factory();
        IUniswapV2Factory uniswapFactory = IUniswapV2Factory(factoryAddress);
        return uniswapFactory.getPair(address(tokenContract), weth);
    }

    /**
     * @notice Get the reserves for this token pair
     * @return reserveETH ETH reserve amount
     * @return reserveToken Token reserve amount
     */
    function getReserves()
        public
        view
        returns (uint256 reserveETH, uint256 reserveToken)
    {
        address pairAddress = getPairAddress();
        // For V3-only tokens, there's no V2 pair - reserves not available via V2
        if (pairAddress == address(0)) {
            // V3 tokens don't have V2 reserves - return zeros or revert based on your needs
            // For now, returning zeros for V3 tokens
            return (0, 0);
        }

        (uint112 reserve0, uint112 reserve1, ) = IUniswapV2Pair(pairAddress)
            .getReserves();
        address token0 = IUniswapV2Pair(pairAddress).token0();

        (reserveETH, reserveToken) = token0 == address(tokenContract)
            ? (reserve1, reserve0)
            : (reserve0, reserve1);
    }

    /**
     * @notice Update both ETH and token provider positions using merkle proof
     * @param user User address
     * @param newETHPosition New ETH position value
     * @param newTokenPosition New token position value
     * @param ethRewardsToAdd Total ETH rewards (accumulated)
     * @param tokenRewardsToAdd Total token rewards (accumulated)
     */
    function _updateUserPosition(
        address user,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) internal {
        // Update ETH position
        ethProviders[user].position = newETHPosition;
        ethProviders[user].pendingRewards = ethRewardsToAdd;
        ethProviders[user].lastUpdateVersion = currentVersion;
        ethProviders[user].rewardDebt =
            (newETHPosition * accRewardPerShare) /
            SCALE;

        // Update token position
        tokenProviders[user].position = newTokenPosition;
        tokenProviders[user].pendingRewards = tokenRewardsToAdd;
        tokenProviders[user].lastUpdateVersion = currentVersion;
        uint256 normalizedPosition = _normalizeTo18Decimals(newTokenPosition);
        tokenProviders[user].rewardDebt =
            (normalizedPosition * accTokenRewardPerShare) /
            SCALE;

        // Emit event for position update
        emit UserPositionUpdated(
            user,
            currentVersion,
            newETHPosition,
            newTokenPosition
        );
    }

    function removeETHLiquidityWithProof(
        uint256 shares,
        uint256 minEthAmount,
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) external {
        if (shares <= 0 || shares > 10000) revert InvalidSharesPercentage();
        if (totalETH == 0) revert NoLiquidity();
        if (ethProviders[msg.sender].position == 0)
            revert NoLiquidityToWithdraw();

        bool needsUpdate = _needsPositionUpdate(msg.sender);
        if (needsUpdate) {
            if (merkleRoot == bytes32(0)) revert NoMerkleRootForUpdates();
            if (
                !_verifyMerkleProof(
                    msg.sender,
                    proof,
                    newETHPosition,
                    newTokenPosition,
                    ethRewardsToAdd,
                    tokenRewardsToAdd
                )
            ) {
                revert InvalidMerkleProof();
            }
            _updateUserPosition(
                msg.sender,
                newETHPosition,
                newTokenPosition,
                ethRewardsToAdd,
                tokenRewardsToAdd
            );
        }

        uint256 amountToWithdraw = _removeETHLiquidityInternal(
            msg.sender,
            shares,
            minEthAmount
        );

        // Deactivate price range only if both positions are empty
        if (
            ethProviders[msg.sender].position == 0 &&
            tokenProviders[msg.sender].position == 0
        ) {
            userPriceRanges[msg.sender].isActive = false;
        }

        emit LiquidityWithdrawn(
            msg.sender,
            address(tokenContract),
            amountToWithdraw,
            amountToWithdraw,
            true,
            userPriceRanges[msg.sender].minPrice,
            userPriceRanges[msg.sender].maxPrice
        );

        try
            getEventAggregator().emitLiquidityEvent(
                msg.sender,
                address(tokenContract),
                1, // ETH_REMOVE
                amountToWithdraw,
                userPriceRanges[msg.sender].minPrice,
                userPriceRanges[msg.sender].maxPrice
            )
        {
            // Success - EventAggregator call completed
        } catch {
            // EventAggregator call failed - continue execution
        }
    }

    function removeTokenLiquidityWithProof(
        uint256 shares,
        uint256 minTokenAmount,
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) external {
        if (shares <= 0 || shares > 10000) revert InvalidSharesPercentage();
        if (totalTokens == 0) revert NoLiquidity();
        if (tokenProviders[msg.sender].position == 0)
            revert NoLiquidityToWithdraw();

        bool needsUpdate = _needsPositionUpdate(msg.sender);
        if (needsUpdate) {
            if (merkleRoot == bytes32(0)) revert NoMerkleRootForUpdates();
            if (
                !_verifyMerkleProof(
                    msg.sender,
                    proof,
                    newETHPosition,
                    newTokenPosition,
                    ethRewardsToAdd,
                    tokenRewardsToAdd
                )
            ) {
                revert InvalidMerkleProof();
            }
            _updateUserPosition(
                msg.sender,
                newETHPosition,
                newTokenPosition,
                ethRewardsToAdd,
                tokenRewardsToAdd
            );
        }

        uint256 amountToWithdraw = _removeTokenLiquidityInternal(
            msg.sender,
            shares,
            minTokenAmount
        );

        // Deactivate price range only if both positions are empty
        if (
            ethProviders[msg.sender].position == 0 &&
            tokenProviders[msg.sender].position == 0
        ) {
            userPriceRanges[msg.sender].isActive = false;
        }

        emit LiquidityWithdrawn(
            msg.sender,
            address(tokenContract),
            amountToWithdraw,
            amountToWithdraw,
            false,
            userPriceRanges[msg.sender].minPrice,
            userPriceRanges[msg.sender].maxPrice
        );

        try
            getEventAggregator().emitLiquidityEvent(
                msg.sender,
                address(tokenContract),
                3, // TOKEN_REMOVE
                amountToWithdraw,
                userPriceRanges[msg.sender].minPrice,
                userPriceRanges[msg.sender].maxPrice
            )
        {
            // Success - EventAggregator call completed
        } catch {
            // EventAggregator call failed - continue execution
        }
    }

    function removeLiquidityWithProof(
        uint256 ethShares,
        uint256 tokenShares,
        uint256 minEthAmount,
        uint256 minTokenAmount,
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) external {
        if (ethShares <= 0 || ethShares > 10000)
            revert InvalidSharesPercentage();
        if (tokenShares <= 0 || tokenShares > 10000)
            revert InvalidSharesPercentage();
        if (totalETH == 0 && totalTokens == 0) revert NoLiquidity();
        if (
            ethProviders[msg.sender].position == 0 &&
            tokenProviders[msg.sender].position == 0
        ) {
            revert NoLiquidityToWithdraw();
        }

        bool needsUpdate = _needsPositionUpdate(msg.sender);
        if (needsUpdate) {
            if (merkleRoot == bytes32(0)) revert NoMerkleRootForUpdates();
            if (
                !_verifyMerkleProof(
                    msg.sender,
                    proof,
                    newETHPosition,
                    newTokenPosition,
                    ethRewardsToAdd,
                    tokenRewardsToAdd
                )
            ) {
                revert InvalidMerkleProof();
            }
            _updateUserPosition(
                msg.sender,
                newETHPosition,
                newTokenPosition,
                ethRewardsToAdd,
                tokenRewardsToAdd
            );
        }

        uint256 ethAmountToWithdraw = 0;
        uint256 tokenAmountToWithdraw = 0;

        if (ethProviders[msg.sender].position > 0) {
            ethAmountToWithdraw = _removeETHLiquidityInternal(
                msg.sender,
                ethShares,
                minEthAmount
            );
        }

        if (tokenProviders[msg.sender].position > 0) {
            tokenAmountToWithdraw = _removeTokenLiquidityInternal(
                msg.sender,
                tokenShares,
                minTokenAmount
            );
        }

        if (
            ethAmountToWithdraw < minEthAmount &&
            tokenAmountToWithdraw < minTokenAmount
        ) revert InsufficientWithdrawal();

        // Deactivate price range only if both positions are empty
        if (
            ethProviders[msg.sender].position == 0 &&
            tokenProviders[msg.sender].position == 0
        ) {
            userPriceRanges[msg.sender].isActive = false;
        }

        // Emit separate events for each position
        if (ethAmountToWithdraw > 0) {
            emit LiquidityWithdrawn(
                msg.sender,
                address(tokenContract),
                ethAmountToWithdraw,
                ethAmountToWithdraw,
                true,
                userPriceRanges[msg.sender].minPrice,
                userPriceRanges[msg.sender].maxPrice
            );

            try
                getEventAggregator().emitLiquidityEvent(
                    msg.sender,
                    address(tokenContract),
                    1, // ETH_REMOVE
                    ethAmountToWithdraw,
                    userPriceRanges[msg.sender].minPrice,
                    userPriceRanges[msg.sender].maxPrice
                )
            {
                // Success - EventAggregator call completed
            } catch {
                // EventAggregator call failed - continue execution
            }
        }

        if (tokenAmountToWithdraw > 0) {
            emit LiquidityWithdrawn(
                msg.sender,
                address(tokenContract),
                tokenAmountToWithdraw,
                tokenAmountToWithdraw,
                false,
                userPriceRanges[msg.sender].minPrice,
                userPriceRanges[msg.sender].maxPrice
            );

            try
                getEventAggregator().emitLiquidityEvent(
                    msg.sender,
                    address(tokenContract),
                    3, // TOKEN_REMOVE
                    tokenAmountToWithdraw,
                    userPriceRanges[msg.sender].minPrice,
                    userPriceRanges[msg.sender].maxPrice
                )
            {
                // Success - EventAggregator call completed
            } catch {
                // EventAggregator call failed - continue execution
            }
        }
    }

    function executeBuyOrder(
        uint256 ethAmount,
        uint256 tokenAmount,
        bytes32 newMerkleRoot
    ) external payable isNotBlocked onlyOrderbook {
        if (ethAmount == 0) revert AmountZero();
        if (tokenAmount == 0) revert AmountZero();
        if (msg.value != ethAmount) revert ETHAmountMismatch();
        if (totalTokens <= tokenAmount) revert InsufficientTokenLiquidity();

        totalTokens -= tokenAmount;
        totalTokensRemoved += tokenAmount;
        totalETH += msg.value;
        totalEthAdded += msg.value;

        if (tokenAmount > 0) {
            tokenContract.safeTransfer(msg.sender, tokenAmount);
        }

        _updateMerkleRootAfterTrade(newMerkleRoot);
        emit PoolBalanceUpdated(
            address(tokenContract),
            totalETH,
            totalTokens,
            totalETH,
            totalTokens
        );
    }

    function executeSellOrder(
        uint256 ethAmount,
        uint256 tokenAmount,
        bytes32 newMerkleRoot
    ) external isNotBlocked onlyOrderbook {
        if (ethAmount == 0) revert AmountZero();
        if (tokenAmount == 0) revert AmountZero();
        if (totalETH <= ethAmount) revert InsufficientETHLiquidity();

        tokenContract.safeTransferFrom(msg.sender, address(this), tokenAmount);

        totalETH -= ethAmount;
        totalEthRemoved += ethAmount;
        totalTokens += tokenAmount;
        totalTokensAdded += tokenAmount;

        (bool success, ) = payable(msg.sender).call{value: ethAmount}("");
        if (!success) revert ETHTransferToOrderbookFailed();

        _updateMerkleRootAfterTrade(newMerkleRoot);
        emit PoolBalanceUpdated(
            address(tokenContract),
            totalETH,
            totalTokens,
            totalETH,
            totalTokens
        );
    }

    /**
     * @notice Distribute pool fee as ETH rewards to liquidity providers
     * @dev Only callable by reward distributor
     */
    function distributePoolFee()
        external
        payable
        onlyRewardDistributor
        nonReentrant
    {
        if (msg.value == 0) revert NoETHSent();
        totalEthAdded += msg.value;
        _updatePoolRewards(msg.value);
        emit PoolFeeDistributed(
            msg.sender,
            msg.value,
            address(tokenContract),
            true
        );
    }

    /**
     * @notice Update pool rewards distribution
     * @param ethAmount Amount of ETH to distribute as rewards
     */
    function _updatePoolRewards(uint256 ethAmount) internal {
        if (ethAmount == 0) revert AmountZero();

        // Distribute ETH rewards to ETH providers only
        if (totalETH > 0) {
            uint256 newAccRewardPerShare = accRewardPerShare +
                ((ethAmount * SCALE) / totalETH);
            if (newAccRewardPerShare < accRewardPerShare)
                revert OverflowInETHRewardCalculation();
            accRewardPerShare = newAccRewardPerShare;
        } else {
            // No liquidity exists - immediately refund the ETH
            (bool success, ) = payable(msg.sender).call{value: ethAmount}("");
            if (!success) revert ETHTransferFailed();
            emit FeeRefunded(msg.sender, ethAmount, true);
        }

        // Track ETH rewards
        rewardBalance += ethAmount;
    }

    /**
     * @notice Update token pool rewards distribution
     * @param tokenAmount Amount of tokens to distribute as rewards
     */
    function _updateTokenRewards(uint256 tokenAmount) internal {
        if (tokenAmount == 0) revert AmountZero();

        // Distribute token rewards to token providers only
        if (totalTokens > 0) {
            // Normalize token amount to 18 decimals for consistent calculations
            uint256 normalizedTokenAmount = _normalizeTo18Decimals(tokenAmount);
            uint256 normalizedTotalTokens = _normalizeTo18Decimals(totalTokens);

            uint256 newAccTokenRewardPerShare = accTokenRewardPerShare +
                ((normalizedTokenAmount * SCALE) / normalizedTotalTokens);
            if (newAccTokenRewardPerShare < accTokenRewardPerShare)
                revert OverflowInTokenRewardCalculation();
            accTokenRewardPerShare = newAccTokenRewardPerShare;

            // Track token rewards distributed
            totalTokenRewardsDistributed += tokenAmount;
        } else {
            // No liquidity exists - immediately refund the tokens
            tokenContract.safeTransfer(msg.sender, tokenAmount);
            emit FeeRefunded(msg.sender, tokenAmount, false);
        }
    }

    /**
     * @notice Distribute token fee as rewards to liquidity providers
     * @param tokenAmount Amount of tokens to distribute as rewards
     * @dev Only callable by reward distributor
     */
    function distributeTokenFee(
        uint256 tokenAmount
    ) external onlyRewardDistributor nonReentrant {
        if (tokenAmount == 0) revert AmountZero();

        // Record balance before transfer to handle fee-on-transfer tokens
        uint256 balanceBefore = tokenContract.balanceOf(address(this));

        // Transfer tokens from orderbook to this contract
        tokenContract.safeTransferFrom(msg.sender, address(this), tokenAmount);

        // Calculate actual received amount (handles fee-on-transfer and rebasing tokens)
        uint256 actualReceived = tokenContract.balanceOf(address(this)) -
            balanceBefore;

        totalTokensAdded += actualReceived;
        _updateTokenRewards(actualReceived);
        emit PoolFeeDistributed(
            msg.sender,
            actualReceived,
            address(tokenContract),
            false
        );
    }

    /**
     * @notice Internal function to remove ETH liquidity
     * @param user User address
     * @param shares Percentage of shares to remove (0-10000)
     * @param minAmount Minimum amount to withdraw
     * @return amountToWithdraw Amount actually withdrawn
     */
    function _removeETHLiquidityInternal(
        address user,
        uint256 shares,
        uint256 minAmount
    ) internal returns (uint256 amountToWithdraw) {
        uint256 pendingReward = (ethProviders[user].position *
            accRewardPerShare) /
            SCALE -
            ethProviders[user].rewardDebt;
        ethProviders[user].pendingRewards += pendingReward;

        amountToWithdraw = (ethProviders[user].position * shares) / 10000;
        if (amountToWithdraw == 0) revert NoSharesToBurn();
        if (amountToWithdraw > ethProviders[user].position) {
            amountToWithdraw = ethProviders[user].position;
        }
        if (amountToWithdraw > totalETH) revert InsufficientPoolBalance();

        ethProviders[user].position -= amountToWithdraw;
        ethProviders[user].rewardDebt =
            (ethProviders[user].position * accRewardPerShare) /
            SCALE;

        totalETH -= amountToWithdraw;
        totalEthRemoved += amountToWithdraw;

        if (amountToWithdraw < minAmount) revert InsufficientWithdrawal();
        (bool success, ) = payable(user).call{value: amountToWithdraw}("");
        if (!success) revert ETHTransferFailed();

        return amountToWithdraw;
    }

    /**
     * @notice Internal function to remove token liquidity
     * @param user User address
     * @param shares Percentage of shares to remove (0-10000)
     * @param minAmount Minimum amount to withdraw
     * @return amountToWithdraw Amount actually withdrawn
     */
    function _removeTokenLiquidityInternal(
        address user,
        uint256 shares,
        uint256 minAmount
    ) internal returns (uint256 amountToWithdraw) {
        uint256 normalizedLpShares = _normalizeTo18Decimals(
            tokenProviders[user].position
        );
        uint256 pendingReward = (normalizedLpShares * accTokenRewardPerShare) /
            SCALE -
            tokenProviders[user].rewardDebt;
        tokenProviders[user].pendingRewards += pendingReward;

        amountToWithdraw = (tokenProviders[user].position * shares) / 10000;
        if (amountToWithdraw == 0) revert NoSharesToBurn();
        if (amountToWithdraw > tokenProviders[user].position) {
            amountToWithdraw = tokenProviders[user].position;
        }
        if (amountToWithdraw > totalTokens) revert InsufficientPoolBalance();

        tokenProviders[user].position -= amountToWithdraw;
        tokenProviders[user].rewardDebt =
            (tokenProviders[user].position * accTokenRewardPerShare) /
            SCALE;

        totalTokens -= amountToWithdraw;
        totalTokensRemoved += amountToWithdraw;

        if (amountToWithdraw < minAmount) revert InsufficientWithdrawal();
        tokenContract.safeTransfer(user, amountToWithdraw);

        return amountToWithdraw;
    }

    function claimRewardsWithProof(
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) external nonReentrant {
        // Calculate total user position
        uint256 totalUserPosition = 0;
        uint256 totalPendingRewards = 0;

        // Sum up ETH and token positions
        totalUserPosition += ethProviders[msg.sender].position;
        totalPendingRewards += ethProviders[msg.sender].pendingRewards;

        totalUserPosition += tokenProviders[msg.sender].position;
        totalPendingRewards += tokenProviders[msg.sender].pendingRewards;

        if (totalUserPosition == 0 && totalPendingRewards == 0) {
            revert NoLiquidityOrRewards();
        }

        bool needsUpdate = _needsPositionUpdate(msg.sender);
        if (needsUpdate) {
            if (merkleRoot == bytes32(0)) revert NoMerkleRootForUpdates();
            if (
                !_verifyMerkleProof(
                    msg.sender,
                    proof,
                    newETHPosition,
                    newTokenPosition,
                    ethRewardsToAdd,
                    tokenRewardsToAdd
                )
            ) {
                revert InvalidMerkleProof();
            }
            _updateUserPosition(
                msg.sender,
                newETHPosition,
                newTokenPosition,
                ethRewardsToAdd,
                tokenRewardsToAdd
            );
        }

        uint256 ethRewards = 0;
        // Calculate ETH rewards from ETH position
        if (ethProviders[msg.sender].position > 0) {
            uint256 ethAccumulated = (ethProviders[msg.sender].position *
                accRewardPerShare) / SCALE;
            ethRewards += ethAccumulated - ethProviders[msg.sender].rewardDebt;
        }
        ethRewards += ethProviders[msg.sender].pendingRewards;

        uint256 tokenProviderRewards = 0;
        // Calculate token rewards from token position
        if (tokenProviders[msg.sender].position > 0) {
            uint256 normalizedLpShares = _normalizeTo18Decimals(
                tokenProviders[msg.sender].position
            );
            uint256 tokenAccumulated = (normalizedLpShares *
                accTokenRewardPerShare) / SCALE;
            tokenProviderRewards +=
                tokenAccumulated -
                tokenProviders[msg.sender].rewardDebt;
        }
        tokenProviderRewards += tokenProviders[msg.sender].pendingRewards;

        uint256 totalRewards = ethRewards + tokenProviderRewards;
        if (totalRewards == 0) revert NoRewards();

        // Reset reward debt and pending rewards for positions
        if (ethProviders[msg.sender].position > 0) {
            ethProviders[msg.sender].rewardDebt =
                (ethProviders[msg.sender].position * accRewardPerShare) /
                SCALE;
        }
        ethProviders[msg.sender].pendingRewards = 0;

        if (tokenProviders[msg.sender].position > 0) {
            uint256 normalizedLpShares = _normalizeTo18Decimals(
                tokenProviders[msg.sender].position
            );
            tokenProviders[msg.sender].rewardDebt =
                (normalizedLpShares * accTokenRewardPerShare) /
                SCALE;
        }
        tokenProviders[msg.sender].pendingRewards = 0;

        if (ethRewards > 0) {
            totalEthRemoved += ethRewards;
            (bool success, ) = payable(msg.sender).call{value: ethRewards}("");
            if (!success) revert ETHWithdrawalFailed();

            try
                getEventAggregator().emitLiquidityEvent(
                    msg.sender,
                    address(tokenContract),
                    4, // ETH_REWARDS_CLAIMED
                    ethRewards,
                    userPriceRanges[msg.sender].minPrice,
                    userPriceRanges[msg.sender].maxPrice
                )
            {
                // Success - EventAggregator call completed
            } catch {
                // EventAggregator call failed - continue execution
            }
        }

        if (tokenProviderRewards > 0) {
            uint256 denormalizedRewards = _denormalizeFrom18Decimals(
                tokenProviderRewards
            );
            totalTokensRemoved += denormalizedRewards;
            tokenContract.safeTransfer(msg.sender, denormalizedRewards);

            try
                getEventAggregator().emitLiquidityEvent(
                    msg.sender,
                    address(tokenContract),
                    5, // TOKEN_REWARDS_CLAIMED
                    denormalizedRewards,
                    userPriceRanges[msg.sender].minPrice,
                    userPriceRanges[msg.sender].maxPrice
                )
            {
                // Success - EventAggregator call completed
            } catch {
                // EventAggregator call failed - continue execution
            }
        }

        emit FeeClaimed(msg.sender, totalRewards, address(tokenContract), true);
    }

    function claimETHRewardsWithProof(
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) external nonReentrant {
        bool needsUpdate = _needsPositionUpdate(msg.sender);
        if (needsUpdate) {
            if (merkleRoot == bytes32(0)) revert NoMerkleRootForUpdates();
            if (
                !_verifyMerkleProof(
                    msg.sender,
                    proof,
                    newETHPosition,
                    newTokenPosition,
                    ethRewardsToAdd,
                    tokenRewardsToAdd
                )
            ) {
                revert InvalidMerkleProof();
            }
            _updateUserPosition(
                msg.sender,
                newETHPosition,
                newTokenPosition,
                ethRewardsToAdd,
                tokenRewardsToAdd
            );
        }

        uint256 ethRewards = 0;
        // Calculate ETH rewards from ETH position
        if (ethProviders[msg.sender].position > 0) {
            uint256 ethAccumulated = (ethProviders[msg.sender].position *
                accRewardPerShare) / SCALE;
            ethRewards += ethAccumulated - ethProviders[msg.sender].rewardDebt;
        }
        ethRewards += ethProviders[msg.sender].pendingRewards;

        if (ethRewards == 0) revert NoRewards();

        // Reset reward debt and pending rewards for ETH position
        if (ethProviders[msg.sender].position > 0) {
            ethProviders[msg.sender].rewardDebt =
                (ethProviders[msg.sender].position * accRewardPerShare) /
                SCALE;
        }
        ethProviders[msg.sender].pendingRewards = 0;

        totalEthRemoved += ethRewards;
        (bool success, ) = payable(msg.sender).call{value: ethRewards}("");
        if (!success) revert ETHWithdrawalFailed();

        try
            getEventAggregator().emitLiquidityEvent(
                msg.sender,
                address(tokenContract),
                4, // ETH_REWARDS_CLAIMED
                ethRewards,
                userPriceRanges[msg.sender].minPrice,
                userPriceRanges[msg.sender].maxPrice
            )
        {
            // Success - EventAggregator call completed
        } catch {
            // EventAggregator call failed - continue execution
        }
        emit FeeClaimed(msg.sender, ethRewards, address(tokenContract), true);
    }

    function claimTokenRewardsWithProof(
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) external nonReentrant {
        bool needsUpdate = _needsPositionUpdate(msg.sender);
        if (needsUpdate) {
            if (merkleRoot == bytes32(0)) revert NoMerkleRootForUpdates();
            if (
                !_verifyMerkleProof(
                    msg.sender,
                    proof,
                    newETHPosition,
                    newTokenPosition,
                    ethRewardsToAdd,
                    tokenRewardsToAdd
                )
            ) {
                revert InvalidMerkleProof();
            }
            _updateUserPosition(
                msg.sender,
                newETHPosition,
                newTokenPosition,
                ethRewardsToAdd,
                tokenRewardsToAdd
            );
        }

        uint256 tokenProviderRewards = 0;
        // Calculate token rewards from token position
        if (tokenProviders[msg.sender].position > 0) {
            uint256 normalizedLpShares = _normalizeTo18Decimals(
                tokenProviders[msg.sender].position
            );
            uint256 tokenAccumulated = (normalizedLpShares *
                accTokenRewardPerShare) / SCALE;
            tokenProviderRewards +=
                tokenAccumulated -
                tokenProviders[msg.sender].rewardDebt;
        }
        tokenProviderRewards += tokenProviders[msg.sender].pendingRewards;

        if (tokenProviderRewards == 0) revert NoTokenProviderRewards();

        // Reset reward debt and pending rewards for token position
        if (tokenProviders[msg.sender].position > 0) {
            uint256 normalizedLpShares = _normalizeTo18Decimals(
                tokenProviders[msg.sender].position
            );
            tokenProviders[msg.sender].rewardDebt =
                (normalizedLpShares * accTokenRewardPerShare) /
                SCALE;
        }
        tokenProviders[msg.sender].pendingRewards = 0;

        uint256 denormalizedRewards = _denormalizeFrom18Decimals(
            tokenProviderRewards
        );
        totalTokensRemoved += denormalizedRewards;
        tokenContract.safeTransfer(msg.sender, denormalizedRewards);

        try
            getEventAggregator().emitLiquidityEvent(
                msg.sender,
                address(tokenContract),
                5, // TOKEN_REWARDS_CLAIMED
                denormalizedRewards,
                userPriceRanges[msg.sender].minPrice,
                userPriceRanges[msg.sender].maxPrice
            )
        {
            // Success - EventAggregator call completed
        } catch {
            // EventAggregator call failed - continue execution
        }

        emit FeeClaimed(
            msg.sender,
            denormalizedRewards,
            address(tokenContract),
            false
        );
    }

    /**
     * @notice Update user position using merkle proof
     * @param version Version of the merkle root
     * @param proof Merkle proof for position update
     * @param newETHPosition New ETH position value
     * @param newTokenPosition New token position value
     * @param ethRewardsToAdd ETH rewards to add
     * @param tokenRewardsToAdd Token rewards to add
     */
    function updateUserPosition(
        uint256 version,
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) external {
        // Check if positions have already been updated to this version
        if (version <= ethProviders[msg.sender].lastUpdateVersion) {
            revert VersionAlreadyProcessed();
        }
        if (version <= tokenProviders[msg.sender].lastUpdateVersion) {
            revert VersionAlreadyProcessed();
        }
        if (version > currentVersion) revert VersionNotAvailable();

        if (
            !_verifyMerkleProof(
                msg.sender,
                proof,
                newETHPosition,
                newTokenPosition,
                ethRewardsToAdd,
                tokenRewardsToAdd
            )
        ) {
            revert InvalidMerkleProof();
        }

        _updateUserPosition(
            msg.sender,
            newETHPosition,
            newTokenPosition,
            ethRewardsToAdd,
            tokenRewardsToAdd
        );
    }

    /**
     * @notice Add ETH liquidity for a specific user (factory only)
     * @param user User address to add liquidity for
     * @dev Only callable by factory contract
     */
    function addETHLiquidityForUser(
        address user,
        uint256 minPrice,
        uint256 maxPrice
    ) external payable onlyFactory {
        if (msg.value < minLiquidity) revert ETHAmountBelowMinimum();
        if (user == address(0)) revert InvalidRecipient();

        _addETHLiquidity(user, msg.value, minPrice, maxPrice);
    }

    /**
     * @notice Add token liquidity for a specific user (factory only)
     * @param user User address to add liquidity for
     * @param tokenAmount Amount of tokens to add
     * @dev Only callable by factory contract
     */
    function addTokenLiquidityForUser(
        address user,
        uint256 tokenAmount,
        uint256 minPrice,
        uint256 maxPrice
    ) external onlyFactory {
        if (tokenAmount < minTokenLiquidity) revert TokenAmountBelowMinimum();
        if (user == address(0)) revert InvalidRecipient();

        _addTokenLiquidity(user, tokenAmount, minPrice, maxPrice);
    }

    /**
     * @notice Get user position details including ETH and token positions
     * @param user User address
     * @return ethPosition ETH position amount
     * @return tokenPosition Token position amount
     * @return ethLPShares ETH LP shares
     * @return tokenLPShares Token LP shares
     * @return ethPendingRewards ETH pending rewards
     * @return tokenPendingRewards Token pending rewards
     * @return lastUpdateVersion Last update version
     */
    function getUserPosition(
        address user
    )
        external
        view
        returns (
            uint256 ethPosition,
            uint256 tokenPosition,
            uint256 ethLPShares,
            uint256 tokenLPShares,
            uint256 ethPendingRewards,
            uint256 tokenPendingRewards,
            uint256 lastUpdateVersion
        )
    {
        // Get ETH position data
        ethPosition = ethProviders[user].position;
        ethLPShares = ethProviders[user].position;
        ethPendingRewards = ethProviders[user].pendingRewards;
        if (ethProviders[user].lastUpdateVersion > lastUpdateVersion) {
            lastUpdateVersion = ethProviders[user].lastUpdateVersion;
        }

        // Get token position data
        tokenPosition = tokenProviders[user].position;
        tokenLPShares = tokenProviders[user].position;
        tokenPendingRewards = tokenProviders[user].pendingRewards;
        if (tokenProviders[user].lastUpdateVersion > lastUpdateVersion) {
            lastUpdateVersion = tokenProviders[user].lastUpdateVersion;
        }
    }

    /**
     * @notice Get ETH pool metrics
     * @return metrics ETH pool metrics including position, rewards, and current price
     */
    function getETHPoolMetrics()
        external
        view
        returns (PoolMetrics memory metrics)
    {
        metrics.position = totalETH;
        metrics.accRewardPerShare = accRewardPerShare;
        metrics.rewardBalance = rewardBalance;
        metrics.accountedPosition = totalETH;
    }

    /**
     * @notice Get token pool metrics
     * @return metrics Token pool metrics including position, rewards, and current price
     */
    function getTokenPoolMetrics()
        external
        view
        returns (PoolMetrics memory metrics)
    {
        metrics.position = totalTokens;
        metrics.accRewardPerShare = accTokenRewardPerShare;
        metrics.rewardBalance = tokenProviderRewardBalance;
        metrics.accountedPosition = totalTokens;
    }

    /**
     * @notice Check if a price is within the specified range
     * @param price Price to check
     * @param minPrice Minimum price of the range
     * @param maxPrice Maximum price of the range
     * @return isWithinRange True if price is within range
     */
    function isPriceWithinRange(
        uint256 price,
        uint256 minPrice,
        uint256 maxPrice
    ) external pure returns (bool isWithinRange) {
        return _isPriceWithinRange(price, minPrice, maxPrice);
    }

    /**
     * @notice Set minimum ETH liquidity requirement
     * @param _minLiquidity Minimum ETH liquidity amount
     * @dev Only callable by owner
     */
    function setMinLiquidity(uint256 _minLiquidity) external onlyOwner {
        if (_minLiquidity == 0) revert InvalidMinLiquidity();
        minLiquidity = _minLiquidity;
        emit MinLiquidityUpdated(_minLiquidity, true);
    }

    /**
     * @notice Set minimum token liquidity requirement
     * @param _minTokenLiquidity Minimum token liquidity amount
     * @dev Only callable by owner
     */
    function setMinTokenLiquidity(
        uint256 _minTokenLiquidity
    ) external onlyOwner {
        if (_minTokenLiquidity == 0) revert InvalidMinTokenLiquidity();
        minTokenLiquidity = _minTokenLiquidity;
        emit MinLiquidityUpdated(_minTokenLiquidity, false);
    }

    /**
     * @notice Emergency withdraw ETH from the contract
     * @param recipient Address to receive the ETH
     * @param amount Amount of ETH to withdraw
     * @dev Only callable by owner in emergency situations
     */
    function emergencyWithdrawETH(
        address payable recipient,
        uint256 amount
    ) external onlyOwner {
        if (recipient == address(0)) revert InvalidRecipient();
        if (address(this).balance == 0) revert InsufficientETHBalance();

        uint256 withdrawAmount = amount == 0 ? address(this).balance : amount;
        if (withdrawAmount > address(this).balance)
            revert InsufficientETHBalance();

        (bool success, ) = recipient.call{value: withdrawAmount}("");
        if (!success) revert ETHWithdrawalFailed();

        emit EmergencyWithdraw(
            address(tokenContract),
            recipient,
            withdrawAmount,
            true
        );
    }

    /**
     * @notice Emergency withdraw tokens from the contract
     * @param tokenAddress Address of the token to withdraw
     * @param recipient Address to receive the tokens
     * @param amount Amount of tokens to withdraw
     * @dev Only callable by owner in emergency situations
     */
    function emergencyWithdrawToken(
        address tokenAddress,
        address recipient,
        uint256 amount
    ) external onlyOwner {
        if (tokenAddress == address(0)) revert InvalidTokenAddress();
        if (recipient == address(0)) revert InvalidRecipient();

        IERC20 emergencyToken = IERC20(tokenAddress);
        uint256 balance = emergencyToken.balanceOf(address(this));
        if (balance == 0) revert InsufficientTokenBalance();

        uint256 withdrawAmount = amount == 0 ? balance : amount;
        if (withdrawAmount > balance) revert InsufficientTokenBalance();

        emergencyToken.safeTransfer(recipient, withdrawAmount);

        emit EmergencyWithdraw(tokenAddress, recipient, withdrawAmount, false);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IEventAggregator {
    // Event types
    function ETH_ADD() external view returns (uint8);

    function ETH_REMOVE() external view returns (uint8);

    function TOKEN_ADD() external view returns (uint8);

    function TOKEN_REMOVE() external view returns (uint8);

    function REWARDS_CLAIMED() external view returns (uint8);

    function TRADE_EXECUTED() external view returns (uint8);

    // Main functions
    function emitLiquidityEvent(
        address user,
        address token,
        uint8 eventType,
        uint256 amount,
        uint256 minPrice,
        uint256 maxPrice
    ) external;

    function emitETHRewardsClaimed(
        address user,
        address token,
        uint256 amount
    ) external;

    function emitTokenRewardsClaimed(
        address user,
        address token,
        uint256 amount
    ) external;

    function emitTradeExecuted(
        address user,
        uint8 tradeType,
        uint256 ethAmount,
        uint256 tokenAmount
    ) external;

    function emitMerkleRootUpdated(
        uint256 version,
        bytes32 merkleRoot
    ) external;

    function emitPoolCreated(address token, address pool) external;

    // View functions
    function getEventTypeName(
        uint8 eventType
    ) external pure returns (string memory name);

    function getTradeTypeName(
        uint8 tradeType
    ) external pure returns (string memory name);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IFallbackExecutor {
    struct DEXConfig {
        address router;
        address factory;
        bool isActive;
        uint256 priority;
    }

    // Events
    event DEXAdded(address indexed dex, address router, address factory);
    event DEXRemoved(address indexed dex);
    event TradeExecuted(
        address indexed token,
        address indexed dex,
        uint256 amountIn,
        uint256 amountOut,
        bool isBuy
    );

    // View functions
    function dexes(
        address dex
    )
        external
        view
        returns (
            address router,
            address factory,
            bool isActive,
            uint256 priority
        );

    function gradientRegistry() external view returns (address);

    function getActiveDEXes() external view returns (address[] memory);

    function getDEXConfig(address dex) external view returns (DEXConfig memory);

    // State changing functions
    function addDEX(address dex, address router, uint256 priority) external;

    function removeDEX(address dex) external;

    function executeTrade(
        address token,
        uint256 amount,
        uint256 minAmountOut,
        bool isBuy
    ) external payable returns (uint256 amountOut);

    function emergencyWithdraw(address[] calldata tokens) external;

    function emergencyWithdrawETH() external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

/**
 * @title IGradientFeeManager
 * @notice Interface for the GradientFeeManager contract
 * @dev Handles partner fee distribution and platform fee management
 */
interface IGradientFeeManager {
    // ========================== Events ==========================

    /// @notice Emitted when ETH fees are withdrawn
    event EthFeesWithdrawn(address indexed recipient, uint256 amount);

    /// @notice Emitted when token fees are withdrawn
    event TokenFeesWithdrawn(
        address indexed token,
        address indexed recipient,
        uint256 amount
    );

    /// @notice Emitted when partner ETH fees are claimed
    event PartnerEthFeesClaimed(
        address indexed token,
        address indexed partnerWallet,
        uint256 amount
    );

    /// @notice Emitted when partner token fees are claimed
    event PartnerTokenFeesClaimed(
        address indexed token,
        address indexed partnerWallet,
        uint256 amount
    );

    /// @notice Emitted when fees are distributed to teams (market maker only)
    event FeeDistributedToTeams(
        address indexed token,
        uint256 grayTeamFee,
        uint256 partnerTeamFee,
        uint256 totalTeamFee
    );

    /// @notice Emitted when fees are distributed to market maker pool
    event FeeDistributedToPool(
        address indexed marketMakerPool,
        address indexed token,
        uint256 amount,
        uint256 totalFee
    );

    // ========================== Fee Distribution Functions ==========================

    /// @notice Distributes market maker token fees according to partner split logic
    /// @param totalFee Total fee amount to distribute
    /// @param token Token address for partner token check
    /// @param marketMakerPool Market maker pool address for distribution
    function distributeMarketMakerTokenFees(
        uint256 totalFee,
        address token,
        address marketMakerPool
    ) external;

    /// @notice Distributes market maker ETH fees according to partner split logic
    /// @param totalFee Total ETH fee amount to distribute
    /// @param token Token address for partner token check
    /// @param marketMakerPool Market maker pool address for distribution
    function distributeMarketMakerEthFees(
        uint256 totalFee,
        address token,
        address marketMakerPool
    ) external payable;

    // ========================== Fee Collection Functions ==========================

    /// @notice Collects ETH fees and updates totals
    /// @param amount Amount in ETH to collect
    /// @param token Token address for potential token-specific fee tracking
    function collectEthFee(uint256 amount, address token) external payable;

    /// @notice Collects token fees and updates totals
    /// @param amount Amount in tokens to collect
    /// @param token Token address
    function collectTokenFee(uint256 amount, address token) external;

    // ========================== Fee Withdrawal Functions ==========================

    /// @notice Withdraws collected ETH fees to the specified address
    /// @param recipient Address to receive the ETH fees
    function withdrawEthFees(address payable recipient) external;

    /// @notice Withdraws collected token fees to the specified address
    /// @param token Address of the token to withdraw fees for
    /// @param recipient Address to receive the token fees
    function withdrawTokenFees(address token, address recipient) external;

    /// @notice Claim partner ETH fees for a specific token
    /// @param token Address of the partner token to claim fees for
    function claimPartnerEthFees(address token) external;

    /// @notice Claim partner token fees for a specific token
    /// @param token Address of the partner token to claim fees for
    function claimPartnerTokenFees(address token) external;

    // ========================== View Functions ==========================

    /// @notice Gets total ETH fees collected
    /// @return uint256 Total ETH fees collected
    function totalEthFeesCollected() external view returns (uint256);

    /// @notice Gets total token fees collected for a specific token
    /// @param token Token address
    /// @return uint256 Total token fees collected
    function totalTokenFeesCollected(
        address token
    ) external view returns (uint256);

    /// @notice Gets partner ETH fees collected for a specific token
    /// @param token Token address
    /// @return uint256 Partner ETH fees collected
    function partnerEthFeesCollected(
        address token
    ) external view returns (uint256);

    /// @notice Gets partner token fees collected for a specific token
    /// @param token Token address
    /// @return uint256 Partner token fees collected
    function partnerTokenFeesCollected(
        address token
    ) external view returns (uint256);

    /// @notice Gets platform ETH fees claimed
    /// @return uint256 Platform ETH fees claimed
    function platformEthFeesClaimed() external view returns (uint256);

    /// @notice Gets platform token fees claimed for a specific token
    /// @param token Token address
    /// @return uint256 Platform token fees claimed
    function platformTokenFeesClaimed(
        address token
    ) external view returns (uint256);

    /// @notice Gets partner ETH fees claimed for a specific token
    /// @param token Token address
    /// @return uint256 Partner ETH fees claimed
    function partnerEthFeesClaimed(
        address token
    ) external view returns (uint256);

    /// @notice Gets partner token fees claimed for a specific token
    /// @param token Token address
    /// @return uint256 Partner token fees claimed
    function partnerTokenFeesClaimed(
        address token
    ) external view returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IGradientMarketMakerFactory {
    // Events
    event PoolCreated(
        address indexed token,
        address indexed pool,
        uint256 timestamp
    );

    // Pool management
    function createPool(address token) external returns (address pool);

    function createPoolWithLiquidity(
        address token,
        address initialLiquidityProvider,
        uint256 initialEthAmount,
        uint256 initialTokenAmount
    ) external payable returns (address pool);

    function getPool(address token) external view returns (address pool);

    function poolExists(address token) external view returns (bool);

    function getAllPools() external view returns (address[] memory);

    function getPoolCount() external view returns (uint256);

    // Pool info
    function pools(uint256 index) external view returns (address);

    function poolCount() external view returns (uint256);

    // Factory info
    function owner() external view returns (address);

    function getEventAggregator() external view returns (address);

    function setEventAggregator(address _eventAggregator) external;

    /**
     * @notice Get the registry address
     * @return registryAddress Address of the GradientRegistry
     */
    function getRegistry() external view returns (address);

    /**
     * @notice Get the Uniswap V3 helper address (public variable, auto-generated getter)
     */
    function univ3Helper() external view returns (address);

    /**
     * @notice Check if a given address is a valid pool
     * @param poolAddress Address to check
     * @return isValid True if the address is a valid pool
     */
    function isValidPool(
        address poolAddress
    ) external view returns (bool isValid);
}

File 22 of 29 : IGradientMarketMakerPoolV3.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
 * @title IGradientMarketMakerPoolV3
 * @notice Interface for individual token market maker pool contracts with price range functionality
 * @dev Each pool is dedicated to one token with concentrated liquidity price ranges
 * @dev Users can specify min/max price ranges when adding liquidity
 */
interface IGradientMarketMakerPoolV3 {
    // Price range struct for concentrated liquidity
    struct PriceRange {
        uint256 minPrice; // Minimum price (in wei per token)
        uint256 maxPrice; // Maximum price (in wei per token)
        bool isActive; // Whether this range is active
    }

    // Enhanced provider position with price range
    struct ProviderPosition {
        uint256 position; // ETH or token position
        uint256 rewardDebt; // Reward debt for accurate calculations
        uint256 pendingRewards; // Pending rewards to claim
        uint256 lastUpdateVersion; // Last version when position was updated
        uint256 lpShares; // LP shares for this provider
        PriceRange priceRange; // Price range for this position
    }

    // Pool metrics with price range support
    struct PoolMetrics {
        uint256 totalPosition; // Total ETH or tokens
        uint256 accRewardPerShare; // Accumulated rewards per share
        uint256 rewardBalance; // Available reward balance
        uint256 accountedPosition; // Accounted position for calculations
        uint256 currentPrice; // Current market price
    }

    // Events
    event LiquidityDeposited(
        address indexed user,
        address indexed token,
        uint256 amount,
        uint256 lpSharesMinted,
        bool isETH,
        uint256 minPrice,
        uint256 maxPrice
    );

    event LiquidityWithdrawn(
        address indexed user,
        address indexed token,
        uint256 amount,
        uint256 lpSharesBurned,
        bool isETH,
        uint256 minPrice,
        uint256 maxPrice
    );

    event PoolFeeDistributed(
        address indexed from,
        uint256 amount,
        address indexed token,
        bool isETH
    );

    event FeeClaimed(
        address indexed user,
        uint256 amount,
        address indexed token,
        bool isETH
    );

    event PoolBalanceUpdated(
        address indexed token,
        uint256 newTotalETH,
        uint256 newTotalTokens,
        uint256 newETHLPShares,
        uint256 newTokenLPShares
    );

    event PriceRangeUpdated(
        address indexed user,
        uint256 oldMinPrice,
        uint256 oldMaxPrice,
        uint256 newMinPrice,
        uint256 newMaxPrice,
        bool isETH
    );

    event MerkleRootUpdated(uint256 indexed version, bytes32 merkleRoot);
    event UserPositionUpdated(
        address indexed user,
        uint256 indexed version,
        uint256 newETHPosition,
        uint256 newTokenPosition
    );

    event MinLiquidityUpdated(uint256 newMinLiquidity, bool isETH);
    event EmergencyWithdraw(
        address indexed token,
        address indexed recipient,
        uint256 amount,
        bool isETH
    );

    // Enhanced liquidity management functions with price ranges
    /**
     * @notice Add ETH liquidity with price range and optional position update
     * @param minPrice Minimum price for this liquidity position
     * @param maxPrice Maximum price for this liquidity position
     * @param proof Merkle proof for position update (required if user has pending updates)
     * @param newETHPosition New ETH position (required if proof provided)
     * @param newTokenPosition New token position (required if proof provided)
     * @param ethRewardsToAdd Off-chain calculated ETH rewards to add
     * @param tokenRewardsToAdd Off-chain calculated token rewards to add
     */
    function addETHLiquidityWithPriceRange(
        uint256 minPrice,
        uint256 maxPrice,
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) external payable;

    /**
     * @notice Add token liquidity with price range and optional position update
     * @param tokenAmount Amount of tokens to deposit
     * @param minPrice Minimum price for this liquidity position
     * @param maxPrice Maximum price for this liquidity position
     * @param proof Merkle proof for position update (required if user has pending updates)
     * @param newETHPosition New ETH position (required if proof provided)
     * @param newTokenPosition New token position (required if proof provided)
     * @param ethRewardsToAdd Off-chain calculated ETH rewards to add
     * @param tokenRewardsToAdd Off-chain calculated token rewards to add
     */
    function addTokenLiquidityWithPriceRange(
        uint256 tokenAmount,
        uint256 minPrice,
        uint256 maxPrice,
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) external;

    /**
     * @notice Add both ETH and token liquidity with price ranges and optional position update
     * @param tokenAmount Amount of tokens to deposit
     * @param ethMinPrice Minimum price for ETH liquidity position
     * @param ethMaxPrice Maximum price for ETH liquidity position
     * @param tokenMinPrice Minimum price for token liquidity position
     * @param tokenMaxPrice Maximum price for token liquidity position
     * @param proof Merkle proof for position update (required if user has pending updates)
     * @param newETHPosition New ETH position (required if proof provided)
     * @param newTokenPosition New token position (required if proof provided)
     * @param ethRewardsToAdd Off-chain calculated ETH rewards to add
     * @param tokenRewardsToAdd Off-chain calculated token rewards to add
     */
    function addLiquidityWithPriceRanges(
        uint256 tokenAmount,
        uint256 ethMinPrice,
        uint256 ethMaxPrice,
        uint256 tokenMinPrice,
        uint256 tokenMaxPrice,
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) external payable;

    /**
     * @notice Update price range for existing ETH liquidity position
     * @param newMinPrice New minimum price
     * @param newMaxPrice New maximum price
     * @param proof Merkle proof for position update (required if user has pending updates)
     * @param newETHPosition New ETH position (required if proof provided)
     * @param newTokenPosition New token position (required if proof provided)
     * @param ethRewardsToAdd Off-chain calculated ETH rewards to add
     * @param tokenRewardsToAdd Off-chain calculated token rewards to add
     */
    function updateETHPositionPriceRange(
        uint256 newMinPrice,
        uint256 newMaxPrice,
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) external;

    /**
     * @notice Update price range for existing token liquidity position
     * @param newMinPrice New minimum price
     * @param newMaxPrice New maximum price
     * @param proof Merkle proof for position update (required if user has pending updates)
     * @param newETHPosition New ETH position (required if proof provided)
     * @param newTokenPosition New token position (required if proof provided)
     * @param ethRewardsToAdd Off-chain calculated ETH rewards to add
     * @param tokenRewardsToAdd Off-chain calculated token rewards to add
     */
    function updateTokenPositionPriceRange(
        uint256 newMinPrice,
        uint256 newMaxPrice,
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) external;

    // Standard liquidity functions (without price ranges for backward compatibility)
    /**
     * @notice Add ETH liquidity with optional position update (no price range)
     * @param proof Merkle proof for position update (required if user has pending updates)
     * @param newETHPosition New ETH position (required if proof provided)
     * @param newTokenPosition New token position (required if proof provided)
     * @param ethRewardsToAdd Off-chain calculated ETH rewards to add
     * @param tokenRewardsToAdd Off-chain calculated token rewards to add
     */
    function addETHLiquidityWithProof(
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) external payable;

    /**
     * @notice Add token liquidity with optional position update (no price range)
     * @param tokenAmount Amount of tokens to deposit
     * @param proof Merkle proof for position update (required if user has pending updates)
     * @param newETHPosition New ETH position (required if proof provided)
     * @param newTokenPosition New token position (required if proof provided)
     * @param ethRewardsToAdd Off-chain calculated ETH rewards to add
     * @param tokenRewardsToAdd Off-chain calculated token rewards to add
     */
    function addTokenLiquidityWithProof(
        uint256 tokenAmount,
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) external;

    // Removal functions with price range support
    /**
     * @notice Remove ETH liquidity with optional position update
     * @param shares Percentage of pool to withdraw (in basis points, 10000 = 100%)
     * @param minEthAmount Minimum amount of ETH to receive
     * @param proof Merkle proof for position update (required if user has pending updates)
     * @param newETHPosition New ETH position (required if proof provided)
     * @param newTokenPosition New token position (required if proof provided)
     * @param ethRewardsToAdd Off-chain calculated ETH rewards to add
     * @param tokenRewardsToAdd Off-chain calculated token rewards to add
     */
    function removeETHLiquidityWithProof(
        uint256 shares,
        uint256 minEthAmount,
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) external;

    /**
     * @notice Remove token liquidity with optional position update
     * @param shares Percentage of pool to withdraw (in basis points, 10000 = 100%)
     * @param minTokenAmount Minimum amount of tokens to receive
     * @param proof Merkle proof for position update (required if user has pending updates)
     * @param newETHPosition New ETH position (required if proof provided)
     * @param newTokenPosition New token position (required if proof provided)
     * @param ethRewardsToAdd Off-chain calculated ETH rewards to add
     * @param tokenRewardsToAdd Off-chain calculated token rewards to add
     */
    function removeTokenLiquidityWithProof(
        uint256 shares,
        uint256 minTokenAmount,
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) external;

    /**
     * @notice Remove both ETH and token liquidity with merkle proof
     * @param ethShares Percentage of ETH liquidity to remove (0-10000)
     * @param tokenShares Percentage of token liquidity to remove (0-10000)
     * @param minEthAmount Minimum ETH amount to receive
     * @param minTokenAmount Minimum token amount to receive
     * @param proof Merkle proof for position update
     * @param newETHPosition New ETH position after update
     * @param newTokenPosition New token position after update
     * @param ethRewardsToAdd Total ETH rewards (accumulated)
     * @param tokenRewardsToAdd Total token rewards (accumulated)
     */
    function removeLiquidityWithProof(
        uint256 ethShares,
        uint256 tokenShares,
        uint256 minEthAmount,
        uint256 minTokenAmount,
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) external;

    // Order execution functions (same as V2)
    /**
     * @notice Execute buy order - Orderbook sends ETH, receives tokens
     * @param ethAmount Amount of ETH sent by orderbook
     * @param tokenAmount Amount of tokens to send to orderbook
     * @param newMerkleRoot New merkle root to update after trade
     */
    function executeBuyOrder(
        uint256 ethAmount,
        uint256 tokenAmount,
        bytes32 newMerkleRoot
    ) external payable;

    /**
     * @notice Execute sell order - Orderbook sends tokens, receives ETH
     * @param ethAmount Amount of ETH to send to orderbook
     * @param tokenAmount Amount of tokens sent by orderbook
     * @param newMerkleRoot New merkle root to update after trade
     */
    function executeSellOrder(
        uint256 ethAmount,
        uint256 tokenAmount,
        bytes32 newMerkleRoot
    ) external;

    // Reward distribution functions (same as V2)
    /**
     * @notice Distributes ETH fees to ETH providers
     */
    function distributePoolFee() external payable;

    /**
     * @notice Distributes token fees to token providers
     * @param tokenAmount Amount of tokens to distribute as fees
     */
    function distributeTokenFee(uint256 tokenAmount) external;

    // Reward claiming functions (same as V2)
    /**
     * @notice Claim all rewards with optional position update
     * @param proof Merkle proof for position update (required if user has pending updates)
     * @param newETHPosition New ETH position (required if proof provided)
     * @param newTokenPosition New token position (required if proof provided)
     * @param ethRewardsToAdd Off-chain calculated ETH rewards to add
     * @param tokenRewardsToAdd Off-chain calculated token rewards to add
     */
    function claimRewardsWithProof(
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) external;

    /**
     * @notice Claim only ETH rewards with optional position update
     * @param proof Merkle proof for position update (required if user has pending updates)
     * @param newETHPosition New ETH position (required if proof provided)
     * @param newTokenPosition New token position (required if proof provided)
     * @param ethRewardsToAdd Off-chain calculated ETH rewards to add
     * @param tokenRewardsToAdd Off-chain calculated token rewards to add
     */
    function claimETHRewardsWithProof(
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) external;

    /**
     * @notice Claim only token rewards with optional position update
     * @param proof Merkle proof for position update (required if user has pending updates)
     * @param newETHPosition New ETH position (required if proof provided)
     * @param newTokenPosition New token position (required if proof provided)
     * @param ethRewardsToAdd Off-chain calculated ETH rewards to add
     * @param tokenRewardsToAdd Off-chain calculated token rewards to add
     */
    function claimTokenRewardsWithProof(
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) external;

    // Position update functions (same as V2)
    /**
     * @notice Update user position using merkle proof
     * @param version Version of the merkle root
     * @param proof Merkle proof for the user's new position
     * @param newETHPosition New ETH position (actual ETH amount)
     * @param newTokenPosition New token position (actual token amount)
     * @param ethRewardsToAdd Off-chain calculated ETH rewards to add
     * @param tokenRewardsToAdd Off-chain calculated token rewards to add
     */
    function updateUserPosition(
        uint256 version,
        bytes32[] calldata proof,
        uint256 newETHPosition,
        uint256 newTokenPosition,
        uint256 ethRewardsToAdd,
        uint256 tokenRewardsToAdd
    ) external;

    // Factory-only functions (same as V2)
    /**
     * @notice Add ETH liquidity for a specific user (factory only)
     * @param user User address to add liquidity for
     */
    function addETHLiquidityForUser(
        address user,
        uint256 minPrice,
        uint256 maxPrice
    ) external payable;

    /**
     * @notice Add token liquidity for a specific user (factory only)
     * @param user User address to add liquidity for
     * @param tokenAmount Amount of tokens to deposit
     */
    function addTokenLiquidityForUser(
        address user,
        uint256 tokenAmount,
        uint256 minPrice,
        uint256 maxPrice
    ) external;

    // View functions with price range support
    /**
     * @notice Get comprehensive user position information including price ranges
     * @param user Address of the user
     * @return ethPosition User's ETH position
     * @return tokenPosition User's token position
     * @return ethLPShares User's ETH LP shares
     * @return tokenLPShares User's token LP shares
     * @return ethPendingRewards User's pending ETH rewards
     * @return tokenPendingRewards User's pending token rewards
     * @return lastUpdateVersion User's last update version
     * @return ethPriceRange User's ETH price range
     * @return tokenPriceRange User's token price range
     */
    function getUserPositionWithPriceRanges(
        address user
    )
        external
        view
        returns (
            uint256 ethPosition,
            uint256 tokenPosition,
            uint256 ethLPShares,
            uint256 tokenLPShares,
            uint256 ethPendingRewards,
            uint256 tokenPendingRewards,
            uint256 lastUpdateVersion,
            PriceRange memory ethPriceRange,
            PriceRange memory tokenPriceRange
        );

    /**
     * @notice Get user position information (backward compatibility)
     * @param user Address of the user
     * @return ethPosition User's ETH position
     * @return tokenPosition User's token position
     * @return ethLPShares User's ETH LP shares
     * @return tokenLPShares User's token LP shares
     * @return ethPendingRewards User's pending ETH rewards
     * @return tokenPendingRewards User's pending token rewards
     * @return lastUpdateVersion User's last update version
     */
    function getUserPosition(
        address user
    )
        external
        view
        returns (
            uint256 ethPosition,
            uint256 tokenPosition,
            uint256 ethLPShares,
            uint256 tokenLPShares,
            uint256 ethPendingRewards,
            uint256 tokenPendingRewards,
            uint256 lastUpdateVersion
        );

    /**
     * @notice Get pool metrics for ETH pool
     * @return metrics ETH pool metrics
     */
    function getETHPoolMetrics()
        external
        view
        returns (PoolMetrics memory metrics);

    /**
     * @notice Get pool metrics for token pool
     * @return metrics Token pool metrics
     */
    function getTokenPoolMetrics()
        external
        view
        returns (PoolMetrics memory metrics);

    /**
     * @notice Get current market price (ETH per token)
     * @return price Current market price in wei per token
     */
    function getCurrentPrice() external view returns (uint256 price);

    /**
     * @notice Check if a price is within a given range
     * @param price Price to check
     * @param minPrice Minimum price
     * @param maxPrice Maximum price
     * @return isWithinRange True if price is within range
     */
    function isPriceWithinRange(
        uint256 price,
        uint256 minPrice,
        uint256 maxPrice
    ) external view returns (bool isWithinRange);

    /**
     * @notice Get the Uniswap V2 pair address for this token
     * @return pairAddress Address of the Uniswap V2 pair
     */
    function getPairAddress() external view returns (address pairAddress);

    /**
     * @notice Get the reserves for this token pair
     * @return reserveETH ETH reserve amount
     * @return reserveToken Token reserve amount
     */
    function getReserves()
        external
        view
        returns (uint256 reserveETH, uint256 reserveToken);

    // Owner functions (same as V2)
    /**
     * @notice Set minimum ETH liquidity requirement
     * @param _minLiquidity New minimum ETH liquidity amount
     */
    function setMinLiquidity(uint256 _minLiquidity) external;

    /**
     * @notice Set minimum token liquidity requirement
     * @param _minTokenLiquidity New minimum token liquidity amount
     */
    function setMinTokenLiquidity(uint256 _minTokenLiquidity) external;

    // Emergency functions (same as V2)
    /**
     * @notice Emergency function to withdraw ETH from the contract
     * @param recipient Address to receive the ETH
     * @param amount Amount of ETH to withdraw (0 = withdraw all)
     */
    function emergencyWithdrawETH(
        address payable recipient,
        uint256 amount
    ) external;

    /**
     * @notice Emergency function to withdraw any ERC20 token from the contract
     * @param tokenAddress Address of the token to withdraw
     * @param recipient Address to receive the tokens
     * @param amount Amount of tokens to withdraw (0 = withdraw all)
     */
    function emergencyWithdrawToken(
        address tokenAddress,
        address recipient,
        uint256 amount
    ) external;

    // Basic view functions (same as V2)
    function token() external view returns (address);

    function factory() external view returns (address);

    function totalETH() external view returns (uint256);

    function totalTokens() external view returns (uint256);

    function merkleRoot() external view returns (bytes32);

    function currentVersion() external view returns (uint256);

    function minLiquidity() external view returns (uint256);

    function minTokenLiquidity() external view returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
 * @title IGradientRegistry
 * @notice Interface for the GradientRegistry contract
 */
interface IGradientRegistry {
    /**
     * @notice Set all main contract addresses at once
     * @param _marketMakerFactory Address of the MarketMakerFactory contract
     * @param _gradientToken Address of the Gradient token contract
     * @param _orderbook Address of the Orderbook contract
     * @param _fallbackExecutor Address of the FallbackExecutor contract
     * @param _router Address of the Uniswap V2 Router contract
     * @param _feeManager Address of the FeeManager contract
     */
    function setMainContracts(
        address _marketMakerFactory,
        address _gradientToken,
        address _orderbook,
        address _fallbackExecutor,
        address _router,
        address _feeManager
    ) external;

    /**
     * @notice Set an individual main contract address
     * @param contractName Name of the contract to update
     * @param newAddress New address for the contract
     */
    function setContractAddress(
        string calldata contractName,
        address newAddress
    ) external;

    /**
     * @notice Set an additional contract address using a key
     * @param key The key to identify the contract
     * @param contractAddress The address of the contract
     */
    function setAdditionalContract(
        bytes32 key,
        address contractAddress
    ) external;

    /**
     * @notice Set the block status of a token
     * @param token The address of the token to set the block status of
     * @param blocked Whether the token should be blocked
     */
    function setTokenBlockStatus(address token, bool blocked) external;

    /**
     * @notice Set a reward distributor address
     * @param rewardDistributor The address of the reward distributor to authorize
     */
    function setRewardDistributor(address rewardDistributor) external;

    /**
     * @notice Authorize or deauthorize a fulfiller
     * @param fulfiller The address of the fulfiller to authorize
     * @param status The status of the fulfiller
     */
    function authorizeFulfiller(address fulfiller, bool status) external;

    /**
     * @notice Check if an address is an authorized fulfiller
     * @param fulfiller The address to check
     * @return bool Whether the address is an authorized fulfiller
     */
    function isAuthorizedFulfiller(
        address fulfiller
    ) external view returns (bool);

    /**
     * @notice Get all main contract addresses
     * @return _marketMakerFactory Address of the MarketMakerFactory contract
     * @return _gradientToken Address of the Gradient token contract
     * @return _orderbook Address of the Orderbook contract
     * @return _fallbackExecutor Address of the FallbackExecutor contract
     * @return _router Address of the Uniswap V2 Router contract
     * @return _feeManager Address of the FeeManager contract
     */
    function getAllMainContracts()
        external
        view
        returns (
            address _marketMakerFactory,
            address _gradientToken,
            address _orderbook,
            address _fallbackExecutor,
            address _router,
            address _feeManager
        );

    // View functions for individual contract addresses
    function marketMakerFactory() external view returns (address);

    // Legacy function for backward compatibility (returns factory address)
    function marketMakerPool() external view returns (address);

    // New function to get pool for specific token
    function getMarketMakerPool(address token) external view returns (address);

    // New function to check if pool exists for token
    function poolExists(address token) external view returns (bool);

    function gradientToken() external view returns (address);

    function orderbook() external view returns (address);

    function fallbackExecutor() external view returns (address);

    function router() external view returns (address);

    function feeManager() external view returns (address);

    // View functions for mappings
    function blockedTokens(address token) external view returns (bool);

    function isRewardDistributor(
        address rewardDistributor
    ) external view returns (bool);

    function authorizedFulfillers(
        address fulfiller
    ) external view returns (bool);

    // Partner token management functions (for market maker fee splits)
    function setPartnerToken(address token, address partnerWallet) external;

    function removePartnerToken(address token) external;

    function checkIsPartnerToken(address token) external view returns (bool);

    function getPartnerWallet(address token) external view returns (address);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IUniswapV2Factory {
    event PairCreated(
        address indexed token0,
        address indexed token1,
        address pair,
        uint
    );

    function feeTo() external view returns (address);

    function feeToSetter() external view returns (address);

    function getPair(
        address tokenA,
        address tokenB
    ) external view returns (address pair);

    function allPairs(uint) external view returns (address pair);

    function allPairsLength() external view returns (uint);

    function createPair(
        address tokenA,
        address tokenB
    ) external returns (address pair);

    function setFeeTo(address) external;

    function setFeeToSetter(address) external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IUniswapV2Pair {
    event Approval(address indexed owner, address indexed spender, uint value);
    event Transfer(address indexed from, address indexed to, uint value);

    function name() external pure returns (string memory);

    function symbol() external pure returns (string memory);

    function decimals() external pure returns (uint8);

    function totalSupply() external view returns (uint);

    function balanceOf(address owner) external view returns (uint);

    function allowance(
        address owner,
        address spender
    ) external view returns (uint);

    function approve(address spender, uint value) external returns (bool);

    function transfer(address to, uint value) external returns (bool);

    function transferFrom(
        address from,
        address to,
        uint value
    ) external returns (bool);

    function DOMAIN_SEPARATOR() external view returns (bytes32);

    function PERMIT_TYPEHASH() external pure returns (bytes32);

    function nonces(address owner) external view returns (uint);

    function permit(
        address owner,
        address spender,
        uint value,
        uint deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    event Mint(address indexed sender, uint amount0, uint amount1);
    event Burn(
        address indexed sender,
        uint amount0,
        uint amount1,
        address indexed to
    );
    event Swap(
        address indexed sender,
        uint amount0In,
        uint amount1In,
        uint amount0Out,
        uint amount1Out,
        address indexed to
    );
    event Sync(uint112 reserve0, uint112 reserve1);

    function MINIMUM_LIQUIDITY() external pure returns (uint);

    function factory() external view returns (address);

    function token0() external view returns (address);

    function token1() external view returns (address);

    function getReserves()
        external
        view
        returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);

    function price0CumulativeLast() external view returns (uint);

    function price1CumulativeLast() external view returns (uint);

    function kLast() external view returns (uint);

    function mint(address to) external returns (uint liquidity);

    function burn(address to) external returns (uint amount0, uint amount1);

    function swap(
        uint amount0Out,
        uint amount1Out,
        address to,
        bytes calldata data
    ) external;

    function skim(address to) external;

    function sync() external;

    function initialize(address, address) external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IUniswapV2Router01 {
    function factory() external pure returns (address);

    function WETH() external pure returns (address);

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint amountADesired,
        uint amountBDesired,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB, uint liquidity);

    function addLiquidityETH(
        address token,
        uint amountTokenDesired,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    )
        external
        payable
        returns (uint amountToken, uint amountETH, uint liquidity);

    function removeLiquidity(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB);

    function removeLiquidityETH(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external returns (uint amountToken, uint amountETH);

    function removeLiquidityWithPermit(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint amountA, uint amountB);

    function removeLiquidityETHWithPermit(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint amountToken, uint amountETH);

    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);

    function swapTokensForExactTokens(
        uint amountOut,
        uint amountInMax,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);

    function swapExactETHForTokens(
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external payable returns (uint[] memory amounts);

    function swapTokensForExactETH(
        uint amountOut,
        uint amountInMax,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);

    function swapExactTokensForETH(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);

    function swapETHForExactTokens(
        uint amountOut,
        address[] calldata path,
        address to,
        uint deadline
    ) external payable returns (uint[] memory amounts);

    function quote(
        uint amountA,
        uint reserveA,
        uint reserveB
    ) external pure returns (uint amountB);

    function getAmountOut(
        uint amountIn,
        uint reserveIn,
        uint reserveOut
    ) external pure returns (uint amountOut);

    function getAmountIn(
        uint amountOut,
        uint reserveIn,
        uint reserveOut
    ) external pure returns (uint amountIn);

    function getAmountsOut(
        uint amountIn,
        address[] calldata path
    ) external view returns (uint[] memory amounts);

    function getAmountsIn(
        uint amountOut,
        address[] calldata path
    ) external view returns (uint[] memory amounts);
}

interface IUniswapV2Router02 is IUniswapV2Router01 {
    function removeLiquidityETHSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external returns (uint amountETH);

    function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint amountETH);

    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;

    function swapExactETHForTokensSupportingFeeOnTransferTokens(
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external payable;

    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;
}

File 27 of 29 : IUniswapV3Factory.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IUniswapV3Factory {
    function getPool(
        address tokenA,
        address tokenB,
        uint24 fee
    ) external view returns (address pool);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IUniswapV3Pool {
    function token0() external view returns (address);

    function token1() external view returns (address);

    function fee() external view returns (uint24);

    function slot0()
        external
        view
        returns (
            uint160 sqrtPriceX96,
            int24 tick,
            uint16 observationIndex,
            uint16 observationCardinality,
            uint16 observationCardinalityNext,
            uint8 feeProtocol,
            bool unlocked
        );

    function liquidity() external view returns (uint128);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
 * @title IUniswapV3PriceHelper
 * @notice Interface for Uniswap V3 price helper contract
 * @dev This helper reduces pool contract size by handling all V3-specific logic
 */
interface IUniswapV3PriceHelper {
    /**
     * @notice Get V3 pool address for a token by trying different fee tiers
     * @param token Address of the token
     * @param weth WETH address
     * @return poolAddress Address of the V3 pool, or address(0) if not found
     */
    function getV3PoolAddress(
        address token,
        address weth
    ) external view returns (address poolAddress);

    /**
     * @notice Get price from Uniswap V3 pool
     * @param token Address of the token
     * @param weth WETH address
     * @param tokenDecimals Number of decimals for the token
     * @return price Price in ETH per token (18 decimals), or 0 if pool doesn't exist
     */
    function getPriceFromV3(
        address token,
        address weth,
        uint8 tokenDecimals
    ) external view returns (uint256 price);

    /**
     * @notice Get current market price from Uniswap (checks V3 first, then V2)
     * @param token Address of the token
     * @param routerAddress Address of the Uniswap V2 Router (to get WETH and V2 factory)
     * @param tokenDecimals Number of decimals for the token
     * @return price Current market price in wei per token
     */
    function getCurrentPrice(
        address token,
        address routerAddress,
        uint8 tokenDecimals
    ) external view returns (uint256 price);

    /**
     * @notice Get amount out for a swap in Uniswap V3 (similar to V2's getAmountOut)
     * @param amountIn Amount of input token
     * @param tokenIn Address of the input token
     * @param tokenOut Address of the output token
     * @return amountOut Amount of output token that would be received
     * @dev Returns 0 if pool doesn't exist or calculation fails
     */
    function getAmountOut(
        uint256 amountIn,
        address tokenIn,
        address tokenOut
    ) external view returns (uint256 amountOut);
}

Settings
{
  "viaIR": true,
  "optimizer": {
    "enabled": true,
    "runs": 1
  },
  "evmVersion": "paris",
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  }
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"contract IGradientRegistry","name":"_gradientRegistry","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldFeePercentage","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newFeePercentage","type":"uint256"}],"name":"DefaultFeePercentageUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldTolerance","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newTolerance","type":"uint256"}],"name":"DustToleranceUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"EmergencyWithdrawETH","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"EmergencyWithdrawToken","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"marketMakerPool","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalFee","type":"uint256"}],"name":"FeeDistributedToPool","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldDeviation","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newDeviation","type":"uint256"}],"name":"MaxPriceDeviationUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newMaxTTL","type":"uint256"}],"name":"MaxTTLUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"orderId","type":"uint256"}],"name":"OrderCancelled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"orderId","type":"uint256"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"enum GradientOrderbook.OrderType","name":"orderType","type":"uint8"},{"indexed":false,"internalType":"enum GradientOrderbook.OrderExecutionType","name":"executionType","type":"uint8"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"price","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"expirationTime","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalCost","type":"uint256"},{"indexed":false,"internalType":"string","name":"objectId","type":"string"},{"indexed":false,"internalType":"bool","name":"isAutofallback","type":"bool"}],"name":"OrderCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"orderId","type":"uint256"}],"name":"OrderExpired","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"orderId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalFilledAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"executionPrice","type":"uint256"}],"name":"OrderFulfilled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"orderId","type":"uint256"},{"indexed":true,"internalType":"address","name":"marketMakerPool","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"price","type":"uint256"}],"name":"OrderFulfilledByMarketMaker","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"orderId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"matchedOrderId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"price","type":"uint256"}],"name":"OrderFulfilledByMatching","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"orderId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"remaining","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalFilledAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"executionPrice","type":"uint256"}],"name":"OrderPartiallyFulfilled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"minSize","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"maxSize","type":"uint256"}],"name":"OrderSizeLimitsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newInterval","type":"uint256"}],"name":"RateLimitUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"oldFeePercentage","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newFeePercentage","type":"uint256"}],"name":"TokenSpecificFeePercentageUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldFactory","type":"address"},{"indexed":true,"internalType":"address","name":"newFactory","type":"address"}],"name":"UniswapV3FactoryUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"priceHelper","type":"address"}],"name":"UniswapV3PriceHelperUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint24[]","name":"feeTiers","type":"uint24[]"}],"name":"V3FeeTiersUpdated","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"DIVISOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_FEE_PERCENTAGE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_TOKEN_SPECIFIC_FEE_PERCENTAGE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_FEE_PERCENTAGE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"orderId","type":"uint256"},{"internalType":"uint256","name":"fillAmount","type":"uint256"},{"internalType":"uint256","name":"minAmountOut","type":"uint256"}],"name":"autoFallbackOrderWithAMM","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"orderId","type":"uint256"}],"name":"cancelOrder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"orderIds","type":"uint256[]"}],"name":"cleanupExpiredOrders","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"enum GradientOrderbook.OrderType","name":"orderType","type":"uint8"},{"internalType":"enum GradientOrderbook.OrderExecutionType","name":"executionType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"uint256","name":"ttl","type":"uint256"},{"internalType":"string","name":"objectId","type":"string"},{"internalType":"bool","name":"isAutofallback","type":"bool"}],"name":"createOrder","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"defaultFeePercentage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dustTolerance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address payable","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"emergencyWithdrawETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"tokens","type":"address[]"},{"internalType":"address","name":"recipient","type":"address"}],"name":"emergencyWithdrawMultipleTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"emergencyWithdrawToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"feeManager","outputs":[{"internalType":"contract IGradientFeeManager","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"buyOrderId","type":"uint256"},{"internalType":"uint256","name":"sellOrderId","type":"uint256"},{"internalType":"uint256","name":"fillAmount","type":"uint256"}],"internalType":"struct GradientOrderbook.OrderMatch[]","name":"matches","type":"tuple[]"}],"name":"fulfillLimitOrders","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"buyOrderId","type":"uint256"},{"internalType":"uint256","name":"sellOrderId","type":"uint256"},{"internalType":"uint256","name":"fillAmount","type":"uint256"}],"internalType":"struct GradientOrderbook.OrderMatch[]","name":"matches","type":"tuple[]"},{"internalType":"uint256[]","name":"executionPrices","type":"uint256[]"}],"name":"fulfillMarketOrders","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"orderIds","type":"uint256[]"},{"internalType":"uint256[]","name":"fillAmounts","type":"uint256[]"},{"internalType":"uint256[]","name":"executionPrices","type":"uint256[]"},{"internalType":"bytes32","name":"merkleRoot","type":"bytes32"}],"name":"fulfillOrdersWithMarketMaker","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"orderId","type":"uint256"},{"internalType":"uint256","name":"fillAmount","type":"uint256"},{"internalType":"uint256","name":"minAmountOut","type":"uint256"}],"name":"fulfillOwnOrderWithAMM","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"enum GradientOrderbook.OrderType","name":"orderType","type":"uint8"},{"internalType":"enum GradientOrderbook.OrderExecutionType","name":"executionType","type":"uint8"}],"name":"getActiveOrders","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"queueKey","type":"bytes32"}],"name":"getActiveOrdersCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"getCurrentFeePercentage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"getCurrentMarketPrice","outputs":[{"internalType":"uint256","name":"marketPrice","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"orderId","type":"uint256"}],"name":"getOrder","outputs":[{"components":[{"internalType":"uint256","name":"orderId","type":"uint256"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"enum GradientOrderbook.OrderType","name":"orderType","type":"uint8"},{"internalType":"enum GradientOrderbook.OrderExecutionType","name":"executionType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"uint256","name":"ethAmount","type":"uint256"},{"internalType":"uint256","name":"ethSpent","type":"uint256"},{"internalType":"uint256","name":"filledAmount","type":"uint256"},{"internalType":"uint256","name":"expirationTime","type":"uint256"},{"internalType":"enum GradientOrderbook.OrderStatus","name":"status","type":"uint8"}],"internalType":"struct GradientOrderbook.Order","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"orderId","type":"uint256"}],"name":"getRemainingAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"getReserves","outputs":[{"internalType":"uint256","name":"reserveETH","type":"uint256"},{"internalType":"uint256","name":"reserveToken","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gradientRegistry","outputs":[{"internalType":"contract IGradientRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"headOrder","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"orderId","type":"uint256"}],"name":"isOrderExpired","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"linkedOrders","outputs":[{"internalType":"uint256","name":"prev","type":"uint256"},{"internalType":"uint256","name":"next","type":"uint256"},{"internalType":"bool","name":"exists","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxOrderSize","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxOrderTtl","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxPriceDeviation","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minOrderSize","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"orders","outputs":[{"internalType":"uint256","name":"orderId","type":"uint256"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"enum GradientOrderbook.OrderType","name":"orderType","type":"uint8"},{"internalType":"enum GradientOrderbook.OrderExecutionType","name":"executionType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"uint256","name":"ethAmount","type":"uint256"},{"internalType":"uint256","name":"ethSpent","type":"uint256"},{"internalType":"uint256","name":"filledAmount","type":"uint256"},{"internalType":"uint256","name":"expirationTime","type":"uint256"},{"internalType":"enum GradientOrderbook.OrderStatus","name":"status","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"removeTokenSpecificFeePercentage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newFeePercentage","type":"uint256"}],"name":"setDefaultFeePercentage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IGradientFeeManager","name":"_feeManager","type":"address"}],"name":"setFeeManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IGradientRegistry","name":"_gradientRegistry","type":"address"}],"name":"setGradientRegistry","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxOrderTtl","type":"uint256"}],"name":"setMaxOrderTtl","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_minOrderSize","type":"uint256"},{"internalType":"uint256","name":"_maxOrderSize","type":"uint256"}],"name":"setOrderSizeLimits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"newFeePercentage","type":"uint256"}],"name":"setTokenSpecificFeePercentage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_uniswapV3Factory","type":"address"}],"name":"setUniswapV3Factory","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IUniswapV3PriceHelper","name":"_uniswapV3PriceHelper","type":"address"}],"name":"setUniswapV3PriceHelper","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint24[]","name":"_feeTiers","type":"uint24[]"}],"name":"setV3FeeTiers","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"tailOrder","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"tokenSpecificFeePercentage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"totalOrderCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"uniswapV3Factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"uniswapV3PriceHelper","outputs":[{"internalType":"contract IUniswapV3PriceHelper","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"newDustTolerance","type":"uint256"}],"name":"updateDustTolerance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newDeviation","type":"uint256"}],"name":"updateMaxPriceDeviation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"v3FeeTiers","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"executionPrice","type":"uint256"}],"name":"validateExecutionPrice","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]

60806040523461022557604051601f6161f938819003918201601f19168301916001600160401b038311848410176101f95780849260209460405283398101031261022557516001600160a01b0381169081900361022557331561020f5760008054336001600160a01b0319821681178355916001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a3600180556101f46010556064601155604051606081016001600160401b038111828210176101f9576040526101f48152610bb86020820152612710604082015260145460036014558060031061017e575b506014600052602060002060009160005b6003811061015457505055600280546001600160a01b031916919091179055606460055564e8d4a51000600d55683635c9adc5dea00000600e5562278d00600f55604051615fce908161022b8239f35b9092602060019161ffff8651169062ffffff8560030260031b92831b921b19161794019101610104565b60146000527fce6d7b5282bd9a3661ae061feed1dbda4e52ab073b1f9285be6e155d9c38d4ec80546001600160481b03168155600a60099290920191909104017fce6d7b5282bd9a3661ae061feed1dbda4e52ab073b1f9285be6e155d9c38d4ed5b8181106101ed57506100f3565b600081556001016101e0565b634e487b7160e01b600052604160045260246000fd5b631e4fbdf760e01b600052600060045260246000fd5b600080fdfe60806040526004361015610018575b361561001657005b005b60003560e01c806304ca1339146146355780630c6a3a8f14613df75780630d34219c14613dcb57806310e1c16614613dad57806313b64e2414613cfb5780631e99981414613cdd5780631f32eb7714613b8157806324f1c5c114613ae9578063277327a5146139705780633410fe6e146139535780633aad938d146139365780633e99c1e41461363d578063457a892414613175578063472d35b9146130f05780634d067345146130cd578063514fcac714612e91578063558e44d314612e7457806359165ac614612cb757806359c6931314612c995780635b54918214612c70578063634eef4714612b7e57806366961d4414612b60578063679230f214612b4457806367bf6ba614612b1b5780636859ebf014612a5057806370cec1ac14612986578063715018a61461293f578063727d2114146128e85780637af187881461289257806381bbc0d3146127f5578063830562bc146127cc57806386ea511e1461279b57806387d11e61146125ce5780638da5cb5b146125a55780638dfb8ccd1461255e5780638f9057df14612005578063975b866214611fe75780639c302fdb14611a585780639f92b715146119f6578063a1a65f9c146119bc578063a674537f1461199e578063a85c38ef146118c8578063a8b37cfe14610edb578063b482403414610ebd578063b7bac4cf14610e0c578063cb25c17d14610de0578063d09ef24114610bf5578063d0fb020314610bcc578063d23a0bd01461094b578063d79e8567146107fa578063e2cfcfee14610747578063ea7bcef414610394578063f1a82c7e14610376578063f2fde38b146102ff578063f6252ff2146102b65763fd31f89a0361000e57346102b15760203660031901126102b157600435600052600a6020526020604060002054604051908152f35b600080fd5b346102b15760203660031901126102b15760206102f760043580600052600783526102f260018060a01b03600160406000200154161515614db5565b615299565b604051908152f35b346102b15760203660031901126102b157610318614658565b61032061556a565b6001600160a01b0316801561036057600080546001600160a01b03198116831782556001600160a01b031690600080516020615f798339815191529080a3005b631e4fbdf760e01b600052600060045260246000fd5b346102b15760003660031901126102b1576020600e54604051908152f35b346102b15760203660031901126102b1576004356001600160401b0381116102b157366023820112156102b1578060040135906103d08261478a565b916103de60405193846146bb565b8083526024602084019160051b830101913683116102b157602401905b828210610737578361040b6154ff565b8051156106fa5760648151116106a95760005b81518110156106a3576104318183614b7a565b519081600052600760205261045760018060a01b03600160406000200154161515614db5565b8160005260076020526040600020916009830160ff815416600481101561068d576104829015614e36565b61048b82614f3c565b1561065457816104ff60019561050493600360ff198254161790558681019081548860ff8260a01c166104bd81614773565b146105f0575b50815460ff8160a01c166104d681614773565b1561052f575b506002888060a01b039101541690549060ff808360a81c169260a01c1690615593565b615742565b7f1ad308dc7017610c82d08084545f7176df5e2f08f078c3c8f8926cd7e5555514600080a20161041e565b8860ff8260a81c1661054081614773565b036105ab5760058201546006830154600091818111156105a4576105649250614aaf565b80610570575b506104dc565b600080808361059d956105868396471015614eb1565b8e8060a01b03165af1610597614abc565b50614efc565b898061056a565b5050610564565b670de0b6b3a76400006105e3600384015460078501546000918181116000146105e9576105d89250614aaf565b600485015490614a74565b04610564565b50506105d8565b600382015460078301546000918181111561064c5761060f9250614aaf565b905b8161061d575b506104c3565b610645916106378b8060a01b0360028601541680926156ff565b918b8060a01b031690615602565b8980610617565b505090610611565b60405162461bcd60e51b815260206004820152601160248201527013dc99195c881b9bdd08195e1c1a5c9959607a1b6044820152606490fd5b634e487b7160e01b600052602160045260246000fd5b60018055005b60405162461bcd60e51b815260206004820152602360248201527f546f6f206d616e79206f726465727320746f20636c65616e207570206174206f6044820152626e636560e81b6064820152608490fd5b60405162461bcd60e51b815260206004820152601560248201527404e6f206f726465727320746f20636c65616e20757605c1b6044820152606490fd5b81358152602091820191016103fb565b346102b15760203660031901126102b157610760614658565b61076861556a565b6001600160a01b031680156107bb57601280546001600160a01b0319811683179091556001600160a01b03167f3ddadec435df2f077c5597f4952343aa7ef9a4db1af45c9e6f92ff7f6e1c67c1600080a3005b60405162461bcd60e51b8152602060048201526017602482015276496e76616c696420666163746f7279206164647265737360481b6044820152606490fd5b346102b15760403660031901126102b1576004356001600160a01b038116908190036102b15760243561082b61556a565b610836821515614be8565b4715610911578061090c5750475b4781116108cc57600080808084865af161085c614abc565b501561088f5760207f20f907b58305c7b76035bc03b26f32b1c4f6560f96be6f3bb54c5c848a2d4ddd91604051908152a2005b60405162461bcd60e51b8152602060048201526015602482015274115512081dda5d1a191c985dd85b0819985a5b1959605a1b6044820152606490fd5b60405162461bcd60e51b8152602060048201526018602482015277496e73756666696369656e74204554482062616c616e636560401b6044820152606490fd5b610844565b60405162461bcd60e51b81526020600482015260126024820152714e6f2045544820746f20776974686472617760701b6044820152606490fd5b346102b15760203660031901126102b1576004356001600160401b0381116102b15761097b903690600401614729565b61098361556a565b8015610b8757600a8111610b4d576001600160401b038111610b3757600160401b8111610b375760145481601455808210610ad8575b508160146000526020600020600a83049060005b828110610a985750600a8202840380610a53575b5050505060405190806020830160208452526040820192906000905b808210610a2c577f8b5911bc3a69df070f5a8ecf2460704729bd313de1b0aba9a8527ff6f0a24a1e84860385a1005b90919384359062ffffff82168092036102b1576020816001938293520195019201906109fd565b9260009360005b818110610a6f575050500155828080806109e1565b9091946020610a8e600192610a8389615273565b908560030290615283565b9601929101610a5a565b6000805b600a8110610ab15750828201556001016109cd565b94906020610acf600192610ac485615273565b908960030290615283565b92019501610a9c565b60146000526020600020600a60098181860104830193010401906003600a84060280610b1a575b505b818110610b0e57506109b9565b60008155600101610b01565b6000198201908154906000199060200360031b1c16905584610aff565b634e487b7160e01b600052604160045260246000fd5b60405162461bcd60e51b8152602060048201526012602482015271546f6f206d616e792066656520746965727360701b6044820152606490fd5b60405162461bcd60e51b815260206004820152601f60248201527f4665652074696572732061727261792063616e6e6f7420626520656d707479006044820152606490fd5b346102b15760003660031901126102b1576003546040516001600160a01b039091168152602090f35b346102b15760203660031901126102b1576004356000610160604051610c1a81614684565b8281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e08201528261010082015282610120820152826101408201520152806000526007602052610c8260018060a01b03600160406000200154161515614db5565b60005260076020526101806040600020610dde6040518080808095610ca682614684565b8054825260018101546001600160a01b0381166020840190815290604084019060ff9060a081901c8216610cd981614773565b835260a81c166060850190610ced81614773565b815260018060a01b03600285015416906080860191825260038501549260a0870193845260048601549460c08801958652610d6861016060ff600960058b01549a60e08d019b8c5261010060068201549d019c8d5261012060078201549e019d8e5261014060088201549f019e8f520154169c019b8c614aa3565b6040519c518d52516001600160a01b031660208d015251610d8881614773565b60408c015251610d9781614773565b60608b0152516001600160a01b031660808a01525160a08901525160c08801525160e08701525161010086015251610120850152516101408401525161016083019061477d565bf35b346102b15760203660031901126102b15760043560005260096020526020604060002054604051908152f35b346102b15760203660031901126102b1576004356001600160a01b038116908190036102b157610e3a61556a565b8015610e7957601380546001600160a01b031916821790557f0b91e9e48ef4e7faf637ee996fa9a770073f525e31b6f7f7e91be3775a602456600080a2005b60405162461bcd60e51b815260206004820152601c60248201527b496e76616c69642070726963652068656c706572206164647265737360201b6044820152606490fd5b346102b15760003660031901126102b1576020600554604051908152f35b346102b15760803660031901126102b1576004356001600160401b0381116102b157610f0b903690600401614729565b6024356001600160401b0381116102b157610f2a903690600401614729565b92906044356001600160401b0381116102b157610f4b903690600401614729565b92909460643593610f5a6154ff565b6002546040516314c530d560e31b815233600482015290602090829060249082906001600160a01b03165afa801561145957610f9e916000916118a9575b50614c56565b851561186d5781861480611864575b610fb690614ce7565b60005b868110610fc65760018055005b610fdc610fd4828588614d3e565b35151561521c565b610fe781838a614d3e565b351561181057610ff8818886614d3e565b3590611005818588614d3e565b359161101282858c614d3e565b3590806000526007602052604060002060405161102e81614684565b8154815260018201546001600160a01b038116602083015260408201906110e09060ff9060a081901c821661106281614773565b845260a81c1692606081019361107781614773565b845260028501546001600160a01b03166080820152600385015460a0820152600485015460c08201908152600586015460e083015260068601546101008301526007860154610120830152600886015461014083015260099095015460ff169061016001614aa3565b600182516110ed81614773565b6110f681614773565b146117d4575b505161110781614773565b61111081614773565b1561176c575b50806000526007602052604060002060ff600982015416600481101561068d576111409015614e36565b61115261114c83614f3c565b15614e75565b600281019260018060a01b038454169161117461116f8385615007565b61599c565b6002546040516316f4f72360e11b815290602090829060049082906001600160a01b03165afa9081156114595760009161174e575b506001600160a01b031690811561170a5760206024926040519384809263bbe4f6db60e01b82528860048301525afa918215611459576000926116da575b506001600160a01b03821695861561168757600182019081549560ff8760a01c169a6112128c614773565b8b158098819961166d575b50156116415761122d8786615ac3565b905b818111156116395750965b611245881515615855565b611262670de0b6b3a764000061125b898b614a74565b049c614773565b156114765750805461127d906001600160a01b0316876156ff565b93883b156102b1578f94898c96604051809e8192633015be1d60e21b83528560048401926112aa93615a04565b0381895a94600095f19b8c15611459576112e29c611465575b5082546001600160a01b03169b6112f090612710906112e8908f614d80565b84614a74565b048092614aaf565b9b816113b2575b50509054825460019b600080516020615f198339815191529860409890979096909590945092611333926001600160a01b039081169116615602565b60078201611342868254614a96565b90555460ff8160a01c1661135581614773565b159081611399575b50611381575b5050611370818387615c75565b82519182526020820152a301610fb9565b60066113909101918254614a96565b90558f80611363565b8b915060a81c60ff166113ab81614773565b143861135d565b816113d09160018060a09c9a98969b99979c1b036003541690615602565b87546003546001600160a01b03918216989116803b156102b15761140f98600080946040519b8c9586948593632a2fc2f760e01b855260048501615ed8565b03925af180156114595760019b600080516020615f198339815191529860409861133393611448575b50939597509b82949698506112f7565b6000611453916146bb565b38611438565b6040513d6000823e3d90fd5b6000611470916146bb565b386112c3565b6114848188969495966156ff565b906020604051809263095ea7b360e01b8252816000816114a8888b600484016155e7565b03925af180156114595761160d575b50883b156102b1578f906000908c6114e3604051948593849363b23d349b60e01b855260048501615a04565b0381838d5af18015611459576115fc575b50546001600160a01b031661271061151461150e83614d80565b8c614a74565b0491611520838c614aaf565b928061156a575b50505097600080516020615f19833981519152949260409492611565600080808060019f60018060a01b038754165af161155f614abc565b50615895565b611333565b60018060a09d9598969997949d1b0360035416803b156102b1576000926115a8926040519e8f9485938492629a7d7960e81b84528460048501615ed8565b03925af195861561145957611565600080808060019f9a60409b600080516020615f198339815191529d6115ec575b50979f50505050508295975081949650611527565b826115f6916146bb565b386115d7565b6000611607916146bb565b386114f4565b61162d9060203d8111611632575b61162581836146bb565b8101906149f3565b6114b7565b503d61161b565b90509661123a565b6003850154600786015460009181811115611666576116609250614aaf565b9061122f565b5050611660565b6001915060a81c60ff1661168081614773565b143861121d565b60405162461bcd60e51b815260206004820152602560248201527f4d61726b6574206d616b657220706f6f6c206e6f7420666f756e6420666f72206044820152643a37b5b2b760d91b6064820152608490fd5b6116fc91925060203d8111611703575b6116f481836146bb565b8101906147a1565b908f6111e7565b503d6116ea565b60405162461bcd60e51b815260206004820152601c60248201527b13585c9ad95d081b585ad95c88199858dd1bdc9e481b9bdd081cd95d60221b6044820152606490fd5b611766915060203d8111611703576116f481836146bb565b8f6111a9565b518203611779578b611116565b60405162461bcd60e51b815260206004820152602d60248201527f457865637574696f6e207072696365206e6f74206d617463686564207769746860448201526c1037b93232b910383934b1b29760991b6064820152608490fd5b516117de81614773565b6117e781614773565b6117fe576117f8825185111561593e565b8d6110fc565b61180b82518510156158e1565b6117f8565b60405162461bcd60e51b815260206004820152602660248201527f457865637574696f6e207072696365206d75737420626520677265617465722060448201526507468616e20360d41b6064820152608490fd5b50858114610fad565b60405162461bcd60e51b8152602060048201526014602482015273139bc81bdc99195c9cc81d1bc8199d5b199a5b1b60621b6044820152606490fd5b6118c2915060203d6020116116325761162581836146bb565b89610f98565b346102b15760203660031901126102b15760043560005260076020526101806040600020610dde81549160018101549060ff8260a01c1660ff8360a81c1660018060a01b03600284015416600384015460048501549060058601549260068701549460078801549660ff600960088b01549a015416996040519c8d5260018060a01b031660208d015261195a81614773565b60408c015261196881614773565b60608b015260808a015260a089015260c088015260e087015261010086015261012085015261014084015261016083019061477d565b346102b15760003660031901126102b1576020600d54604051908152f35b346102b15760203660031901126102b1576001600160a01b036119dd614658565b1660005260066020526020604060002054604051908152f35b346102b15760203660031901126102b1577f72a51ea60242ee3def7dbd2c9b40d17fa8a3f853c78d14f0f9641a507af6e3c86040600435611a3561556a565b611a436101f4821115614f78565b600554908060055582519182526020820152a1005b346102b157611a6636614759565b91611a6f6154ff565b600081815260076020526040902060010154611a95906001600160a01b03161515614db5565b806000526007602052611ab960018060a01b03600160406000200154163314614df8565b611ac482151561521c565b80600052600760205260406000209060ff60098301541693600485101561068d57611af160049515614e36565b600092600181019485549060ff8260a01c1695611b0d87614773565b861580938194611fcd575b5015611fa35750611b2c8884015484615ac3565b905b81811115611f9b5750945b85151590611b4682615855565b60025460405163745e9e9f60e01b8152996020918b919082906001600160a01b03165afa98891561145957600099611f7a575b506001600160a01b038916988915611f3a57600093611b99600093614773565b15611da15750505050611c2b95600160ff875460a81c16611bb981614773565b03611d8157600582015460068301549081811115611d7657602091611bdd91614aaf565b935b60028401805460405163815a1a0d60e01b81526001600160a01b039091166004820152602481018790526044810192909252600160648301529098909289918290879082906084820190565b03925af196871561145957600097611d3d575b508054611c799190611c6390611c5d906001600160a01b03168a615c03565b89614aaf565b905487546001600160a01b039081169116615602565b8515611d2d57670de0b6b3a76400008202828104670de0b6b3a76400001483151715611d17576106a396611cac91614a54565b945b60078201611cbd868254614a96565b90555460ff8160a01c16611cd081614773565b159081611cfd575b50611ce5575b5050615c75565b6006611cf49101918254614a96565b90558480611cde565b6001915060a81c60ff16611d1081614773565b1487611cd8565b634e487b7160e01b600052601160045260246000fd5b6106a39550600481015494611cae565b90966020823d602011611d6e575b81611d58602093836146bb565b81010312611d6b57505195611c79611c3e565b80fd5b3d9150611d4b565b505060206000611bdd565b6020670de0b6b3a7640000611d9a600485015488614a74565b0493611bdf565b90919294986002850191611de5602060018060a01b0385541692611dc5848d6156ff565b9384918860405180968195829463095ea7b360e01b8452600484016155e7565b03925af18015611f2f579160209391611e479d93611f14575b50845460405163815a1a0d60e01b81526001600160a01b0390911660048201526024810191909152604481019290925260006064830152909a8b91908290869082906084820190565b03925af1988915611f09578299611ed1575b5054611e989190819081908190611e8390611e7d906001600160a01b03168e615b8b565b8d614aaf565b8b546001600160a01b03165af161155f614abc565b15611d2d57670de0b6b3a7640000860295808704670de0b6b3a76400001490151715611d1757611ecb846106a397614a54565b94611cae565b9098506020813d602011611f01575b81611eed602093836146bb565b81010312611efd57519781611e59565b5080fd5b3d9150611ee0565b6040513d84823e3d90fd5b611f2a90853d87116116325761162581836146bb565b611dfe565b6040513d87823e3d90fd5b60405162461bcd60e51b815260206004820152601860248201527711985b1b189858dad15e1958dd5d1bdc881b9bdd081cd95d60421b6044820152606490fd5b611f9491995060203d602011611703576116f481836146bb565b9789611b79565b905094611b39565b600384015460078501549081811115611fc657611fc09250614aaf565b90611b2e565b5050611fc0565b6001915060a81c60ff16611fe081614773565b148a611b18565b346102b15760003660031901126102b1576020601054604051908152f35b346102b15760203660031901126102b1576004356001600160401b0381116102b1576120359036906004016146f9565b61203d6154ff565b6002546040516314c530d560e31b815233600482015290602090829060249082906001600160a01b03165afa8015611459576120809160009161253f5750614c56565b61208b811515614c9d565b60005b81811061209b5760018055005b6120af366120aa838587614d2e565b614d4e565b90815160005260076020526040600020602083019081516000526007602052604060002060ff600983015416600481101561068d571580612527575b6120f49061563c565b6120fe8551614f3c565b1580612516575b156124d757600190612153828401549360ff8560a01c1661212581614773565b15806124b7575b61213590615680565b6002848060a01b0391015416838060a01b03600284015416146156c2565b0154906001600160a01b03808216908316146124665760a81c60ff1661217881614773565b15908161244e575b50156124165782516000526007602052604060002090805160005260076020526040600020906004830192835490600484019182548091106123d1576003820154600783019081546000918181116000146123c9576121df9250614aaf565b955b600381015492600782019384546000918181116000146123c2576122059250614aaf565b60408c015198808a116123ba575b508089116123b0575b506122cf916122c960016122b68b670de0b6b3a764000061224b6122b097612245841515615855565b83614a74565b049461229e600080808060028601956122886122708d8c8060a01b038a541690615b8b565b6122828c8060a01b038a54168b615c03565b9d614aaf565b908a808060a01b03910154165af161155f614abc565b838060a01b03905416958680926156ff565b946156ff565b97019660018060a01b0388541692614aaf565b91615602565b6122da868254614a96565b90556122e7858254614a96565b9055845482549081811161231f575b505050906123108392612319959460019851915491615c75565b51915491615c75565b0161208e565b60008093670de0b6b3a7640000612346612340849685969c9b9a999c614aaf565b89614a74565b91549104906001600160a01b03165af161235e614abc565b501561236f579091928880806122f6565b60405162461bcd60e51b8152602060048201526019602482015278115512081cd85d9a5b99dcc81c995d1d5c9b8819985a5b1959603a1b6044820152606490fd5b97506122cf61221c565b98508e612213565b5050612205565b5050956121e1565b60405162461bcd60e51b815260206004820152601f60248201527f5072696365206d69736d6174636820666f72206c696d6974206f7264657273006044820152606490fd5b60405162461bcd60e51b815260206004820152601060248201526f4e6f74206c696d6974206f726465727360801b6044820152606490fd5b60ff915060a81c1661245f81614773565b1586612180565b60405162461bcd60e51b815260206004820152602360248201527f53656c6c657220616e642062757965722063616e6e6f74206265207468652073604482015262616d6560e81b6064820152608490fd5b506121358460ff8186015460a01c166124cf81614773565b14905061212c565b60405162461bcd60e51b81526020600482015260176024820152760c481bd9881d1a19481bdc99195c9cc8195e1c1a5c9959604a1b6044820152606490fd5b506125218351614f3c565b15612105565b5060ff600982015416600481101561068d57156120eb565b612558915060203d6020116116325761162581836146bb565b84610f98565b346102b15760203660031901126102b1576004356014548110156102b15762ffffff60209160146000526003600a84600020818404015492060260031b1c16604051908152f35b346102b15760003660031901126102b1576000546040516001600160a01b039091168152602090f35b346102b15760403660031901126102b1576004356001600160401b0381116102b1576125fe903690600401614729565b9061260761466e565b9161261061556a565b6001600160a01b03831690612626821515614be8565b8015612760576014811161270f5760005b81811061264057005b61264b818386614d3e565b356001600160a01b03811691908290036102b15761266a821515614ba4565b6040516370a0823160e01b8152306004820152602081602481865afa9283156114595785916000946126d6575b50836001946126aa575b50505001612637565b6020816126c7600080516020615f59833981519152938c86615602565b604051908152a38387806126a1565b915091926020823d8211612707575b816126f2602093836146bb565b81010312611d6b575051919084906001612697565b3d91506126e5565b60405162461bcd60e51b815260206004820152602360248201527f546f6f206d616e7920746f6b656e7320746f207769746864726177206174206f6044820152626e636560e81b6064820152608490fd5b60405162461bcd60e51b8152602060048201526013602482015272139bc81d1bdad95b9cc81cdc1958da599a5959606a1b6044820152606490fd5b346102b15760403660031901126102b15760206127c26127b9614658565b60243590615007565b6040519015158152f35b346102b15760003660031901126102b1576002546040516001600160a01b039091168152602090f35b346102b15760203660031901126102b15760043561281161556a565b61271081116128535760407f1a0e49c8d547d5d440b1e87ef8c3f4dcedad3129fe3308e075c0d72395c3451191601154908060115582519182526020820152a1005b60405162461bcd60e51b8152602060048201526017602482015276088eae6e840e8ded8cae4c2dcc6ca40e8dede40d0d2ced604b1b6044820152606490fd5b346102b15760403660031901126102b157600435600052600b60205260406000206024356000526020526060604060002080549060ff600260018301549201541690604051928352602083015215156040820152f35b346102b15760203660031901126102b1577f5829309fa85e85a75b744b2b1ae6b2913b3d2a9c94df75a5b61de31f962256be602060043561292761556a565b612932811515614a0b565b80600f55604051908152a1005b346102b15760003660031901126102b15761295861556a565b600080546001600160a01b0319811682556001600160a01b0316600080516020615f798339815191528280a3005b346102b15760203660031901126102b15761299f614658565b6129a761556a565b6001600160a01b03166129bb811515614ba4565b8060005260066020526040600020548015612a00576040600080516020615ef983398151915291836000526006602052600082812055815190815260006020820152a2005b60405162461bcd60e51b815260206004820152602260248201527f4e6f207370656369666963206665652073657420666f72207468697320746f6b60448201526132b760f11b6064820152608490fd5b346102b15760403660031901126102b157612a69614658565b60243590612a7561556a565b6001600160a01b031690612a8a821515614ba4565b60328110612add57604081612ab261012c600080516020615ef9833981519152941115614f78565b83600052600660205281600020549084600052600660205280836000205582519182526020820152a2005b60405162461bcd60e51b81526020600482015260166024820152754665652070657263656e7461676520746f6f206c6f7760501b6044820152606490fd5b346102b15760003660031901126102b1576013546040516001600160a01b039091168152602090f35b346102b15760003660031901126102b157602060405160328152f35b346102b15760003660031901126102b1576020600f54604051908152f35b346102b15760203660031901126102b1576004356001600160a01b038116908190036102b157612bac61556a565b8015612c2f57600280546001600160a01b0319168217905560405163d0fb020360e01b815290602090829060049082905afa90811561145957600091612c10575b50600380546001600160a01b0319166001600160a01b0392909216919091179055005b612c29915060203d602011611703576116f481836146bb565b81612bed565b60405162461bcd60e51b8152602060048201526019602482015278496e76616c6964206772616469656e7420726567697374727960381b6044820152606490fd5b346102b15760003660031901126102b1576012546040516001600160a01b039091168152602090f35b346102b15760203660031901126102b15760206127c2600435614f3c565b346102b157612cc536614759565b9190612ccf6154ff565b600082815260076020526040902060010154612cf5906001600160a01b03161515614db5565b6002546040516314c530d560e31b81523360048201526001600160a01b039091169290602081602481875afa801561145957612d3891600091612e555750614c56565b612d4382151561521c565b80600052600760205260406000209360ff60098601541694600486101561068d57612d7060049615614e36565b600093600182019586549160ff8360a01c1696612d8c88614773565b871580948195612e3b575b5015612e115750612dab8985015485615ac3565b905b81811115612e095750955b602087151592612dc784615855565b60405163745e9e9f60e01b81529a8b9182905afa98891561145957600099611f7a57506001600160a01b038916988915611f3a57600093611b99600093614773565b905095612db8565b600385015460078601549081811115612e3457612e2e9250614aaf565b90612dad565b5050612e2e565b6001915060a81c60ff16612e4e81614773565b148b612d97565b612e6e915060203d6020116116325761162581836146bb565b86610f98565b346102b15760003660031901126102b15760206040516101f48152f35b346102b15760203660031901126102b157600435612ead6154ff565b600081815260076020526040902060010154612ed3906001600160a01b03161515614db5565b806000526007602052612ef760018060a01b03600160406000200154163314614df8565b80600052600760205260406000206009810160ff815416600481101561068d57612fc0928492612f2a6104ff9315614e36565b612f3661114c85614f3c565b600260ff198254161790556001810190815460ff8160a01c16612f5881614773565b61306757508154600160ff8260a81c16612f7181614773565b0361302457600582015460068301548082111561301b57612f9191614aaf565b80612feb575b50505b60020154905460ff60a882901c81169260a09290921c16906001600160a01b0316615593565b7f61b9399f2f0f32ca39ce8d7be32caed5ec22fe07a6daba3a467ed479ec606582600080a260018055005b6000808083613014956130018396471015614eb1565b6001600160a01b03165af1610597614abc565b8680612f97565b50506000612f91565b6003820154600783015480821115613052576105d861304c91670de0b6b3a764000093614aaf565b04612f91565b5050670de0b6b3a764000061304c60006105d8565b60038201546007830154808211156130c35761308291614aaf565b905b81613091575b5050612f9a565b6130bc916130ac60018060a01b0360028601541680926156ff565b916001600160a01b031690615602565b868061308a565b5050600090613084565b346102b15760203660031901126102b15760206102f76130eb614658565b614d80565b346102b15760203660031901126102b1576004356001600160a01b038116908190036102b15761311e61556a565b801561313a57600380546001600160a01b031916919091179055005b60405162461bcd60e51b815260206004820152601360248201527224b73b30b634b2103332b29036b0b730b3b2b960691b6044820152606490fd5b346102b15760403660031901126102b1576004356001600160401b0381116102b1576131a59036906004016146f9565b906024356001600160401b0381116102b1576131c5903690600401614729565b90926131cf6154ff565b6002546040516314c530d560e31b815233600482015290602090829060249082906001600160a01b03165afa80156114595761321291600091612e555750614c56565b61321d811515614c9d565b613228828214614ce7565b60005b8181106132385760018055005b613243818386614d2e565b9061325b613252828689614d3e565b35923690614d4e565b918251600052600760205260406000209260208101938451600052600760205260406000209060ff600982015416600481101561068d571580613625575b6132a29061563c565b6132ac8351614f3c565b1580613614575b156135de5760ff8161330260018094015491838360a01c166132d481614773565b15806135bf575b6132e490615680565b6002858060a01b0391015416848060a01b03600287015416146156c2565b60a81c1661330f81614773565b149081156135a1575b501561356857805160005260076020526040600020938051600052600760205260406000209161335761116f8560018060a01b0360028a015416615007565b6001860192600160ff855460a81c1661336f81614773565b14613553575b6001810196600160ff895460a81c1661338d81614773565b1461353e575b61339d8682615ac3565b9460038301549260078101938454600091818111600014613536576133c29250614aaf565b995b60408601519780891161352e575b508a8811613523575b60019a5093600289989796929488946134a1868c9861346e6134ec9f600080806134e69f613459613452613441670de0b6b3a764000061342786996134218e1515615855565b8d614a74565b9a909f0180549f909a049e6001600160a01b03168f615b8b565b9960018060a01b038a541690615c03565b988d614aaf565b90546001600160a01b03165af161155f614abc565b546001600160a01b0316906122c96134908361348a818d6156ff565b936156ff565b85546001600160a01b031692614aaf565b600782016134b0878254614a96565b90555460ff8160a01c166134c381614773565b15908161350a575b506134f2575b50506134de838254614a96565b905551615c75565b51615c75565b0161322b565b60066135019101918254614a96565b90558f806134d1565b8e915060a81c60ff1661351c81614773565b14386134cb565b9299965089926133db565b97508f6133d2565b5050996133c4565b61354e60048301548710156158e1565b613393565b613563600488015486111561593e565b613375565b60405162461bcd60e51b81526020600482015260116024820152704e6f74206d61726b6574206f726465727360781b6044820152606490fd5b600180925060ff91015460a81c166135b881614773565b1489613318565b506132e485858189015460a01c166135d681614773565b1490506132db565b60405162461bcd60e51b815260206004820152600e60248201526d13dc99195c9cc8195e1c1a5c995960921b6044820152606490fd5b5061361f8651614f3c565b156132b3565b5060ff600983015416600481101561068d5715613299565b346102b15760203660031901126102b157613656614658565b6002546040516303e21fa960e61b815290602090829060049082906001600160a01b03165afa90811561145957600091613917575b506001600160a01b031680156138e15760405163c45a015560e01b815290602082600481845afa918215611459576000926138bc575b506020600491604051928380926315ab88c960e31b82525afa80156114595761370c9260209260009261389d575b50604051808095819463e6a4390560e01b83528860048401614c28565b03916001600160a01b03165afa9081156114595760009161387e575b506001600160a01b031690811561384357604051630240bc6b60e21b815290606082600481865afa9081156114595760009283926137df575b50602060049460405195868092630dfe168160e01b82525afa908115611459576040946000926137be575b506001600160a01b039182169116036137b957905b82516001600160701b03928316815291166020820152f35b6137a1565b6137d891925060203d602011611703576116f481836146bb565b908561378c565b929391506060833d60601161383b575b816137fc606093836146bb565b810103126138375761380d83614c42565b93604061381c60208601614c42565b94015163ffffffff811603611d6b5750909291906020613761565b8380fd5b3d91506137ef565b60405162461bcd60e51b815260206004820152601360248201527214185a5c88191bd95cc81b9bdd08195e1a5cdd606a1b6044820152606490fd5b613897915060203d602011611703576116f481836146bb565b82613728565b6138b5919250833d8511611703576116f481836146bb565b90856136ef565b60049192506138d9602091823d8411611703576116f481836146bb565b9291506136c1565b60405162461bcd60e51b815260206004820152600e60248201526d149bdd5d195c881b9bdd081cd95d60921b6044820152606490fd5b613930915060203d602011611703576116f481836146bb565b8261368b565b346102b15760003660031901126102b157602060405161012c8152f35b346102b15760003660031901126102b15760206040516127108152f35b346102b15760603660031901126102b157613989614658565b61399161466e565b906044359061399e61556a565b6001600160a01b0316906139b3821515614ba4565b6001600160a01b038316926139c9841515614be8565b6040516370a0823160e01b815230600482015291602083602481875afa92831561145957600093613ab5575b508215613a785780613a725750815b8211613a305781613a27600080516020615f598339815191529360209386615602565b604051908152a3005b60405162461bcd60e51b815260206004820152601a602482015279496e73756666696369656e7420746f6b656e2062616c616e636560301b6044820152606490fd5b91613a04565b60405162461bcd60e51b81526020600482015260156024820152744e6f20746f6b656e7320746f20776974686472617760581b6044820152606490fd5b90926020823d602011613ae1575b81613ad0602093836146bb565b81010312611d6b57505191856139f5565b3d9150613ac3565b346102b15760203660031901126102b157600435613b0561556a565b6127108111613b475760407f9af3456a3485c9955fbe2f71c7de1d0f89f93ff6ae53e43ec73f541db505abdc91601054908060105582519182526020820152a1005b60405162461bcd60e51b8152602060048201526012602482015271088caecd2c2e8d2dedc40e8dede40d0d2ced60731b6044820152606490fd5b346102b15760603660031901126102b157613b9a614658565b60243560028110156102b1576044359060028210156102b157613bbc92615593565b613bc581614aec565b90613bcf8261478a565b613bdc60405191826146bb565b828152613be88361478a565b6020820190601f1901368237826000526009602052604060002054906000915b80151580613cd4575b15613c8f5780600052600760205260ff60096040600020015416600481101561068d571580613c7f575b613c63575b84600052600b602052604060002090600052602052600160406000200154613c08565b918083613c73613c799387614b7a565b52614a87565b91613c40565b50613c8981614f3c565b15613c3b565b81846040519182916020830190602084525180915260408301919060005b818110613cbb575050500390f35b8251845285945060209384019390920191600101613cad565b50858310613c11565b346102b15760003660031901126102b1576020601154604051908152f35b346102b15760403660031901126102b157600435602435613d1a61556a565b80821015613d5c57816040917f34a3b94861a601870265c6c4d74c28d1a09f6c803ce42b95e749f46127c8f17093600d5580600e5582519182526020820152a1005b60405162461bcd60e51b815260206004820152602360248201527f4d696e2073697a65206d757374206265206c657373207468616e206d61782073604482015262697a6560e81b6064820152608490fd5b346102b15760203660031901126102b15760206102f7600435614aec565b346102b15760203660031901126102b15760043560005260086020526020604060002054604051908152f35b6101003660031901126102b15760043560028110156102b1576024359060028210156102b1576044356001600160a01b03811691908281036102b15760c435936084359260643592916001600160401b0387116102b157366023880112156102b1578660040135613e67816146de565b97613e75604051998a6146bb565b81895236602483830101116102b15781600092602460209301838c01378901015260e435908115158092036102b157861561460057833b156145ca57600254604051630736b32b60e31b81526004810189905290602090829060249082906001600160a01b03165afa908115611459576000916145ab575b5061457357613efa6154ff565b841561452e5785156144f357613f1360a4351515614a0b565b600f5460a435116144bf57613f288486615521565b6000968060001904821161447b57670de0b6b3a7640000613f498284614a74565b0496600d54881061444457600e54881161440d57613f6686614773565b8515998a898b82156143c957509050341061438c578a978a8a955b6004549d8e613f8f81614a87565b6004558b613f9f60a43542614a96565b998a93613fab83614773565b15614385575b8960405195613fbf87614684565b8487528d602088013381526040890195613fd881614773565b86526060890191613fe881614773565b8252608089018c815260a08a0193845260c08a0194855260e08a019586526101008a018b81526101208b018c81526101408c019a8b526101608c018d8152998d5260076020526040909c209a518b55915160018b0180546001600160a01b0319166001600160a01b03929092169190911781559651979a979196909290919061407082614773565b61407982614773565b8254905161408681614773565b61408f81614773565b61ffff60a01b1990911660a09290921b60ff60a01b169190911760a89190911b60ff60a81b16179055516002880180546001600160a01b0319166001600160a01b0392909216919091179055516003870155516004808701919091559051600586015590516006850155905160078401559051600883015591516009909101918110156143715761412f9288928b9260ff80198354169116179055615593565b808b52600a6020528b600260408d20546040519061414c826146a0565b81528d604060208301918083528184019560018752878252600b6020528282209082526020522091518255516001820155019051151560ff80198354169116179055808b52600a60205260408b2054151560001461435e57808b52600b60205260408b20818c52600a60205260408c20548c526020528b600160408d2001555b808b52600a6020528b60408c2055808b52600860205260408b20548c8c52600c60205260408c20558a52600860205260408a208054906001820180921161434a57556040519461421b88614773565b87865261422781614773565b602086015260408501526060840152608083015260a08201528460c082015261012060e082015287519182610120830152865b83811061433357507f734faa6f759e00a9988c3e404f6a5c1ef7e553472f08bcafe2dde32c43d1736682899289610140876142b2999897010152610100820152610140813395601f80199101168101030190a3614773565b8061432a575b6142cc575b60208360018055604051908152f35b81806142d9819334614aaf565b335af16142e4614abc565b50156142f15781806142bd565b60405162461bcd60e51b8152602060048201526011602482015270115512081c995d1d5c9b8819985a5b1959607a1b6044820152606490fd5b508034116142b8565b80602080928c01015161014082860101520161425a565b634e487b7160e01b8c52601160045260248cfd5b808b5260096020528b60408c20556141cc565b634e487b7160e01b8d52602160045260248dfd5b5084613fb1565b60405162461bcd60e51b8152602060048201526015602482015274125b9cdd59999a58da595b9d08115512081cd95b9d605a1b6044820152606490fd5b909561440860409b939b516323b872dd60e01b6020820152336024820152306044820152866064820152606481526144026084826146bb565b85615a68565b613f81565b60405162461bcd60e51b815260206004820152600f60248201526e4f7264657220746f6f206c6172676560881b6044820152606490fd5b60405162461bcd60e51b815260206004820152600f60248201526e13dc99195c881d1bdbc81cdb585b1b608a1b6044820152606490fd5b606460405162461bcd60e51b815260206004820152602060248201527f50726963652063616c63756c6174696f6e20776f756c64206f766572666c6f776044820152fd5b60405162461bcd60e51b815260206004820152600c60248201526b54544c20746f6f206c6f6e6760a01b6044820152606490fd5b60405162461bcd60e51b8152602060048201526013602482015272496e76616c69642070726963652072616e676560681b6044820152606490fd5b60405162461bcd60e51b815260206004820152601d60248201527f416d6f756e74206d7573742062652067726561746572207468616e20300000006044820152606490fd5b60405162461bcd60e51b815260206004820152601060248201526f151bdad95b881a5cc8189b1bd8dad95960821b6044820152606490fd5b6145c4915060203d6020116116325761162581836146bb565b89613eed565b60405162461bcd60e51b815260206004820152600e60248201526d139bdd08184818dbdb9d1c9858dd60921b6044820152606490fd5b60405162461bcd60e51b815260206004820152600d60248201526c24b73b30b634b2103a37b5b2b760991b6044820152606490fd5b346102b15760203660031901126102b15760206102f7614653614658565b6147d9565b600435906001600160a01b03821682036102b157565b602435906001600160a01b03821682036102b157565b61018081019081106001600160401b03821117610b3757604052565b606081019081106001600160401b03821117610b3757604052565b601f909101601f19168101906001600160401b03821190821017610b3757604052565b6001600160401b038111610b3757601f01601f191660200190565b9181601f840112156102b1578235916001600160401b0383116102b157602080850194606085020101116102b157565b9181601f840112156102b1578235916001600160401b0383116102b1576020808501948460051b0101116102b157565b60609060031901126102b157600435906024359060443590565b6002111561068d57565b90600482101561068d5752565b6001600160401b038111610b375760051b60200190565b908160209103126102b157516001600160a01b03811681036102b15790565b908160209103126102b1575160ff811681036102b15790565b6013546001600160a01b0316908161484f575b6147f69150615355565b80156147ff5790565b60405162461bcd60e51b815260206004820152602260248201527f4e6f206c697175696469747920617661696c61626c6520696e205632206f7220604482015261563360f01b6064820152608490fd5b6002546040516303e21fa960e61b815290602090829060049082906001600160a01b03165afa908115611459576000916149d4575b506001600160a01b0316918261489b575b506147ec565b60405163313ce56760e01b815291906001600160a01b038216602084600481845afa9384156114595760009461499f575b5060206004939495604051948580926315ab88c960e31b82525afa9081156114595760209360009261497f575b506064919260ff604051978895869463bbbb384960e01b8652600486015260018060a01b031660248501521660448301525afa9182156114595760009261494b575b50816149475781614895565b5090565b90916020823d602011614977575b81614966602093836146bb565b81010312611d6b575051903861493b565b3d9150614959565b6064925061499990853d8711611703576116f481836146bb565b916148f9565b60049394506149c5602091823d84116149cd575b6149bd81836146bb565b8101906147c0565b9493506148cc565b503d6149b3565b6149ed915060203d602011611703576116f481836146bb565b38614884565b908160209103126102b1575180151581036102b15790565b15614a1257565b60405162461bcd60e51b815260206004820152601a602482015279054544c206d7573742062652067726561746572207468616e20360341b6044820152606490fd5b8115614a5e570490565b634e487b7160e01b600052601260045260246000fd5b81810292918115918404141715611d1757565b6000198114611d175760010190565b91908201809211611d1757565b600482101561068d5752565b91908203918211611d1757565b3d15614ae7573d90614acd826146de565b91614adb60405193846146bb565b82523d6000602084013e565b606090565b600090806000526009602052604060002054805b614b0957505090565b80600052600760205260ff60096040600020015416600481101561068d571580614b6a575b614b5a575b81600052600b60205260406000209060005260205260016040600020015490819091614b00565b91614b6490614a87565b91614b33565b50614b7481614f3c565b15614b2e565b8051821015614b8e5760209160051b010190565b634e487b7160e01b600052603260045260246000fd5b15614bab57565b60405162461bcd60e51b8152602060048201526015602482015274496e76616c696420746f6b656e206164647265737360581b6044820152606490fd5b15614bef57565b60405162461bcd60e51b8152602060048201526011602482015270125b9d985b1a59081c9958da5c1a595b9d607a1b6044820152606490fd5b6001600160a01b0391821681529116602082015260400190565b51906001600160701b03821682036102b157565b15614c5d57565b60405162461bcd60e51b815260206004820152601860248201527710d85b1b195c881a5cc81b9bdd08185d5d1a1bdc9a5e995960421b6044820152606490fd5b15614ca457565b60405162461bcd60e51b815260206004820152601b60248201527a139bc81bdc99195c881b585d18da195cc81d1bc8199d5b199a5b1b602a1b6044820152606490fd5b15614cee57565b60405162461bcd60e51b815260206004820152601860248201527709ad2e6dac2e8c6d0cac840c2e4e4c2f2e640d8cadccee8d60431b6044820152606490fd5b9190811015614b8e576060020190565b9190811015614b8e5760051b0190565b91908260609103126102b157604051614d66816146a0565b604080829480358452602081013560208501520135910152565b6001600160a01b031660008181526006602052604090205415614dae57600052600660205260406000205490565b5060055490565b15614dbc57565b60405162461bcd60e51b815260206004820152601460248201527313dc99195c88191bd95cc81b9bdd08195e1a5cdd60621b6044820152606490fd5b15614dff57565b60405162461bcd60e51b815260206004820152600f60248201526e2737ba1037b93232b91037bbb732b960891b6044820152606490fd5b15614e3d57565b60405162461bcd60e51b815260206004820152601060248201526f4f72646572206e6f742061637469766560801b6044820152606490fd5b15614e7c57565b60405162461bcd60e51b815260206004820152600d60248201526c13dc99195c88195e1c1a5c9959609a1b6044820152606490fd5b15614eb857565b60405162461bcd60e51b815260206004820152601c60248201527b125b9cdd59999a58da595b9d08115512081a5b8818dbdb9d1c9858dd60221b6044820152606490fd5b15614f0357565b60405162461bcd60e51b8152602060048201526011602482015270115512081c99599d5b990819985a5b1959607a1b6044820152606490fd5b600081815260076020526040902060010154614f62906001600160a01b03161515614db5565b6000526007602052600860406000200154421190565b15614f7f57565b60405162461bcd60e51b815260206004820152601760248201527608ccaca40e0cae4c6cadce8c2ceca40e8dede40d0d2ced604b1b6044820152606490fd5b15614fc557565b60405162461bcd60e51b815260206004820152601a602482015279507269636520646966666572656e636520746f6f206c6172676560301b6044820152606490fd5b6013546001600160a01b0316806150fc575b50615023906147d9565b9081156150f5578181146150ee5781808211156150955761504391614aaf565b9061506f7e068db8bac710cb295e9e1b089a027525460aa64c2f837b4a2339c0ebedfa43831115614fbe565b6127108202918083046127101490151715611d175761508d91614a54565b601054101590565b9061509f91614aaf565b906150cb7e068db8bac710cb295e9e1b089a027525460aa64c2f837b4a2339c0ebedfa43831115614fbe565b6127108202918083046127101490151715611d17576150e991614a54565b61508d565b5050600190565b5050600090565b6002546040516303e21fa960e61b815290602090829060049082906001600160a01b03165afa908115611459576000916151fd575b506001600160a01b031680615147575b50615019565b6020600491604051928380926315ab88c960e31b82525afa801561145957615191926020926000926151de575b506040518080958194630d4d933b60e21b83528860048401614c28565b03915afa600091816151bd575b506151aa575b80615141565b6001600160a01b03166150ee57386151a4565b6151d791925060203d602011611703576116f481836146bb565b903861519e565b6151f6919250833d8511611703576116f481836146bb565b9038615174565b615216915060203d602011611703576116f481836146bb565b38615131565b1561522357565b60405162461bcd60e51b815260206004820152602260248201527f46696c6c20616d6f756e74206d7573742062652067726561746572207468616e604482015261020360f41b6064820152608490fd5b3562ffffff811681036102b15790565b9062ffffff809160031b9316831b921b19161790565b60005260076020526040600020600181015460ff8160a01c166152bb81614773565b159081615306575b50156152e957600660058201549101548082116000146150f5576152e691614aaf565b90565b600760038201549101548082116000146150f5576152e691614aaf565b6001915060a81c60ff1661531981614773565b14386152c3565b60ff6011199116019060ff8211611d1757565b60ff166012039060ff8211611d1757565b60ff16604d8111611d1757600a0a90565b60408051630fa6707960e21b81526001600160a01b0390921660048301819052919081602481305afa8060009283926154c3575b5061539657505050600090565b81158080156154bb575b6154b25760206004946040519586809263313ce56760e01b82525afa93841561145957600094615491575b5060ff84169360128503615407575090919250670de0b6b3a76400008302928304670de0b6b3a7640000141715611d17576152e691614a54565b565b91936012111561545857509061542761542261542d93615333565b615344565b90614a54565b90670de0b6b3a7640000820291808304670de0b6b3a76400001490151715611d17576152e691614a54565b919261542761542261546993615320565b90670de0b6b3a76400008302928304670de0b6b3a7640000141715611d17576152e691614a54565b6154ab91945060203d6020116149cd576149bd81836146bb565b92386153cb565b50505050600090565b5081156153a0565b929091506040833d6040116154f7575b816154e0604093836146bb565b81010312611d6b5750602082519201519038615389565b3d91506154d3565b600260015414615510576002600155565b633ee5aeb560e01b60005260046000fd5b9061552b90615a1a565b9060ff82166012810361553e5750905090565b6012111561555b576155556154226152e693615333565b90614a74565b6154276154226152e693615320565b6000546001600160a01b0316330361557e57565b63118cdaa760e01b6000523360045260246000fd5b60405160609190911b6001600160601b031916602082019081529290916155b981614773565b60f81b60348301526155ca81614773565b60f81b6035820152601681526155e16036826146bb565b51902090565b6001600160a01b039091168152602081019190915260400190565b615637615405939261562960405194859263a9059cbb60e01b6020850152602484016155e7565b03601f1981018452836146bb565b615a68565b1561564357565b60405162461bcd60e51b81526020600482015260156024820152744f7264657273206d7573742062652061637469766560581b6044820152606490fd5b1561568757565b60405162461bcd60e51b8152602060048201526013602482015272496e76616c6964206f7264657220747970657360681b6044820152606490fd5b156156c957565b60405162461bcd60e51b815260206004820152600e60248201526d0a8ded6cadc40dad2e6dac2e8c6d60931b6044820152606490fd5b9061570990615a1a565b9060ff82166012810361571c5750905090565b60121115615733576154276154226152e693615333565b6155556154226152e693615320565b80600052600b602052604060002082600052602052604060002060ff6002820154161561581b5780541561580257600181015482600052600b602052604060002082546000526020526001604060002001555b600181018054909190156157eb57549082600052600b602052604060002090546000526020526040600020555b600052600b60205260406000209060005260205260006002604082208281558260018201550155565b90505481600052600a6020526040600020556157c2565b6001810154826000526009602052604060002055615795565b60405162461bcd60e51b81526020600482015260126024820152714f72646572206e6f7420696e20717565756560701b6044820152606490fd5b1561585c57565b60405162461bcd60e51b8152602060048201526011602482015270139bc8185b5bdd5b9d081d1bc8199a5b1b607a1b6044820152606490fd5b1561589c57565b60405162461bcd60e51b815260206004820152601d60248201527f455448207472616e7366657220746f2073656c6c6572206661696c65640000006044820152606490fd5b156158e857565b60405162461bcd60e51b815260206004820152602860248201527f457865637574696f6e2070726963652062656c6f772073656c6c65722773206d604482015267696e20707269636560c01b6064820152608490fd5b1561594557565b60405162461bcd60e51b815260206004820152602960248201527f457865637574696f6e20707269636520657863656564732062757965722773206044820152686d617820707269636560b81b6064820152608490fd5b156159a357565b60405162461bcd60e51b815260206004820152603360248201527f457865637574696f6e20707269636520646576696174657320746f6f206d7563604482015272682066726f6d206d61726b657420707269636560681b6064820152608490fd5b6040919493926060820195825260208201520152565b60405163313ce56760e01b815290602090829060049082906001600160a01b03165afa90811561145957600091615a4f575090565b6152e6915060203d6020116149cd576149bd81836146bb565b906000602091828151910182855af115611459576000513d615aba57506001600160a01b0381163b155b615a995750565b635274afe760e01b60009081526001600160a01b0391909116600452602490fd5b60011415615a92565b6001810154600160ff8260a81c16615ada81614773565b149081615b5a575b5015615b3b5760066005820154910154600091818111600014615b335761542d9250614aaf565b670de0b6b3a7640000820291808304670de0b6b3a76400001490151715611d17576152e691614a54565b505090615b09565b9050600760038201549101548082116000146150f5576152e691614aaf565b60ff915060a01c16615b6b81614773565b1538615ae2565b9081526001600160a01b03909116602082015260400190565b615b989061555583614d80565b612710600091049182615baa57505090565b6003546001600160a01b031690813b15615bff57615be284928492604051948580948193637b904e7560e01b83528360048401615b72565b03925af18015611f0957615bf557505090565b81614947916146bb565b8280fd5b615c109061555583614d80565b612710600091049182615c2257505090565b600354615c3e9084906001600160a01b03908116908416615602565b6003546001600160a01b031690813b15615bff57615be28392839260405194858094819362262d0160e91b83528a60048401615b72565b918260005260076020526040600020918360018401805460ff8160a01c16615c9c81614773565b159081615ebe575b5015615dee576005850194855495600682019687546000818311600014615de85750615cd08183614aaf565b905b81159283159384615dba575b508110801590615db3575b15615d65575050600080516020615f39833981519152979383615d5796936104ff936009615d499701600160ff19825416179055615d5c575b5060020154905460ff60a882901c81169260a09290921c16906001600160a01b0316615593565b549260405193849384615a04565b0390a2565b54865538615d22565b604080519889526020890192909252908701525050505060608201929092527f686bccf6e038d1f0f07e859bb6b9eada3eab6ae5537f18f14d450031da6f0165925090508060808101615d57565b5082615ce9565b97509250612710820296828804612710141715611d1757615ddc838c98614a54565b60115410159238615cde565b90615cd2565b6003850194855495600782019687546000818311600014615eb85750615e148183614aaf565b905b81159283159384615e8a575b5081148015615db35715615d65575050600080516020615f39833981519152979383615d5796936104ff936009615d499701600160ff19825416179055615d5c575060020154905460ff60a882901c81169260a09290921c16906001600160a01b0316615593565b97509250612710820296828804612710141715611d1757615eac838c98614a54565b60115410159238615e22565b90615e16565b6001915060a81c60ff16615ed181614773565b1438615ca4565b9081526001600160a01b039182166020820152911660408201526060019056fee2f9e9065995f6d98a1dc7a86e975c10b2848f8af0a077c8d675b71d486dbd11e332d84b9d7b6ed7b61613e3346be661c47cd1bcb330e8cdb0887aa2d3425e6ea4360fd9d66ebd27e7e5e650a0dbc45ff3fe0e9560cf7b07b3c748dc95489f52da0612d7ca9ff90ca7143a6021ba8938994f8d045b2834ae585fd07b27ea697c8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0a2646970667358221220f268f1a79f159bf21dc887e82749790727a43825edffa65e9c80194f34e47a0364736f6c634300081a0033000000000000000000000000db7e3b94bd55fb9d93112830b23bae82e33527c9

Deployed Bytecode

0x60806040526004361015610018575b361561001657005b005b60003560e01c806304ca1339146146355780630c6a3a8f14613df75780630d34219c14613dcb57806310e1c16614613dad57806313b64e2414613cfb5780631e99981414613cdd5780631f32eb7714613b8157806324f1c5c114613ae9578063277327a5146139705780633410fe6e146139535780633aad938d146139365780633e99c1e41461363d578063457a892414613175578063472d35b9146130f05780634d067345146130cd578063514fcac714612e91578063558e44d314612e7457806359165ac614612cb757806359c6931314612c995780635b54918214612c70578063634eef4714612b7e57806366961d4414612b60578063679230f214612b4457806367bf6ba614612b1b5780636859ebf014612a5057806370cec1ac14612986578063715018a61461293f578063727d2114146128e85780637af187881461289257806381bbc0d3146127f5578063830562bc146127cc57806386ea511e1461279b57806387d11e61146125ce5780638da5cb5b146125a55780638dfb8ccd1461255e5780638f9057df14612005578063975b866214611fe75780639c302fdb14611a585780639f92b715146119f6578063a1a65f9c146119bc578063a674537f1461199e578063a85c38ef146118c8578063a8b37cfe14610edb578063b482403414610ebd578063b7bac4cf14610e0c578063cb25c17d14610de0578063d09ef24114610bf5578063d0fb020314610bcc578063d23a0bd01461094b578063d79e8567146107fa578063e2cfcfee14610747578063ea7bcef414610394578063f1a82c7e14610376578063f2fde38b146102ff578063f6252ff2146102b65763fd31f89a0361000e57346102b15760203660031901126102b157600435600052600a6020526020604060002054604051908152f35b600080fd5b346102b15760203660031901126102b15760206102f760043580600052600783526102f260018060a01b03600160406000200154161515614db5565b615299565b604051908152f35b346102b15760203660031901126102b157610318614658565b61032061556a565b6001600160a01b0316801561036057600080546001600160a01b03198116831782556001600160a01b031690600080516020615f798339815191529080a3005b631e4fbdf760e01b600052600060045260246000fd5b346102b15760003660031901126102b1576020600e54604051908152f35b346102b15760203660031901126102b1576004356001600160401b0381116102b157366023820112156102b1578060040135906103d08261478a565b916103de60405193846146bb565b8083526024602084019160051b830101913683116102b157602401905b828210610737578361040b6154ff565b8051156106fa5760648151116106a95760005b81518110156106a3576104318183614b7a565b519081600052600760205261045760018060a01b03600160406000200154161515614db5565b8160005260076020526040600020916009830160ff815416600481101561068d576104829015614e36565b61048b82614f3c565b1561065457816104ff60019561050493600360ff198254161790558681019081548860ff8260a01c166104bd81614773565b146105f0575b50815460ff8160a01c166104d681614773565b1561052f575b506002888060a01b039101541690549060ff808360a81c169260a01c1690615593565b615742565b7f1ad308dc7017610c82d08084545f7176df5e2f08f078c3c8f8926cd7e5555514600080a20161041e565b8860ff8260a81c1661054081614773565b036105ab5760058201546006830154600091818111156105a4576105649250614aaf565b80610570575b506104dc565b600080808361059d956105868396471015614eb1565b8e8060a01b03165af1610597614abc565b50614efc565b898061056a565b5050610564565b670de0b6b3a76400006105e3600384015460078501546000918181116000146105e9576105d89250614aaf565b600485015490614a74565b04610564565b50506105d8565b600382015460078301546000918181111561064c5761060f9250614aaf565b905b8161061d575b506104c3565b610645916106378b8060a01b0360028601541680926156ff565b918b8060a01b031690615602565b8980610617565b505090610611565b60405162461bcd60e51b815260206004820152601160248201527013dc99195c881b9bdd08195e1c1a5c9959607a1b6044820152606490fd5b634e487b7160e01b600052602160045260246000fd5b60018055005b60405162461bcd60e51b815260206004820152602360248201527f546f6f206d616e79206f726465727320746f20636c65616e207570206174206f6044820152626e636560e81b6064820152608490fd5b60405162461bcd60e51b815260206004820152601560248201527404e6f206f726465727320746f20636c65616e20757605c1b6044820152606490fd5b81358152602091820191016103fb565b346102b15760203660031901126102b157610760614658565b61076861556a565b6001600160a01b031680156107bb57601280546001600160a01b0319811683179091556001600160a01b03167f3ddadec435df2f077c5597f4952343aa7ef9a4db1af45c9e6f92ff7f6e1c67c1600080a3005b60405162461bcd60e51b8152602060048201526017602482015276496e76616c696420666163746f7279206164647265737360481b6044820152606490fd5b346102b15760403660031901126102b1576004356001600160a01b038116908190036102b15760243561082b61556a565b610836821515614be8565b4715610911578061090c5750475b4781116108cc57600080808084865af161085c614abc565b501561088f5760207f20f907b58305c7b76035bc03b26f32b1c4f6560f96be6f3bb54c5c848a2d4ddd91604051908152a2005b60405162461bcd60e51b8152602060048201526015602482015274115512081dda5d1a191c985dd85b0819985a5b1959605a1b6044820152606490fd5b60405162461bcd60e51b8152602060048201526018602482015277496e73756666696369656e74204554482062616c616e636560401b6044820152606490fd5b610844565b60405162461bcd60e51b81526020600482015260126024820152714e6f2045544820746f20776974686472617760701b6044820152606490fd5b346102b15760203660031901126102b1576004356001600160401b0381116102b15761097b903690600401614729565b61098361556a565b8015610b8757600a8111610b4d576001600160401b038111610b3757600160401b8111610b375760145481601455808210610ad8575b508160146000526020600020600a83049060005b828110610a985750600a8202840380610a53575b5050505060405190806020830160208452526040820192906000905b808210610a2c577f8b5911bc3a69df070f5a8ecf2460704729bd313de1b0aba9a8527ff6f0a24a1e84860385a1005b90919384359062ffffff82168092036102b1576020816001938293520195019201906109fd565b9260009360005b818110610a6f575050500155828080806109e1565b9091946020610a8e600192610a8389615273565b908560030290615283565b9601929101610a5a565b6000805b600a8110610ab15750828201556001016109cd565b94906020610acf600192610ac485615273565b908960030290615283565b92019501610a9c565b60146000526020600020600a60098181860104830193010401906003600a84060280610b1a575b505b818110610b0e57506109b9565b60008155600101610b01565b6000198201908154906000199060200360031b1c16905584610aff565b634e487b7160e01b600052604160045260246000fd5b60405162461bcd60e51b8152602060048201526012602482015271546f6f206d616e792066656520746965727360701b6044820152606490fd5b60405162461bcd60e51b815260206004820152601f60248201527f4665652074696572732061727261792063616e6e6f7420626520656d707479006044820152606490fd5b346102b15760003660031901126102b1576003546040516001600160a01b039091168152602090f35b346102b15760203660031901126102b1576004356000610160604051610c1a81614684565b8281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e08201528261010082015282610120820152826101408201520152806000526007602052610c8260018060a01b03600160406000200154161515614db5565b60005260076020526101806040600020610dde6040518080808095610ca682614684565b8054825260018101546001600160a01b0381166020840190815290604084019060ff9060a081901c8216610cd981614773565b835260a81c166060850190610ced81614773565b815260018060a01b03600285015416906080860191825260038501549260a0870193845260048601549460c08801958652610d6861016060ff600960058b01549a60e08d019b8c5261010060068201549d019c8d5261012060078201549e019d8e5261014060088201549f019e8f520154169c019b8c614aa3565b6040519c518d52516001600160a01b031660208d015251610d8881614773565b60408c015251610d9781614773565b60608b0152516001600160a01b031660808a01525160a08901525160c08801525160e08701525161010086015251610120850152516101408401525161016083019061477d565bf35b346102b15760203660031901126102b15760043560005260096020526020604060002054604051908152f35b346102b15760203660031901126102b1576004356001600160a01b038116908190036102b157610e3a61556a565b8015610e7957601380546001600160a01b031916821790557f0b91e9e48ef4e7faf637ee996fa9a770073f525e31b6f7f7e91be3775a602456600080a2005b60405162461bcd60e51b815260206004820152601c60248201527b496e76616c69642070726963652068656c706572206164647265737360201b6044820152606490fd5b346102b15760003660031901126102b1576020600554604051908152f35b346102b15760803660031901126102b1576004356001600160401b0381116102b157610f0b903690600401614729565b6024356001600160401b0381116102b157610f2a903690600401614729565b92906044356001600160401b0381116102b157610f4b903690600401614729565b92909460643593610f5a6154ff565b6002546040516314c530d560e31b815233600482015290602090829060249082906001600160a01b03165afa801561145957610f9e916000916118a9575b50614c56565b851561186d5781861480611864575b610fb690614ce7565b60005b868110610fc65760018055005b610fdc610fd4828588614d3e565b35151561521c565b610fe781838a614d3e565b351561181057610ff8818886614d3e565b3590611005818588614d3e565b359161101282858c614d3e565b3590806000526007602052604060002060405161102e81614684565b8154815260018201546001600160a01b038116602083015260408201906110e09060ff9060a081901c821661106281614773565b845260a81c1692606081019361107781614773565b845260028501546001600160a01b03166080820152600385015460a0820152600485015460c08201908152600586015460e083015260068601546101008301526007860154610120830152600886015461014083015260099095015460ff169061016001614aa3565b600182516110ed81614773565b6110f681614773565b146117d4575b505161110781614773565b61111081614773565b1561176c575b50806000526007602052604060002060ff600982015416600481101561068d576111409015614e36565b61115261114c83614f3c565b15614e75565b600281019260018060a01b038454169161117461116f8385615007565b61599c565b6002546040516316f4f72360e11b815290602090829060049082906001600160a01b03165afa9081156114595760009161174e575b506001600160a01b031690811561170a5760206024926040519384809263bbe4f6db60e01b82528860048301525afa918215611459576000926116da575b506001600160a01b03821695861561168757600182019081549560ff8760a01c169a6112128c614773565b8b158098819961166d575b50156116415761122d8786615ac3565b905b818111156116395750965b611245881515615855565b611262670de0b6b3a764000061125b898b614a74565b049c614773565b156114765750805461127d906001600160a01b0316876156ff565b93883b156102b1578f94898c96604051809e8192633015be1d60e21b83528560048401926112aa93615a04565b0381895a94600095f19b8c15611459576112e29c611465575b5082546001600160a01b03169b6112f090612710906112e8908f614d80565b84614a74565b048092614aaf565b9b816113b2575b50509054825460019b600080516020615f198339815191529860409890979096909590945092611333926001600160a01b039081169116615602565b60078201611342868254614a96565b90555460ff8160a01c1661135581614773565b159081611399575b50611381575b5050611370818387615c75565b82519182526020820152a301610fb9565b60066113909101918254614a96565b90558f80611363565b8b915060a81c60ff166113ab81614773565b143861135d565b816113d09160018060a09c9a98969b99979c1b036003541690615602565b87546003546001600160a01b03918216989116803b156102b15761140f98600080946040519b8c9586948593632a2fc2f760e01b855260048501615ed8565b03925af180156114595760019b600080516020615f198339815191529860409861133393611448575b50939597509b82949698506112f7565b6000611453916146bb565b38611438565b6040513d6000823e3d90fd5b6000611470916146bb565b386112c3565b6114848188969495966156ff565b906020604051809263095ea7b360e01b8252816000816114a8888b600484016155e7565b03925af180156114595761160d575b50883b156102b1578f906000908c6114e3604051948593849363b23d349b60e01b855260048501615a04565b0381838d5af18015611459576115fc575b50546001600160a01b031661271061151461150e83614d80565b8c614a74565b0491611520838c614aaf565b928061156a575b50505097600080516020615f19833981519152949260409492611565600080808060019f60018060a01b038754165af161155f614abc565b50615895565b611333565b60018060a09d9598969997949d1b0360035416803b156102b1576000926115a8926040519e8f9485938492629a7d7960e81b84528460048501615ed8565b03925af195861561145957611565600080808060019f9a60409b600080516020615f198339815191529d6115ec575b50979f50505050508295975081949650611527565b826115f6916146bb565b386115d7565b6000611607916146bb565b386114f4565b61162d9060203d8111611632575b61162581836146bb565b8101906149f3565b6114b7565b503d61161b565b90509661123a565b6003850154600786015460009181811115611666576116609250614aaf565b9061122f565b5050611660565b6001915060a81c60ff1661168081614773565b143861121d565b60405162461bcd60e51b815260206004820152602560248201527f4d61726b6574206d616b657220706f6f6c206e6f7420666f756e6420666f72206044820152643a37b5b2b760d91b6064820152608490fd5b6116fc91925060203d8111611703575b6116f481836146bb565b8101906147a1565b908f6111e7565b503d6116ea565b60405162461bcd60e51b815260206004820152601c60248201527b13585c9ad95d081b585ad95c88199858dd1bdc9e481b9bdd081cd95d60221b6044820152606490fd5b611766915060203d8111611703576116f481836146bb565b8f6111a9565b518203611779578b611116565b60405162461bcd60e51b815260206004820152602d60248201527f457865637574696f6e207072696365206e6f74206d617463686564207769746860448201526c1037b93232b910383934b1b29760991b6064820152608490fd5b516117de81614773565b6117e781614773565b6117fe576117f8825185111561593e565b8d6110fc565b61180b82518510156158e1565b6117f8565b60405162461bcd60e51b815260206004820152602660248201527f457865637574696f6e207072696365206d75737420626520677265617465722060448201526507468616e20360d41b6064820152608490fd5b50858114610fad565b60405162461bcd60e51b8152602060048201526014602482015273139bc81bdc99195c9cc81d1bc8199d5b199a5b1b60621b6044820152606490fd5b6118c2915060203d6020116116325761162581836146bb565b89610f98565b346102b15760203660031901126102b15760043560005260076020526101806040600020610dde81549160018101549060ff8260a01c1660ff8360a81c1660018060a01b03600284015416600384015460048501549060058601549260068701549460078801549660ff600960088b01549a015416996040519c8d5260018060a01b031660208d015261195a81614773565b60408c015261196881614773565b60608b015260808a015260a089015260c088015260e087015261010086015261012085015261014084015261016083019061477d565b346102b15760003660031901126102b1576020600d54604051908152f35b346102b15760203660031901126102b1576001600160a01b036119dd614658565b1660005260066020526020604060002054604051908152f35b346102b15760203660031901126102b1577f72a51ea60242ee3def7dbd2c9b40d17fa8a3f853c78d14f0f9641a507af6e3c86040600435611a3561556a565b611a436101f4821115614f78565b600554908060055582519182526020820152a1005b346102b157611a6636614759565b91611a6f6154ff565b600081815260076020526040902060010154611a95906001600160a01b03161515614db5565b806000526007602052611ab960018060a01b03600160406000200154163314614df8565b611ac482151561521c565b80600052600760205260406000209060ff60098301541693600485101561068d57611af160049515614e36565b600092600181019485549060ff8260a01c1695611b0d87614773565b861580938194611fcd575b5015611fa35750611b2c8884015484615ac3565b905b81811115611f9b5750945b85151590611b4682615855565b60025460405163745e9e9f60e01b8152996020918b919082906001600160a01b03165afa98891561145957600099611f7a575b506001600160a01b038916988915611f3a57600093611b99600093614773565b15611da15750505050611c2b95600160ff875460a81c16611bb981614773565b03611d8157600582015460068301549081811115611d7657602091611bdd91614aaf565b935b60028401805460405163815a1a0d60e01b81526001600160a01b039091166004820152602481018790526044810192909252600160648301529098909289918290879082906084820190565b03925af196871561145957600097611d3d575b508054611c799190611c6390611c5d906001600160a01b03168a615c03565b89614aaf565b905487546001600160a01b039081169116615602565b8515611d2d57670de0b6b3a76400008202828104670de0b6b3a76400001483151715611d17576106a396611cac91614a54565b945b60078201611cbd868254614a96565b90555460ff8160a01c16611cd081614773565b159081611cfd575b50611ce5575b5050615c75565b6006611cf49101918254614a96565b90558480611cde565b6001915060a81c60ff16611d1081614773565b1487611cd8565b634e487b7160e01b600052601160045260246000fd5b6106a39550600481015494611cae565b90966020823d602011611d6e575b81611d58602093836146bb565b81010312611d6b57505195611c79611c3e565b80fd5b3d9150611d4b565b505060206000611bdd565b6020670de0b6b3a7640000611d9a600485015488614a74565b0493611bdf565b90919294986002850191611de5602060018060a01b0385541692611dc5848d6156ff565b9384918860405180968195829463095ea7b360e01b8452600484016155e7565b03925af18015611f2f579160209391611e479d93611f14575b50845460405163815a1a0d60e01b81526001600160a01b0390911660048201526024810191909152604481019290925260006064830152909a8b91908290869082906084820190565b03925af1988915611f09578299611ed1575b5054611e989190819081908190611e8390611e7d906001600160a01b03168e615b8b565b8d614aaf565b8b546001600160a01b03165af161155f614abc565b15611d2d57670de0b6b3a7640000860295808704670de0b6b3a76400001490151715611d1757611ecb846106a397614a54565b94611cae565b9098506020813d602011611f01575b81611eed602093836146bb565b81010312611efd57519781611e59565b5080fd5b3d9150611ee0565b6040513d84823e3d90fd5b611f2a90853d87116116325761162581836146bb565b611dfe565b6040513d87823e3d90fd5b60405162461bcd60e51b815260206004820152601860248201527711985b1b189858dad15e1958dd5d1bdc881b9bdd081cd95d60421b6044820152606490fd5b611f9491995060203d602011611703576116f481836146bb565b9789611b79565b905094611b39565b600384015460078501549081811115611fc657611fc09250614aaf565b90611b2e565b5050611fc0565b6001915060a81c60ff16611fe081614773565b148a611b18565b346102b15760003660031901126102b1576020601054604051908152f35b346102b15760203660031901126102b1576004356001600160401b0381116102b1576120359036906004016146f9565b61203d6154ff565b6002546040516314c530d560e31b815233600482015290602090829060249082906001600160a01b03165afa8015611459576120809160009161253f5750614c56565b61208b811515614c9d565b60005b81811061209b5760018055005b6120af366120aa838587614d2e565b614d4e565b90815160005260076020526040600020602083019081516000526007602052604060002060ff600983015416600481101561068d571580612527575b6120f49061563c565b6120fe8551614f3c565b1580612516575b156124d757600190612153828401549360ff8560a01c1661212581614773565b15806124b7575b61213590615680565b6002848060a01b0391015416838060a01b03600284015416146156c2565b0154906001600160a01b03808216908316146124665760a81c60ff1661217881614773565b15908161244e575b50156124165782516000526007602052604060002090805160005260076020526040600020906004830192835490600484019182548091106123d1576003820154600783019081546000918181116000146123c9576121df9250614aaf565b955b600381015492600782019384546000918181116000146123c2576122059250614aaf565b60408c015198808a116123ba575b508089116123b0575b506122cf916122c960016122b68b670de0b6b3a764000061224b6122b097612245841515615855565b83614a74565b049461229e600080808060028601956122886122708d8c8060a01b038a541690615b8b565b6122828c8060a01b038a54168b615c03565b9d614aaf565b908a808060a01b03910154165af161155f614abc565b838060a01b03905416958680926156ff565b946156ff565b97019660018060a01b0388541692614aaf565b91615602565b6122da868254614a96565b90556122e7858254614a96565b9055845482549081811161231f575b505050906123108392612319959460019851915491615c75565b51915491615c75565b0161208e565b60008093670de0b6b3a7640000612346612340849685969c9b9a999c614aaf565b89614a74565b91549104906001600160a01b03165af161235e614abc565b501561236f579091928880806122f6565b60405162461bcd60e51b8152602060048201526019602482015278115512081cd85d9a5b99dcc81c995d1d5c9b8819985a5b1959603a1b6044820152606490fd5b97506122cf61221c565b98508e612213565b5050612205565b5050956121e1565b60405162461bcd60e51b815260206004820152601f60248201527f5072696365206d69736d6174636820666f72206c696d6974206f7264657273006044820152606490fd5b60405162461bcd60e51b815260206004820152601060248201526f4e6f74206c696d6974206f726465727360801b6044820152606490fd5b60ff915060a81c1661245f81614773565b1586612180565b60405162461bcd60e51b815260206004820152602360248201527f53656c6c657220616e642062757965722063616e6e6f74206265207468652073604482015262616d6560e81b6064820152608490fd5b506121358460ff8186015460a01c166124cf81614773565b14905061212c565b60405162461bcd60e51b81526020600482015260176024820152760c481bd9881d1a19481bdc99195c9cc8195e1c1a5c9959604a1b6044820152606490fd5b506125218351614f3c565b15612105565b5060ff600982015416600481101561068d57156120eb565b612558915060203d6020116116325761162581836146bb565b84610f98565b346102b15760203660031901126102b1576004356014548110156102b15762ffffff60209160146000526003600a84600020818404015492060260031b1c16604051908152f35b346102b15760003660031901126102b1576000546040516001600160a01b039091168152602090f35b346102b15760403660031901126102b1576004356001600160401b0381116102b1576125fe903690600401614729565b9061260761466e565b9161261061556a565b6001600160a01b03831690612626821515614be8565b8015612760576014811161270f5760005b81811061264057005b61264b818386614d3e565b356001600160a01b03811691908290036102b15761266a821515614ba4565b6040516370a0823160e01b8152306004820152602081602481865afa9283156114595785916000946126d6575b50836001946126aa575b50505001612637565b6020816126c7600080516020615f59833981519152938c86615602565b604051908152a38387806126a1565b915091926020823d8211612707575b816126f2602093836146bb565b81010312611d6b575051919084906001612697565b3d91506126e5565b60405162461bcd60e51b815260206004820152602360248201527f546f6f206d616e7920746f6b656e7320746f207769746864726177206174206f6044820152626e636560e81b6064820152608490fd5b60405162461bcd60e51b8152602060048201526013602482015272139bc81d1bdad95b9cc81cdc1958da599a5959606a1b6044820152606490fd5b346102b15760403660031901126102b15760206127c26127b9614658565b60243590615007565b6040519015158152f35b346102b15760003660031901126102b1576002546040516001600160a01b039091168152602090f35b346102b15760203660031901126102b15760043561281161556a565b61271081116128535760407f1a0e49c8d547d5d440b1e87ef8c3f4dcedad3129fe3308e075c0d72395c3451191601154908060115582519182526020820152a1005b60405162461bcd60e51b8152602060048201526017602482015276088eae6e840e8ded8cae4c2dcc6ca40e8dede40d0d2ced604b1b6044820152606490fd5b346102b15760403660031901126102b157600435600052600b60205260406000206024356000526020526060604060002080549060ff600260018301549201541690604051928352602083015215156040820152f35b346102b15760203660031901126102b1577f5829309fa85e85a75b744b2b1ae6b2913b3d2a9c94df75a5b61de31f962256be602060043561292761556a565b612932811515614a0b565b80600f55604051908152a1005b346102b15760003660031901126102b15761295861556a565b600080546001600160a01b0319811682556001600160a01b0316600080516020615f798339815191528280a3005b346102b15760203660031901126102b15761299f614658565b6129a761556a565b6001600160a01b03166129bb811515614ba4565b8060005260066020526040600020548015612a00576040600080516020615ef983398151915291836000526006602052600082812055815190815260006020820152a2005b60405162461bcd60e51b815260206004820152602260248201527f4e6f207370656369666963206665652073657420666f72207468697320746f6b60448201526132b760f11b6064820152608490fd5b346102b15760403660031901126102b157612a69614658565b60243590612a7561556a565b6001600160a01b031690612a8a821515614ba4565b60328110612add57604081612ab261012c600080516020615ef9833981519152941115614f78565b83600052600660205281600020549084600052600660205280836000205582519182526020820152a2005b60405162461bcd60e51b81526020600482015260166024820152754665652070657263656e7461676520746f6f206c6f7760501b6044820152606490fd5b346102b15760003660031901126102b1576013546040516001600160a01b039091168152602090f35b346102b15760003660031901126102b157602060405160328152f35b346102b15760003660031901126102b1576020600f54604051908152f35b346102b15760203660031901126102b1576004356001600160a01b038116908190036102b157612bac61556a565b8015612c2f57600280546001600160a01b0319168217905560405163d0fb020360e01b815290602090829060049082905afa90811561145957600091612c10575b50600380546001600160a01b0319166001600160a01b0392909216919091179055005b612c29915060203d602011611703576116f481836146bb565b81612bed565b60405162461bcd60e51b8152602060048201526019602482015278496e76616c6964206772616469656e7420726567697374727960381b6044820152606490fd5b346102b15760003660031901126102b1576012546040516001600160a01b039091168152602090f35b346102b15760203660031901126102b15760206127c2600435614f3c565b346102b157612cc536614759565b9190612ccf6154ff565b600082815260076020526040902060010154612cf5906001600160a01b03161515614db5565b6002546040516314c530d560e31b81523360048201526001600160a01b039091169290602081602481875afa801561145957612d3891600091612e555750614c56565b612d4382151561521c565b80600052600760205260406000209360ff60098601541694600486101561068d57612d7060049615614e36565b600093600182019586549160ff8360a01c1696612d8c88614773565b871580948195612e3b575b5015612e115750612dab8985015485615ac3565b905b81811115612e095750955b602087151592612dc784615855565b60405163745e9e9f60e01b81529a8b9182905afa98891561145957600099611f7a57506001600160a01b038916988915611f3a57600093611b99600093614773565b905095612db8565b600385015460078601549081811115612e3457612e2e9250614aaf565b90612dad565b5050612e2e565b6001915060a81c60ff16612e4e81614773565b148b612d97565b612e6e915060203d6020116116325761162581836146bb565b86610f98565b346102b15760003660031901126102b15760206040516101f48152f35b346102b15760203660031901126102b157600435612ead6154ff565b600081815260076020526040902060010154612ed3906001600160a01b03161515614db5565b806000526007602052612ef760018060a01b03600160406000200154163314614df8565b80600052600760205260406000206009810160ff815416600481101561068d57612fc0928492612f2a6104ff9315614e36565b612f3661114c85614f3c565b600260ff198254161790556001810190815460ff8160a01c16612f5881614773565b61306757508154600160ff8260a81c16612f7181614773565b0361302457600582015460068301548082111561301b57612f9191614aaf565b80612feb575b50505b60020154905460ff60a882901c81169260a09290921c16906001600160a01b0316615593565b7f61b9399f2f0f32ca39ce8d7be32caed5ec22fe07a6daba3a467ed479ec606582600080a260018055005b6000808083613014956130018396471015614eb1565b6001600160a01b03165af1610597614abc565b8680612f97565b50506000612f91565b6003820154600783015480821115613052576105d861304c91670de0b6b3a764000093614aaf565b04612f91565b5050670de0b6b3a764000061304c60006105d8565b60038201546007830154808211156130c35761308291614aaf565b905b81613091575b5050612f9a565b6130bc916130ac60018060a01b0360028601541680926156ff565b916001600160a01b031690615602565b868061308a565b5050600090613084565b346102b15760203660031901126102b15760206102f76130eb614658565b614d80565b346102b15760203660031901126102b1576004356001600160a01b038116908190036102b15761311e61556a565b801561313a57600380546001600160a01b031916919091179055005b60405162461bcd60e51b815260206004820152601360248201527224b73b30b634b2103332b29036b0b730b3b2b960691b6044820152606490fd5b346102b15760403660031901126102b1576004356001600160401b0381116102b1576131a59036906004016146f9565b906024356001600160401b0381116102b1576131c5903690600401614729565b90926131cf6154ff565b6002546040516314c530d560e31b815233600482015290602090829060249082906001600160a01b03165afa80156114595761321291600091612e555750614c56565b61321d811515614c9d565b613228828214614ce7565b60005b8181106132385760018055005b613243818386614d2e565b9061325b613252828689614d3e565b35923690614d4e565b918251600052600760205260406000209260208101938451600052600760205260406000209060ff600982015416600481101561068d571580613625575b6132a29061563c565b6132ac8351614f3c565b1580613614575b156135de5760ff8161330260018094015491838360a01c166132d481614773565b15806135bf575b6132e490615680565b6002858060a01b0391015416848060a01b03600287015416146156c2565b60a81c1661330f81614773565b149081156135a1575b501561356857805160005260076020526040600020938051600052600760205260406000209161335761116f8560018060a01b0360028a015416615007565b6001860192600160ff855460a81c1661336f81614773565b14613553575b6001810196600160ff895460a81c1661338d81614773565b1461353e575b61339d8682615ac3565b9460038301549260078101938454600091818111600014613536576133c29250614aaf565b995b60408601519780891161352e575b508a8811613523575b60019a5093600289989796929488946134a1868c9861346e6134ec9f600080806134e69f613459613452613441670de0b6b3a764000061342786996134218e1515615855565b8d614a74565b9a909f0180549f909a049e6001600160a01b03168f615b8b565b9960018060a01b038a541690615c03565b988d614aaf565b90546001600160a01b03165af161155f614abc565b546001600160a01b0316906122c96134908361348a818d6156ff565b936156ff565b85546001600160a01b031692614aaf565b600782016134b0878254614a96565b90555460ff8160a01c166134c381614773565b15908161350a575b506134f2575b50506134de838254614a96565b905551615c75565b51615c75565b0161322b565b60066135019101918254614a96565b90558f806134d1565b8e915060a81c60ff1661351c81614773565b14386134cb565b9299965089926133db565b97508f6133d2565b5050996133c4565b61354e60048301548710156158e1565b613393565b613563600488015486111561593e565b613375565b60405162461bcd60e51b81526020600482015260116024820152704e6f74206d61726b6574206f726465727360781b6044820152606490fd5b600180925060ff91015460a81c166135b881614773565b1489613318565b506132e485858189015460a01c166135d681614773565b1490506132db565b60405162461bcd60e51b815260206004820152600e60248201526d13dc99195c9cc8195e1c1a5c995960921b6044820152606490fd5b5061361f8651614f3c565b156132b3565b5060ff600983015416600481101561068d5715613299565b346102b15760203660031901126102b157613656614658565b6002546040516303e21fa960e61b815290602090829060049082906001600160a01b03165afa90811561145957600091613917575b506001600160a01b031680156138e15760405163c45a015560e01b815290602082600481845afa918215611459576000926138bc575b506020600491604051928380926315ab88c960e31b82525afa80156114595761370c9260209260009261389d575b50604051808095819463e6a4390560e01b83528860048401614c28565b03916001600160a01b03165afa9081156114595760009161387e575b506001600160a01b031690811561384357604051630240bc6b60e21b815290606082600481865afa9081156114595760009283926137df575b50602060049460405195868092630dfe168160e01b82525afa908115611459576040946000926137be575b506001600160a01b039182169116036137b957905b82516001600160701b03928316815291166020820152f35b6137a1565b6137d891925060203d602011611703576116f481836146bb565b908561378c565b929391506060833d60601161383b575b816137fc606093836146bb565b810103126138375761380d83614c42565b93604061381c60208601614c42565b94015163ffffffff811603611d6b5750909291906020613761565b8380fd5b3d91506137ef565b60405162461bcd60e51b815260206004820152601360248201527214185a5c88191bd95cc81b9bdd08195e1a5cdd606a1b6044820152606490fd5b613897915060203d602011611703576116f481836146bb565b82613728565b6138b5919250833d8511611703576116f481836146bb565b90856136ef565b60049192506138d9602091823d8411611703576116f481836146bb565b9291506136c1565b60405162461bcd60e51b815260206004820152600e60248201526d149bdd5d195c881b9bdd081cd95d60921b6044820152606490fd5b613930915060203d602011611703576116f481836146bb565b8261368b565b346102b15760003660031901126102b157602060405161012c8152f35b346102b15760003660031901126102b15760206040516127108152f35b346102b15760603660031901126102b157613989614658565b61399161466e565b906044359061399e61556a565b6001600160a01b0316906139b3821515614ba4565b6001600160a01b038316926139c9841515614be8565b6040516370a0823160e01b815230600482015291602083602481875afa92831561145957600093613ab5575b508215613a785780613a725750815b8211613a305781613a27600080516020615f598339815191529360209386615602565b604051908152a3005b60405162461bcd60e51b815260206004820152601a602482015279496e73756666696369656e7420746f6b656e2062616c616e636560301b6044820152606490fd5b91613a04565b60405162461bcd60e51b81526020600482015260156024820152744e6f20746f6b656e7320746f20776974686472617760581b6044820152606490fd5b90926020823d602011613ae1575b81613ad0602093836146bb565b81010312611d6b57505191856139f5565b3d9150613ac3565b346102b15760203660031901126102b157600435613b0561556a565b6127108111613b475760407f9af3456a3485c9955fbe2f71c7de1d0f89f93ff6ae53e43ec73f541db505abdc91601054908060105582519182526020820152a1005b60405162461bcd60e51b8152602060048201526012602482015271088caecd2c2e8d2dedc40e8dede40d0d2ced60731b6044820152606490fd5b346102b15760603660031901126102b157613b9a614658565b60243560028110156102b1576044359060028210156102b157613bbc92615593565b613bc581614aec565b90613bcf8261478a565b613bdc60405191826146bb565b828152613be88361478a565b6020820190601f1901368237826000526009602052604060002054906000915b80151580613cd4575b15613c8f5780600052600760205260ff60096040600020015416600481101561068d571580613c7f575b613c63575b84600052600b602052604060002090600052602052600160406000200154613c08565b918083613c73613c799387614b7a565b52614a87565b91613c40565b50613c8981614f3c565b15613c3b565b81846040519182916020830190602084525180915260408301919060005b818110613cbb575050500390f35b8251845285945060209384019390920191600101613cad565b50858310613c11565b346102b15760003660031901126102b1576020601154604051908152f35b346102b15760403660031901126102b157600435602435613d1a61556a565b80821015613d5c57816040917f34a3b94861a601870265c6c4d74c28d1a09f6c803ce42b95e749f46127c8f17093600d5580600e5582519182526020820152a1005b60405162461bcd60e51b815260206004820152602360248201527f4d696e2073697a65206d757374206265206c657373207468616e206d61782073604482015262697a6560e81b6064820152608490fd5b346102b15760203660031901126102b15760206102f7600435614aec565b346102b15760203660031901126102b15760043560005260086020526020604060002054604051908152f35b6101003660031901126102b15760043560028110156102b1576024359060028210156102b1576044356001600160a01b03811691908281036102b15760c435936084359260643592916001600160401b0387116102b157366023880112156102b1578660040135613e67816146de565b97613e75604051998a6146bb565b81895236602483830101116102b15781600092602460209301838c01378901015260e435908115158092036102b157861561460057833b156145ca57600254604051630736b32b60e31b81526004810189905290602090829060249082906001600160a01b03165afa908115611459576000916145ab575b5061457357613efa6154ff565b841561452e5785156144f357613f1360a4351515614a0b565b600f5460a435116144bf57613f288486615521565b6000968060001904821161447b57670de0b6b3a7640000613f498284614a74565b0496600d54881061444457600e54881161440d57613f6686614773565b8515998a898b82156143c957509050341061438c578a978a8a955b6004549d8e613f8f81614a87565b6004558b613f9f60a43542614a96565b998a93613fab83614773565b15614385575b8960405195613fbf87614684565b8487528d602088013381526040890195613fd881614773565b86526060890191613fe881614773565b8252608089018c815260a08a0193845260c08a0194855260e08a019586526101008a018b81526101208b018c81526101408c019a8b526101608c018d8152998d5260076020526040909c209a518b55915160018b0180546001600160a01b0319166001600160a01b03929092169190911781559651979a979196909290919061407082614773565b61407982614773565b8254905161408681614773565b61408f81614773565b61ffff60a01b1990911660a09290921b60ff60a01b169190911760a89190911b60ff60a81b16179055516002880180546001600160a01b0319166001600160a01b0392909216919091179055516003870155516004808701919091559051600586015590516006850155905160078401559051600883015591516009909101918110156143715761412f9288928b9260ff80198354169116179055615593565b808b52600a6020528b600260408d20546040519061414c826146a0565b81528d604060208301918083528184019560018752878252600b6020528282209082526020522091518255516001820155019051151560ff80198354169116179055808b52600a60205260408b2054151560001461435e57808b52600b60205260408b20818c52600a60205260408c20548c526020528b600160408d2001555b808b52600a6020528b60408c2055808b52600860205260408b20548c8c52600c60205260408c20558a52600860205260408a208054906001820180921161434a57556040519461421b88614773565b87865261422781614773565b602086015260408501526060840152608083015260a08201528460c082015261012060e082015287519182610120830152865b83811061433357507f734faa6f759e00a9988c3e404f6a5c1ef7e553472f08bcafe2dde32c43d1736682899289610140876142b2999897010152610100820152610140813395601f80199101168101030190a3614773565b8061432a575b6142cc575b60208360018055604051908152f35b81806142d9819334614aaf565b335af16142e4614abc565b50156142f15781806142bd565b60405162461bcd60e51b8152602060048201526011602482015270115512081c995d1d5c9b8819985a5b1959607a1b6044820152606490fd5b508034116142b8565b80602080928c01015161014082860101520161425a565b634e487b7160e01b8c52601160045260248cfd5b808b5260096020528b60408c20556141cc565b634e487b7160e01b8d52602160045260248dfd5b5084613fb1565b60405162461bcd60e51b8152602060048201526015602482015274125b9cdd59999a58da595b9d08115512081cd95b9d605a1b6044820152606490fd5b909561440860409b939b516323b872dd60e01b6020820152336024820152306044820152866064820152606481526144026084826146bb565b85615a68565b613f81565b60405162461bcd60e51b815260206004820152600f60248201526e4f7264657220746f6f206c6172676560881b6044820152606490fd5b60405162461bcd60e51b815260206004820152600f60248201526e13dc99195c881d1bdbc81cdb585b1b608a1b6044820152606490fd5b606460405162461bcd60e51b815260206004820152602060248201527f50726963652063616c63756c6174696f6e20776f756c64206f766572666c6f776044820152fd5b60405162461bcd60e51b815260206004820152600c60248201526b54544c20746f6f206c6f6e6760a01b6044820152606490fd5b60405162461bcd60e51b8152602060048201526013602482015272496e76616c69642070726963652072616e676560681b6044820152606490fd5b60405162461bcd60e51b815260206004820152601d60248201527f416d6f756e74206d7573742062652067726561746572207468616e20300000006044820152606490fd5b60405162461bcd60e51b815260206004820152601060248201526f151bdad95b881a5cc8189b1bd8dad95960821b6044820152606490fd5b6145c4915060203d6020116116325761162581836146bb565b89613eed565b60405162461bcd60e51b815260206004820152600e60248201526d139bdd08184818dbdb9d1c9858dd60921b6044820152606490fd5b60405162461bcd60e51b815260206004820152600d60248201526c24b73b30b634b2103a37b5b2b760991b6044820152606490fd5b346102b15760203660031901126102b15760206102f7614653614658565b6147d9565b600435906001600160a01b03821682036102b157565b602435906001600160a01b03821682036102b157565b61018081019081106001600160401b03821117610b3757604052565b606081019081106001600160401b03821117610b3757604052565b601f909101601f19168101906001600160401b03821190821017610b3757604052565b6001600160401b038111610b3757601f01601f191660200190565b9181601f840112156102b1578235916001600160401b0383116102b157602080850194606085020101116102b157565b9181601f840112156102b1578235916001600160401b0383116102b1576020808501948460051b0101116102b157565b60609060031901126102b157600435906024359060443590565b6002111561068d57565b90600482101561068d5752565b6001600160401b038111610b375760051b60200190565b908160209103126102b157516001600160a01b03811681036102b15790565b908160209103126102b1575160ff811681036102b15790565b6013546001600160a01b0316908161484f575b6147f69150615355565b80156147ff5790565b60405162461bcd60e51b815260206004820152602260248201527f4e6f206c697175696469747920617661696c61626c6520696e205632206f7220604482015261563360f01b6064820152608490fd5b6002546040516303e21fa960e61b815290602090829060049082906001600160a01b03165afa908115611459576000916149d4575b506001600160a01b0316918261489b575b506147ec565b60405163313ce56760e01b815291906001600160a01b038216602084600481845afa9384156114595760009461499f575b5060206004939495604051948580926315ab88c960e31b82525afa9081156114595760209360009261497f575b506064919260ff604051978895869463bbbb384960e01b8652600486015260018060a01b031660248501521660448301525afa9182156114595760009261494b575b50816149475781614895565b5090565b90916020823d602011614977575b81614966602093836146bb565b81010312611d6b575051903861493b565b3d9150614959565b6064925061499990853d8711611703576116f481836146bb565b916148f9565b60049394506149c5602091823d84116149cd575b6149bd81836146bb565b8101906147c0565b9493506148cc565b503d6149b3565b6149ed915060203d602011611703576116f481836146bb565b38614884565b908160209103126102b1575180151581036102b15790565b15614a1257565b60405162461bcd60e51b815260206004820152601a602482015279054544c206d7573742062652067726561746572207468616e20360341b6044820152606490fd5b8115614a5e570490565b634e487b7160e01b600052601260045260246000fd5b81810292918115918404141715611d1757565b6000198114611d175760010190565b91908201809211611d1757565b600482101561068d5752565b91908203918211611d1757565b3d15614ae7573d90614acd826146de565b91614adb60405193846146bb565b82523d6000602084013e565b606090565b600090806000526009602052604060002054805b614b0957505090565b80600052600760205260ff60096040600020015416600481101561068d571580614b6a575b614b5a575b81600052600b60205260406000209060005260205260016040600020015490819091614b00565b91614b6490614a87565b91614b33565b50614b7481614f3c565b15614b2e565b8051821015614b8e5760209160051b010190565b634e487b7160e01b600052603260045260246000fd5b15614bab57565b60405162461bcd60e51b8152602060048201526015602482015274496e76616c696420746f6b656e206164647265737360581b6044820152606490fd5b15614bef57565b60405162461bcd60e51b8152602060048201526011602482015270125b9d985b1a59081c9958da5c1a595b9d607a1b6044820152606490fd5b6001600160a01b0391821681529116602082015260400190565b51906001600160701b03821682036102b157565b15614c5d57565b60405162461bcd60e51b815260206004820152601860248201527710d85b1b195c881a5cc81b9bdd08185d5d1a1bdc9a5e995960421b6044820152606490fd5b15614ca457565b60405162461bcd60e51b815260206004820152601b60248201527a139bc81bdc99195c881b585d18da195cc81d1bc8199d5b199a5b1b602a1b6044820152606490fd5b15614cee57565b60405162461bcd60e51b815260206004820152601860248201527709ad2e6dac2e8c6d0cac840c2e4e4c2f2e640d8cadccee8d60431b6044820152606490fd5b9190811015614b8e576060020190565b9190811015614b8e5760051b0190565b91908260609103126102b157604051614d66816146a0565b604080829480358452602081013560208501520135910152565b6001600160a01b031660008181526006602052604090205415614dae57600052600660205260406000205490565b5060055490565b15614dbc57565b60405162461bcd60e51b815260206004820152601460248201527313dc99195c88191bd95cc81b9bdd08195e1a5cdd60621b6044820152606490fd5b15614dff57565b60405162461bcd60e51b815260206004820152600f60248201526e2737ba1037b93232b91037bbb732b960891b6044820152606490fd5b15614e3d57565b60405162461bcd60e51b815260206004820152601060248201526f4f72646572206e6f742061637469766560801b6044820152606490fd5b15614e7c57565b60405162461bcd60e51b815260206004820152600d60248201526c13dc99195c88195e1c1a5c9959609a1b6044820152606490fd5b15614eb857565b60405162461bcd60e51b815260206004820152601c60248201527b125b9cdd59999a58da595b9d08115512081a5b8818dbdb9d1c9858dd60221b6044820152606490fd5b15614f0357565b60405162461bcd60e51b8152602060048201526011602482015270115512081c99599d5b990819985a5b1959607a1b6044820152606490fd5b600081815260076020526040902060010154614f62906001600160a01b03161515614db5565b6000526007602052600860406000200154421190565b15614f7f57565b60405162461bcd60e51b815260206004820152601760248201527608ccaca40e0cae4c6cadce8c2ceca40e8dede40d0d2ced604b1b6044820152606490fd5b15614fc557565b60405162461bcd60e51b815260206004820152601a602482015279507269636520646966666572656e636520746f6f206c6172676560301b6044820152606490fd5b6013546001600160a01b0316806150fc575b50615023906147d9565b9081156150f5578181146150ee5781808211156150955761504391614aaf565b9061506f7e068db8bac710cb295e9e1b089a027525460aa64c2f837b4a2339c0ebedfa43831115614fbe565b6127108202918083046127101490151715611d175761508d91614a54565b601054101590565b9061509f91614aaf565b906150cb7e068db8bac710cb295e9e1b089a027525460aa64c2f837b4a2339c0ebedfa43831115614fbe565b6127108202918083046127101490151715611d17576150e991614a54565b61508d565b5050600190565b5050600090565b6002546040516303e21fa960e61b815290602090829060049082906001600160a01b03165afa908115611459576000916151fd575b506001600160a01b031680615147575b50615019565b6020600491604051928380926315ab88c960e31b82525afa801561145957615191926020926000926151de575b506040518080958194630d4d933b60e21b83528860048401614c28565b03915afa600091816151bd575b506151aa575b80615141565b6001600160a01b03166150ee57386151a4565b6151d791925060203d602011611703576116f481836146bb565b903861519e565b6151f6919250833d8511611703576116f481836146bb565b9038615174565b615216915060203d602011611703576116f481836146bb565b38615131565b1561522357565b60405162461bcd60e51b815260206004820152602260248201527f46696c6c20616d6f756e74206d7573742062652067726561746572207468616e604482015261020360f41b6064820152608490fd5b3562ffffff811681036102b15790565b9062ffffff809160031b9316831b921b19161790565b60005260076020526040600020600181015460ff8160a01c166152bb81614773565b159081615306575b50156152e957600660058201549101548082116000146150f5576152e691614aaf565b90565b600760038201549101548082116000146150f5576152e691614aaf565b6001915060a81c60ff1661531981614773565b14386152c3565b60ff6011199116019060ff8211611d1757565b60ff166012039060ff8211611d1757565b60ff16604d8111611d1757600a0a90565b60408051630fa6707960e21b81526001600160a01b0390921660048301819052919081602481305afa8060009283926154c3575b5061539657505050600090565b81158080156154bb575b6154b25760206004946040519586809263313ce56760e01b82525afa93841561145957600094615491575b5060ff84169360128503615407575090919250670de0b6b3a76400008302928304670de0b6b3a7640000141715611d17576152e691614a54565b565b91936012111561545857509061542761542261542d93615333565b615344565b90614a54565b90670de0b6b3a7640000820291808304670de0b6b3a76400001490151715611d17576152e691614a54565b919261542761542261546993615320565b90670de0b6b3a76400008302928304670de0b6b3a7640000141715611d17576152e691614a54565b6154ab91945060203d6020116149cd576149bd81836146bb565b92386153cb565b50505050600090565b5081156153a0565b929091506040833d6040116154f7575b816154e0604093836146bb565b81010312611d6b5750602082519201519038615389565b3d91506154d3565b600260015414615510576002600155565b633ee5aeb560e01b60005260046000fd5b9061552b90615a1a565b9060ff82166012810361553e5750905090565b6012111561555b576155556154226152e693615333565b90614a74565b6154276154226152e693615320565b6000546001600160a01b0316330361557e57565b63118cdaa760e01b6000523360045260246000fd5b60405160609190911b6001600160601b031916602082019081529290916155b981614773565b60f81b60348301526155ca81614773565b60f81b6035820152601681526155e16036826146bb565b51902090565b6001600160a01b039091168152602081019190915260400190565b615637615405939261562960405194859263a9059cbb60e01b6020850152602484016155e7565b03601f1981018452836146bb565b615a68565b1561564357565b60405162461bcd60e51b81526020600482015260156024820152744f7264657273206d7573742062652061637469766560581b6044820152606490fd5b1561568757565b60405162461bcd60e51b8152602060048201526013602482015272496e76616c6964206f7264657220747970657360681b6044820152606490fd5b156156c957565b60405162461bcd60e51b815260206004820152600e60248201526d0a8ded6cadc40dad2e6dac2e8c6d60931b6044820152606490fd5b9061570990615a1a565b9060ff82166012810361571c5750905090565b60121115615733576154276154226152e693615333565b6155556154226152e693615320565b80600052600b602052604060002082600052602052604060002060ff6002820154161561581b5780541561580257600181015482600052600b602052604060002082546000526020526001604060002001555b600181018054909190156157eb57549082600052600b602052604060002090546000526020526040600020555b600052600b60205260406000209060005260205260006002604082208281558260018201550155565b90505481600052600a6020526040600020556157c2565b6001810154826000526009602052604060002055615795565b60405162461bcd60e51b81526020600482015260126024820152714f72646572206e6f7420696e20717565756560701b6044820152606490fd5b1561585c57565b60405162461bcd60e51b8152602060048201526011602482015270139bc8185b5bdd5b9d081d1bc8199a5b1b607a1b6044820152606490fd5b1561589c57565b60405162461bcd60e51b815260206004820152601d60248201527f455448207472616e7366657220746f2073656c6c6572206661696c65640000006044820152606490fd5b156158e857565b60405162461bcd60e51b815260206004820152602860248201527f457865637574696f6e2070726963652062656c6f772073656c6c65722773206d604482015267696e20707269636560c01b6064820152608490fd5b1561594557565b60405162461bcd60e51b815260206004820152602960248201527f457865637574696f6e20707269636520657863656564732062757965722773206044820152686d617820707269636560b81b6064820152608490fd5b156159a357565b60405162461bcd60e51b815260206004820152603360248201527f457865637574696f6e20707269636520646576696174657320746f6f206d7563604482015272682066726f6d206d61726b657420707269636560681b6064820152608490fd5b6040919493926060820195825260208201520152565b60405163313ce56760e01b815290602090829060049082906001600160a01b03165afa90811561145957600091615a4f575090565b6152e6915060203d6020116149cd576149bd81836146bb565b906000602091828151910182855af115611459576000513d615aba57506001600160a01b0381163b155b615a995750565b635274afe760e01b60009081526001600160a01b0391909116600452602490fd5b60011415615a92565b6001810154600160ff8260a81c16615ada81614773565b149081615b5a575b5015615b3b5760066005820154910154600091818111600014615b335761542d9250614aaf565b670de0b6b3a7640000820291808304670de0b6b3a76400001490151715611d17576152e691614a54565b505090615b09565b9050600760038201549101548082116000146150f5576152e691614aaf565b60ff915060a01c16615b6b81614773565b1538615ae2565b9081526001600160a01b03909116602082015260400190565b615b989061555583614d80565b612710600091049182615baa57505090565b6003546001600160a01b031690813b15615bff57615be284928492604051948580948193637b904e7560e01b83528360048401615b72565b03925af18015611f0957615bf557505090565b81614947916146bb565b8280fd5b615c109061555583614d80565b612710600091049182615c2257505090565b600354615c3e9084906001600160a01b03908116908416615602565b6003546001600160a01b031690813b15615bff57615be28392839260405194858094819362262d0160e91b83528a60048401615b72565b918260005260076020526040600020918360018401805460ff8160a01c16615c9c81614773565b159081615ebe575b5015615dee576005850194855495600682019687546000818311600014615de85750615cd08183614aaf565b905b81159283159384615dba575b508110801590615db3575b15615d65575050600080516020615f39833981519152979383615d5796936104ff936009615d499701600160ff19825416179055615d5c575b5060020154905460ff60a882901c81169260a09290921c16906001600160a01b0316615593565b549260405193849384615a04565b0390a2565b54865538615d22565b604080519889526020890192909252908701525050505060608201929092527f686bccf6e038d1f0f07e859bb6b9eada3eab6ae5537f18f14d450031da6f0165925090508060808101615d57565b5082615ce9565b97509250612710820296828804612710141715611d1757615ddc838c98614a54565b60115410159238615cde565b90615cd2565b6003850194855495600782019687546000818311600014615eb85750615e148183614aaf565b905b81159283159384615e8a575b5081148015615db35715615d65575050600080516020615f39833981519152979383615d5796936104ff936009615d499701600160ff19825416179055615d5c575060020154905460ff60a882901c81169260a09290921c16906001600160a01b0316615593565b97509250612710820296828804612710141715611d1757615eac838c98614a54565b60115410159238615e22565b90615e16565b6001915060a81c60ff16615ed181614773565b1438615ca4565b9081526001600160a01b039182166020820152911660408201526060019056fee2f9e9065995f6d98a1dc7a86e975c10b2848f8af0a077c8d675b71d486dbd11e332d84b9d7b6ed7b61613e3346be661c47cd1bcb330e8cdb0887aa2d3425e6ea4360fd9d66ebd27e7e5e650a0dbc45ff3fe0e9560cf7b07b3c748dc95489f52da0612d7ca9ff90ca7143a6021ba8938994f8d045b2834ae585fd07b27ea697c8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0a2646970667358221220f268f1a79f159bf21dc887e82749790727a43825edffa65e9c80194f34e47a0364736f6c634300081a0033

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

000000000000000000000000db7e3b94bd55fb9d93112830b23bae82e33527c9

-----Decoded View---------------
Arg [0] : _gradientRegistry (address): 0xdB7E3b94bd55FB9D93112830b23BAE82E33527c9

-----Encoded View---------------
1 Constructor Arguments found :
Arg [0] : 000000000000000000000000db7e3b94bd55fb9d93112830b23bae82e33527c9


Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export  ]
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.