BNB Price: $619.00 (+0.60%)
 

Overview

BNB Balance

BNB Smart Chain LogoBNB Smart Chain LogoBNB Smart Chain Logo0 BNB

BNB Value

$0.00

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Block
From
To
Update Fee Rate ...925946432026-04-15 2:00:0914 mins ago1776218409IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
Update Fee Rate ...925946432026-04-15 2:00:0914 mins ago1776218409IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
Update Fee Rate ...925946352026-04-15 2:00:0614 mins ago1776218406IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
Update Fee Rate ...925946342026-04-15 2:00:0514 mins ago1776218405IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
Update Fee Rate ...925946242026-04-15 2:00:0114 mins ago1776218401IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
Update Fee Rate ...925946242026-04-15 2:00:0114 mins ago1776218401IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
Update Fee Rate ...925866502026-04-15 1:00:101 hr ago1776214810IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
Update Fee Rate ...925866492026-04-15 1:00:101 hr ago1776214810IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
Update Fee Rate ...925866412026-04-15 1:00:061 hr ago1776214806IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
Update Fee Rate ...925866402026-04-15 1:00:061 hr ago1776214806IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
Update Fee Rate ...925866302026-04-15 1:00:011 hr ago1776214801IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
Update Fee Rate ...925866292026-04-15 1:00:011 hr ago1776214801IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
Update Fee Rate ...925786562026-04-15 0:00:102 hrs ago1776211210IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
Update Fee Rate ...925786562026-04-15 0:00:102 hrs ago1776211210IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
Update Fee Rate ...925786472026-04-15 0:00:062 hrs ago1776211206IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
Update Fee Rate ...925786472026-04-15 0:00:062 hrs ago1776211206IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
Update Fee Rate ...925786382026-04-15 0:00:022 hrs ago1776211202IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
Update Fee Rate ...925786382026-04-15 0:00:022 hrs ago1776211202IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
Update Fee Rate ...925706592026-04-14 23:00:103 hrs ago1776207610IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
Update Fee Rate ...925706582026-04-14 23:00:093 hrs ago1776207609IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
Update Fee Rate ...925706502026-04-14 23:00:053 hrs ago1776207605IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
Update Fee Rate ...925706492026-04-14 23:00:053 hrs ago1776207605IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
Update Fee Rate ...925706402026-04-14 23:00:013 hrs ago1776207601IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
Update Fee Rate ...925706392026-04-14 23:00:013 hrs ago1776207601IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
Update Fee Rate ...925626642026-04-14 22:00:104 hrs ago1776204010IN
0xC9063Dc5...4bc5d7c36
0 BNB0.000008580.1125
View all transactions

Parent Transaction Hash Block From To
View All Internal Transactions
Cross-Chain Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
CTFExchangeFeeManager

Compiler Version
v0.8.27+commit.40a35a09

Optimization Enabled:
Yes with 1 runs

Other Settings:
cancun EvmVersion
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import { IFeeEstimation } from "./interfaces/IFeeEstimation.sol";
import { Order, Side } from "./libraries/OrderStructs.sol";
import { CalculatorHelper } from "./libraries/CalculatorHelper.sol";
import { DecimalConversionLib } from "./libraries/DecimalConversionLib.sol";
import { UserTierManager } from "./mixins/UserTierManager.sol";
import { ReferralManager } from "./mixins/ReferralManager.sol";
import { Fees } from "./mixins/Fees.sol";
import { TraderReferralInfo, ReferrerConfig, CollateralPriceConfig } from "./libraries/ReferralStructs.sol";
import { IERC20Metadata } from "openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";

/// @title CTF Exchange Fee Manager
/// @notice Unified fee manager supporting multiple collateral tokens and shared referrals
/// @author OLAB
contract CTFExchangeFeeManager is IFeeEstimation, UserTierManager, ReferralManager, Fees {

    // ============ Errors ============

    error InvalidCollateralToken();
    error CollateralNotConfigured();
    error NoDefaultCollateralSet();
    error GlobalMinFeeExceedsMaximum();
    error FeeRateExceedsMaximum();
    error InvalidDiscount();

    // ============ Structs ============

    struct FeeRateSettings {
        uint256 makerFeeRateBps;
        uint256 takerFeeRateBps;
        bool enabled;
        uint256 minFeeAmount;
    }

    struct CollateralConfig {
        uint8 decimals;
        uint256 globalDefaultMinFee;
    }

    // ============ Storage ============

    /// @notice Fee settings per token ID (shared across all collateral tokens)
    mapping(uint256 => FeeRateSettings) public feeRateSettings;

    /// @notice Collateral tokens configuration
    mapping(address => CollateralConfig) public collateralConfigs;

    /// @notice Default collateral token
    address public defaultCollateralToken;

    // ============ Events ============


    event FeeRateSettingsChanged(
        uint256 indexed tokenId,
        uint256 makerFeeRateBps,
        uint256 takerFeeRateBps,
        bool enabled,
        uint256 minFeeAmount
    );

    // ============ Constructor ============

    constructor(address _defaultCollateralToken) {
        if (_defaultCollateralToken != address(0)) {
            // Auto-detect decimals for default collateral
            uint8 decimals;
            try IERC20Metadata(_defaultCollateralToken).decimals() returns (uint8 _decimals) {
                decimals = _decimals;
            } catch {
                decimals = 18; // Default fallback
            }

            collateralConfigs[_defaultCollateralToken] = CollateralConfig({
                decimals: decimals,
                globalDefaultMinFee: 0
            });

            defaultCollateralToken = _defaultCollateralToken;
        }
    }

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

    /// @notice Set collateral token configuration directly (simplified management)
    /// @param collateralToken The collateral token address
    /// @param decimals Token decimals
    /// @param globalDefaultMinFee Global default minimum fee for this token
    function setCollateralConfig(address collateralToken, uint8 decimals, uint256 globalDefaultMinFee) external onlyOperator {
        if (collateralToken == address(0)) revert InvalidCollateralToken();

        collateralConfigs[collateralToken] = CollateralConfig({
            decimals: decimals,
            globalDefaultMinFee: globalDefaultMinFee
        });
    }

    /// @notice Set the default collateral token (simplified)
    /// @param newDefaultCollateral The new default collateral token address
    function setDefaultCollateralToken(address newDefaultCollateral) external onlyOperator {
        if (newDefaultCollateral == address(0)) revert InvalidCollateralToken();

        CollateralConfig memory config = collateralConfigs[newDefaultCollateral];
        if (config.decimals == 0) revert CollateralNotConfigured();

        defaultCollateralToken = newDefaultCollateral;
    }

    /// @notice Update global default minimum fee for default collateral
    /// @param newGlobalDefaultMinFee New global default minimum fee amount
    function updateGlobalDefaultMinFee(uint256 newGlobalDefaultMinFee) external onlyOperator {
        if (defaultCollateralToken == address(0)) revert NoDefaultCollateralSet();

        CollateralConfig memory config = collateralConfigs[defaultCollateralToken];
        uint256 maxAllowedMinFee = 10 * (10 ** config.decimals);
        if (newGlobalDefaultMinFee > maxAllowedMinFee) revert GlobalMinFeeExceedsMaximum();

        collateralConfigs[defaultCollateralToken].globalDefaultMinFee = newGlobalDefaultMinFee;
    }

    /// @notice Get global default minimum fee for default collateral
    /// @return The current global default minimum fee amount
    function getGlobalDefaultMinFee() external view returns (uint256) {
        if (defaultCollateralToken == address(0)) revert NoDefaultCollateralSet();
        return collateralConfigs[defaultCollateralToken].globalDefaultMinFee;
    }

    /// @notice Update fee rate settings for a token (shared across all collaterals)
    /// @dev Restricted to operator role
    function updateFeeRateSettings(
        uint256 tokenId,
        uint256 makerFeeRateBps,
        uint256 takerFeeRateBps,
        bool enabled,
        uint256 minFeeAmount
    ) external onlyOperator {
        uint256 maxFeeRate = getMaxFeeRate();
        if (makerFeeRateBps > maxFeeRate || takerFeeRateBps > maxFeeRate) revert FeeRateExceedsMaximum();

        feeRateSettings[tokenId] = FeeRateSettings({
            makerFeeRateBps: makerFeeRateBps,
            takerFeeRateBps: takerFeeRateBps,
            enabled: enabled,
            minFeeAmount: minFeeAmount
        });
        emit FeeRateSettingsChanged(tokenId, makerFeeRateBps, takerFeeRateBps, enabled, minFeeAmount);
    }

    // ============ Fee Estimation Functions ============

    /// @notice Estimate order fee with specific collateral token
    /// @param user The user address
    /// @param order The order to estimate fee for
    /// @param collateralToken The collateral token to use for calculations
    /// @return estimate The fee estimate
    function estimateFee(
        address user,
        Order calldata order,
        address collateralToken
    ) external view returns (FeeEstimate memory estimate) {
        return this.estimateFeeWithAmount(user, order, order.makerAmount, collateralToken);
    }

    /// @notice Estimate order fee with fill amount and collateral token
    /// @param user The user address
    /// @param order The order to estimate fee for
    /// @param fillAmount The fill amount
    /// @param collateralToken The collateral token to use for calculations
    /// @return estimate The fee estimate
    function estimateFeeWithAmount(
        address user,
        Order calldata order,
        uint256 fillAmount,
        address collateralToken
    ) external view returns (FeeEstimate memory estimate) {
        uint256 takingAmount = CalculatorHelper.calculateTakingAmount(fillAmount, order.makerAmount, order.takerAmount);
        // Use the fee rate from order directly (caller should have set it correctly)
        uint256 feeRateBps = order.feeRateBps;

        // Calculate base fee
        if (order.takerAmount == 0) {
            // Market order: use worst-case price (0.5) for conservative estimate
            estimate.baseFee = (fillAmount * feeRateBps) / (4 * 10000);
        } else {
            // Limit order: use curved fee formula
            uint256 outcomeTokens = order.side == Side.BUY ? takingAmount : fillAmount;
            estimate.baseFee = CalculatorHelper._calculateBaseFee(
                feeRateBps,
                outcomeTokens,
                fillAmount,
                takingAmount,
                order.side
            );
        }
        estimate.feeRateBps = feeRateBps;

        // Get discount info for transparency
        estimate.userDiscountBps = this.getUserDiscountBps(user);
        estimate.referralDiscountBps = _getReferralDiscountBps(user);

        // Get minimum fee amount
        estimate.minFeeAmount = _getMinFeeAmount(order.tokenId, collateralToken);

        // ✅ FIX: Use computeFees to ensure consistency with actual trading
        // This ensures rebate calculation is based on discounted amount (before minFee)
        (uint256 finalFee, uint256 rebate,) = this.computeFees(
            user,
            estimate.baseFee,
            estimate.minFeeAmount,
            0  // No admin discount in fee estimation
        );

        estimate.totalFee = finalFee;
        estimate.referrerRebateAmount = rebate;
        estimate.minFeeApplied = (finalFee > estimate.baseFee * (10000 - estimate.userDiscountBps) * (10000 - estimate.referralDiscountBps) / 100000000);

        // Calculate discount amounts for transparency
        estimate.userDiscountAmount = estimate.baseFee * estimate.userDiscountBps / 10000;
        estimate.referralDiscountAmount = (estimate.baseFee - estimate.userDiscountAmount) * estimate.referralDiscountBps / 10000;

        // Calculate platform net revenue
        estimate.platformRevenue = estimate.totalFee - estimate.referrerRebateAmount;

        // Calculate price
        if (order.takerAmount == 0) {
            // Market order: price is unknown at estimation time
            estimate.price = 0;
        } else {
            estimate.price = CalculatorHelper._calculatePrice(fillAmount, takingAmount, order.side);
        }

        return estimate;
    }

    /// @inheritdoc IFeeEstimation
    function estimateOrderFee(address user, Order calldata order, bool isTaker)
        external
        view
        override
        returns (FeeEstimate memory estimate)
    {
        if (defaultCollateralToken == address(0)) revert NoDefaultCollateralSet();
        if (isTaker) {
            // Check if maker has zero fee rate for special case handling
            (uint256 makerFeeRateBps,, bool enabled,) = _getFeeRateSettingsWithCollateral(order.tokenId);
            bool makerHasZeroFeeRate = enabled && makerFeeRateBps == 0;
            return _estimateTakerFeeWithCollateral(user, order, order.makerAmount, defaultCollateralToken, makerHasZeroFeeRate);
        } else {
            return _estimateMakerFeeWithCollateral(user, order, order.makerAmount, defaultCollateralToken);
        }
    }

    /// @inheritdoc IFeeEstimation
    function estimateOrderFeeWithFillAmount(address user, Order calldata order, uint256 fillAmount, bool isTaker)
        external
        view
        override
        returns (FeeEstimate memory estimate)
    {
        if (defaultCollateralToken == address(0)) revert NoDefaultCollateralSet();
        if (isTaker) {
            // Check if maker has zero fee rate for special case handling
            (uint256 makerFeeRateBps,, bool enabled,) = _getFeeRateSettingsWithCollateral(order.tokenId);
            bool makerHasZeroFeeRate = enabled && makerFeeRateBps == 0;
            return _estimateTakerFeeWithCollateral(user, order, fillAmount, defaultCollateralToken, makerHasZeroFeeRate);
        } else {
            return _estimateMakerFeeWithCollateral(user, order, fillAmount, defaultCollateralToken);
        }
    }


    // ============ Internal Functions ============

    /// @notice Internal function to add collateral token support
    /// @param collateralToken The collateral token address
    /// @param globalDefaultMinFee Initial global default minimum fee

    /// @notice Get collateral token precision multiplier for calculations
    /// @param collateralToken The collateral token address
    /// @return precision multiplier (10^decimals)
    function _getCollateralPrecision(address collateralToken) internal view returns (uint256) {
        CollateralConfig memory config = collateralConfigs[collateralToken];
        // If no config exists, default to 18 decimals
        uint8 decimals = config.decimals == 0 ? 18 : config.decimals;
        return 10 ** decimals;
    }

    /// @notice Get minimum fee amount for a token and collateral combination
    /// @param tokenId The token ID
    /// @param collateralToken The collateral token address
    /// @return minFee The minimum fee amount
    function _getMinFeeAmount(uint256 tokenId, address collateralToken) internal view returns (uint256) {
        FeeRateSettings memory settings = feeRateSettings[tokenId];
        CollateralConfig memory collateralConfig = collateralConfigs[collateralToken];

        // Use token-specific min fee if set and enabled, otherwise use global default
        if (settings.enabled && settings.minFeeAmount > 0) {
            return settings.minFeeAmount;
        }
        return collateralConfig.globalDefaultMinFee;
    }


    /// @notice Get fee rate for order (unchanged from original)
    function _getFeeRateForOrder(Order calldata order) internal view returns (uint256 feeRateBps) {
        FeeRateSettings memory settings = feeRateSettings[order.tokenId];

        if (settings.enabled) {
            return settings.takerFeeRateBps; // Default to taker rate for estimation
        } else {
            return order.feeRateBps;
        }
    }

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


    /// @notice Get collateral token configuration (simplified)
    /// @param collateralToken The collateral token address
    /// @return config The collateral configuration
    function getCollateralConfig(address collateralToken) external view returns (CollateralConfig memory config) {
        return collateralConfigs[collateralToken];
    }

    /// @notice Get global default minimum fee for a specific collateral token
    /// @param collateralToken The collateral token address
    /// @return The global default minimum fee for this collateral
    function getCollateralGlobalMinFee(address collateralToken) external view returns (uint256) {
        return collateralConfigs[collateralToken].globalDefaultMinFee;
    }

    // ============ ReferralManager Implementation ============


    // ============ Internal Helper Functions ============

    /// @notice Get referral discount rate for a user
    /// @param user The user address
    /// @return discountBps The discount rate in basis points
    function _getReferralDiscountBps(address user) internal view returns (uint256 discountBps) {
        TraderReferralInfo storage traderInfo = traderReferrals[user];

        // Return 0 if trader has no referral setup
        if (traderInfo.referrer == address(0)) {
            return 0;
        }

        ReferrerConfig storage config = referrerConfigs[traderInfo.referrer];

        // Check validity conditions
        if (!config.isActive ||
            config.expiresAt <= block.timestamp ||
            !isDiscountValid(user)) {
            return 0;
        }

        return config.discountRate;
    }

    /// @notice Apply all discounts to fee amount
    /// @param user User address
    /// @param amount Original fee amount
    /// @return discountedAmount Fee amount after all discounts
    function _applyAllDiscounts(address user, uint256 amount) internal view returns (uint256 discountedAmount) {
        discountedAmount = amount;

        // Apply user tier discount
        uint256 userDiscountBps = this.getUserDiscountBps(user);
        if (userDiscountBps > 0) {
            if (userDiscountBps > 10000) revert InvalidDiscount();
            discountedAmount = discountedAmount * (10000 - userDiscountBps) / 10000;
        }

        // Apply referral discount
        uint256 refDiscountBps = _getReferralDiscountBps(user);
        if (refDiscountBps > 0) {
            if (refDiscountBps > 10000) revert InvalidDiscount();
            discountedAmount = discountedAmount * (10000 - refDiscountBps) / 10000;
        }
    }

    /// @notice Calculate referrer rebate amount for a user's fee
    /// @param user The user address
    /// @param finalFeeAmount The final fee amount after all discounts
    /// @return rebateAmount The rebate amount to be paid to referrer
    function _calculateReferrerRebate(address user, uint256 finalFeeAmount) internal view returns (uint256 rebateAmount) {
        TraderReferralInfo storage traderInfo = traderReferrals[user];

        // Return 0 if trader has no referral setup
        if (traderInfo.referrer == address(0)) {
            return 0;
        }

        ReferrerConfig storage config = referrerConfigs[traderInfo.referrer];

        // Check validity conditions for rebate
        if (!config.isActive ||
            config.expiresAt <= block.timestamp ||
            !isRebateValid(traderInfo.referrer, user)) {
            return 0;
        }

        return (finalFeeAmount * config.rebateRate) / 10000;
    }

    // ============ Additional Inherited Functions ============

    /// @inheritdoc IFeeEstimation
    function estimateMatchingFees(
        address takerUser,
        Order calldata takerOrder,
        Order[] calldata makerOrders,
        uint256 takerFillAmount,
        uint256[] calldata makerFillAmounts
    ) external view override returns (FeeEstimate memory takerFee, FeeEstimate[] memory makerFees) {
        if (defaultCollateralToken == address(0)) revert NoDefaultCollateralSet();
        return _estimateMatchingFeesWithCollateral(takerUser, takerOrder, makerOrders, takerFillAmount, makerFillAmounts, defaultCollateralToken);
    }

    /// @inheritdoc IFeeEstimation
    function estimateMakerFee(
        address makerUser,
        Order calldata makerOrder,
        uint256 fillAmount
    ) external view override returns (FeeEstimate memory estimate) {
        if (defaultCollateralToken == address(0)) revert NoDefaultCollateralSet();
        return _estimateMakerFeeWithCollateral(makerUser, makerOrder, fillAmount, defaultCollateralToken);
    }

    /// @inheritdoc IFeeEstimation
    function estimateTakerFee(
        address takerUser,
        Order calldata takerOrder,
        uint256 fillAmount
    ) external view override returns (FeeEstimate memory estimate) {
        if (defaultCollateralToken == address(0)) revert NoDefaultCollateralSet();
        // Check if maker has zero fee rate for special case handling
        (uint256 makerFeeRateBps,, bool enabled,) = _getFeeRateSettingsWithCollateral(takerOrder.tokenId);
        bool makerHasZeroFeeRate = enabled && makerFeeRateBps == 0;
        return _estimateTakerFeeWithCollateral(takerUser, takerOrder, fillAmount, defaultCollateralToken, makerHasZeroFeeRate);
    }

    /// @inheritdoc IFeeEstimation
    function getFeeRateSettings(uint256 tokenId)
        external view override returns (
            uint256 makerFeeRateBps,
            uint256 takerFeeRateBps,
            bool enabled,
            uint256 minFeeAmount
        )
    {
        if (defaultCollateralToken == address(0)) revert NoDefaultCollateralSet();
        return _getFeeRateSettingsWithCollateral(tokenId);
    }

    /// @inheritdoc IFeeEstimation
    function getUserDiscountInfo(address user)
        external view override returns (
            uint256 userTierDiscountBps,
            uint256 referralDiscountBps
        )
    {
        userTierDiscountBps = this.getUserDiscountBps(user);
        referralDiscountBps = _getReferralDiscountBps(user);
    }

    /// @inheritdoc IFeeEstimation
    function estimateMinimumFee(uint256 tokenId, uint256 feeAmount)
        external view override returns (uint256 finalFee, bool minFeeApplied)
    {
        if (defaultCollateralToken == address(0)) revert NoDefaultCollateralSet();
        uint256 minFee = _getMinFeeAmount(tokenId, defaultCollateralToken);
        if (feeAmount < minFee) {
            return (minFee, true);
        }
        return (feeAmount, false);
    }

    /// @inheritdoc IFeeEstimation
    function computeFees(
        address user,
        uint256 baseFee,
        uint256 minFee,
        uint256 adminDiscountBps
    ) external view override returns (uint256 finalFee, uint256 rebate, address referrer) {
        // Step 1: Apply admin discount first (if provided)
        uint256 feeAfterAdmin = baseFee;
        if (adminDiscountBps > 0) {
            if (adminDiscountBps > 10000) revert InvalidDiscount();
            feeAfterAdmin = baseFee * (10000 - adminDiscountBps) / 10000;
        }

        // Step 2: Apply tier + referral discounts
        uint256 discounted = _applyAllDiscounts(user, feeAfterAdmin);

        // Step 3: Apply minimum fee protection
        finalFee = discounted < minFee ? minFee : discounted;

        // Calculate rebate on discounted amount (before minFee, after all discounts)
        rebate = _calculateReferrerRebate(user, discounted);

        // Get referrer if rebate exists
        if (rebate > 0) {
            referrer = traderReferrals[user].referrer;
        }
    }

    // ============ Internal Implementation Functions ============

    function _estimateMatchingFeesWithCollateral(
        address takerUser,
        Order calldata takerOrder,
        Order[] calldata makerOrders,
        uint256 takerFillAmount,
        uint256[] calldata makerFillAmounts,
        address collateralToken
    ) internal view returns (FeeEstimate memory takerFee, FeeEstimate[] memory makerFees) {
        // Get fee rate settings for taker and maker
        (uint256 makerFeeRateBps,, bool enabled,) = _getFeeRateSettingsWithCollateral(takerOrder.tokenId);
        bool makerHasZeroFeeRate = enabled && makerFeeRateBps == 0;

        // Estimate taker fee using taker-specific logic with maker fee context
        takerFee = _estimateTakerFeeWithCollateral(takerUser, takerOrder, takerFillAmount, collateralToken, makerHasZeroFeeRate);

        // Estimate maker fees using maker-specific logic
        makerFees = new FeeEstimate[](makerOrders.length);
        for (uint256 i = 0; i < makerOrders.length; i++) {
            makerFees[i] = _estimateMakerFeeWithCollateral(makerOrders[i].maker, makerOrders[i], makerFillAmounts[i], collateralToken);
        }
    }

    function _estimateMakerFeeWithCollateral(
        address makerUser,
        Order calldata makerOrder,
        uint256 fillAmount,
        address collateralToken
    ) internal view returns (FeeEstimate memory estimate) {
        // In collateral-only fee model: check if maker receives collateral
        uint256 takingAmount = CalculatorHelper.calculateTakingAmount(fillAmount, makerOrder.makerAmount, makerOrder.takerAmount);
        // Derive asset IDs: BUY orders have (makerAssetId=0, takerAssetId=tokenId), SELL orders have (makerAssetId=tokenId, takerAssetId=0)
        uint256 takerAssetId = makerOrder.side == Side.BUY ? makerOrder.tokenId : 0;

        if (takerAssetId == 0) {
            // Maker receives collateral token - use maker fee rate
            // Create a modified order with maker fee rate
            Order memory modifiedOrder = makerOrder;
            (uint256 makerFeeRateBps,, bool enabled,) = _getFeeRateSettingsWithCollateral(makerOrder.tokenId);
            if (enabled) {
                modifiedOrder.feeRateBps = makerFeeRateBps;
            }

            // Special case: if maker fee rate is 0, return zero fee (fee transferred to taker)
            if (modifiedOrder.feeRateBps == 0) {
                estimate.baseFee = 0;
                estimate.totalFee = 0;
                estimate.platformRevenue = 0;
                estimate.userDiscountBps = 0;
                estimate.userDiscountAmount = 0;
                estimate.referralDiscountBps = 0;
                estimate.referralDiscountAmount = 0;
                estimate.referrerRebateAmount = 0;
                estimate.minFeeAmount = 0;
                estimate.minFeeApplied = false;
                estimate.feeRateBps = 0;
                estimate.price = CalculatorHelper._calculatePrice(fillAmount, takingAmount, makerOrder.side);
                return estimate;
            }

            return this.estimateFeeWithAmount(makerUser, modifiedOrder, fillAmount, collateralToken);
        } else {
            // Maker receives conditional tokens - no fee
            estimate.baseFee = 0;
            estimate.totalFee = 0;
            estimate.platformRevenue = 0;
            estimate.userDiscountBps = 0;
            estimate.userDiscountAmount = 0;
            estimate.referralDiscountBps = 0;
            estimate.referralDiscountAmount = 0;
            estimate.referrerRebateAmount = 0;
            estimate.minFeeAmount = 0;
            estimate.minFeeApplied = false;
            estimate.feeRateBps = 0;
            estimate.price = CalculatorHelper._calculatePrice(fillAmount, takingAmount, makerOrder.side);
        }
    }

    function _estimateTakerFeeWithCollateral(
        address takerUser,
        Order calldata takerOrder,
        uint256 fillAmount,
        address collateralToken,
        bool makerHasZeroFeeRate
    ) internal view returns (FeeEstimate memory estimate) {
        // Derive asset IDs: BUY orders have (makerAssetId=0, takerAssetId=tokenId), SELL orders have (makerAssetId=tokenId, takerAssetId=0)
        uint256 takerAssetId = takerOrder.side == Side.BUY ? takerOrder.tokenId : 0;

        if (takerAssetId == 0) {
            // Taker receives collateral token - use taker fee rate
            // Create a modified order with taker fee rate
            Order memory modifiedOrder = takerOrder;
            (,uint256 takerFeeRateBps, bool enabled,) = _getFeeRateSettingsWithCollateral(takerOrder.tokenId);
            if (enabled) {
                modifiedOrder.feeRateBps = takerFeeRateBps;
            }

            return this.estimateFeeWithAmount(takerUser, modifiedOrder, fillAmount, collateralToken);
        } else if (makerHasZeroFeeRate) {
            // Special case: Taker receives outcome token BUT maker fee rate is 0
            // In this case, taker still pays fee (charged from taker's collateral)
            // This matches Trading.sol line 181-200 logic
            Order memory modifiedOrder = takerOrder;
            (,uint256 takerFeeRateBps, bool enabled,) = _getFeeRateSettingsWithCollateral(takerOrder.tokenId);
            if (enabled) {
                modifiedOrder.feeRateBps = takerFeeRateBps;
            }

            return this.estimateFeeWithAmount(takerUser, modifiedOrder, fillAmount, collateralToken);
        } else {
            // Taker receives conditional tokens and maker has non-zero fee - no base fee
            // However, minFee still applies (matches Trading.sol behavior)
            // Use same pattern as above: call estimateFeeWithAmount with feeRateBps = 0
            Order memory modifiedOrder = takerOrder;
            modifiedOrder.feeRateBps = 0;  // No base fee, but minFee will still be applied

            return this.estimateFeeWithAmount(takerUser, modifiedOrder, fillAmount, collateralToken);
        }
    }

    function _getFeeRateSettingsWithCollateral(uint256 tokenId)
        internal view returns (
            uint256 makerFeeRateBps,
            uint256 takerFeeRateBps,
            bool enabled,
            uint256 minFeeAmount
        )
    {
        FeeRateSettings memory settings = feeRateSettings[tokenId];
        makerFeeRateBps = settings.makerFeeRateBps;
        takerFeeRateBps = settings.takerFeeRateBps;
        enabled = settings.enabled;
        // Return stored minFeeAmount regardless of enabled status for getFeeRateSettings interface
        minFeeAmount = settings.minFeeAmount;
    }

    // ============ Decimal Conversion Override ============

    /// @notice Convert token amount to USD value (scaled by 1e6)
    /// @dev Uses DecimalConversionLib to reduce contract size
    /// @param collateralToken The collateral token address
    /// @param amount Token amount
    /// @return usdValue USD value (scaled by 1e6)
    function _convertToUsd(address collateralToken, uint256 amount)
        internal
        view
        override
        returns (uint256 usdValue)
    {
        CollateralConfig memory config = collateralConfigs[collateralToken];
        return DecimalConversionLib.convertToUsd(collateralToken, amount, config.decimals);
    }

    /// @notice Convert USD value (scaled by 1e6) to token amount
    /// @dev Uses DecimalConversionLib to reduce contract size
    /// @param collateralToken The collateral token address
    /// @param usdValue USD value (scaled by 1e6)
    /// @return amount Token amount
    function _convertFromUsd(address collateralToken, uint256 usdValue)
        internal
        view
        override
        returns (uint256 amount)
    {
        CollateralConfig memory config = collateralConfigs[collateralToken];
        return DecimalConversionLib.convertFromUsd(collateralToken, usdValue, config.decimals);
    }

}

// SPDX-License-Identifier: MIT
pragma solidity <0.9.0;

import { Order } from "../libraries/OrderStructs.sol";

/// @title Fee Estimation Interface
/// @notice Interface for estimating trading fees before order execution
interface IFeeEstimation {
    
    /// @notice Detailed fee breakdown for transparency
    struct FeeEstimate {
        uint256 totalFee;              // Final fee amount to be paid by user
        uint256 baseFee;               // Fee before any discounts
        uint256 userDiscountBps;       // User tier discount applied (basis points)
        uint256 promoDiscountBps;      // Promotional discount applied (basis points)
        uint256 userDiscountAmount;    // Discount amount from user tier
        uint256 promoDiscountAmount;   // Discount amount from promotions
        uint256 referralDiscountBps;   // Additional referral discount (basis points)
        uint256 referralDiscountAmount;// Discount amount from referrals
        uint256 referrerRebateAmount;  // Rebate amount to be paid to referrer
        uint256 platformRevenue;       // Net revenue for the platform (totalFee - referrerRebateAmount)
        uint256 minFeeAmount;          // Minimum fee that applies
        bool minFeeApplied;            // Whether minimum fee was applied
        uint256 feeRateBps;            // Fee rate used in calculation
        uint256 price;                 // Calculated price of the trade
    }

    /// @notice Estimate fee for a single order if filled completely
    /// @param user The user placing the order
    /// @param order The order to estimate fee for
    /// @param isTaker Whether the user is acting as taker (true) or maker (false)
    /// @return estimate Detailed fee breakdown
    function estimateOrderFee(address user, Order calldata order, bool isTaker) 
        external view returns (FeeEstimate memory estimate);

    /// @notice Estimate fee for a single order with custom fill amount
    /// @param user The user placing the order
    /// @param order The order to estimate fee for  
    /// @param fillAmount The amount to fill (in maker amount terms)
    /// @param isTaker Whether the user is acting as taker (true) or maker (false)
    /// @return estimate Detailed fee breakdown
    function estimateOrderFeeWithFillAmount(address user, Order calldata order, uint256 fillAmount, bool isTaker)
        external view returns (FeeEstimate memory estimate);


    /// @notice Estimate fees for a taker order matching against specific maker orders
    /// @param takerUser The taker user address
    /// @param takerOrder The taker order
    /// @param makerOrders Array of maker orders to match against
    /// @param takerFillAmount Amount to fill on taker order (in maker amount terms)
    /// @param makerFillAmounts Array of amounts to fill on each maker order
    /// @return takerFee Fee estimate for the taker
    /// @return makerFees Array of fee estimates for each maker
    function estimateMatchingFees(
        address takerUser,
        Order calldata takerOrder,
        Order[] calldata makerOrders, 
        uint256 takerFillAmount,
        uint256[] calldata makerFillAmounts
    ) external view returns (FeeEstimate memory takerFee, FeeEstimate[] memory makerFees);

    /// @notice Estimate fee for a user acting as maker in a trade
    /// @param makerUser The maker user address
    /// @param makerOrder The maker order
    /// @param fillAmount Amount to fill (in maker amount terms)
    /// @return estimate Detailed fee breakdown for the maker
    function estimateMakerFee(
        address makerUser,
        Order calldata makerOrder,
        uint256 fillAmount
    ) external view returns (FeeEstimate memory estimate);

    /// @notice Estimate fee for a user acting as taker in a trade
    /// @param takerUser The taker user address  
    /// @param takerOrder The taker order
    /// @param fillAmount Amount to fill (in maker amount terms)
    /// @return estimate Detailed fee breakdown for the taker
    function estimateTakerFee(
        address takerUser,
        Order calldata takerOrder,
        uint256 fillAmount
    ) external view returns (FeeEstimate memory estimate);

    /// @notice Get the current fee rate settings for a token
    /// @param tokenId The token ID
    /// @return makerFeeRateBps Maker fee rate in basis points
    /// @return takerFeeRateBps Taker fee rate in basis points  
    /// @return enabled Whether custom fee rates are enabled for this token
    /// @return minFeeAmount Minimum fee amount for this token
    function getFeeRateSettings(uint256 tokenId) 
        external view returns (
            uint256 makerFeeRateBps, 
            uint256 takerFeeRateBps, 
            bool enabled, 
            uint256 minFeeAmount
        );

    /// @notice Check all applicable discounts for a user
    /// @param user The user address
    /// @return userTierDiscountBps Discount from user tier
    /// @return referralDiscountBps Discount from referral system
    function getUserDiscountInfo(address user)
        external view returns (
            uint256 userTierDiscountBps,
            uint256 referralDiscountBps
        );

    /// @notice Estimate the minimum fee for a token given an amount
    /// @param tokenId The token ID
    /// @param feeAmount The calculated fee amount
    /// @return finalFee The final fee after applying minimum fee rules
    /// @return minFeeApplied Whether minimum fee was applied
    function estimateMinimumFee(uint256 tokenId, uint256 feeAmount)
        external view returns (uint256 finalFee, bool minFeeApplied);

    /// @notice Calculate fees for trade execution
    /// @param user User address
    /// @param baseFee Base fee before discounts
    /// @param minFee Minimum fee requirement
    /// @param adminDiscountBps Additional admin discount in basis points (0 = no admin discount)
    /// @return finalFee User pays this amount
    /// @return rebate Referrer receives this amount
    /// @return referrer Referrer address
    function computeFees(
        address user,
        uint256 baseFee,
        uint256 minFee,
        uint256 adminDiscountBps
    ) external view returns (uint256 finalFee, uint256 rebate, address referrer);

    /// @notice Get the default collateral token address
    /// @return The default collateral token address
    function defaultCollateralToken() external view returns (address);

    /// @notice Get the global default minimum fee for the default collateral token
    /// @return The global default minimum fee amount
    function getGlobalDefaultMinFee() external view returns (uint256);

    /// @notice Get global default minimum fee for a specific collateral token
    /// @param collateralToken The collateral token address
    /// @return The global default minimum fee for this collateral
    function getCollateralGlobalMinFee(address collateralToken) external view returns (uint256);
}

File 3 of 21 : OrderStructs.sol
// SPDX-License-Identifier: MIT
pragma solidity <0.9.0;

bytes32 constant ORDER_TYPEHASH = keccak256(
    "Order(uint256 salt,address maker,address signer,address taker,uint256 tokenId,uint256 makerAmount,uint256 takerAmount,uint256 expiration,uint256 nonce,uint256 feeRateBps,uint8 side,uint8 signatureType)"
);

struct Order {
    /// @notice Unique salt to ensure entropy
    uint256 salt;
    /// @notice Maker of the order, i.e the source of funds for the order
    address maker;
    /// @notice Signer of the order
    address signer;
    /// @notice Address of the order taker. The zero address is used to indicate a public order
    address taker;
    /// @notice Token Id of the CTF ERC1155 asset to be bought or sold
    /// If BUY, this is the tokenId of the asset to be bought, i.e the makerAssetId
    /// If SELL, this is the tokenId of the asset to be sold, i.e the takerAssetId
    uint256 tokenId;
    /// @notice Maker amount, i.e the maximum amount of tokens to be sold
    uint256 makerAmount;
    /// @notice Taker amount, i.e the minimum amount of tokens to be received
    uint256 takerAmount;
    /// @notice Timestamp after which the order is expired
    uint256 expiration;
    /// @notice Nonce used for onchain cancellations
    uint256 nonce;
    /// @notice Fee rate, in basis points, charged to the order maker, charged on proceeds
    uint256 feeRateBps;
    /// @notice The side of the order: BUY or SELL
    Side side;
    /// @notice Signature type used by the Order: EOA, OLAB_PROXY or OLAB_GNOSIS_SAFE
    SignatureType signatureType;
    /// @notice The order signature
    bytes signature;
}

enum SignatureType
// 0: ECDSA EIP712 signatures signed by EOAs
{
    EOA,
    // 1: EIP712 signatures signed by EOAs that own OLAB Proxy wallets
    OLAB_PROXY,
    // 2: EIP712 signatures signed by EOAs that own OLAB Gnosis safes
    OLAB_GNOSIS_SAFE
}

enum Side
// 0: buy
{
    BUY,
    // 1: sell
    SELL
}

enum MatchType
// 0: buy vs sell
{
    COMPLEMENTARY,
    // 1: both buys
    MINT,
    // 2: both sells
    MERGE
}

struct OrderStatus {
    bool isFilledOrCancelled;
    uint256 remaining;
}

// SPDX-License-Identifier: MIT
pragma solidity <0.9.0;

import { Order, Side } from "../libraries/OrderStructs.sol";

library CalculatorHelper {
    uint256 internal constant ONE = 10 ** 18;

    uint256 internal constant BPS_DIVISOR = 10_000;

    /// @notice Higher precision divisor for intermediate calculations to reduce rounding errors
    /// @dev Uses 10^12 as a safe middle ground that works for both 6-decimal (USDC) and 18-decimal (DAI) tokens
    uint256 internal constant HIGH_PRECISION_DIVISOR = 10 ** 12;

    /// @notice Maximum safe value to prevent overflow in multiplication
    uint256 internal constant MAX_SAFE_VALUE = type(uint256).max / HIGH_PRECISION_DIVISOR;

    function calculateTakingAmount(uint256 makingAmount, uint256 makerAmount, uint256 takerAmount)
        internal
        pure
        returns (uint256)
    {
        if (makerAmount == 0) return 0;
        return makingAmount * takerAmount / makerAmount;
    }

    /// @notice Calculates the fee for an order (original function for backward compatibility)
    /// @dev Fees are calculated using symmetric formula for both BUY and SELL orders
    /// @param feeRateBps       - Fee rate, in basis points
    /// @param outcomeTokens    - The number of outcome tokens
    /// @param makerAmount      - The maker amount of the order
    /// @param takerAmount      - The taker amount of the order
    /// @param side             - The side of the order
    function calculateFee(
        uint256 feeRateBps,
        uint256 outcomeTokens,
        uint256 makerAmount,
        uint256 takerAmount,
        Side side
    ) internal pure returns (uint256 fee) {
        return calculateFeeWithDiscount(feeRateBps, outcomeTokens, makerAmount, takerAmount, side, 0);
    }

    /// @notice Calculates the fee for an order with user tier discount
    /// @dev Fees are calculated using symmetric formula for both BUY and SELL orders, then discount is applied
    /// @param feeRateBps       - Fee rate, in basis points
    /// @param outcomeTokens    - The number of outcome tokens
    /// @param makerAmount      - The maker amount of the order
    /// @param takerAmount      - The taker amount of the order
    /// @param side             - The side of the order
    /// @param discountBps      - Discount rate in basis points (0-10000)
    function calculateFeeWithDiscount(
        uint256 feeRateBps,
        uint256 outcomeTokens,
        uint256 makerAmount,
        uint256 takerAmount,
        Side side,
        uint256 discountBps
    ) internal pure returns (uint256 fee) {
        return calculateFeeWithMultipleDiscounts(feeRateBps, outcomeTokens, makerAmount, takerAmount, side, discountBps, 0);
    }

    /// @notice Calculates the fee for an order with multiple discounts (user tier + promotional)
    /// @dev Fees are calculated using symmetric formula, then multiple discounts are applied sequentially
    /// @param feeRateBps          - Fee rate, in basis points
    /// @param outcomeTokens       - The number of outcome tokens
    /// @param makerAmount         - The maker amount of the order
    /// @param takerAmount         - The taker amount of the order
    /// @param side                - The side of the order
    /// @param userDiscountBps     - User tier discount rate in basis points (0-10000)
    /// @param promoDiscountBps    - Promotional discount rate in basis points (0-10000)
    function calculateFeeWithMultipleDiscounts(
        uint256 feeRateBps,
        uint256 outcomeTokens,
        uint256 makerAmount,
        uint256 takerAmount,
        Side side,
        uint256 userDiscountBps,
        uint256 promoDiscountBps
    ) internal pure returns (uint256 fee) {
        // Calculate base fee using unified formula
        fee = _calculateBaseFee(feeRateBps, outcomeTokens, makerAmount, takerAmount, side);

        if (fee > 0) {
            // Apply discounts using higher precision to minimize rounding errors
            if (userDiscountBps > 0 && userDiscountBps <= BPS_DIVISOR) {
                require(fee <= MAX_SAFE_VALUE, "CalculatorHelper: fee too large for discount calculation");
                // Use higher precision intermediate calculation
                fee = (fee * HIGH_PRECISION_DIVISOR * (BPS_DIVISOR - userDiscountBps)) / (HIGH_PRECISION_DIVISOR * BPS_DIVISOR);
            }

            // Apply promotional discount on top of user discount
            if (promoDiscountBps > 0 && promoDiscountBps <= BPS_DIVISOR) {
                require(fee <= MAX_SAFE_VALUE, "CalculatorHelper: fee too large for promo discount calculation");
                // Use higher precision intermediate calculation
                fee = (fee * HIGH_PRECISION_DIVISOR * (BPS_DIVISOR - promoDiscountBps)) / (HIGH_PRECISION_DIVISOR * BPS_DIVISOR);
            }
        }
    }

    /// @notice Calculates the base fee without any discounts
    /// @dev This is the core fee calculation used by all other fee functions
    /// @param feeRateBps       - Fee rate, in basis points
    /// @param outcomeTokens    - The number of outcome tokens
    /// @param makerAmount      - The maker amount of the order
    /// @param takerAmount      - The taker amount of the order
    /// @param side             - The side of the order
    /// @return fee             - The base fee amount before discounts
    function _calculateBaseFee(
        uint256 feeRateBps,
        uint256 outcomeTokens,
        uint256 makerAmount,
        uint256 takerAmount,
        Side side
    ) internal pure returns (uint256 fee) {
        if (feeRateBps > 0) {
            uint256 price = _calculatePrice(makerAmount, takerAmount, side);
            if (price > 0 && price <= ONE) {
                // Check for potential overflow before calculation
                require(outcomeTokens <= MAX_SAFE_VALUE, "CalculatorHelper: outcomeTokens too large");
                require(feeRateBps <= BPS_DIVISOR, "CalculatorHelper: invalid fee rate");

                // UNIFIED SYMMETRIC FORMULA: Same for both BUY and SELL, and for trading & estimation
                // Fee = feeRate * price * (1-price) * outcomeTokens
                // This ensures:
                // 1. Fees are highest at price=0.5 (max = 0.25 * feeRate * outcomeTokens)
                // 2. Fees approach 0 as price approaches 0 or 1
                // 3. Consistent calculation across all fee functions
                //
                // Note: price * (ONE - price) produces units of ONE^2, so we divide by ONE^2
                fee = feeRateBps * price * (ONE - price) * outcomeTokens / (BPS_DIVISOR * ONE * ONE);
            }
        }
    }

    function calculatePrice(Order memory order) internal pure returns (uint256) {
        return _calculatePrice(order.makerAmount, order.takerAmount, order.side);
    }

    function _calculatePrice(uint256 makerAmount, uint256 takerAmount, Side side) internal pure returns (uint256) {
        if (side == Side.BUY) return takerAmount != 0 ? makerAmount * ONE / takerAmount : 0;
        return makerAmount != 0 ? takerAmount * ONE / makerAmount : 0;
    }

    function isCrossing(Order memory a, Order memory b) internal pure returns (bool) {
        if (a.takerAmount == 0 || b.takerAmount == 0) return true;

        return _isCrossing(calculatePrice(a), calculatePrice(b), a.side, b.side);
    }

    function _isCrossing(uint256 priceA, uint256 priceB, Side sideA, Side sideB) internal pure returns (bool) {
        if (sideA == Side.BUY) {
            if (sideB == Side.BUY) {
                // if a and b are bids
                return priceA + priceB >= ONE;
            }
            // if a is bid and b is ask
            return priceA >= priceB;
        }
        if (sideB == Side.BUY) {
            // if a is ask and b is bid
            return priceB >= priceA;
        }
        // if a and b are asks
        return priceA + priceB <= ONE;
    }
}

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

import {IERC20Metadata} from "openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";

/**
 * @title DecimalConversionLib
 * @notice Library for converting token amounts to/from standardized 6-decimal USD representation
 * @dev Used by CTFExchangeFeeManager to reduce contract size while maintaining decimal conversion functionality
 */
library DecimalConversionLib {
    /**
     * @notice Convert token amount to 6-decimal USD value
     * @param collateralToken The ERC20 token address
     * @param amount The amount in token's native decimals
     * @param cachedDecimals Cached decimal value (0 if unknown)
     * @return usdValue The equivalent value in 6-decimal USD representation
     */
    function convertToUsd(
        address collateralToken,
        uint256 amount,
        uint8 cachedDecimals
    ) internal view returns (uint256 usdValue) {
        uint8 decimals = cachedDecimals;

        // Fetch decimals if not cached
        if (decimals == 0) {
            try IERC20Metadata(collateralToken).decimals() returns (uint8 _decimals) {
                decimals = _decimals;
            } catch {
                decimals = 18; // Default to 18 decimals
            }
        }

        // Convert to 6-decimal representation
        if (decimals > 6) {
            usdValue = amount / (10 ** (decimals - 6));
        } else if (decimals < 6) {
            usdValue = amount * (10 ** (6 - decimals));
        } else {
            usdValue = amount;
        }
    }

    /**
     * @notice Convert 6-decimal USD value to token amount
     * @param collateralToken The ERC20 token address
     * @param usdValue The value in 6-decimal USD representation
     * @param cachedDecimals Cached decimal value (0 if unknown)
     * @return amount The equivalent amount in token's native decimals
     */
    function convertFromUsd(
        address collateralToken,
        uint256 usdValue,
        uint8 cachedDecimals
    ) internal view returns (uint256 amount) {
        uint8 decimals = cachedDecimals;

        // Fetch decimals if not cached
        if (decimals == 0) {
            try IERC20Metadata(collateralToken).decimals() returns (uint8 _decimals) {
                decimals = _decimals;
            } catch {
                decimals = 18; // Default to 18 decimals
            }
        }

        // Convert from 6-decimal representation
        if (decimals > 6) {
            amount = usdValue * (10 ** (decimals - 6));
        } else if (decimals < 6) {
            amount = usdValue / (10 ** (6 - decimals));
        } else {
            amount = usdValue;
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity <0.9.0;

import { Auth } from "./Auth.sol";
import { TierConfig, UserTierInfo } from "../libraries/UserTierStructs.sol";

/// @title UserTierManager
/// @notice Manages user tiers with configurable fee discounts and rebate boosts
/// @dev Clean implementation focused on core functionality
abstract contract UserTierManager is Auth {

    /// @notice Counter for generating unique tier IDs
    uint256 public nextTierId;

    /// @notice Mapping from tier ID to tier configuration
    mapping(uint256 => TierConfig) public tierConfigs;

    /// @notice Mapping from user address to their tier information
    mapping(address => UserTierInfo) public userTierInfo;

    // ============ Events ============

    /// @notice Emitted when a new tier is created
    event TierCreated(
        uint256 indexed tierId,
        string name,
        uint256 feeDiscountBps,
        uint256 validUntil,
        address indexed createdBy
    );

    /// @notice Emitted when a tier configuration is updated
    event TierConfigUpdated(uint256 indexed tierId, address indexed updatedBy);

    /// @notice Emitted when a tier is activated/deactivated
    event TierStatusChanged(uint256 indexed tierId, bool isActive, address indexed updatedBy);

    /// @notice Emitted when a user's tier is updated
    event UserTierUpdated(
        address indexed user,
        uint256 oldTierId,
        uint256 newTierId,
        address indexed updatedBy
    );

    /// @notice Emitted when user trade volume is updated
    event UserVolumeUpdated(address indexed user, uint256 newTotalVolume, uint256 addedVolume);

    // ============ Errors ============

    error InvalidDiscountRate();
    error InvalidUser();
    error InvalidTier();
    error TierNotActive();
    error TierExpired();
    error ArrayLengthMismatch();

    // ============ Constants ============

    uint256 private constant BPS_DIVISOR = 10_000;

    constructor() {
        // Initialize default tier (ID 0) - no discounts or boosts
        nextTierId = 1;
        tierConfigs[0] = TierConfig({
            isActive: true,
            name: "Default",
            feeDiscountBps: 0,
            validUntil: 0, // permanent
            metadata: bytes32(0)
        });
    }

    // ============ Tier Management Functions ============

    /// @notice Creates a new tier with specified configuration
    /// @param name Human-readable tier name
    /// @param feeDiscountBps Fee discount in basis points (0-10000)
    /// @param validUntil Tier validity expiration (0 = permanent)
    /// @param metadata Additional tier-specific metadata
    /// @return tierId The ID of the created tier
    function createTier(
        string calldata name,
        uint256 feeDiscountBps,
        uint256 validUntil,
        bytes32 metadata
    ) external onlyAdmin returns (uint256 tierId) {
        if (feeDiscountBps > BPS_DIVISOR) revert InvalidDiscountRate();

        tierId = nextTierId++;
        
        tierConfigs[tierId] = TierConfig({
            isActive: true,
            name: name,
            feeDiscountBps: feeDiscountBps,
            validUntil: validUntil,
            metadata: metadata
        });

        emit TierCreated(
            tierId,
            name,
            feeDiscountBps,
            validUntil,
            msg.sender
        );
    }

    /// @notice Updates an existing tier configuration
    /// @param tierId The tier ID to update
    /// @param config The new tier configuration
    function updateTierConfig(uint256 tierId, TierConfig calldata config) external onlyAdmin {
        if (!tierConfigs[tierId].isActive) revert InvalidTier();
        if (config.feeDiscountBps > BPS_DIVISOR) revert InvalidDiscountRate();

        tierConfigs[tierId] = config;
        emit TierConfigUpdated(tierId, msg.sender);
    }

    /// @notice Activates or deactivates a tier
    /// @param tierId The tier ID
    /// @param isActive New status
    function setTierStatus(uint256 tierId, bool isActive) external onlyAdmin {
        tierConfigs[tierId].isActive = isActive;
        emit TierStatusChanged(tierId, isActive, msg.sender);
    }

    // ============ User Tier Assignment ============

    /// @notice Sets the tier for a user
    /// @param user The user address
    /// @param tierId The new tier ID for the user
    function setUserTier(address user, uint256 tierId) external onlyOperatorAdmin {
        if (user == address(0)) revert InvalidUser();
        if (!tierConfigs[tierId].isActive) revert TierNotActive();

        uint256 oldTierId = userTierInfo[user].tierId;
        
        userTierInfo[user] = UserTierInfo({
            tierId: tierId,
            assignedAt: block.timestamp,
            totalVolume: userTierInfo[user].totalVolume, // preserve volume
            isActive: true
        });

        emit UserTierUpdated(user, oldTierId, tierId, msg.sender);
    }

    /// @notice Sets the tier for multiple users in batch
    /// @param users Array of user addresses
    /// @param tierIds Array of corresponding tier IDs
    function setUserTiersBatch(address[] calldata users, uint256[] calldata tierIds) external onlyOperatorAdmin {
        if (users.length != tierIds.length) revert ArrayLengthMismatch();

        for (uint256 i = 0; i < users.length; i++) {
            if (users[i] == address(0)) revert InvalidUser();
            if (!tierConfigs[tierIds[i]].isActive) revert TierNotActive();

            uint256 oldTierId = userTierInfo[users[i]].tierId;

            userTierInfo[users[i]] = UserTierInfo({
                tierId: tierIds[i],
                assignedAt: block.timestamp,
                totalVolume: userTierInfo[users[i]].totalVolume, // preserve volume
                isActive: true
            });

            emit UserTierUpdated(users[i], oldTierId, tierIds[i], msg.sender);
        }
    }

    /// @notice Removes a user from their current tier (completely deletes user data)
    /// @param user The user address to remove from tier
    /// @dev This completely deletes the user's tier data including volume history
    function removeUserFromTier(address user) external onlyOperatorAdmin {
        if (user == address(0)) revert InvalidUser();

        uint256 oldTierId = userTierInfo[user].tierId;

        // Completely delete user tier data
        delete userTierInfo[user];

        emit UserTierUpdated(user, oldTierId, 0, msg.sender);
    }

    /// @notice Removes multiple users from their tiers in batch
    /// @param users Array of user addresses to remove from tiers
    /// @dev This completely deletes all users' tier data including volume history
    function removeUsersFromTierBatch(address[] calldata users) external onlyOperatorAdmin {
        for (uint256 i = 0; i < users.length; i++) {
            if (users[i] == address(0)) revert InvalidUser();

            uint256 oldTierId = userTierInfo[users[i]].tierId;

            // Completely delete user tier data
            delete userTierInfo[users[i]];

            emit UserTierUpdated(users[i], oldTierId, 0, msg.sender);
        }
    }

    // ============ User Information Updates ============

    /// @notice Updates user's trading volume (called by trading contract)
    /// @param user The user address
    /// @param volumeToAdd Volume to add to user's total (in USDC)
    function updateUserVolume(address user, uint256 volumeToAdd) external onlyOperator {
        if (volumeToAdd == 0) return;

        UserTierInfo storage info = userTierInfo[user];
        info.totalVolume += volumeToAdd;

        emit UserVolumeUpdated(user, info.totalVolume, volumeToAdd);
    }

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

    /// @notice Gets the discount rate for a user based on their tier
    /// @param user The user address
    /// @return discountBps The discount rate in basis points
    function getUserDiscountBps(address user) external view returns (uint256 discountBps) {
        UserTierInfo storage info = userTierInfo[user];
        if (!info.isActive) return 0;

        TierConfig storage config = tierConfigs[info.tierId];
        if (!config.isActive) return 0;
        if (config.validUntil > 0 && block.timestamp > config.validUntil) return 0;

        return config.feeDiscountBps;
    }


    /// @notice Gets the tier information for a user
    /// @param user The user address
    /// @return info The user's tier information
    function getUserTierInfo(address user) external view returns (UserTierInfo memory info) {
        return userTierInfo[user];
    }

    /// @notice Gets the configuration for a tier
    /// @param tierId The tier ID
    /// @return config The tier configuration
    function getTierConfig(uint256 tierId) external view returns (TierConfig memory config) {
        return tierConfigs[tierId];
    }


    /// @notice Gets all available tiers
    /// @return tiers Array of tier configurations with their IDs
    function getAllTiers() external view returns (TierConfig[] memory tiers) {
        tiers = new TierConfig[](nextTierId);
        for (uint256 i = 0; i < nextTierId; i++) {
            tiers[i] = tierConfigs[i];
        }
    }

    // ============ Internal Functions ============

    /// @notice Applies user tier discount to a fee amount
    /// @param user The user address
    /// @param originalFee The original fee amount
    /// @return discountedFee The fee after applying user tier discount
    function applyUserDiscount(address user, uint256 originalFee) internal view returns (uint256 discountedFee) {
        uint256 discountBps = this.getUserDiscountBps(user);
        if (discountBps == 0) return originalFee;
        
        return originalFee * (BPS_DIVISOR - discountBps) / BPS_DIVISOR;
    }

    /// @notice Internal function for getting user tier discount (for compatibility)
    /// @param user The user address
    /// @return discountBps The discount rate in basis points
    function _getUserDiscountBps(address user) internal view virtual returns (uint256 discountBps) {
        return this.getUserDiscountBps(user);
    }
}

File 7 of 21 : ReferralManager.sol
// SPDX-License-Identifier: MIT
pragma solidity <0.9.0;

import { ECDSA } from "openzeppelin-contracts/utils/cryptography/ECDSA.sol";
import { SignerManager } from "./SignerManager.sol";
import { IReferralManager } from "../interfaces/IReferralManager.sol";
import {
    ReferrerConfig,
    TraderReferralInfo,
    AuthorizationSignature,
    TraderReferralBindingSignature,
    RebateStats,
    DiscountStats,
    CollateralPriceConfig
} from "../libraries/ReferralStructs.sol";

/// @title ReferralManager
/// @notice Manages multi-collateral referral system with detailed tracking
abstract contract ReferralManager is SignerManager, IReferralManager {
    using ECDSA for bytes32;

    // ============ Constants ============

    uint256 private constant BPS_DIVISOR = 10_000;

    bytes32 public constant REFERRER_CONFIG_TYPEHASH = keccak256(
        "ReferrerConfig(address referrer,uint256 discountRate,uint256 rebateRate,uint256 discountValidityDuration,uint256 rebateValidityDuration,uint256 expiresAt,uint256 monthlyRebateLimit,uint256 deadline)"
    );

    bytes32 public constant TRADER_REFERRAL_BINDING_TYPEHASH = keccak256(
        "TraderReferralBinding(address trader,address referrer,uint256 deadline)"
    );

    // ============ Storage ============

    /// @notice Mapping from referrer address to their configuration
    mapping(address => ReferrerConfig) public referrerConfigs;

    /// @notice Mapping from trader address to their referral information
    mapping(address => TraderReferralInfo) public traderReferrals;


    /// @notice USD-based rebate statistics per referrer (across all collaterals)
    mapping(address => RebateStats) public rebateStats;

    /// @notice USD-based discount statistics per trader (across all collaterals)
    mapping(address => DiscountStats) public discountStats;



    /// @notice Collateral token USD price configurations
    mapping(address => CollateralPriceConfig) public collateralPriceConfigs;

    /// @notice Set of registered referrers (to track one-time registration)
    mapping(address => bool) public registeredReferrers;

    /// @notice Used signatures to prevent replay attacks
    mapping(bytes32 => bool) public usedSignatures;


    /// @notice Global override for discount validity (operator-controlled)
    bool public globalDiscountOverride = false;

    /// @notice Global override for rebate validity (operator-controlled)
    bool public globalRebateOverride = false;

    // ============ Abstract Functions ============

    // ============ Constructor ============

    constructor() {
        // Authorization signer will be set by admin
    }

    // ============ External Functions ============

    /// @inheritdoc IReferralManager
    function setReferrerConfig(AuthorizationSignature calldata auth) external {
        // Auto-register if not already registered
        if (!registeredReferrers[auth.referrer]) {
            registeredReferrers[auth.referrer] = true;
            emit ReferrerRegistered(auth.referrer);
        }

        _setReferrerConfig(auth, true);
    }

    /// @notice Internal function to set referrer configuration
    /// @param auth Authorization signature
    /// @param skipRegistrationCheck Whether to skip registration requirement check
    function _setReferrerConfig(AuthorizationSignature calldata auth, bool skipRegistrationCheck) internal {
        // Verify referrer is registered (unless skipping check)
        if (!skipRegistrationCheck && !registeredReferrers[auth.referrer]) {
            revert ReferrerNotRegistered();
        }

        // Verify signature hasn't been used
        bytes32 signatureHash = keccak256(auth.signature);
        if (usedSignatures[signatureHash]) revert InvalidAuthorizationSignature();

        // Verify signature hasn't expired
        if (block.timestamp > auth.deadline) revert AuthSignatureExpired();

        // Verify rates are valid
        if (auth.discountRate > BPS_DIVISOR || auth.rebateRate > BPS_DIVISOR) {
            revert InvalidRates();
        }

        // Verify config expiration is in the future
        if (auth.expiresAt <= block.timestamp) revert ReferrerConfigExpired();

        // Verify authorization signature
        bytes32 structHash = keccak256(abi.encode(
            REFERRER_CONFIG_TYPEHASH,
            auth.referrer,
            auth.discountRate,
            auth.rebateRate,
            auth.discountValidityDuration,
            auth.rebateValidityDuration,
            auth.expiresAt,
            auth.monthlyRebateLimit,
            auth.deadline
        ));

        bytes32 hash = _hashReferralTypedDataV4(structHash);
        address signer = hash.recover(auth.signature);

        if (!_isAuthorizedSigner(signer)) revert InvalidAuthorizationSignature();

        // Mark signature as used
        usedSignatures[signatureHash] = true;

        // Set referrer configuration
        referrerConfigs[auth.referrer] = ReferrerConfig({
            isActive: true,
            discountRate: auth.discountRate,
            rebateRate: auth.rebateRate,
            discountValidityDuration: auth.discountValidityDuration,
            rebateValidityDuration: auth.rebateValidityDuration,
            expiresAt: auth.expiresAt,
            monthlyRebateLimit: auth.monthlyRebateLimit,
            totalVolume: referrerConfigs[auth.referrer].totalVolume, // Preserve existing volume
            totalReferrals: referrerConfigs[auth.referrer].totalReferrals, // Preserve existing referrals
            currentMonthUsdRebates: referrerConfigs[auth.referrer].currentMonthUsdRebates, // Preserve existing monthly rebates
            currentMonthStart: referrerConfigs[auth.referrer].currentMonthStart // Preserve existing month start
        });

        emit ReferrerConfigSet(
            auth.referrer,
            auth.discountRate,
            auth.rebateRate,
            auth.discountValidityDuration,
            auth.rebateValidityDuration,
            auth.expiresAt
        );
    }

    /// @inheritdoc IReferralManager
    function setTraderReferrer(address trader, address referrer) external onlyOperatorAdmin {
        _setTraderReferrer(trader, referrer);
    }

    /// @inheritdoc IReferralManager
    function setGlobalDiscountOverride(bool enabled) external onlyOperator {
        globalDiscountOverride = enabled;
        emit GlobalDiscountOverrideUpdated(enabled);
    }

    /// @inheritdoc IReferralManager
    function setGlobalRebateOverride(bool enabled) external onlyOperator {
        globalRebateOverride = enabled;
        emit GlobalRebateOverrideUpdated(enabled);
    }

    /// @inheritdoc IReferralManager
    function recordRebatePayment(
        address trader,
        address referrer,
        uint256 rebateAmount,
        uint256 totalFeeAmount,
        address collateralToken
    ) external onlyOperator {
        // Record the rebate transaction (will emit RebateEarned)
        _recordRebateTransaction(referrer, trader, collateralToken, rebateAmount);

        // Update referrer's total volume with the total fee amount
        ReferrerConfig storage config = referrerConfigs[referrer];
        config.totalVolume += totalFeeAmount;
    }

    /// @inheritdoc IReferralManager
    function recordDiscountUsage(
        address trader,
        address collateralToken,
        uint256 discountAmount
    ) external onlyOperator {
        _recordDiscountTransaction(trader, collateralToken, discountAmount);
    }

    /// @notice Set USD price configuration for a collateral token (operator only)
    /// @param collateralToken The collateral token address
    /// @param usdPrice USD price per unit (in wei, e.g., 1e18 = $1.00)
    /// @param decimals Token decimals for price calculation
    /// @param isActive Whether this price config is active
    function setCollateralPriceConfig(
        address collateralToken,
        uint256 usdPrice,
        uint256 decimals,
        bool isActive
    ) external onlyOperator {
        require(collateralToken != address(0), "Invalid collateral token");

        collateralPriceConfigs[collateralToken] = CollateralPriceConfig({
            collateralToken: collateralToken,
            usdPrice: usdPrice,
            decimals: decimals,
            isActive: isActive
        });

        emit CollateralPriceConfigUpdated(collateralToken, usdPrice, decimals, isActive);
    }

    /// @notice Get USD price configuration for a collateral token
    /// @param collateralToken The collateral token address
    /// @return config The price configuration
    function getCollateralPriceConfig(address collateralToken) external view returns (CollateralPriceConfig memory config) {
        return collateralPriceConfigs[collateralToken];
    }

    /// @notice Convert token amount to USD value
    /// @param collateralToken The collateral token address
    /// @param amount Token amount
    /// @return usdValue USD value (in wei)
    function _convertToUsd(address collateralToken, uint256 amount) internal view virtual returns (uint256 usdValue) {
        CollateralPriceConfig storage priceConfig = collateralPriceConfigs[collateralToken];

        // If no price config or inactive, default to 1:1 (like USDC)
        if (!priceConfig.isActive || priceConfig.usdPrice == 0) {
            return amount; // Default 1:1 conversion
        }

        // Convert: (amount * usdPrice) / (10^decimals)
        // This handles different token decimals correctly
        usdValue = (amount * priceConfig.usdPrice) / (10 ** priceConfig.decimals);
    }

    /// @notice Convert USD value to token amount (inverse of _convertToUsd)
    /// @param collateralToken The collateral token address
    /// @param usdValue USD value (in wei)
    /// @return amount Token amount
    function _convertFromUsd(address collateralToken, uint256 usdValue) internal view virtual returns (uint256 amount) {
        CollateralPriceConfig storage priceConfig = collateralPriceConfigs[collateralToken];

        // If no price config or inactive, default to 1:1 (like USDC)
        if (!priceConfig.isActive || priceConfig.usdPrice == 0) {
            return usdValue; // Default 1:1 conversion
        }

        // Convert: (usdValue * 10^decimals) / usdPrice
        // This is the inverse of _convertToUsd
        amount = (usdValue * (10 ** priceConfig.decimals)) / priceConfig.usdPrice;
    }


    /// @inheritdoc IReferralManager
    function bindToReferrer(TraderReferralBindingSignature calldata auth) external {
        // Verify caller is the trader to be bound
        if (msg.sender != auth.trader) revert UnauthorizedTraderBinding();

        // Verify signature hasn't been used
        bytes32 signatureHash = keccak256(auth.signature);
        if (usedSignatures[signatureHash]) revert InvalidAuthorizationSignature();

        // Verify signature hasn't expired
        if (block.timestamp > auth.deadline) revert AuthSignatureExpired();

        // Verify authorization signature
        bytes32 structHash = keccak256(abi.encode(
            TRADER_REFERRAL_BINDING_TYPEHASH,
            auth.trader,
            auth.referrer,
            auth.deadline
        ));

        bytes32 hash = _hashReferralTypedDataV4(structHash);
        address signer = hash.recover(auth.signature);

        if (!_isAuthorizedSigner(signer)) revert InvalidAuthorizationSignature();

        // Mark signature as used
        usedSignatures[signatureHash] = true;

        // Set trader referrer
        _setTraderReferrer(auth.trader, auth.referrer);
    }

    /// @notice Internal function to set trader referrer
    /// @param trader The trader address
    /// @param referrer The referrer address
    function _setTraderReferrer(address trader, address referrer) internal {
        if (trader == address(0) || referrer == address(0)) revert InvalidRates();
        if (trader == referrer) revert CannotReferSelf();
        if (!registeredReferrers[referrer]) revert ReferrerNotRegistered();
        if (traderReferrals[trader].referrer != address(0)) revert AlreadyHasReferrer();

        ReferrerConfig storage config = referrerConfigs[referrer];
        if (!config.isActive || config.expiresAt <= block.timestamp) {
            revert ReferrerConfigExpired();
        }

        // Set trader referral info
        traderReferrals[trader] = TraderReferralInfo({
            referrer: referrer,
            activatedAt: block.timestamp
        });

        // Update referrer statistics
        config.totalReferrals++;

        emit TraderReferrerSet(trader, referrer, block.timestamp);
    }

    /// @inheritdoc IReferralManager
    function processTradeReferral(
        address trader,
        uint256 feeAmount,
        address collateralToken
    ) external onlyOperator returns (uint256 traderDiscount, uint256 referrerRebate) {
        (traderDiscount, referrerRebate) = _processTradeReferralWithCollateral(trader, feeAmount, collateralToken);

        return (traderDiscount, referrerRebate);
    }

    // ============ Internal Functions ============

    /// @notice Internal function to process trade referral with collateral tracking
    function _processTradeReferralWithCollateral(
        address trader,
        uint256 finalFeeAmount,
        address collateralToken
    ) internal returns (uint256 traderDiscount, uint256 referrerRebate) {
        // Reuse calculateReferralRewards for consistent calculation logic
        (traderDiscount, referrerRebate) = this.calculateReferralRewards(trader, finalFeeAmount, collateralToken);

        // If no rewards calculated, return early
        if (traderDiscount == 0 && referrerRebate == 0) {
            return (0, 0);
        }

        TraderReferralInfo storage traderInfo = traderReferrals[trader];
        ReferrerConfig storage config = referrerConfigs[traderInfo.referrer];

        // Record discount transaction if any
        if (traderDiscount > 0) {
            _recordDiscountTransaction(trader, collateralToken, traderDiscount);
        }

        // Record rebate transaction if any
        if (referrerRebate > 0) {
            _recordRebateTransaction(traderInfo.referrer, trader, collateralToken, referrerRebate);
        }

        // Update overall statistics
        config.totalVolume += finalFeeAmount;

        return (traderDiscount, referrerRebate);
    }

    /// @notice Record a discount transaction
    function _recordDiscountTransaction(
        address trader,
        address collateralToken,
        uint256 discountAmount
    ) internal {
        // Update trader's USD-based discount stats (across all collaterals)
        uint256 discountUsdValue = _convertToUsd(collateralToken, discountAmount);
        discountStats[trader].totalDiscountReceivedUsd += discountUsdValue;
    }

    /// @notice Record a rebate transaction
    function _recordRebateTransaction(
        address referrer,
        address trader,
        address collateralToken,
        uint256 rebateAmount
    ) internal {
        // Update referrer's USD-based rebate stats (across all collaterals)
        uint256 rebateUsdValue = _convertToUsd(collateralToken, rebateAmount);
        rebateStats[referrer].totalRebatesReceivedUsd += rebateUsdValue;

        // Update global monthly USD rebates for the referrer (for limit checking)
        _updateGlobalMonthlyUsdRebates(referrer, rebateUsdValue);

        // Emit event with current rebate amount only (original token amount)
        emit RebateEarned(referrer, trader, collateralToken, rebateAmount);
    }


    /// @notice Update global monthly USD rebate statistics for a referrer (across all collaterals)
    function _updateGlobalMonthlyUsdRebates(address referrer, uint256 usdRebateAmount) internal {
        ReferrerConfig storage config = referrerConfigs[referrer];
        uint256 currentMonth = block.timestamp / 30 days;
        uint256 lastMonth = config.currentMonthStart / 30 days;

        // Reset monthly stats if we've moved to a new month
        if (currentMonth > lastMonth) {
            config.currentMonthUsdRebates = 0;
            config.currentMonthStart = block.timestamp;
        }

        // Update total USD rebates for current month
        config.currentMonthUsdRebates += usdRebateAmount;
    }

    /// @notice Hash typed data according to EIP-712
    /// @param structHash The struct hash to sign
    /// @return The hash ready for signature
    function _hashReferralTypedDataV4(bytes32 structHash) internal view returns (bytes32) {
        bytes32 domainHash = keccak256(abi.encode(
            keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
            keccak256("ReferralManager"),
            keccak256("1"),
            block.chainid,
            address(this)
        ));
        return keccak256(abi.encodePacked("\x19\x01", domainHash, structHash));
    }

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

    /// @inheritdoc IReferralManager
    function getReferrerConfig(address referrer) external view returns (ReferrerConfig memory config) {
        return referrerConfigs[referrer];
    }

    /// @inheritdoc IReferralManager
    function getTraderReferralInfo(address trader) external view returns (TraderReferralInfo memory info) {
        return traderReferrals[trader];
    }


    /// @inheritdoc IReferralManager
    function getRebateStats(address referrer)
        external view returns (RebateStats memory stats) {
        return rebateStats[referrer];
    }


    /// @inheritdoc IReferralManager
    function isRegisteredReferrer(address referrer) external view returns (bool isRegistered) {
        return registeredReferrers[referrer];
    }

    /// @inheritdoc IReferralManager
    function calculateReferralRewards(address trader, uint256 feeAmount, address collateralToken)
        external view returns (uint256 traderDiscount, uint256 referrerRebate) {
        TraderReferralInfo storage traderInfo = traderReferrals[trader];

        // Return 0 if trader has no referral setup
        if (traderInfo.referrer == address(0)) {
            return (0, 0);
        }

        ReferrerConfig storage config = referrerConfigs[traderInfo.referrer];

        // Check validity conditions for discount
        if (!config.isActive ||
            config.expiresAt <= block.timestamp ||
            !isDiscountValid(trader)) {
            traderDiscount = 0;
        } else {
            traderDiscount = (feeAmount * config.discountRate) / BPS_DIVISOR;
        }

        // Calculate rebate based on final fee (after discount)
        uint256 finalFeeAmount = feeAmount - traderDiscount;

        // Check validity conditions for rebate
        if (!config.isActive ||
            config.expiresAt <= block.timestamp ||
            !isRebateValid(traderInfo.referrer, trader)) {
            referrerRebate = 0;
        } else {
            referrerRebate = (finalFeeAmount * config.rebateRate) / BPS_DIVISOR;

            // Check monthly rebate limit for estimation (convert to USD for comparison)
            if (referrerRebate > 0 && config.monthlyRebateLimit > 0) {
                uint256 remainingMonthlyAllowanceUsd = this.getRemainingMonthlyRebateAllowance(traderInfo.referrer);

                // Convert rebate amount to USD for comparison with USD limit
                uint256 rebateUsdValue = _convertToUsd(collateralToken, referrerRebate);

                // If rebate in USD exceeds remaining allowance, cap it
                if (rebateUsdValue > remainingMonthlyAllowanceUsd) {
                    // Convert remaining USD allowance back to token amount
                    // This is approximate reverse conversion: remainingUsd * 10^decimals / usdPrice
                    CollateralPriceConfig storage priceConfig = collateralPriceConfigs[collateralToken];
                    if (priceConfig.isActive && priceConfig.usdPrice > 0) {
                        referrerRebate = (remainingMonthlyAllowanceUsd * (10 ** priceConfig.decimals)) / priceConfig.usdPrice;
                    } else {
                        // Default 1:1 conversion for tokens like USDC
                        referrerRebate = remainingMonthlyAllowanceUsd;
                    }
                }
            }
        }

        return (traderDiscount, referrerRebate);
    }

    /// @inheritdoc IReferralManager
    function isReferrerConfigValid(address referrer) external view returns (bool isValid) {
        ReferrerConfig storage config = referrerConfigs[referrer];
        return config.isActive && config.expiresAt > block.timestamp;
    }

    /// @inheritdoc IReferralManager
    function isDiscountValid(address trader) public view returns (bool isValid) {
        TraderReferralInfo storage traderInfo = traderReferrals[trader];
        if (traderInfo.referrer == address(0)) return false;

        ReferrerConfig storage config = referrerConfigs[traderInfo.referrer];

        // Global override: if enabled, all bound traders enjoy permanent discount
        if (globalDiscountOverride) {
            return true;
        }

        // Check for permanent validity (avoid overflow)
        if (config.discountValidityDuration == 0 || config.discountValidityDuration == type(uint256).max) {
            return true;
        }

        // Time-based validity check
        return block.timestamp <= traderInfo.activatedAt + config.discountValidityDuration;
    }

    /// @inheritdoc IReferralManager
    function isRebateValid(address referrer, address trader) public view returns (bool isValid) {
        TraderReferralInfo storage traderInfo = traderReferrals[trader];
        if (traderInfo.referrer != referrer) return false;

        ReferrerConfig storage config = referrerConfigs[referrer];

        // Global override: if enabled, all bound traders enjoy permanent rebates
        if (globalRebateOverride) {
            return true;
        }

        // Check for permanent validity (avoid overflow)
        if (config.rebateValidityDuration == 0 || config.rebateValidityDuration == type(uint256).max) {
            return true;
        }

        // Time-based validity check
        return block.timestamp <= traderInfo.activatedAt + config.rebateValidityDuration;
    }



    /// @inheritdoc IReferralManager
    function getRemainingMonthlyRebateAllowance(address referrer)
        external view returns (uint256 remainingAllowance) {
        ReferrerConfig storage config = referrerConfigs[referrer];
        if (config.monthlyRebateLimit == 0) {
            return type(uint256).max; // No limit
        }

        // Use global monthly USD rebates (across all collaterals)
        uint256 currentMonth = block.timestamp / 30 days;
        uint256 lastMonth = config.currentMonthStart / 30 days;

        uint256 currentMonthUsdRebates;
        if (currentMonth > lastMonth) {
            // New month, no rebates yet
            currentMonthUsdRebates = 0;
        } else {
            // Same month, use global USD rebates
            currentMonthUsdRebates = config.currentMonthUsdRebates;
        }

        // Check against USD limit
        if (currentMonthUsdRebates >= config.monthlyRebateLimit) {
            return 0;
        }

        // Return remaining allowance in USD
        return config.monthlyRebateLimit - currentMonthUsdRebates;
    }

    // ============ UI Support Functions ============

    /// @inheritdoc IReferralManager
    function getReferrerDashboard(address referrer)
        external view returns (
            ReferrerConfig memory config,
            bool isActive
        ) {
        config = referrerConfigs[referrer];
        isActive = registeredReferrers[referrer] && config.isActive && config.expiresAt > block.timestamp;
    }

    /// @inheritdoc IReferralManager
    function getTraderReferralStatus(address trader)
        external view returns (
            TraderReferralInfo memory info,
            bool isDiscountActive,
            uint256 remainingDiscountTime
        ) {
        info = traderReferrals[trader];
        isDiscountActive = isDiscountValid(trader);

        if (info.referrer != address(0) && isDiscountActive) {
            ReferrerConfig storage config = referrerConfigs[info.referrer];
            if (config.discountValidityDuration == 0) {
                remainingDiscountTime = 0; // Unlimited
            } else {
                uint256 expiryTime = info.activatedAt + config.discountValidityDuration;
                remainingDiscountTime = expiryTime > block.timestamp ? expiryTime - block.timestamp : 0;
            }
        } else {
            remainingDiscountTime = 0;
        }
    }

    /// @inheritdoc IReferralManager
    function batchGetTradersReferralInfo(address[] calldata traders)
        external view returns (address[] memory referrers, bool[] memory discountsActive) {
        uint256 length = traders.length;
        referrers = new address[](length);
        discountsActive = new bool[](length);

        for (uint256 i = 0; i < length; i++) {
            TraderReferralInfo storage info = traderReferrals[traders[i]];
            referrers[i] = info.referrer;
            discountsActive[i] = isDiscountValid(traders[i]);
        }
    }

    /// @inheritdoc IReferralManager
    function getDiscountStats(address trader)
        external view returns (DiscountStats memory stats) {
        return discountStats[trader];
    }



    /// @inheritdoc IReferralManager
    function getTraderDiscountInfo(address trader)
        external view returns (
            TraderReferralInfo memory referralInfo,
            DiscountStats memory stats
        ) {
        referralInfo = traderReferrals[trader];
        stats = discountStats[trader];
    }

    /// @notice Get referrer's monthly rebate limit and current usage
    /// @param referrer The referrer address
    /// @return monthlyRebateLimit Monthly limit in USD (scaled by 1e6)
    /// @return currentMonthStart Timestamp of current month start
    /// @return currentMonthUsdRebates Total USD rebates paid this month
    function getReferrerMonthlyLimit(address referrer)
        external view returns (
            uint256 monthlyRebateLimit,
            uint256 currentMonthStart,
            uint256 currentMonthUsdRebates
        )
    {
        ReferrerConfig storage config = referrerConfigs[referrer];
        return (config.monthlyRebateLimit, config.currentMonthStart, config.currentMonthUsdRebates);
    }

    /// @notice Convert token amount to USD value
    /// @param collateralToken The collateral token address
    /// @param amount Token amount
    /// @return usdValue USD value (scaled by 1e6)
    function convertToUsd(address collateralToken, uint256 amount)
        external view returns (uint256 usdValue)
    {
        return _convertToUsd(collateralToken, amount);
    }

    /// @notice Convert USD value to token amount
    /// @param collateralToken The collateral token address
    /// @param usdValue USD value (scaled by 1e6)
    /// @return amount Token amount
    function convertFromUsd(address collateralToken, uint256 usdValue)
        external view returns (uint256 amount)
    {
        return _convertFromUsd(collateralToken, usdValue);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity <0.9.0;

import { IFees } from "../interfaces/IFees.sol";

abstract contract Fees is IFees {
    /// @notice Maximum fee rate that can be signed into an Order
    uint256 internal constant MAX_FEE_RATE_BIPS = 4000; // 4000 bips or 40%

    address public override treasury;

    /// @notice Returns the maximum fee rate for an order
    function getMaxFeeRate() public pure override returns (uint256) {
        return MAX_FEE_RATE_BIPS;
    }

    function _setTreasury(address _treasury) internal {
        treasury = _treasury;
    }
}

File 9 of 21 : ReferralStructs.sol
// SPDX-License-Identifier: MIT
pragma solidity <0.9.0;

/// @title Referral system data structures with multi-collateral support
/// @notice Contains all data structures used by the multi-collateral referral system

/// @notice Configuration for a referrer (keyed by wallet address)
struct ReferrerConfig {
    bool isActive;                      // Whether this referrer is active
    uint256 discountRate;               // Discount rate for traders in basis points (0-10000)
    uint256 rebateRate;                 // Rebate rate for referrer in basis points (0-10000)
    uint256 discountValidityDuration;   // How long trader can use discounts (seconds)
    uint256 rebateValidityDuration;     // How long referrer can earn rebates (seconds)
    uint256 expiresAt;                  // When this config expires (timestamp)
    uint256 monthlyRebateLimit;         // Monthly rebate limit in USD (in wei, e.g., 1000e18 = $1000, 0 = no limit)
    uint256 totalVolume;                // Total volume generated through this referrer
    uint256 totalReferrals;             // Total number of people referred
    uint256 currentMonthUsdRebates;     // Current month total USD rebates (across all collaterals)
    uint256 currentMonthStart;          // Current month start timestamp for global USD tracking
}

/// @notice Information about a trader's referral setup
struct TraderReferralInfo {
    address referrer;                   // The referrer's wallet address
    uint256 activatedAt;                // When the trader activated this referrer
}

/// @notice USD price configuration for a collateral token
struct CollateralPriceConfig {
    address collateralToken;            // The collateral token address
    uint256 usdPrice;                   // USD price per unit (in wei, e.g., 1e18 = $1.00)
    uint256 decimals;                   // Token decimals for price calculation
    bool isActive;                      // Whether this price config is active
}

/// @notice Authorization signature for setting referrer config
struct AuthorizationSignature {
    address referrer;                   // Referrer address
    uint256 discountRate;               // Discount rate in basis points
    uint256 rebateRate;                 // Rebate rate in basis points
    uint256 discountValidityDuration;   // Discount validity duration in seconds
    uint256 rebateValidityDuration;     // Rebate validity duration in seconds
    uint256 expiresAt;                  // Config expiration timestamp
    uint256 monthlyRebateLimit;         // Monthly rebate limit in wei (0 = no limit)
    uint256 deadline;                   // Signature deadline timestamp
    bytes signature;                    // Server authorization signature
}

/// @notice Authorization signature for binding trader to referrer
struct TraderReferralBindingSignature {
    address trader;                     // Trader address to be bound
    address referrer;                   // Referrer address to bind to
    uint256 deadline;                   // Signature deadline timestamp
    bytes signature;                    // Server authorization signature
}


/// @notice Discount statistics for a trader (USD-based, across all collaterals)
struct DiscountStats {
    uint256 totalDiscountReceivedUsd;   // Total discount value in USD across all collaterals
}


/// @notice Rebate statistics for a referrer (USD-based, across all collaterals)
struct RebateStats {
    uint256 totalRebatesReceivedUsd;    // Total rebate value in USD across all collaterals
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
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
pragma solidity <0.9.0;

import { IAuth } from "../interfaces/IAuth.sol";

/// @title Auth
/// @notice Provides admin and operator roles and access control modifiers
abstract contract Auth is IAuth {
    /// @dev The set of addresses authorized as Admins
    mapping(address => uint256) public admins;

    /// @dev The set of addresses authorized as OperatorAdmins, who can grant operator role to specific address.
    mapping(address => uint256) public operatorAdmins;

    /// @dev The set of addresses authorized as Operators
    mapping(address => uint256) public operators;

    modifier onlyAdmin() {
        if (admins[msg.sender] != 1) revert NotAdmin();
        _;
    }

    modifier onlyOperatorAdmin() {
        if (operatorAdmins[msg.sender] != 1) revert NotOperatorAdmin();
        _;
    }

    modifier onlyOperator() {
        if (operators[msg.sender] != 1) revert NotOperator();
        _;
    }

    constructor() {
        admins[msg.sender] = 1;
        operatorAdmins[msg.sender] = 1;
        operators[msg.sender] = 1;
    }

    function isAdmin(address usr) external view returns (bool) {
        return admins[usr] == 1;
    }

    function isOperatorAdmin(address usr) external view returns (bool) {
        return operatorAdmins[usr] == 1;
    }

    function isOperator(address usr) external view returns (bool) {
        return operators[usr] == 1;
    }

    /// @notice Adds a new admin
    /// Can only be called by a current admin
    /// @param admin_ - The new admin
    function addAdmin(address admin_) external onlyAdmin {
        admins[admin_] = 1;
        emit NewAdmin(admin_, msg.sender);
    }

    /// @notice Adds a new operator admin
    /// Can only be called by a current admin
    /// @param operatorAdmin_ - The new operator admin
    function addOperatorAdmin(address operatorAdmin_) external onlyAdmin {
        operatorAdmins[operatorAdmin_] = 1;
        emit NewOperatorAdmin(operatorAdmin_, msg.sender);
    }

    /// @notice Adds a new operator
    /// Can only be called by a current operator admin
    /// @param operator_ - The new operator
    function addOperator(address operator_) external onlyOperatorAdmin {
        operators[operator_] = 1;
        emit NewOperator(operator_, msg.sender);
    }

    /// @notice Removes an existing Admin
    /// Can only be called by a current admin
    /// @param admin - The admin to be removed
    function removeAdmin(address admin) external onlyAdmin {
        if (admins[admin] != 1) {
            revert NotAdmin();
        }
        admins[admin] = 0;
        emit RemovedAdmin(admin, msg.sender);
    }

    /// @notice Removes an existing operator admin
    /// Can only be called by a current operator admin
    /// @param operatorAdmin - The operator admin to be removed
    function removeOperatorAdmin(address operatorAdmin) external onlyAdmin {
        if (operatorAdmins[operatorAdmin] != 1) {
            revert NotOperatorAdmin();
        }
        operatorAdmins[operatorAdmin] = 0;
        emit RemovedOperatorAdmin(operatorAdmin, msg.sender);
    }

    /// @notice Removes an existing operator
    /// Can only be called by a current admin
    /// @param operator - The operator to be removed
    function removeOperator(address operator) external onlyOperatorAdmin {
        if(operators[operator] != 1) {
            revert NotOperator();
        }
        operators[operator] = 0;
        emit RemovedOperator(operator, msg.sender);
    }

    /// @notice Removes the admin role for the caller
    /// Can only be called by an existing admin
    function renounceAdminRole() external onlyAdmin {
        admins[msg.sender] = 0;
        emit RemovedAdmin(msg.sender, msg.sender);
    }

    /// @notice Removes the operator admin role for the caller
    /// Can only be called by an exiting operator admin
    function renounceOperatorAdminRole() external onlyOperatorAdmin {
        operatorAdmins[msg.sender] = 0;
        emit RemovedOperatorAdmin(msg.sender, msg.sender);
    }

    /// @notice Removes the operator role for the caller
    /// Can only be called by an exiting operator
    function renounceOperatorRole() external onlyOperator {
        operators[msg.sender] = 0;
        emit RemovedOperator(msg.sender, msg.sender);
    }
}

File 12 of 21 : UserTierStructs.sol
// SPDX-License-Identifier: MIT
pragma solidity <0.9.0;

/// @title User Tier System Data Structures
/// @notice Contains structures for flexible user tier management system

/// @notice Configuration for a user tier
/// @dev Streamlined tier configuration focused on fee discounts
struct TierConfig {
    bool isActive;                  // Whether this tier is currently active
    string name;                    // Human-readable tier name (e.g., "Bronze", "Gold", "VIP")
    uint256 feeDiscountBps;         // Fee discount in basis points (0-10000)
    uint256 validUntil;             // Tier validity expiration timestamp (0 = permanent)
    bytes32 metadata;               // Additional tier-specific metadata
}

/// @notice User's tier information
/// @dev Tracks current tier and trading statistics
struct UserTierInfo {
    uint256 tierId;                 // Current tier ID
    uint256 assignedAt;             // When tier was assigned
    uint256 totalVolume;            // Total trading volume by user (in USDC)
    bool isActive;                  // Whether user tier is active
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/cryptography/ECDSA.sol)

pragma solidity ^0.8.0;

import "../Strings.sol";

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSA {
    enum RecoverError {
        NoError,
        InvalidSignature,
        InvalidSignatureLength,
        InvalidSignatureS,
        InvalidSignatureV // Deprecated in v4.8
    }

    function _throwError(RecoverError error) private pure {
        if (error == RecoverError.NoError) {
            return; // no error: do nothing
        } else if (error == RecoverError.InvalidSignature) {
            revert("ECDSA: invalid signature");
        } else if (error == RecoverError.InvalidSignatureLength) {
            revert("ECDSA: invalid signature length");
        } else if (error == RecoverError.InvalidSignatureS) {
            revert("ECDSA: invalid signature 's' value");
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature` or error string. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     *
     * Documentation for signature generation:
     * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
     * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
     *
     * _Available since v4.3._
     */
    function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
        if (signature.length == 65) {
            bytes32 r;
            bytes32 s;
            uint8 v;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            /// @solidity memory-safe-assembly
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
            return tryRecover(hash, v, r, s);
        } else {
            return (address(0), RecoverError.InvalidSignatureLength);
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, signature);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
     *
     * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
     *
     * _Available since v4.3._
     */
    function tryRecover(
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal pure returns (address, RecoverError) {
        bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
        uint8 v = uint8((uint256(vs) >> 255) + 27);
        return tryRecover(hash, v, r, s);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
     *
     * _Available since v4.2._
     */
    function recover(
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, r, vs);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
     * `r` and `s` signature fields separately.
     *
     * _Available since v4.3._
     */
    function tryRecover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address, RecoverError) {
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            return (address(0), RecoverError.InvalidSignatureS);
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        if (signer == address(0)) {
            return (address(0), RecoverError.InvalidSignature);
        }

        return (signer, RecoverError.NoError);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function recover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from a `hash`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
        // 32 is the length in bytes of hash,
        // enforced by the type signature above
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from `s`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
    }

    /**
     * @dev Returns an Ethereum Signed Typed Data, created from a
     * `domainSeparator` and a `structHash`. This produces hash corresponding
     * to the one signed with the
     * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
     * JSON-RPC method as part of EIP-712.
     *
     * See {recover}.
     */
    function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
    }
}

// SPDX-License-Identifier: MIT
pragma solidity <0.9.0;

import { Auth } from "./Auth.sol";
import { ISignerManager } from "../interfaces/ISignerManager.sol";

/// @title SignerManager
/// @notice Manages hierarchical authorization signer permissions for referral system
/// @dev Implements a three-tier permission system:
///      - Super Admin (contract deployer): Can manage Referral Signer Admins
///      - Referral Signer Admin: Can manage Referral Signers (multiple allowed)
///      - Referral Signer: Can authorize referral configs and trader referrals (multiple allowed)
abstract contract SignerManager is Auth, ISignerManager {
    
    // ============ Storage ============
    
    /// @notice Mapping from address to referral signer admin status
    mapping(address => bool) public referralSignerAdmins;
    
    /// @notice Mapping from address to referral signer status  
    mapping(address => bool) public referralSigners;
    
    /// @notice Track total number of referral signer admins
    uint256 public totalReferralSignerAdmins;
    
    /// @notice Track total number of referral signers
    uint256 public totalReferralSigners;
    
    // ============ Modifiers ============
    
    modifier onlyReferralSignerAdmin() {
        if (!referralSignerAdmins[msg.sender]) revert NotSignerAdmin();
        _;
    }
    
    modifier onlyReferralSigner() {
        if (!referralSigners[msg.sender]) revert NotSigner();
        _;
    }
    
    modifier onlyReferralSignerOrAdmin() {
        if (!referralSigners[msg.sender] && !referralSignerAdmins[msg.sender] && admins[msg.sender] != 1) {
            revert NotAuthorizedSigner();
        }
        _;
    }
    
    // ============ Constructor ============
    
    constructor() {
        // Contract deployer becomes the first referral signer admin and referral signer
        referralSignerAdmins[msg.sender] = true;
        referralSigners[msg.sender] = true;
        totalReferralSignerAdmins = 1;
        totalReferralSigners = 1;
        
        emit SignerAdminAdded(msg.sender, msg.sender);
        emit SignerAdded(msg.sender, msg.sender);
    }
    
    // ============ Super Admin Functions (only Admin) ============
    
    /// @inheritdoc ISignerManager
    function addSignerAdmin(address signerAdmin) external onlyAdmin {
        if (signerAdmin == address(0)) revert InvalidAddress();
        if (referralSignerAdmins[signerAdmin]) revert SignerAdminAlreadyExists();
        
        referralSignerAdmins[signerAdmin] = true;
        totalReferralSignerAdmins++;
        
        emit SignerAdminAdded(signerAdmin, msg.sender);
    }
    
    /// @inheritdoc ISignerManager
    function removeSignerAdmin(address signerAdmin) external onlyAdmin {
        if (!referralSignerAdmins[signerAdmin]) revert SignerAdminNotFound();
        
        referralSignerAdmins[signerAdmin] = false;
        totalReferralSignerAdmins--;
        
        emit SignerAdminRemoved(signerAdmin, msg.sender);
    }
    
    // ============ Signer Admin Functions ============
    
    /// @inheritdoc ISignerManager
    function addSigner(address signer) external onlyReferralSignerAdmin {
        if (signer == address(0)) revert InvalidAddress();
        if (referralSigners[signer]) revert SignerAlreadyExists();
        
        referralSigners[signer] = true;
        totalReferralSigners++;
        
        emit SignerAdded(signer, msg.sender);
    }
    
    /// @inheritdoc ISignerManager
    function removeSigner(address signer) external onlyReferralSignerAdmin {
        if (!referralSigners[signer]) revert SignerNotFound();
        
        referralSigners[signer] = false;
        totalReferralSigners--;
        
        emit SignerRemoved(signer, msg.sender);
    }
    
    /// @inheritdoc ISignerManager
    function batchAddSigners(address[] calldata newSigners) external onlyReferralSignerAdmin {
        uint256 length = newSigners.length;
        if (length == 0) revert InvalidInput();
        
        for (uint256 i = 0; i < length; i++) {
            address signer = newSigners[i];
            if (signer == address(0)) revert InvalidAddress();
            if (referralSigners[signer]) continue; // Skip if already exists
            
            referralSigners[signer] = true;
            totalReferralSigners++;
            
            emit SignerAdded(signer, msg.sender);
        }
    }
    
    /// @inheritdoc ISignerManager
    function batchRemoveSigners(address[] calldata removedSigners) external onlyReferralSignerAdmin {
        uint256 length = removedSigners.length;
        if (length == 0) revert InvalidInput();
        
        for (uint256 i = 0; i < length; i++) {
            address signer = removedSigners[i];
            if (!referralSigners[signer]) continue; // Skip if doesn't exist
            
            referralSigners[signer] = false;
            totalReferralSigners--;
            
            emit SignerRemoved(signer, msg.sender);
        }
    }
    
    // ============ Self-Management Functions ============
    
    /// @inheritdoc ISignerManager
    function renounceSignerAdmin() external onlyReferralSignerAdmin {
        referralSignerAdmins[msg.sender] = false;
        totalReferralSignerAdmins--;
        
        emit SignerAdminRemoved(msg.sender, msg.sender);
    }
    
    /// @inheritdoc ISignerManager
    function renounceSigner() external onlyReferralSigner {
        referralSigners[msg.sender] = false;
        totalReferralSigners--;
        
        emit SignerRemoved(msg.sender, msg.sender);
    }
    
    // ============ View Functions ============
    
    /// @inheritdoc ISignerManager
    function isSignerAdmin(address account) external view returns (bool) {
        return referralSignerAdmins[account];
    }
    
    /// @inheritdoc ISignerManager
    function isSigner(address account) external view returns (bool) {
        return referralSigners[account];
    }
    
    /// @inheritdoc ISignerManager
    function isAuthorizedSigner(address account) external view returns (bool) {
        return referralSigners[account] || referralSignerAdmins[account] || admins[account] == 1;
    }
    
    /// @inheritdoc ISignerManager
    function getSignerStats() external view returns (uint256 signerAdminCount, uint256 signerCount) {
        return (totalReferralSignerAdmins, totalReferralSigners);
    }
    
    // ============ Internal Functions ============
    
    /// @notice Internal function to check if an address is an authorized signer
    /// @param account The address to check
    /// @return True if the address is authorized to sign
    function _isAuthorizedSigner(address account) internal view returns (bool) {
        return referralSigners[account] || referralSignerAdmins[account] || admins[account] == 1;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity <0.9.0;

import {
    ReferrerConfig,
    TraderReferralInfo,
    AuthorizationSignature,
    TraderReferralBindingSignature,
    RebateStats,
    DiscountStats,
    CollateralPriceConfig
} from "../libraries/ReferralStructs.sol";

/// @title Referral Manager Interface
/// @notice Interface for multi-collateral referral system management
interface IReferralManager {

    // ============ Events ============

    /// @notice Emitted when a new referrer is registered
    event ReferrerRegistered(address indexed referrer);

    /// @notice Emitted when referrer configuration is set/updated
    event ReferrerConfigSet(
        address indexed referrer,
        uint256 discountRate,
        uint256 rebateRate,
        uint256 discountValidityDuration,
        uint256 rebateValidityDuration,
        uint256 expiresAt
    );

    /// @notice Emitted when a trader activates a referrer
    event TraderReferrerSet(address indexed trader, address indexed referrer, uint256 activatedAt);

    /// @notice Enhanced rebate earned event with collateral token information
    event RebateEarned(
        address indexed referrer,
        address indexed trader,
        address indexed collateralToken,
        uint256 amount
    );



    /// @notice Emitted when global discount override is updated
    event GlobalDiscountOverrideUpdated(bool enabled);

    /// @notice Emitted when global rebate override is updated
    event GlobalRebateOverrideUpdated(bool enabled);

    /// @notice Emitted when collateral price configuration is updated
    event CollateralPriceConfigUpdated(
        address indexed collateralToken,
        uint256 usdPrice,
        uint256 decimals,
        bool isActive
    );

    // ============ Errors ============

    error ReferrerAlreadyRegistered();
    error ReferrerNotRegistered();
    error InvalidAuthSignature();
    error AuthSignatureExpired();
    error InvalidRates();
    error CannotReferSelf();
    error ReferrerConfigExpired();
    error DiscountPeriodExpired();
    error RebatePeriodExpired();
    error AlreadyHasReferrer();
    error InvalidAuthorizationSignature();
    error UnauthorizedTraderBinding();
    error UnsupportedCollateralToken();

    // ============ Core Functions ============

    /// @notice Sets referrer configuration with server authorization (auto-registers if not already registered)
    /// @param auth The authorization signature from server
    function setReferrerConfig(AuthorizationSignature calldata auth) external;

    /// @notice Sets referrer for a trader (admin only)
    /// @param trader The trader address
    /// @param referrer The referrer wallet address
    function setTraderReferrer(address trader, address referrer) external;

    /// @notice Set global discount override (operator only)
    /// @param enabled Whether to enable global discount override
    function setGlobalDiscountOverride(bool enabled) external;

    /// @notice Set global rebate override (operator only)
    /// @param enabled Whether to enable global rebate override
    function setGlobalRebateOverride(bool enabled) external;

    /// @notice Record rebate payment after actual transfer (called by Engine)
    /// @dev Updates stats and emits RebateEarned event
    /// @param trader The trader address
    /// @param referrer The referrer address
    /// @param rebateAmount The rebate amount in collateral tokens
    /// @param totalFeeAmount The total fee amount before rebate deduction
    /// @param collateralToken The collateral token address
    function recordRebatePayment(
        address trader,
        address referrer,
        uint256 rebateAmount,
        uint256 totalFeeAmount,
        address collateralToken
    ) external;

    /// @notice Record discount usage for a trader (called by Engine)
    /// @dev Updates discount stats for historical tracking
    /// @param trader The trader who received the discount
    /// @param collateralToken The collateral token address
    /// @param discountAmount The discount amount in collateral tokens
    function recordDiscountUsage(
        address trader,
        address collateralToken,
        uint256 discountAmount
    ) external;

    /// @notice Set USD price configuration for a collateral token (operator only)
    /// @param collateralToken The collateral token address
    /// @param usdPrice USD price per unit (in wei, e.g., 1e18 = $1.00)
    /// @param decimals Token decimals for price calculation
    /// @param isActive Whether this price config is active
    function setCollateralPriceConfig(
        address collateralToken,
        uint256 usdPrice,
        uint256 decimals,
        bool isActive
    ) external;

    /// @notice Get USD price configuration for a collateral token
    /// @param collateralToken The collateral token address
    /// @return config The price configuration
    function getCollateralPriceConfig(address collateralToken) external view returns (CollateralPriceConfig memory config);

    /// @notice Allows a trader to bind themselves to a referrer with server authorization
    /// @param auth The authorization signature for trader-referrer binding
    function bindToReferrer(TraderReferralBindingSignature calldata auth) external;

    /// @notice Processes referral for a trade with specific collateral token
    /// @param trader The trader address
    /// @param feeAmount The total fee amount
    /// @param collateralToken The collateral token used in this trade
    /// @return traderDiscount The discount amount for the trader
    /// @return referrerRebate The rebate amount for the referrer
    function processTradeReferral(
        address trader,
        uint256 feeAmount,
        address collateralToken
    ) external returns (uint256 traderDiscount, uint256 referrerRebate);

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

    /// @notice Gets referrer configuration
    /// @param referrer The referrer address
    /// @return config The referrer configuration
    function getReferrerConfig(address referrer) external view returns (ReferrerConfig memory config);

    /// @notice Gets trader referral information
    /// @param trader The trader address
    /// @return info The trader referral information
    function getTraderReferralInfo(address trader) external view returns (TraderReferralInfo memory info);


    /// @notice Gets USD-based rebate statistics for a referrer (across all collaterals)
    /// @param referrer The referrer address
    /// @return stats The rebate statistics in USD
    function getRebateStats(address referrer)
        external view returns (RebateStats memory stats);


    /// @notice Checks if an address is registered as a referrer
    /// @param referrer The referrer address
    /// @return isRegistered Whether the address is a registered referrer
    function isRegisteredReferrer(address referrer) external view returns (bool isRegistered);

    /// @notice Calculates referral discount and rebate for a fee amount with specific collateral
    /// @param trader The trader address
    /// @param feeAmount The fee amount
    /// @param collateralToken The collateral token used
    /// @return traderDiscount The discount amount for the trader
    /// @return referrerRebate The rebate amount for the referrer
    function calculateReferralRewards(address trader, uint256 feeAmount, address collateralToken)
        external view returns (uint256 traderDiscount, uint256 referrerRebate);

    /// @notice Checks if a referrer config is currently valid
    /// @param referrer The referrer address
    /// @return isValid Whether the config is valid and not expired
    function isReferrerConfigValid(address referrer) external view returns (bool isValid);

    /// @notice Checks if discount is valid for a trader
    /// @param trader The trader address
    /// @return isValid Whether discount is still valid for this trader
    function isDiscountValid(address trader) external view returns (bool isValid);

    /// @notice Checks if rebate is valid for a referrer based on trader activation
    /// @param referrer The referrer address
    /// @param trader The trader address
    /// @return isValid Whether rebate is still valid for this referrer-trader pair
    function isRebateValid(address referrer, address trader) external view returns (bool isValid);


    /// @notice Gets remaining monthly rebate allowance for a referrer (USD-based)
    /// @param referrer The referrer address
    /// @return remainingAllowance Remaining rebate allowance for current month in USD
    function getRemainingMonthlyRebateAllowance(address referrer)
        external view returns (uint256 remainingAllowance);

    // ============ UI Support Functions ============

    /// @notice Gets comprehensive referrer dashboard data in one call
    /// @param referrer The referrer address
    /// @return config The referrer configuration
    /// @return isActive Whether the referrer is currently active
    function getReferrerDashboard(address referrer)
        external view returns (
            ReferrerConfig memory config,
            bool isActive
        );

    /// @notice Gets trader's referral status and potential benefits
    /// @param trader The trader address
    /// @return info The trader referral information
    /// @return isDiscountActive Whether the trader currently has active discount
    /// @return remainingDiscountTime Time remaining for discount validity (0 if unlimited)
    function getTraderReferralStatus(address trader)
        external view returns (
            TraderReferralInfo memory info,
            bool isDiscountActive,
            uint256 remainingDiscountTime
        );

    /// @notice Batch query for multiple traders' referral status
    /// @param traders Array of trader addresses to query
    /// @return referrers Array of referrer addresses (address(0) if no referrer)
    /// @return discountsActive Array of discount validity status
    function batchGetTradersReferralInfo(address[] calldata traders)
        external view returns (
            address[] memory referrers,
            bool[] memory discountsActive
        );

    /// @notice Gets USD-based discount statistics for a trader (across all collaterals)
    /// @param trader The trader address
    /// @return stats The discount statistics in USD
    function getDiscountStats(address trader)
        external view returns (DiscountStats memory stats);



    /// @notice Gets comprehensive discount information for a trader
    /// @param trader The trader address
    /// @return referralInfo Basic referral information
    /// @return stats USD-based discount statistics
    function getTraderDiscountInfo(address trader)
        external view returns (
            TraderReferralInfo memory referralInfo,
            DiscountStats memory stats
        );

    /// @notice Get referrer's monthly rebate limit and current usage
    /// @param referrer The referrer address
    /// @return monthlyRebateLimit Monthly limit in USD (scaled by 1e6)
    /// @return currentMonthStart Timestamp of current month start
    /// @return currentMonthUsdRebates Total USD rebates paid this month
    function getReferrerMonthlyLimit(address referrer)
        external view returns (
            uint256 monthlyRebateLimit,
            uint256 currentMonthStart,
            uint256 currentMonthUsdRebates
        );

    /// @notice Convert token amount to USD value
    /// @param collateralToken The collateral token address
    /// @param amount Token amount
    /// @return usdValue USD value (scaled by 1e6)
    function convertToUsd(address collateralToken, uint256 amount)
        external view returns (uint256 usdValue);

    /// @notice Convert USD value to token amount
    /// @param collateralToken The collateral token address
    /// @param usdValue USD value (scaled by 1e6)
    /// @return amount Token amount
    function convertFromUsd(address collateralToken, uint256 usdValue)
        external view returns (uint256 amount);
}

// SPDX-License-Identifier: MIT
pragma solidity <0.9.0;

interface IFeesEE {
    error FeeTooHigh();

    /// @notice Emitted when a fee is charged
    event FeeCharged(address indexed receiver, uint256 tokenId, uint256 amount);
}

abstract contract IFees is IFeesEE {
    function treasury() external view virtual returns (address);
    function getMaxFeeRate() public pure virtual returns (uint256);
}

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

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
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 amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

    /**
     * @dev Moves `amount` 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 amount) 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 `amount` 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 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` 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 amount
    ) external returns (bool);
}

// SPDX-License-Identifier: MIT
pragma solidity <0.9.0;

interface IAuthEE {
    error NotAdmin();
    error NotOperatorAdmin();
    error NotOperator();

    /// @notice Emitted when a new admin is added
    event NewAdmin(address indexed newAdminAddress, address indexed admin);

    /// @notice Emitted when a new operator is added
    event NewOperatorAdmin(address indexed newOperatorAdminAddress, address indexed admin);

    /// @notice Emitted when a new operator is added
    event NewOperator(address indexed newOperatorAddress, address indexed admin);

    /// @notice Emitted when an admin is removed
    event RemovedAdmin(address indexed removedAdmin, address indexed admin);

    /// @notice Emitted when an operator admin is removed
    event RemovedOperatorAdmin(address indexed removedAdmin, address indexed admin);

    /// @notice Emitted when an operator is removed
    event RemovedOperator(address indexed removedOperator, address indexed admin);
}

interface IAuth is IAuthEE {
    function isAdmin(address) external view returns (bool);

    function isOperatorAdmin(address usr) external view returns (bool);

    function isOperator(address) external view returns (bool);

    function addAdmin(address) external;

    function addOperatorAdmin(address operatorAdmin_) external;

    function addOperator(address) external;

    function removeAdmin(address) external;

    function removeOperatorAdmin(address operatorAdmin) external;

    function removeOperator(address) external;

    function renounceAdminRole() external;

    function renounceOperatorAdminRole() external;

    function renounceOperatorRole() external;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)

pragma solidity ^0.8.0;

import "./math/Math.sol";

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant _SYMBOLS = "0123456789abcdef";
    uint8 private constant _ADDRESS_LENGTH = 20;

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = _SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity <0.9.0;

/// @title ISignerManager
/// @notice Interface for hierarchical referral authorization signer management
interface ISignerManager {
    
    // ============ Events ============
    
    /// @notice Emitted when a new referral signer admin is added
    /// @param signerAdmin The address of the new referral signer admin
    /// @param addedBy The address that added the referral signer admin
    event SignerAdminAdded(address indexed signerAdmin, address indexed addedBy);
    
    /// @notice Emitted when a referral signer admin is removed
    /// @param signerAdmin The address of the removed referral signer admin
    /// @param removedBy The address that removed the referral signer admin
    event SignerAdminRemoved(address indexed signerAdmin, address indexed removedBy);
    
    /// @notice Emitted when a new referral signer is added
    /// @param signer The address of the new referral signer
    /// @param addedBy The address that added the referral signer
    event SignerAdded(address indexed signer, address indexed addedBy);
    
    /// @notice Emitted when a referral signer is removed
    /// @param signer The address of the removed referral signer
    /// @param removedBy The address that removed the referral signer
    event SignerRemoved(address indexed signer, address indexed removedBy);
    
    // ============ Errors ============
    
    /// @notice Thrown when caller is not a referral signer admin
    error NotSignerAdmin();
    
    /// @notice Thrown when caller is not a referral signer
    error NotSigner();
    
    /// @notice Thrown when caller is not an authorized referral signer
    error NotAuthorizedSigner();
    
    /// @notice Thrown when trying to add a referral signer admin that already exists
    error SignerAdminAlreadyExists();
    
    /// @notice Thrown when trying to remove a referral signer admin that doesn't exist
    error SignerAdminNotFound();
    
    /// @notice Thrown when trying to add a referral signer that already exists
    error SignerAlreadyExists();
    
    /// @notice Thrown when trying to remove a referral signer that doesn't exist
    error SignerNotFound();
    
    /// @notice Thrown when an invalid address is provided
    error InvalidAddress();
    
    /// @notice Thrown when invalid input is provided
    error InvalidInput();
    
    // ============ Super Admin Functions (only Admin) ============
    
    /// @notice Adds a new referral signer admin
    /// @dev Can only be called by contract admin (super admin)
    /// @param signerAdmin The address to add as referral signer admin
    function addSignerAdmin(address signerAdmin) external;
    
    /// @notice Removes a referral signer admin
    /// @dev Can only be called by contract admin (super admin)
    /// @param signerAdmin The address to remove from referral signer admin
    function removeSignerAdmin(address signerAdmin) external;
    
    // ============ Referral Signer Admin Functions ============
    
    /// @notice Adds a new referral signer
    /// @dev Can only be called by referral signer admin
    /// @param signer The address to add as referral signer
    function addSigner(address signer) external;
    
    /// @notice Removes a referral signer
    /// @dev Can only be called by referral signer admin
    /// @param signer The address to remove from referral signer
    function removeSigner(address signer) external;
    
    /// @notice Adds multiple referral signers in a single transaction
    /// @dev Can only be called by referral signer admin
    /// @param newSigners Array of addresses to add as referral signers
    function batchAddSigners(address[] calldata newSigners) external;
    
    /// @notice Removes multiple referral signers in a single transaction
    /// @dev Can only be called by referral signer admin
    /// @param removedSigners Array of addresses to remove from referral signers
    function batchRemoveSigners(address[] calldata removedSigners) external;
    
    // ============ Self-Management Functions ============
    
    /// @notice Allows a referral signer admin to renounce their role
    function renounceSignerAdmin() external;
    
    /// @notice Allows a referral signer to renounce their role
    function renounceSigner() external;
    
    // ============ View Functions ============
    
    /// @notice Checks if an address is a referral signer admin
    /// @param account The address to check
    /// @return True if the address is a referral signer admin
    function isSignerAdmin(address account) external view returns (bool);
    
    /// @notice Checks if an address is a referral signer
    /// @param account The address to check
    /// @return True if the address is a referral signer
    function isSigner(address account) external view returns (bool);
    
    /// @notice Checks if an address is authorized to sign referrals (referral signer, referral signer admin, or admin)
    /// @param account The address to check
    /// @return True if the address is authorized to sign referrals
    function isAuthorizedSigner(address account) external view returns (bool);
    
    /// @notice Gets the current counts of referral signer admins and referral signers
    /// @return signerAdminCount The total number of referral signer admins
    /// @return signerCount The total number of referral signers
    function getSignerStats() external view returns (uint256 signerAdminCount, uint256 signerCount);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/math/Math.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
     * with further edits by Uniswap Labs also under MIT license.
     */
    function mulDiv(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1);

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
            // See https://cs.stackexchange.com/q/138556/92363.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
            // in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(
        uint256 x,
        uint256 y,
        uint256 denominator,
        Rounding rounding
    ) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10**64) {
                value /= 10**64;
                result += 64;
            }
            if (value >= 10**32) {
                value /= 10**32;
                result += 32;
            }
            if (value >= 10**16) {
                value /= 10**16;
                result += 16;
            }
            if (value >= 10**8) {
                value /= 10**8;
                result += 8;
            }
            if (value >= 10**4) {
                value /= 10**4;
                result += 4;
            }
            if (value >= 10**2) {
                value /= 10**2;
                result += 2;
            }
            if (value >= 10**1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
        }
    }
}

Settings
{
  "remappings": [
    "openzeppelin/=lib/openzeppelin-contracts/contracts/",
    "openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/",
    "solmate/=lib/solmate/src/",
    "conditional-tokens-contracts/=lib/conditional-tokens-contracts/contracts/",
    "openzeppelin-solidity/=lib/conditional-tokens-contracts/openzeppelin-solidity/",
    "common/=src/common/",
    "creator/=src/creator/",
    "dev/=src/dev/",
    "exchange/=src/exchange/",
    "mocks/=src/mocks/",
    "ds-test/=lib/forge-std/lib/ds-test/src/",
    "forge-std/=lib/forge-std/src/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 1
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "none",
    "appendCBOR": true
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "cancun",
  "viaIR": true
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"_defaultCollateralToken","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyHasReferrer","type":"error"},{"inputs":[],"name":"ArrayLengthMismatch","type":"error"},{"inputs":[],"name":"AuthSignatureExpired","type":"error"},{"inputs":[],"name":"CannotReferSelf","type":"error"},{"inputs":[],"name":"CollateralNotConfigured","type":"error"},{"inputs":[],"name":"DiscountPeriodExpired","type":"error"},{"inputs":[],"name":"FeeRateExceedsMaximum","type":"error"},{"inputs":[],"name":"FeeTooHigh","type":"error"},{"inputs":[],"name":"GlobalMinFeeExceedsMaximum","type":"error"},{"inputs":[],"name":"InvalidAddress","type":"error"},{"inputs":[],"name":"InvalidAuthSignature","type":"error"},{"inputs":[],"name":"InvalidAuthorizationSignature","type":"error"},{"inputs":[],"name":"InvalidCollateralToken","type":"error"},{"inputs":[],"name":"InvalidDiscount","type":"error"},{"inputs":[],"name":"InvalidDiscountRate","type":"error"},{"inputs":[],"name":"InvalidInput","type":"error"},{"inputs":[],"name":"InvalidRates","type":"error"},{"inputs":[],"name":"InvalidTier","type":"error"},{"inputs":[],"name":"InvalidUser","type":"error"},{"inputs":[],"name":"NoDefaultCollateralSet","type":"error"},{"inputs":[],"name":"NotAdmin","type":"error"},{"inputs":[],"name":"NotAuthorizedSigner","type":"error"},{"inputs":[],"name":"NotOperator","type":"error"},{"inputs":[],"name":"NotOperatorAdmin","type":"error"},{"inputs":[],"name":"NotSigner","type":"error"},{"inputs":[],"name":"NotSignerAdmin","type":"error"},{"inputs":[],"name":"RebatePeriodExpired","type":"error"},{"inputs":[],"name":"ReferrerAlreadyRegistered","type":"error"},{"inputs":[],"name":"ReferrerConfigExpired","type":"error"},{"inputs":[],"name":"ReferrerNotRegistered","type":"error"},{"inputs":[],"name":"SignerAdminAlreadyExists","type":"error"},{"inputs":[],"name":"SignerAdminNotFound","type":"error"},{"inputs":[],"name":"SignerAlreadyExists","type":"error"},{"inputs":[],"name":"SignerNotFound","type":"error"},{"inputs":[],"name":"TierExpired","type":"error"},{"inputs":[],"name":"TierNotActive","type":"error"},{"inputs":[],"name":"UnauthorizedTraderBinding","type":"error"},{"inputs":[],"name":"UnsupportedCollateralToken","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"collateralToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"usdPrice","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"decimals","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isActive","type":"bool"}],"name":"CollateralPriceConfigUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FeeCharged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"makerFeeRateBps","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"takerFeeRateBps","type":"uint256"},{"indexed":false,"internalType":"bool","name":"enabled","type":"bool"},{"indexed":false,"internalType":"uint256","name":"minFeeAmount","type":"uint256"}],"name":"FeeRateSettingsChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"enabled","type":"bool"}],"name":"GlobalDiscountOverrideUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"enabled","type":"bool"}],"name":"GlobalRebateOverrideUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newAdminAddress","type":"address"},{"indexed":true,"internalType":"address","name":"admin","type":"address"}],"name":"NewAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newOperatorAddress","type":"address"},{"indexed":true,"internalType":"address","name":"admin","type":"address"}],"name":"NewOperator","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newOperatorAdminAddress","type":"address"},{"indexed":true,"internalType":"address","name":"admin","type":"address"}],"name":"NewOperatorAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"referrer","type":"address"},{"indexed":true,"internalType":"address","name":"trader","type":"address"},{"indexed":true,"internalType":"address","name":"collateralToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"RebateEarned","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"referrer","type":"address"},{"indexed":false,"internalType":"uint256","name":"discountRate","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"rebateRate","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"discountValidityDuration","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"rebateValidityDuration","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"expiresAt","type":"uint256"}],"name":"ReferrerConfigSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"referrer","type":"address"}],"name":"ReferrerRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"removedAdmin","type":"address"},{"indexed":true,"internalType":"address","name":"admin","type":"address"}],"name":"RemovedAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"removedOperator","type":"address"},{"indexed":true,"internalType":"address","name":"admin","type":"address"}],"name":"RemovedOperator","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"removedAdmin","type":"address"},{"indexed":true,"internalType":"address","name":"admin","type":"address"}],"name":"RemovedOperatorAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"signer","type":"address"},{"indexed":true,"internalType":"address","name":"addedBy","type":"address"}],"name":"SignerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"signerAdmin","type":"address"},{"indexed":true,"internalType":"address","name":"addedBy","type":"address"}],"name":"SignerAdminAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"signerAdmin","type":"address"},{"indexed":true,"internalType":"address","name":"removedBy","type":"address"}],"name":"SignerAdminRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"signer","type":"address"},{"indexed":true,"internalType":"address","name":"removedBy","type":"address"}],"name":"SignerRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tierId","type":"uint256"},{"indexed":true,"internalType":"address","name":"updatedBy","type":"address"}],"name":"TierConfigUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tierId","type":"uint256"},{"indexed":false,"internalType":"string","name":"name","type":"string"},{"indexed":false,"internalType":"uint256","name":"feeDiscountBps","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"validUntil","type":"uint256"},{"indexed":true,"internalType":"address","name":"createdBy","type":"address"}],"name":"TierCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tierId","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isActive","type":"bool"},{"indexed":true,"internalType":"address","name":"updatedBy","type":"address"}],"name":"TierStatusChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"trader","type":"address"},{"indexed":true,"internalType":"address","name":"referrer","type":"address"},{"indexed":false,"internalType":"uint256","name":"activatedAt","type":"uint256"}],"name":"TraderReferrerSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"oldTierId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newTierId","type":"uint256"},{"indexed":true,"internalType":"address","name":"updatedBy","type":"address"}],"name":"UserTierUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"newTotalVolume","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"addedVolume","type":"uint256"}],"name":"UserVolumeUpdated","type":"event"},{"inputs":[],"name":"REFERRER_CONFIG_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TRADER_REFERRAL_BINDING_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"admin_","type":"address"}],"name":"addAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator_","type":"address"}],"name":"addOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operatorAdmin_","type":"address"}],"name":"addOperatorAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"signer","type":"address"}],"name":"addSigner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"signerAdmin","type":"address"}],"name":"addSignerAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"admins","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"newSigners","type":"address[]"}],"name":"batchAddSigners","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"traders","type":"address[]"}],"name":"batchGetTradersReferralInfo","outputs":[{"internalType":"address[]","name":"referrers","type":"address[]"},{"internalType":"bool[]","name":"discountsActive","type":"bool[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"removedSigners","type":"address[]"}],"name":"batchRemoveSigners","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"trader","type":"address"},{"internalType":"address","name":"referrer","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct TraderReferralBindingSignature","name":"auth","type":"tuple"}],"name":"bindToReferrer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"trader","type":"address"},{"internalType":"uint256","name":"feeAmount","type":"uint256"},{"internalType":"address","name":"collateralToken","type":"address"}],"name":"calculateReferralRewards","outputs":[{"internalType":"uint256","name":"traderDiscount","type":"uint256"},{"internalType":"uint256","name":"referrerRebate","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"collateralConfigs","outputs":[{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint256","name":"globalDefaultMinFee","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"collateralPriceConfigs","outputs":[{"internalType":"address","name":"collateralToken","type":"address"},{"internalType":"uint256","name":"usdPrice","type":"uint256"},{"internalType":"uint256","name":"decimals","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"baseFee","type":"uint256"},{"internalType":"uint256","name":"minFee","type":"uint256"},{"internalType":"uint256","name":"adminDiscountBps","type":"uint256"}],"name":"computeFees","outputs":[{"internalType":"uint256","name":"finalFee","type":"uint256"},{"internalType":"uint256","name":"rebate","type":"uint256"},{"internalType":"address","name":"referrer","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"collateralToken","type":"address"},{"internalType":"uint256","name":"usdValue","type":"uint256"}],"name":"convertFromUsd","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"collateralToken","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"convertToUsd","outputs":[{"internalType":"uint256","name":"usdValue","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"feeDiscountBps","type":"uint256"},{"internalType":"uint256","name":"validUntil","type":"uint256"},{"internalType":"bytes32","name":"metadata","type":"bytes32"}],"name":"createTier","outputs":[{"internalType":"uint256","name":"tierId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"defaultCollateralToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"discountStats","outputs":[{"internalType":"uint256","name":"totalDiscountReceivedUsd","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order","name":"order","type":"tuple"},{"internalType":"address","name":"collateralToken","type":"address"}],"name":"estimateFee","outputs":[{"components":[{"internalType":"uint256","name":"totalFee","type":"uint256"},{"internalType":"uint256","name":"baseFee","type":"uint256"},{"internalType":"uint256","name":"userDiscountBps","type":"uint256"},{"internalType":"uint256","name":"promoDiscountBps","type":"uint256"},{"internalType":"uint256","name":"userDiscountAmount","type":"uint256"},{"internalType":"uint256","name":"promoDiscountAmount","type":"uint256"},{"internalType":"uint256","name":"referralDiscountBps","type":"uint256"},{"internalType":"uint256","name":"referralDiscountAmount","type":"uint256"},{"internalType":"uint256","name":"referrerRebateAmount","type":"uint256"},{"internalType":"uint256","name":"platformRevenue","type":"uint256"},{"internalType":"uint256","name":"minFeeAmount","type":"uint256"},{"internalType":"bool","name":"minFeeApplied","type":"bool"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"}],"internalType":"struct IFeeEstimation.FeeEstimate","name":"estimate","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order","name":"order","type":"tuple"},{"internalType":"uint256","name":"fillAmount","type":"uint256"},{"internalType":"address","name":"collateralToken","type":"address"}],"name":"estimateFeeWithAmount","outputs":[{"components":[{"internalType":"uint256","name":"totalFee","type":"uint256"},{"internalType":"uint256","name":"baseFee","type":"uint256"},{"internalType":"uint256","name":"userDiscountBps","type":"uint256"},{"internalType":"uint256","name":"promoDiscountBps","type":"uint256"},{"internalType":"uint256","name":"userDiscountAmount","type":"uint256"},{"internalType":"uint256","name":"promoDiscountAmount","type":"uint256"},{"internalType":"uint256","name":"referralDiscountBps","type":"uint256"},{"internalType":"uint256","name":"referralDiscountAmount","type":"uint256"},{"internalType":"uint256","name":"referrerRebateAmount","type":"uint256"},{"internalType":"uint256","name":"platformRevenue","type":"uint256"},{"internalType":"uint256","name":"minFeeAmount","type":"uint256"},{"internalType":"bool","name":"minFeeApplied","type":"bool"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"}],"internalType":"struct IFeeEstimation.FeeEstimate","name":"estimate","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"makerUser","type":"address"},{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order","name":"makerOrder","type":"tuple"},{"internalType":"uint256","name":"fillAmount","type":"uint256"}],"name":"estimateMakerFee","outputs":[{"components":[{"internalType":"uint256","name":"totalFee","type":"uint256"},{"internalType":"uint256","name":"baseFee","type":"uint256"},{"internalType":"uint256","name":"userDiscountBps","type":"uint256"},{"internalType":"uint256","name":"promoDiscountBps","type":"uint256"},{"internalType":"uint256","name":"userDiscountAmount","type":"uint256"},{"internalType":"uint256","name":"promoDiscountAmount","type":"uint256"},{"internalType":"uint256","name":"referralDiscountBps","type":"uint256"},{"internalType":"uint256","name":"referralDiscountAmount","type":"uint256"},{"internalType":"uint256","name":"referrerRebateAmount","type":"uint256"},{"internalType":"uint256","name":"platformRevenue","type":"uint256"},{"internalType":"uint256","name":"minFeeAmount","type":"uint256"},{"internalType":"bool","name":"minFeeApplied","type":"bool"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"}],"internalType":"struct IFeeEstimation.FeeEstimate","name":"estimate","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"takerUser","type":"address"},{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order","name":"takerOrder","type":"tuple"},{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order[]","name":"makerOrders","type":"tuple[]"},{"internalType":"uint256","name":"takerFillAmount","type":"uint256"},{"internalType":"uint256[]","name":"makerFillAmounts","type":"uint256[]"}],"name":"estimateMatchingFees","outputs":[{"components":[{"internalType":"uint256","name":"totalFee","type":"uint256"},{"internalType":"uint256","name":"baseFee","type":"uint256"},{"internalType":"uint256","name":"userDiscountBps","type":"uint256"},{"internalType":"uint256","name":"promoDiscountBps","type":"uint256"},{"internalType":"uint256","name":"userDiscountAmount","type":"uint256"},{"internalType":"uint256","name":"promoDiscountAmount","type":"uint256"},{"internalType":"uint256","name":"referralDiscountBps","type":"uint256"},{"internalType":"uint256","name":"referralDiscountAmount","type":"uint256"},{"internalType":"uint256","name":"referrerRebateAmount","type":"uint256"},{"internalType":"uint256","name":"platformRevenue","type":"uint256"},{"internalType":"uint256","name":"minFeeAmount","type":"uint256"},{"internalType":"bool","name":"minFeeApplied","type":"bool"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"}],"internalType":"struct IFeeEstimation.FeeEstimate","name":"takerFee","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalFee","type":"uint256"},{"internalType":"uint256","name":"baseFee","type":"uint256"},{"internalType":"uint256","name":"userDiscountBps","type":"uint256"},{"internalType":"uint256","name":"promoDiscountBps","type":"uint256"},{"internalType":"uint256","name":"userDiscountAmount","type":"uint256"},{"internalType":"uint256","name":"promoDiscountAmount","type":"uint256"},{"internalType":"uint256","name":"referralDiscountBps","type":"uint256"},{"internalType":"uint256","name":"referralDiscountAmount","type":"uint256"},{"internalType":"uint256","name":"referrerRebateAmount","type":"uint256"},{"internalType":"uint256","name":"platformRevenue","type":"uint256"},{"internalType":"uint256","name":"minFeeAmount","type":"uint256"},{"internalType":"bool","name":"minFeeApplied","type":"bool"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"}],"internalType":"struct IFeeEstimation.FeeEstimate[]","name":"makerFees","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"feeAmount","type":"uint256"}],"name":"estimateMinimumFee","outputs":[{"internalType":"uint256","name":"finalFee","type":"uint256"},{"internalType":"bool","name":"minFeeApplied","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order","name":"order","type":"tuple"},{"internalType":"bool","name":"isTaker","type":"bool"}],"name":"estimateOrderFee","outputs":[{"components":[{"internalType":"uint256","name":"totalFee","type":"uint256"},{"internalType":"uint256","name":"baseFee","type":"uint256"},{"internalType":"uint256","name":"userDiscountBps","type":"uint256"},{"internalType":"uint256","name":"promoDiscountBps","type":"uint256"},{"internalType":"uint256","name":"userDiscountAmount","type":"uint256"},{"internalType":"uint256","name":"promoDiscountAmount","type":"uint256"},{"internalType":"uint256","name":"referralDiscountBps","type":"uint256"},{"internalType":"uint256","name":"referralDiscountAmount","type":"uint256"},{"internalType":"uint256","name":"referrerRebateAmount","type":"uint256"},{"internalType":"uint256","name":"platformRevenue","type":"uint256"},{"internalType":"uint256","name":"minFeeAmount","type":"uint256"},{"internalType":"bool","name":"minFeeApplied","type":"bool"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"}],"internalType":"struct IFeeEstimation.FeeEstimate","name":"estimate","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order","name":"order","type":"tuple"},{"internalType":"uint256","name":"fillAmount","type":"uint256"},{"internalType":"bool","name":"isTaker","type":"bool"}],"name":"estimateOrderFeeWithFillAmount","outputs":[{"components":[{"internalType":"uint256","name":"totalFee","type":"uint256"},{"internalType":"uint256","name":"baseFee","type":"uint256"},{"internalType":"uint256","name":"userDiscountBps","type":"uint256"},{"internalType":"uint256","name":"promoDiscountBps","type":"uint256"},{"internalType":"uint256","name":"userDiscountAmount","type":"uint256"},{"internalType":"uint256","name":"promoDiscountAmount","type":"uint256"},{"internalType":"uint256","name":"referralDiscountBps","type":"uint256"},{"internalType":"uint256","name":"referralDiscountAmount","type":"uint256"},{"internalType":"uint256","name":"referrerRebateAmount","type":"uint256"},{"internalType":"uint256","name":"platformRevenue","type":"uint256"},{"internalType":"uint256","name":"minFeeAmount","type":"uint256"},{"internalType":"bool","name":"minFeeApplied","type":"bool"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"}],"internalType":"struct IFeeEstimation.FeeEstimate","name":"estimate","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"takerUser","type":"address"},{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order","name":"takerOrder","type":"tuple"},{"internalType":"uint256","name":"fillAmount","type":"uint256"}],"name":"estimateTakerFee","outputs":[{"components":[{"internalType":"uint256","name":"totalFee","type":"uint256"},{"internalType":"uint256","name":"baseFee","type":"uint256"},{"internalType":"uint256","name":"userDiscountBps","type":"uint256"},{"internalType":"uint256","name":"promoDiscountBps","type":"uint256"},{"internalType":"uint256","name":"userDiscountAmount","type":"uint256"},{"internalType":"uint256","name":"promoDiscountAmount","type":"uint256"},{"internalType":"uint256","name":"referralDiscountBps","type":"uint256"},{"internalType":"uint256","name":"referralDiscountAmount","type":"uint256"},{"internalType":"uint256","name":"referrerRebateAmount","type":"uint256"},{"internalType":"uint256","name":"platformRevenue","type":"uint256"},{"internalType":"uint256","name":"minFeeAmount","type":"uint256"},{"internalType":"bool","name":"minFeeApplied","type":"bool"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"}],"internalType":"struct IFeeEstimation.FeeEstimate","name":"estimate","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"feeRateSettings","outputs":[{"internalType":"uint256","name":"makerFeeRateBps","type":"uint256"},{"internalType":"uint256","name":"takerFeeRateBps","type":"uint256"},{"internalType":"bool","name":"enabled","type":"bool"},{"internalType":"uint256","name":"minFeeAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllTiers","outputs":[{"components":[{"internalType":"bool","name":"isActive","type":"bool"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"feeDiscountBps","type":"uint256"},{"internalType":"uint256","name":"validUntil","type":"uint256"},{"internalType":"bytes32","name":"metadata","type":"bytes32"}],"internalType":"struct TierConfig[]","name":"tiers","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"collateralToken","type":"address"}],"name":"getCollateralConfig","outputs":[{"components":[{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint256","name":"globalDefaultMinFee","type":"uint256"}],"internalType":"struct CTFExchangeFeeManager.CollateralConfig","name":"config","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"collateralToken","type":"address"}],"name":"getCollateralGlobalMinFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"collateralToken","type":"address"}],"name":"getCollateralPriceConfig","outputs":[{"components":[{"internalType":"address","name":"collateralToken","type":"address"},{"internalType":"uint256","name":"usdPrice","type":"uint256"},{"internalType":"uint256","name":"decimals","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"}],"internalType":"struct CollateralPriceConfig","name":"config","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"trader","type":"address"}],"name":"getDiscountStats","outputs":[{"components":[{"internalType":"uint256","name":"totalDiscountReceivedUsd","type":"uint256"}],"internalType":"struct DiscountStats","name":"stats","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getFeeRateSettings","outputs":[{"internalType":"uint256","name":"makerFeeRateBps","type":"uint256"},{"internalType":"uint256","name":"takerFeeRateBps","type":"uint256"},{"internalType":"bool","name":"enabled","type":"bool"},{"internalType":"uint256","name":"minFeeAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getGlobalDefaultMinFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMaxFeeRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"referrer","type":"address"}],"name":"getRebateStats","outputs":[{"components":[{"internalType":"uint256","name":"totalRebatesReceivedUsd","type":"uint256"}],"internalType":"struct RebateStats","name":"stats","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"referrer","type":"address"}],"name":"getReferrerConfig","outputs":[{"components":[{"internalType":"bool","name":"isActive","type":"bool"},{"internalType":"uint256","name":"discountRate","type":"uint256"},{"internalType":"uint256","name":"rebateRate","type":"uint256"},{"internalType":"uint256","name":"discountValidityDuration","type":"uint256"},{"internalType":"uint256","name":"rebateValidityDuration","type":"uint256"},{"internalType":"uint256","name":"expiresAt","type":"uint256"},{"internalType":"uint256","name":"monthlyRebateLimit","type":"uint256"},{"internalType":"uint256","name":"totalVolume","type":"uint256"},{"internalType":"uint256","name":"totalReferrals","type":"uint256"},{"internalType":"uint256","name":"currentMonthUsdRebates","type":"uint256"},{"internalType":"uint256","name":"currentMonthStart","type":"uint256"}],"internalType":"struct ReferrerConfig","name":"config","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"referrer","type":"address"}],"name":"getReferrerDashboard","outputs":[{"components":[{"internalType":"bool","name":"isActive","type":"bool"},{"internalType":"uint256","name":"discountRate","type":"uint256"},{"internalType":"uint256","name":"rebateRate","type":"uint256"},{"internalType":"uint256","name":"discountValidityDuration","type":"uint256"},{"internalType":"uint256","name":"rebateValidityDuration","type":"uint256"},{"internalType":"uint256","name":"expiresAt","type":"uint256"},{"internalType":"uint256","name":"monthlyRebateLimit","type":"uint256"},{"internalType":"uint256","name":"totalVolume","type":"uint256"},{"internalType":"uint256","name":"totalReferrals","type":"uint256"},{"internalType":"uint256","name":"currentMonthUsdRebates","type":"uint256"},{"internalType":"uint256","name":"currentMonthStart","type":"uint256"}],"internalType":"struct ReferrerConfig","name":"config","type":"tuple"},{"internalType":"bool","name":"isActive","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"referrer","type":"address"}],"name":"getReferrerMonthlyLimit","outputs":[{"internalType":"uint256","name":"monthlyRebateLimit","type":"uint256"},{"internalType":"uint256","name":"currentMonthStart","type":"uint256"},{"internalType":"uint256","name":"currentMonthUsdRebates","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"referrer","type":"address"}],"name":"getRemainingMonthlyRebateAllowance","outputs":[{"internalType":"uint256","name":"remainingAllowance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getSignerStats","outputs":[{"internalType":"uint256","name":"signerAdminCount","type":"uint256"},{"internalType":"uint256","name":"signerCount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"getTierConfig","outputs":[{"components":[{"internalType":"bool","name":"isActive","type":"bool"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"feeDiscountBps","type":"uint256"},{"internalType":"uint256","name":"validUntil","type":"uint256"},{"internalType":"bytes32","name":"metadata","type":"bytes32"}],"internalType":"struct TierConfig","name":"config","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"trader","type":"address"}],"name":"getTraderDiscountInfo","outputs":[{"components":[{"internalType":"address","name":"referrer","type":"address"},{"internalType":"uint256","name":"activatedAt","type":"uint256"}],"internalType":"struct TraderReferralInfo","name":"referralInfo","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalDiscountReceivedUsd","type":"uint256"}],"internalType":"struct DiscountStats","name":"stats","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"trader","type":"address"}],"name":"getTraderReferralInfo","outputs":[{"components":[{"internalType":"address","name":"referrer","type":"address"},{"internalType":"uint256","name":"activatedAt","type":"uint256"}],"internalType":"struct TraderReferralInfo","name":"info","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"trader","type":"address"}],"name":"getTraderReferralStatus","outputs":[{"components":[{"internalType":"address","name":"referrer","type":"address"},{"internalType":"uint256","name":"activatedAt","type":"uint256"}],"internalType":"struct TraderReferralInfo","name":"info","type":"tuple"},{"internalType":"bool","name":"isDiscountActive","type":"bool"},{"internalType":"uint256","name":"remainingDiscountTime","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getUserDiscountBps","outputs":[{"internalType":"uint256","name":"discountBps","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getUserDiscountInfo","outputs":[{"internalType":"uint256","name":"userTierDiscountBps","type":"uint256"},{"internalType":"uint256","name":"referralDiscountBps","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getUserTierInfo","outputs":[{"components":[{"internalType":"uint256","name":"tierId","type":"uint256"},{"internalType":"uint256","name":"assignedAt","type":"uint256"},{"internalType":"uint256","name":"totalVolume","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"}],"internalType":"struct UserTierInfo","name":"info","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"globalDiscountOverride","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"globalRebateOverride","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"isAdmin","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"isAuthorizedSigner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"trader","type":"address"}],"name":"isDiscountValid","outputs":[{"internalType":"bool","name":"isValid","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"isOperator","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"isOperatorAdmin","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"referrer","type":"address"},{"internalType":"address","name":"trader","type":"address"}],"name":"isRebateValid","outputs":[{"internalType":"bool","name":"isValid","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"referrer","type":"address"}],"name":"isReferrerConfigValid","outputs":[{"internalType":"bool","name":"isValid","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"referrer","type":"address"}],"name":"isRegisteredReferrer","outputs":[{"internalType":"bool","name":"isRegistered","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"isSigner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"isSignerAdmin","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextTierId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"operatorAdmins","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"operators","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"trader","type":"address"},{"internalType":"uint256","name":"feeAmount","type":"uint256"},{"internalType":"address","name":"collateralToken","type":"address"}],"name":"processTradeReferral","outputs":[{"internalType":"uint256","name":"traderDiscount","type":"uint256"},{"internalType":"uint256","name":"referrerRebate","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rebateStats","outputs":[{"internalType":"uint256","name":"totalRebatesReceivedUsd","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"trader","type":"address"},{"internalType":"address","name":"collateralToken","type":"address"},{"internalType":"uint256","name":"discountAmount","type":"uint256"}],"name":"recordDiscountUsage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"trader","type":"address"},{"internalType":"address","name":"referrer","type":"address"},{"internalType":"uint256","name":"rebateAmount","type":"uint256"},{"internalType":"uint256","name":"totalFeeAmount","type":"uint256"},{"internalType":"address","name":"collateralToken","type":"address"}],"name":"recordRebatePayment","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"referralSignerAdmins","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"referralSigners","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"referrerConfigs","outputs":[{"internalType":"bool","name":"isActive","type":"bool"},{"internalType":"uint256","name":"discountRate","type":"uint256"},{"internalType":"uint256","name":"rebateRate","type":"uint256"},{"internalType":"uint256","name":"discountValidityDuration","type":"uint256"},{"internalType":"uint256","name":"rebateValidityDuration","type":"uint256"},{"internalType":"uint256","name":"expiresAt","type":"uint256"},{"internalType":"uint256","name":"monthlyRebateLimit","type":"uint256"},{"internalType":"uint256","name":"totalVolume","type":"uint256"},{"internalType":"uint256","name":"totalReferrals","type":"uint256"},{"internalType":"uint256","name":"currentMonthUsdRebates","type":"uint256"},{"internalType":"uint256","name":"currentMonthStart","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"registeredReferrers","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"}],"name":"removeAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"removeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operatorAdmin","type":"address"}],"name":"removeOperatorAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"signer","type":"address"}],"name":"removeSigner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"signerAdmin","type":"address"}],"name":"removeSignerAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"removeUserFromTier","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"users","type":"address[]"}],"name":"removeUsersFromTierBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceAdminRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOperatorAdminRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOperatorRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceSigner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceSignerAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"collateralToken","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint256","name":"globalDefaultMinFee","type":"uint256"}],"name":"setCollateralConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"collateralToken","type":"address"},{"internalType":"uint256","name":"usdPrice","type":"uint256"},{"internalType":"uint256","name":"decimals","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"}],"name":"setCollateralPriceConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newDefaultCollateral","type":"address"}],"name":"setDefaultCollateralToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"enabled","type":"bool"}],"name":"setGlobalDiscountOverride","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"enabled","type":"bool"}],"name":"setGlobalRebateOverride","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"referrer","type":"address"},{"internalType":"uint256","name":"discountRate","type":"uint256"},{"internalType":"uint256","name":"rebateRate","type":"uint256"},{"internalType":"uint256","name":"discountValidityDuration","type":"uint256"},{"internalType":"uint256","name":"rebateValidityDuration","type":"uint256"},{"internalType":"uint256","name":"expiresAt","type":"uint256"},{"internalType":"uint256","name":"monthlyRebateLimit","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct AuthorizationSignature","name":"auth","type":"tuple"}],"name":"setReferrerConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"}],"name":"setTierStatus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"trader","type":"address"},{"internalType":"address","name":"referrer","type":"address"}],"name":"setTraderReferrer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"tierId","type":"uint256"}],"name":"setUserTier","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"users","type":"address[]"},{"internalType":"uint256[]","name":"tierIds","type":"uint256[]"}],"name":"setUserTiersBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"tierConfigs","outputs":[{"internalType":"bool","name":"isActive","type":"bool"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"feeDiscountBps","type":"uint256"},{"internalType":"uint256","name":"validUntil","type":"uint256"},{"internalType":"bytes32","name":"metadata","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalReferralSignerAdmins","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalReferralSigners","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"traderReferrals","outputs":[{"internalType":"address","name":"referrer","type":"address"},{"internalType":"uint256","name":"activatedAt","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"treasury","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerFeeRateBps","type":"uint256"},{"internalType":"uint256","name":"takerFeeRateBps","type":"uint256"},{"internalType":"bool","name":"enabled","type":"bool"},{"internalType":"uint256","name":"minFeeAmount","type":"uint256"}],"name":"updateFeeRateSettings","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newGlobalDefaultMinFee","type":"uint256"}],"name":"updateGlobalDefaultMinFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tierId","type":"uint256"},{"components":[{"internalType":"bool","name":"isActive","type":"bool"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"feeDiscountBps","type":"uint256"},{"internalType":"uint256","name":"validUntil","type":"uint256"},{"internalType":"bytes32","name":"metadata","type":"bytes32"}],"internalType":"struct TierConfig","name":"config","type":"tuple"}],"name":"updateTierConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"volumeToAdd","type":"uint256"}],"name":"updateUserVolume","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"usedSignatures","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"userTierInfo","outputs":[{"internalType":"uint256","name":"tierId","type":"uint256"},{"internalType":"uint256","name":"assignedAt","type":"uint256"},{"internalType":"uint256","name":"totalVolume","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"}],"stateMutability":"view","type":"function"}]

608080604052346102b057602081616390803803809161001f82856103bf565b8339810103126102b057516001600160a01b038116908190036102b057335f90815260208181526040808320600190819055808352818420819055600290925291829020819055600355519060a082016001600160401b03811183821017610390576040526001825260405191610095836103a4565b6007835266111959985d5b1d60ca1b602084015260208101928352604081015f8152606082015f815260808301915f83525f8052600460205260405f209351151560ff8019865416911617845560018401955195865160018060401b038111610390578154600181811c91168015610386575b602082101461037257601f811161032d575b506020601f82116001146102c757819060049798995f926102bc575b50508160011b915f199060031b1c19161790555b51600285015551600384015551910155335f52600660205260405f20600160ff19825416179055335f52600760205260405f20600160ff198254161790556001600855600160095560405133337f1b3bdd505031d7440a31317a144c6a332fd0894111458e9ccf17a35191261f765f80a333337f12146497b3b826918ec47f0cac7272a09ed06b30c16c030e99ec48ff5dd60b475f80a361ffff196011541660115581610201575b604051615fad90816103e38239f35b63313ce56760e01b8152602081600481855afa5f918161027a575b50610275575060125b60ff60405191610234836103a4565b1681526001602082015f8152835f52601360205260ff60405f2093511660ff198454161783555191015560018060a01b031960145416176014555f806101f2565b610225565b9091506020813d6020116102b4575b81610296602093836103bf565b810103126102b0575160ff811681036102b057905f61021c565b5f80fd5b3d9150610289565b015190505f80610136565b601f19821698835f52815f20995f5b818110610315575091600498999a918460019594106102fd575b505050811b01905561014a565b01515f1960f88460031b161c191690555f80806102f0565b838301518c556001909b019a602093840193016102d6565b825f5260205f20601f830160051c81019160208410610368575b601f0160051c01905b81811061035d575061011a565b5f8155600101610350565b9091508190610347565b634e487b7160e01b5f52602260045260245ffd5b90607f1690610108565b634e487b7160e01b5f52604160045260245ffd5b604081019081106001600160401b0382111761039057604052565b601f909101601f19168101906001600160401b038211908210176103905760405256fe6080806040526004361015610012575f80fd5b5f905f3560e01c90816302bca7cf146142f3575080630594fd04146142d657806306a743d914614253578063087374b114613ff95780630bd9878214613fd15780630e316ab714613f3e5780631096366f14613ee757806313e7c9d814613eaf5780631785f53c14613e435780631807683914613dcc5780631bcdeaa214613bc65780631ebb4fdc14613b4d57806322b1098c14613b055780632301306a14613ae057806324d7806c14613aa6578063261ecd1d146138c657806326fc511b1461376757806327f68850146137275780632fbc42a11461370a578063308a0380146136ae57806330cdf0551461368b57806331632e89146136535780633381d5d514611fc257806333a12804146135655780633492600d1461347757806334d429ac146133f857806335679806146133a45780633a78b9b9146133675780633d6d3598146133205780633fc0f619146132b3578063429b62e51461327c578063437e3763146131c2578063459e60041461210f57806346b3d9c31461317957806347489ec01461316057806349045e161461313d578063498c35fd146130395780634a2a11f51461301d5780634e043a2f14612f2b578063534ef88314612ec957806357f1915d14612db757806358c97e4b14612d905780635be0af4414612d315780635f8d833114612c995780636187da2314612b9457806361d027b314612b685780636471af8314612a2e578063649777fd14612a075780636639bf57146129705780636d70f7ae146129355780636e15adec146129095780636e663ab81461275a5780636f27bc311461273757806370480275146126cd5780637213bd3b1461266257806373561c3b14612627578063751cc57a146125ef57806377487eb7146125885780637787bdbc1461214c5780637df73e271461210f5780637e2cd926146120a75780637f0166dd146120275780637f1dc74814611fff5780637f82f03e14611fc257806383b8a5ae14611f6e57806383ba715e14611f2757806383db4fec14611ed25780638af8198c14611e5e5780638cf18e6414611e1a5780638de02b3414611d805780639040a55814611cea5780639870d7fe14611c6b5780639f89789c14611c47578063a412922914611867578063a5c23ddf14611837578063a975b6d7146117e3578063ac8a584a14611765578063ae16f525146115d4578063afd9c2c914611526578063b18fdd05146114ed578063b4c4fa501461148f578063b5f666f914611454578063bba1264e1461125a578063bc4f7c8214611219578063bf4bdd79146111d0578063bfb0f7a414611185578063c49d52b514611167578063c60d4fb4146110c6578063c905a74614611089578063ca5610701461101b578063ce7185c314610fc4578063d02829e214610f34578063d0c5a1d314610e60578063d283538614610d1d578063dbbd8e2d14610c59578063e5c8b03d14610bec578063e614573c146109f6578063eb12d61e1461093c578063eccdbdab1461089b578063ee95799614610836578063f0cb5df614610722578063f1899c9e146106e3578063f217ba6c146105f1578063f438cc391461059b578063f6a44b3814610537578063f978fd61146105085763fa34043a146104b7575f80fd5b34610505576104c5366144b6565b92919091338152600260205260016040822054036104f65760406104ea858585615ab1565b82519182526020820152f35b631f0853c160e21b8152600490fd5b80fd5b50346105055760203660031901126105055760ff60406020926004358152601084522054166040519015158152f35b50346105055760203660031901126105055760408091610555614376565b61055d614dc2565b506001600160a01b03168152601360205220815161057a8161464e565b6020600160ff84541693848452015491019081528251918252516020820152f35b5034610505576060366003190112610505576105b5614376565b6105bd61438c565b338352600260205260016040842054036105e257906105df9160443591615a67565b80f35b631f0853c160e21b8352600483fd5b5034610505576020366003190112610505576004356001600160401b0381116106df57610622903690600401614468565b90338352600660205260ff604084205416156106d05781156106c157825b82811061064b578380f35b6001906001600160a01b03610669610664838787614ad7565b614a2e565b16808652600760205260ff604087205416156106bb5780865260076020526040862060ff19815416905561069e600954614862565b60095533905f516020615f615f395f51905f528780a35b01610640565b506106b5565b63b4fa3fb360e01b8352600483fd5b631cb9b3c360e01b8352600483fd5b5080fd5b50346105055760203660031901126105055760209060ff906040906001600160a01b0361070e614376565b168152600f84522054166040519015158152f35b5034610505576020366003190112610505576004356001600160401b0381116106df57610753903690600401614468565b3383526001602052600160408420540361082757825b818110610774578380f35b6001600160a01b0361078a610664838587614ad7565b1615610818576001906001600160a01b036107a9610664838688614ad7565b16855260056020526040852054828060a01b036107ca610664848789614ad7565b16865260056020526107de60408720614931565b828060a01b036107f2610664848789614ad7565b166040519182528660208301525f516020615ea15f395f51905f5260403393a301610769565b63fd684c3b60e01b8452600484fd5b63384da73760e01b8352600483fd5b5034610505576040366003190112610505576108936020916040610858614376565b6001600160a01b03811683526013855291206040519091906108798161464e565b84600160ff85541694858452015491015260243590615d46565b604051908152f35b5034610505576020366003190112610505576108b5614376565b33825281602052600160408320540361092d576001600160a01b03168082526006602052604082205460ff161561091e5780825260066020526040822060ff198154169055610905600854614862565b60085533905f516020615e615f395f51905f528380a380f35b63e1f6d3ad60e01b8252600482fd5b637bfa4b9f60e01b8252600482fd5b503461050557602036600319011261050557610956614376565b338252600660205260ff604083205416156109e7576001600160a01b031680156109d857808252600760205260ff6040832054166109c957808252600760205260408220600160ff198254161790556109b060095461478f565b60095533905f516020615f415f395f51905f528380a380f35b630e1857b360e21b8252600482fd5b63e6c4247b60e01b8252600482fd5b631cb9b3c360e01b8252600482fd5b5034610505576040366003190112610505576004356001600160401b0381116106df57610a27903690600401614468565b6024356001600160401b038111610be857610a46903690600401614468565b909133855260016020526001604086205403610bd957818103610bca57845b818110610a70578580f35b6001600160a01b03610a86610664838589614ad7565b1615610bbb57610a97818486614ad7565b358652600460205260ff60408720541615610bac576001906001600160a01b03610ac561066483868a614ad7565b16875260056020526040872054610b65610ae0838789614ad7565b35848060a01b03610af561066486898d614ad7565b168a5260056020526003868b8b60406002818420015492815196610b1888614618565b87526020870192428452828801948552610b436106648c60608b01988f8a528f8060a01b0394614ad7565b16815260056020522094518555518885015551600284015551151591016147e2565b610b7361066483868a614ad7565b90610b7f838789614ad7565b3560405191825260208201525f516020615ea15f395f51905f5260403393868060a01b031692a301610a65565b632015bd1360e11b8652600486fd5b63fd684c3b60e01b8652600486fd5b63512509d360e11b8552600485fd5b63384da73760e01b8552600485fd5b8380fd5b5034610505578060031936011261050557338152600760205260ff60408220541615610c4a5733815260076020526040812060ff198154169055610c31600954614862565b60095533335f516020615f615f395f51905f528380a380f35b63143606b960e31b8152600490fd5b503461050557602036600319011261050557604061016091610c79614376565b610c81614dda565b5060018060a01b03168152600a60205220600a60405191610ca183614669565b60ff8154161515835260018101546020840152600281015460408401526003810154606084015260048101546080840152600581015460a0840152600681015460c0840152600781015460e0840152600881015461010084015260098101546101208401520154610140820152610d1b6040518092614508565bf35b503461050557608036600319011261050557610d37614376565b60243590604435610d466143b6565b9133855260026020526001604086205403610e51576001600160a01b0316928315610e11577f996199d7179aeb585815ed35e5fe33af482726603891755fe1a853634caf2a6792606092610dfd604051610d9f81614618565b878152602080820186815260408084018681529615158985018181528c8e52600e909452908c20935184546001600160a01b0319166001600160a01b03919091161784559051600184015594516002830155511515906003016147e2565b60405192835260208301526040820152a280f35b60405162461bcd60e51b815260206004820152601860248201527724b73b30b634b21031b7b63630ba32b930b6103a37b5b2b760411b6044820152606490fd5b631f0853c160e21b8552600485fd5b50346105055760203660031901126105055760043533825260026020526001604083205403610f25576014546001600160a01b03168015610f16578083526013602052610ecd60408420604051610eb68161464e565b6020600160ff84541693848452015491015261509d565b80600a0290600a820403610f02578211610ef35782526013602052600160408320015580f35b6307d8b17160e41b8352600483fd5b634e487b7160e01b84526011600452602484fd5b633adf0c5360e11b8352600483fd5b631f0853c160e21b8252600482fd5b5034610505576020366003190112610505576040608091610f53614376565b610f5b614f83565b506001600160a01b03168152600560205220604051610f7981614618565b815491828252600181015460208301908152606060ff600360028501549460408701958652015416930192151583526040519384525160208401525160408301525115156060820152f35b503461050557610fd336614578565b91610fdc61486e565b506014546001600160a01b031693841561100c576101c0610fff868686866152a6565b610d1b60405180926143d4565b633adf0c5360e11b8152600490fd5b503461050557602036600319011261050557611035614376565b33825281602052600160408320540361092d576001600160a01b031680825260016020819052604083205533907f8e82d295288aabe804a503407d755226d5683a4bde33edcd5849e2c51e197d858380a380f35b5034610505576020366003190112610505576020906001906040906001600160a01b036110b4614376565b16815260138452200154604051908152f35b5034610505576020366003190112610505576110e0614376565b33825260026020526001604083205403610f25576001600160a01b031680156111585780825260136020526040822060405161111b8161464e565b6020600160ff8454169384845201549101521561114957601480546001600160a01b03191691909117905580f35b6377ed3bad60e11b8252600482fd5b633a001e0560e11b8252600482fd5b50346105055780600319360112610505576020600854604051908152f35b50346105055780600319360112610505576014546001600160a01b031680156111c1576040826001926020945260138452200154604051908152f35b633adf0c5360e11b8252600482fd5b50346105055760203660031901126105055760409060043581526012602052208054611215600183015492600360ff6002830154169101549060405194859485614498565b0390f35b503461050557604036600319011261050557611233614376565b61123b61438c565b903383526001602052600160408420540361082757906105df91615634565b5034610505576040366003190112610505576004356024356001600160401b03811161145057806004019060a06003198236030112610be85733845283602052600160408520540361144157828452600460205260ff60408520541615611432576044810135612710811161142357838552600460205260408520928035801515810361141f576112eb90856147e2565b6112fd60018501916024850190614a42565b906001600160401b03821161140b576113208261131a85546145c5565b856147f3565b8790601f83116001146113a15782608495936004979593611356938c92611396575b50508160011b915f199060031b1c19161790565b90555b600285015560648101356003850155013591015533907ff73c3981f3b4778eed7762bb81f29d27251da4979f555ce3e34f380046a265fc8380a380f35b013590505f80611342565b8389526020892091601f1984168a5b8181106113f357509260019285926084989660049a9896106113da575b505050811b019055611359565b01355f19600384901b60f8161c191690555f80806113cd565b919360206001819287870135815501950192016113b0565b634e487b7160e01b88526041600452602488fd5b8680fd5b6314bba91760e11b8552600485fd5b63e142361760e01b8452600484fd5b637bfa4b9f60e01b8452600484fd5b8280fd5b50346105055760403660031901126105055761146e614376565b33825260026020526001604083205403610f25576105df906024359061503c565b5034610505576060366003190112610505576114a9614376565b90602435906001600160401b038211610505576101a06003198336030112610505575060443580151581036114e9576101c092610fff9260040190614fd6565b5f80fd5b5034610505576020366003190112610505576020906040906001600160a01b03611515614376565b168152600d83522054604051908152f35b503461050557602036600319011261050557610160906040906001600160a01b0361154f614376565b168152600a6020522060ff815416906001810154906002810154600382015460048301546005840154600685015491600786015493600887015495600a6009890154980154986040519a15158b5260208b015260408a01526060890152608088015260a087015260c086015260e0850152610100840152610120830152610140820152f35b5034610505576020366003190112610505576004356001600160401b0381116106df57611605903690600401614468565b61161181939293614e2b565b9161161f60405193846146a1565b81835261162b82614e2b565b6020840190601f190136823761164083614e2b565b9461164e60405196876146a1565b83865261165a84614e2b565b602087019490601f1901368637835b8181106116f65750505060405194859460408601906040875251809152606086019290845b8181106116d4575050506020908583038287015251918281520192915b8181106116b9575050500390f35b825115158452859450602093840193909201916001016116ab565b82516001600160a01b031685528897506020948501949092019160010161168e565b959694959294926001906001600160a01b03611716610664838688614ad7565b168652600b602052818060a01b03604087205416611734828b614e42565b5261174b611746610664838688614ad7565b614f2e565b6117558287614e42565b9015159052019695949296611669565b50346105055760203660031901126105055761177f614376565b338252600160205260016040832054036117d4576001600160a01b0316808252600260205260408220545f1901610f2557808252600260205281604081205533905f516020615f815f395f51905f528380a380f35b63384da73760e01b8252600482fd5b5034610505576020366003190112610505576060906040906001600160a01b0361180b614376565b168152600a602052206006810154906009600a8201549101549060405192835260208301526040820152f35b5034610505576040366003190112610505576040611859602435600435614fa7565b825191825215156020820152f35b346114e95760203660031901126114e9576004356001600160401b0381116114e9578060040161012060031983360301126114e9576001600160a01b036118ad82614a2e565b165f52600f60205260ff60405f20541615611be2575b6101048201916118dd6118d68484614a42565b369161479d565b6020815191012090815f52601060205260ff60405f205416611b975760e481013592834211611bd35760248201359161271083118015611bc4575b611bb55760a48101359142831115611ba65761193381614a2e565b9060448301359760648401359260848501359460c40135986040519060208201925f516020615f215f395f51905f528452600160a01b600190031660408301528860608301528b60808301528560a08301528660c08301528760e08301528a61010083015261012082015261012081526119af610140826146a1565b5190206119bb9061556e565b906119c69083614a42565b36906119d19261479d565b6119da91615bd5565b6119e390615c0a565b6119ec90614d6b565b15611b975787600a7fafa3ee9ff419401419ed31f5401fb2992206536c8c187144d59ca3755f718ad89860a0985f52601060205260405f20600160ff198254161790556001808a1b03611a3e85614a2e565b165f5281602052600760405f2001546001808b1b03611a5c86614a2e565b165f5282602052600860405f2001546001808c1b03611a7a87614a2e565b165f5283602052600960405f200154916001808d1b03611a9988614a2e565b165f52846020528460405f200154938a8a8d60405199611ab88b614669565b60018b5260208b0191825260408b0190815260608b018d815260808c0193845260a08c0194855260c08c0195865260e08c019687526101008c019788526101208c019889526101408c01998a52916001600160a01b03611b178e614a2e565b165f528a602052611b2f60405f209c5115158d6147e2565b5160018c01555160028b01555160038a015551600489015551600588015551600687015551600786015551600885015551600984015551910155611b78600180881b0391614a2e565b16966040519485526020850152604084015260608301526080820152a2005b630e479e9960e21b5f5260045ffd5b63a9cf949160e01b5f5260045ffd5b63d1107a7960e01b5f5260045ffd5b50612710604482013511611918565b63fd1d678360e01b5f5260045ffd5b6001600160a01b03611bf382614a2e565b165f908152600f60205260409020805460ff191660011790556001600160a01b03611c1d82614a2e565b167ffb681d60cd4ec9a3a53e878e0667916cd932d9df9f7743a139e58afa9a64259c5f80a26118c3565b346114e9575f3660031901126114e957604060085460095482519182526020820152f35b346114e95760203660031901126114e957611c84614376565b335f526001602052600160405f205403611cdb576001600160a01b03165f818152600260205260408120600190553391907ff1e04d73c4304b5ff164f9d10c7473e2a1593b740674a6107975e2a7001c1e5c9080a3005b63384da73760e01b5f5260045ffd5b346114e95760203660031901126114e957611d03614376565b611d0b614f83565b5060018060a01b03165f52600e602052608060405f20604051611d2d81614618565b60018060a01b0382541691828252600181015460208301908152606060ff600360028501549460408701958652015416930192151583526040519384525160208401525160408301525115156060820152f35b346114e95760203660031901126114e957611d99614376565b6040516330cdf05560e01b81526001600160a01b038216600482015290602082602481305afa918215611e0f575f92611dd9575b6040836104ea846158f4565b91506020823d602011611e07575b81611df4602093836146a1565b810103126114e9579051906104ea611dcd565b3d9150611de7565b6040513d5f823e3d90fd5b346114e95760203660031901126114e9576001600160a01b03611e3b614376565b165f5260136020526040805f20600160ff82541691015482519182526020820152f35b346114e95760203660031901126114e9576004355f52600460205260405f2060ff815416611e8e600183016146c4565b916002810154906004600382015491015490611ebe6040519586951515865260a0602087015260a0860190614312565b926040850152606084015260808301520390f35b346114e95760203660031901126114e957611eeb614376565b5f604051611ef881614633565b5260018060a01b03165f52600c602052602060405f2060405190611f1b82614633565b54809152604051908152f35b346114e9575f3660031901126114e957335f526001602052600160405f205403611cdb57335f5260016020525f604081205533335f516020615ee15f395f51905f525f80a3005b346114e9575f3660031901126114e957335f525f602052600160405f205403611fb357335f525f6020525f604081205533335f516020615ec15f395f51905f525f80a3005b637bfa4b9f60e01b5f5260045ffd5b346114e95760203660031901126114e9576001600160a01b03611fe3614376565b165f526006602052602060ff60405f2054166040519015158152f35b346114e95760203660031901126114e957602061201d611746614376565b6040519015158152f35b346114e95760203660031901126114e9576120406143c5565b335f526002602052600160405f2054036120985760207f05278e2faa68922bfef2c25501f766668736214ed2d748288e03a8d2133b0a7591151560115461ff008260081b169061ff00191617601155604051908152a1005b631f0853c160e21b5f5260045ffd5b346114e9576120b536614578565b916120be61486e565b506014546001600160a01b03168015612100576101c093836120e66080610fff960135615434565b5095919050856120f7575b5061547f565b159450876120f1565b633adf0c5360e11b5f5260045ffd5b346114e95760203660031901126114e9576001600160a01b03612130614376565b165f526007602052602060ff60405f2054166040519015158152f35b346114e95760803660031901126114e957612165614376565b602435906001600160401b0382116114e9576101a060031983360301126114e95760643591604435906001600160a01b03841684036114e9576121a661486e565b9260c4820135906121bc8260a4850135866158dc565b95911591610124840135831561240657619c406121d98288614b08565b0460208801525b6101808701526040516330cdf05560e01b81526001600160a01b038316600482018190529092602084602481305afa938415611e0f575f946123d0575b506122316122439160408a019586526158f4565b9260c089019384526084870135615961565b92836101408901526020880193845160405193630dccc75760e31b85526004850152602484015260448301525f6064830152606082608481305afa928315611e0f575f925f9461237f575b5082895261010089019384528451825161271003612710811161236b576122b491614b08565b9481516127100390612710821161236b576127106122f961230f958d61016084996305f5e1006122ea6123079961231f9f614b08565b04109101528351905190614b08565b04908160808d015251614b39565b905190614b08565b0460e08701528551905190614b39565b610120850152156123455750506101c091505f6101a0820152610d1b60405180926143d4565b610144013560028110156114e9576101c093612360926159f6565b6101a0820152610fff565b634e487b7160e01b5f52601160045260245ffd5b925092506060823d6060116123c8575b8161239c606093836146a1565b810103126114e957815160208301516040909301516001600160a01b038116036114e95791928a61228e565b3d915061238f565b9093506020813d6020116123fe575b816123ec602093836146a1565b810103126114e957519261223161221d565b3d91506123df565b61014485013560028110156114e9578061258257885b5f9183612430575b505060208801526121e0565b61243b908b8a6159f6565b9081151580612570575b15612424579091507b0119799812dea11197f27f0f6e885c8ba7eb31f476caf7411a86338781116125195761271083116124c9576124838284614b08565b91670de0b6b3a76400000391670de0b6b3a7640000831161236b576124b0926124ab91614b08565b614b08565b6b1d6329f1c35ca4bfabb9f56160281b90048980612424565b60405162461bcd60e51b815260206004820152602260248201527f43616c63756c61746f7248656c7065723a20696e76616c696420666565207261604482015261746560f01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602960248201527f43616c63756c61746f7248656c7065723a206f7574636f6d65546f6b656e7320604482015268746f6f206c6172676560b81b6064820152608490fd5b50670de0b6b3a7640000821115612445565b8661241c565b346114e95760203660031901126114e9576001600160a01b036125a9614376565b165f52600e602052608060405f2060018060a01b038154169060018101549060ff6003600283015492015416916040519384526020840152604083015215156060820152f35b346114e95760203660031901126114e9576001600160a01b03612610614376565b165f52600c602052602060405f2054604051908152f35b346114e95760203660031901126114e9576001600160a01b03612648614376565b165f5260016020526020600160405f205414604051908152f35b346114e95760203660031901126114e95761267b6143c5565b335f526002602052600160405f2054036120985760207f8828972f46c2f707f1ab9eb979e7832a17059cc5702692d7b4551a0324c7f11191151560ff196011541660ff821617601155604051908152a1005b346114e95760203660031901126114e9576126e6614376565b335f525f602052600160405f205403611fb35760018060a01b0316805f525f602052600160405f205533907ff9ffabca9c8276e99321725bcb43fb076a6c66a54b7f21c4e8146d8519b417dc5f80a3005b346114e95760203660031901126114e9576020610893612755614376565b614ed1565b346114e95760803660031901126114e957612773614376565b604435905f60243580606435806128da575b50506040516330cdf05560e01b81526001600160a01b0384166004820181905291908190602081602481305afa908115611e0f575f916128a8575b5080612879575b50506127d2846158f4565b80612838575b506127ef906060958082105f146128305794615856565b9081612811575b50604051928352602083015260018060a01b03166040820152f35b5f908152600b60205260409020546001600160a01b03169150846127f6565b508094615856565b612710811161286a576127100394612710861161236b576127106128616060976127ef94614b08565b049150946127d8565b6304cbf51b60e51b5f5260045ffd5b909150612710811161286a576127100390612710821161236b57612710916128a091614b08565b0485806127c7565b90506020813d6020116128d2575b816128c3602093836146a1565b810103126114e95751876127c0565b3d91506128b6565b909150612710811161286a576127100390612710821161236b576127109161290191614b08565b048480612785565b346114e95760403660031901126114e957602061201d612927614376565b61292f61438c565b90614e56565b346114e95760203660031901126114e9576001600160a01b03612956614376565b165f5260026020526020600160405f205414604051908152f35b346114e95760603660031901126114e957612989614376565b6024359060ff82168092036114e957335f526002602052600160405f205403612098576001600160a01b031680156129f857600190604051926129cb8461464e565b8352602083019060443582525f52601360205260ff60405f2093511660ff19845416178355519101555f80f35b633a001e0560e11b5f5260045ffd5b346114e9575f3660031901126114e95760206040515f516020615f015f395f51905f528152f35b346114e9575f3660031901126114e957600354612a4a81614e2b565b90612a5860405192836146a1565b808252601f19612a6782614e2b565b015f5b818110612b515750505f5b818110612ae357826040518091602082016020835281518091526040830190602060408260051b8601019301915f905b828210612ab457505050500390f35b91936001919395506020612ad38192603f198a82030186528851614336565b9601920192018594939192612aa5565b806001915f52600460205260405f20600460405191612b01836145fd565b60ff81541615158352612b158582016146c4565b6020840152600281015460408401526003810154606084015201546080820152612b3f8286614e42565b52612b4a8185614e42565b5001612a75565b602090612b5c614764565b82828701015201612a6a565b346114e9575f3660031901126114e95760115460405160109190911c6001600160a01b03168152602090f35b346114e95760203660031901126114e957610180612bb0614376565b612bb8614dda565b506001600160a01b03165f818152600a602052604090819020905191612bdd83614669565b60ff8254161515835260018201546020840152600282015460408401526003820154606084015260048201546080840152600a60058301549260a08501938452600681015460c0860152600781015460e08601526008810154610100860152600981015461012086015201546101408401525f52600f60205260ff60405f2054169081612c8e575b81612c83575b50612c796040518093614508565b1515610160820152f35b905051421083612c6b565b825115159150612c65565b346114e95760203660031901126114e9576060612cb4614376565b612cbc614dc2565b505f604051612cca81614633565b5260018060a01b0316805f52600b60205260405f2090600160405192612cef8461464e565b818060a01b038154168452015460208301525f52600d60205260405f2060405190612d1982614633565b548152612d2960405180936144f0565b516040820152f35b346114e95760203660031901126114e9576001600160a01b03612d52614376565b165f526005602052608060405f2080549060018101549060ff6003600283015492015416916040519384526020840152604083015215156060820152f35b346114e9575f3660031901126114e95760206040515f516020615f215f395f51905f528152f35b346114e95760403660031901126114e9576020612dd2614376565b6001600160a01b03165f8181526013835260409081902090516024359290612df98161464e565b84600160ff8554169485845201549101528115612e64575b5060ff81166006811115612e3b575090612e35612e3061089393615d33565b61509d565b90614b08565b60061115612e5e5790612e53612e30612e5993615d22565b90614b1b565b610893565b50610893565b60405163313ce56760e01b815291508390829060049082905afa5f9181612e9a575b50612e95575060125b83612e11565b612e8f565b612ebb919250843d8611612ec2575b612eb381836146a1565b810190615d09565b9084612e86565b503d612ea9565b346114e95760203660031901126114e957612ee2614376565b612eea614dc2565b5060018060a01b03165f52600b6020526040805f206001825191612f0d8361464e565b818060a01b03815416835201546020820152610d1b825180926144f0565b346114e95760203660031901126114e9576080612f46614376565b612f4e614dc2565b5060018060a01b0381165f52600b60205260405f2090612f90600160405193612f768561464e565b818060a01b03815416855201549160208401928352614f2e565b82519091906001600160a01b031680151580613016575b1561300d575f52600a602052600360405f20015480155f14612fe35750505f905b612fd560405180946144f0565b151560408301526060820152f35b612fed9151614afb565b4281111561300657613000904290614b39565b90612fc8565b505f613000565b50505f90612fc8565b5082612fa7565b346114e9575f3660031901126114e9576020604051610fa08152f35b346114e95760403660031901126114e957613052614376565b60243590335f526001602052600160405f205403611cdb576001600160a01b0316801561312e57815f52600460205260ff60405f2054161561311f57805f52600560205260405f205491815f5260056020526130ff600260405f2001546003604051916130be83614618565b848352602083019042825260408401908152606084019160018352875f52600560205260405f209451855551600185015551600284015551151591016147e2565b60405192835260208301525f516020615ea15f395f51905f5260403393a3005b632015bd1360e11b5f5260045ffd5b63fd684c3b60e01b5f5260045ffd5b346114e95760203660031901126114e957602061201d61315b614376565b614d6b565b346114e95760406104ea613173366144b6565b91614b46565b346114e95760203660031901126114e957613192614376565b5f60405161319f81614633565b5260018060a01b03165f52600d602052602060405f2060405190611f1b82614633565b346114e95760203660031901126114e9576131db614376565b335f525f602052600160405f205403611fb3576001600160a01b0316801561326d57805f52600660205260ff60405f20541661325e57805f52600660205260405f20600160ff1982541617905561323360085461478f565b60085533907f1b3bdd505031d7440a31317a144c6a332fd0894111458e9ccf17a35191261f765f80a3005b63032cd66d60e61b5f5260045ffd5b63e6c4247b60e01b5f5260045ffd5b346114e95760203660031901126114e9576001600160a01b0361329d614376565b165f525f602052602060405f2054604051908152f35b346114e95760203660031901126114e9576132cc614376565b335f525f602052600160405f205403611fb3576001600160a01b03165f818152600160205260409020545f1901611cdb57805f5260016020525f604081205533905f516020615ee15f395f51905f525f80a3005b346114e9575f3660031901126114e957335f526002602052600160405f20540361209857335f5260026020525f604081205533335f516020615f815f395f51905f525f80a3005b346114e95760203660031901126114e9576001600160a01b03613388614376565b165f52600f602052602060ff60405f2054166040519015158152f35b346114e95760203660031901126114e9576001600160a01b036133c5614376565b165f52600a602052602060405f2060ff81541690816133ea575b506040519015158152f35b6005915001544210826133df565b346114e95760a03660031901126114e957613411614376565b61341961438c565b90608435906001600160a01b03821682036114e957335f526002602052600160405f2054036120985761345091604435918461577c565b60018060a01b03165f52600a602052600760405f20016134736064358254614afb565b9055005b346114e95760203660031901126114e9576004356001600160401b0381116114e9576134a7903690600401614468565b90335f52600660205260ff60405f20541615613556578115613547575f5b8281106134ce57005b6001600160a01b036134e4610664838686614ad7565b1690811561326d57816001925f52600760205260ff60405f20541661354157805f52600760205260405f208360ff1982541617905561352460095461478f565b60095533905f516020615f415f395f51905f525f80a35b016134c5565b5061353b565b63b4fa3fb360e01b5f5260045ffd5b631cb9b3c360e01b5f5260045ffd5b346114e95760a03660031901126114e9576004356024356044356135876143b6565b9160843592335f526002602052600160405f20540361209857610fa082118015613648575b613639577f727c14dbdafd47c44927d04ba768ca9942afb2ecfa8917aec6d46504ac41f13093613634916040516135e281614618565b84815260036020820187815261362460408401851515815260608501928784528c5f52601260205260405f2095518655516001860155511515600285016147e2565b5191015560405194859485614498565b0390a2005b6301ce37c360e01b5f5260045ffd5b50610fa083116135ac565b346114e95760203660031901126114e9576001600160a01b03613674614376565b165f526001602052602060405f2054604051908152f35b346114e95760203660031901126114e95760206108936136a9614376565b614a74565b346114e9575f3660031901126114e957335f52600660205260ff60405f2054161561355657335f52600660205260405f2060ff1981541690556136f2600854614862565b60085533335f516020615e615f395f51905f525f80a3005b346114e9575f3660031901126114e9576020600954604051908152f35b346114e95760203660031901126114e9576014546001600160a01b03161561210057611215613757600435615434565b9060409492945194859485614498565b346114e95760203660031901126114e9576004356001600160401b0381116114e95780600401608060031983360301126114e9576001600160a01b036137ac82614a2e565b1633036138b75760648201906137c56118d68383614a42565b6020815191012092835f52601060205260ff60405f205416611b9757604481013592834211611bd35761387661315b916138706118d661386961387e96602461380d8a614a2e565b9101996138198b614a2e565b906040519160208301935f516020615f015f395f51905f52855260018060a01b0316604084015260018060a01b0316606083015260808201526080815261386160a0826146a1565b51902061556e565b9287614a42565b90615bd5565b919091615c0a565b15611b97576138a96138af916138b5945f52601060205260405f20600160ff19825416179055614a2e565b91614a2e565b90615634565b005b636ee77e2960e01b5f5260045ffd5b346114e95760a03660031901126114e9576138df614376565b602435906001600160401b0382116114e9576101a060031983360301126114e9576044356001600160401b0381116114e95761391f903690600401614468565b90916084356001600160401b0381116114e957613940903690600401614468565b93909461394b61486e565b506014546001600160a01b031692831561210057836139939261396c61486e565b5061397a6084820135615434565b509491905084613a9d575b50606435916004019061547f565b9461399d84614e2b565b946139ab60405196876146a1565b848652601f196139ba86614e2b565b015f5b818110613a865750505f5b858110613a305760405180886101e082016139e3838d6143d4565b6101e06101c08401528151809152602061020084019201905f5b818110613a0b575050500390f35b9193509160206101c082613a2260019488516143d4565b0194019101918493926139fd565b80613a6a86613a4d6020613a476001968c8b61554b565b01614a2e565b613a58848b8a61554b565b613a6385888a614ad7565b35916152a6565b613a74828a614e42565b52613a7f8189614e42565b50016139c8565b602090613a9161486e565b82828b010152016139bd565b1593508a613985565b346114e95760203660031901126114e9576001600160a01b03613ac7614376565b165f525f6020526020600160405f205414604051908152f35b346114e9575f3660031901126114e957602060ff60115460081c166040519015158152f35b346114e95760203660031901126114e9576001600160a01b03613b26614376565b165f52600b6020526040805f206001808060a01b0382541691015482519182526020820152f35b346114e95760403660031901126114e95760043560243590811515918281036114e957335f525f602052600160405f205403611fb357613b9890825f52600460205260405f206147e2565b6040519182527f3ba1e4aaeb9438f838c8418b7f4ba93a32aa7c37d79277b7ced11e47c20042b360203393a3005b346114e95760603660031901126114e957613bdf614376565b6024356001600160401b0381116114e9578036036101a06003198201126114e9576044356001600160a01b03811692908390036114e957613c1e61486e565b5060a48101359160405194631de1ef6f60e21b865260018060a01b03166004860152608060248601528160040135608486015260018060a01b03613c64602484016143a2565b1660a48601526001600160a01b03613c7e604484016143a2565b1660c48601526001600160a01b03613c98606484016143a2565b1660e486015260848201356101048601528261012486015260c482013561014486015260e48201356101648601526101048201356101848601526101248201356101a486015261014482013560028110156114e957613cfc906101c4870190614a00565b61016482013560038110156114e957613d1a906101e4870190614a21565b61018482013590602219018112156114e957016004810135916024909101906001600160401b0383116114e95782360382136114e9576101c093613d7086949385946101a0610204870152610224860191614842565b91604484015260648301520381305afa8015611e0f576101c0915f91613d9f575b50610d1b60405180926143d4565b613dbf9150823d8111613dc5575b613db781836146a1565b810190614948565b82613d91565b503d613dad565b346114e95760203660031901126114e957613de5614376565b335f526001602052600160405f205403611cdb576001600160a01b0316801561312e575f81815260056020526040902080549190613e2290614931565b6040519182525f60208301525f516020615ea15f395f51905f5260403393a3005b346114e95760203660031901126114e957613e5c614376565b335f525f602052600160405f205403611fb3576001600160a01b03165f818152602081905260409020545f1901611fb357805f525f6020525f604081205533905f516020615ec15f395f51905f525f80a3005b346114e95760203660031901126114e9576001600160a01b03613ed0614376565b165f526002602052602060405f2054604051908152f35b346114e95760803660031901126114e957613f00614376565b6024356001600160401b0381116114e9576101a060031982360301126114e9576101c091610fff91613f306143b6565b9160443591600401906148d4565b346114e95760203660031901126114e957613f57614376565b335f52600660205260ff60405f20541615613556576001600160a01b03165f8181526007602052604090205460ff1615613fc257805f52600760205260405f2060ff198154169055613faa600954614862565b60095533905f516020615f615f395f51905f525f80a3005b63bdb712ef60e01b5f5260045ffd5b346114e9575f3660031901126114e9576014546040516001600160a01b039091168152602090f35b346114e95760803660031901126114e9576004356001600160401b0381116114e957366023820112156114e9576004810135906001600160401b0382116114e957602481019060248336920101116114e95760243560443592335f525f602052600160405f205403611fb3576127108211614244576003549261407b8461478f565b6003556040519161408b836145fd565b6001835261409a36828461479d565b956020840196875260408401928584526060850182815260808601906064358252885f5260046020526140d460405f2097511515886147e2565b98518051999095600188016001600160401b038c11614230578b8b9861410060209e61131a85546145c5565b8d90601f83116001146141905792614148835f516020615e815f395f51905f529c9d97946004979461416d9b9a975f926141855750508160011b915f199060031b1c19161790565b90555b5160028501555160038401555191015560405193606085526060850191614842565b948783015260408201528033940390a3604051908152f35b015190505f80611342565b601f9b9695949392919b19821690835f528c5f20915f5b8181106141fc57509260049694925f516020615e815f395f51905f529d9e6001938361416d9d9c9b9997106141e4575b505050811b01905561414b565b01515f1960f88460031b161c191690555f80806141d7565b939597999b9d9496989a9c5090916020600181928786015181550195019301908f9c9a98969492919d9b999795939d6141a7565b634e487b7160e01b5f52604160045260245ffd5b6314bba91760e11b5f5260045ffd5b346114e95760203660031901126114e95761426c614764565b506004355f52600460205261121560405f2060046040519161428d836145fd565b60ff815416151583526142a2600182016146c4565b6020840152600281015460408401526003810154606084015201546080820152604051918291602083526020830190614336565b346114e9575f3660031901126114e9576020600354604051908152f35b346114e9575f3660031901126114e95760209060ff6011541615158152f35b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b90815115158152608080614359602085015160a0602086015260a0850190614312565b936040810151604085015260608101516060850152015191015290565b600435906001600160a01b03821682036114e957565b602435906001600160a01b03821682036114e957565b35906001600160a01b03821682036114e957565b6064359081151582036114e957565b6004359081151582036114e957565b6101a08091805184526020810151602085015260408101516040850152606081015160608501526080810151608085015260a081015160a085015260c081015160c085015260e081015160e085015261010081015161010085015261012081015161012085015261014081015161014085015261016081015115156101608501526101808101516101808501520151910152565b9181601f840112156114e9578235916001600160401b0383116114e9576020808501948460051b0101116114e957565b90949392606092608083019683526020830152151560408201520152565b60609060031901126114e9576004356001600160a01b03811681036114e95790602435906044356001600160a01b03811681036114e95790565b80516001600160a01b03168252602090810151910152565b61014080918051151584526020810151602085015260408101516040850152606081015160608501526080810151608085015260a081015160a085015260c081015160c085015260e081015160e08501526101008101516101008501526101208101516101208501520151910152565b9060606003198301126114e9576004356001600160a01b03811681036114e95791602435906001600160401b0382116114e9576101a09082900360031901126114e9576004019060443590565b90600182811c921680156145f3575b60208310146145df57565b634e487b7160e01b5f52602260045260245ffd5b91607f16916145d4565b60a081019081106001600160401b0382111761423057604052565b608081019081106001600160401b0382111761423057604052565b602081019081106001600160401b0382111761423057604052565b604081019081106001600160401b0382111761423057604052565b61016081019081106001600160401b0382111761423057604052565b6101c081019081106001600160401b0382111761423057604052565b601f909101601f19168101906001600160401b0382119082101761423057604052565b9060405191825f8254926146d7846145c5565b808452936001811690811561474257506001146146fe575b506146fc925003836146a1565b565b90505f9291925260205f20905f915b8183106147265750509060206146fc928201015f6146ef565b602091935080600191548385890101520191019091849261470d565b9050602092506146fc94915060ff191682840152151560051b8201015f6146ef565b60405190614771826145fd565b5f608083828152606060208201528260408201528260608201520152565b5f19811461236b5760010190565b9192916001600160401b03821161423057604051916147c6601f8201601f1916602001846146a1565b8294818452818301116114e9578281602093845f960137010152565b9060ff801983541691151516179055565b601f821161480057505050565b5f5260205f20906020601f840160051c83019310614838575b601f0160051c01905b81811061482d575050565b5f8155600101614822565b9091508190614819565b908060209392818452848401375f828201840152601f01601f1916010190565b801561236b575f190190565b6040519061487b82614685565b5f6101a0838281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e082015282610100820152826101208201528261014082015282610160820152826101808201520152565b9291906148df61486e565b506014546001600160a01b0316928315612100571561492857908161490c608061491c9695940135615434565b50959190508561491f575061547f565b90565b1594505f6120f1565b61491c936152a6565b60035f918281558260018201558260028201550155565b90816101c09103126114e9576040519061496182614685565b805182526020810151602083015260408101516040830152606081015160608301526080810151608083015260a081015160a083015260c081015160c083015260e081015160e08301526101008101516101008301526101208101516101208301526101408101516101408301526101608101519081151582036114e9576101a09161016084015261018081015161018084015201516101a082015290565b906002821015614a0d5752565b634e487b7160e01b5f52602160045260245ffd5b906003821015614a0d5752565b356001600160a01b03811681036114e95790565b903590601e19813603018212156114e957018035906001600160401b0382116114e9576020019181360383136114e957565b6001600160a01b03165f908152600560205260409020600381015460ff1615614ac857545f52600460205260405f2060ff81541615614ac85760038101548015159081614acd575b50614ac8576002015490565b505f90565b905042115f614abc565b9190811015614ae75760051b0190565b634e487b7160e01b5f52603260045260245ffd5b9190820180921161236b57565b8181029291811591840414171561236b57565b8115614b25570490565b634e487b7160e01b5f52601260045260245ffd5b9190820391821161236b57565b6001600160a01b038082165f908152600b6020526040902054929493919216908115614d6057815f52600a60205260405f209260ff8454161590811590818390614d52575b8015614d42575b15614d2657614ba25f8099614b39565b9291614d17575b8115614d05575b5015614bbe57505050505f90565b614bd2612710916002869896015490614b08565b04948515159081614cf7575b50614bea575b50509190565b60405191636f27bc3160e01b83526004830152602082602481305afa918215611e0f575f92614cc3575b5081614c5460018060a01b03831692835f5260136020528760405f2091604051614c3d8161464e565b6020600160ff865416958684520154910152615d46565b1115614be457909193505f52600e60205260405f2060ff60038201541680614cb6575b15614caf57600281015491604d831161236b57614c9d600191614ca694600a0a90614b08565b91015490614b1b565b915b5f80614be4565b5091614ca8565b5060018101541515614c77565b9091506020813d602011614cef575b81614cdf602093836146a1565b810103126114e95751905f614c14565b3d9150614cd2565b60069150015415155f614bde565b614d10915084614e56565b155f614bb0565b60058601544210159150614ba9565b614ba2612710614d3a60018901548b614b08565b048099614b39565b50614d4c81614f2e565b15614b92565b506005860154421015614b8b565b50505090505f905f90565b6001600160a01b03165f8181526007602052604090205460ff16908115614daa575b8115614d97575090565b90505f525f602052600160405f20541490565b8091505f52600660205260ff60405f20541690614d8d565b60405190614dcf8261464e565b5f6020838281520152565b60405190614de782614669565b5f610140838281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e082015282610100820152826101208201520152565b6001600160401b0381116142305760051b60200190565b8051821015614ae75760209160051b010190565b6001600160a01b039182165f908152600b60205260409020805490929182169116819003614ecb575f52600a60205260405f2060ff60115460081c16614eba576004015480158015614ec1575b614eba576001614eb4920154614afb565b42111590565b5050600190565b505f198114614ea3565b50505f90565b6001600160a01b03165f908152600a602052604090206006810154908115614f275762278d00600a8201540462278d004204115f14614f1e57505f5b81811015614ecb5761491c91614b39565b60090154614f0d565b50505f1990565b6001600160a01b039081165f908152600b6020526040902080549091168015614ecb575f52600a60205260405f2060ff60115416614eba576003015480158015614ec157614eba576001614eb4920154614afb565b60405190614f9082614618565b5f6060838281528260208201528260408201520152565b6014546001600160a01b0316801561210057614fc291615961565b808210614fcf5750905f90565b9160019150565b919091614fe161486e565b506014546001600160a01b0316918215612100571561502d5790829161500d608061491c950135615434565b509491905084615024575b5060a08101359161547f565b1593505f615018565b8260a061491c940135916152a6565b81156150995760407f2136f9ac419f7f8d55572b5e123f4b7c8021446b15168de5a6034589797bc1479160018060a01b031692835f5260056020526002825f200190615089818354614afb565b80925582519182526020820152a2565b5050565b60ff16604d811161236b57600a0a90565b91906101a0838203126114e957604051906101a082016001600160401b03811183821017614230576040528193803583526150eb602082016143a2565b60208401526150fc604082016143a2565b604084015261510d606082016143a2565b60608401526080810135608084015260a081013560a084015260c081013560c084015260e081013560e084015261010081013561010084015261012081013561012084015261014081013560028110156114e95761014084015261016081013560038110156114e957610160840152610180810135906001600160401b0382116114e9570181601f820112156114e957610180918160206151b09335910161479d565b910152565b929493906101806060936152909260018060a01b03168652608060208701528051608087015260018060a01b0360208201511660a087015260018060a01b0360408201511660c087015260018060a01b03858201511660e0870152608081015161010087015260a081015161012087015260c081015161014087015260e0810151610160870152610100810151828701526101208101516101a08701526152666101408201516101c0880190614a00565b61527a6101608201516101e0880190614a21565b01516101a0610200860152610220850190614312565b60408401959095526001600160a01b0316910152565b6152ae61486e565b6152c160c084013560a0850135866158dc565b6101408401359060028210156114e9575f8261542e5760808601355b6153d1576152f860806152f036896150ae565b970135615434565b5090506153c5575b506101208601511561536257505050506153336101c093946040519586948594631de1ef6f60e21b8652600486016151b5565b0381305afa908115611e0f575f91615349575090565b61491c91506101c03d8111613dc557613db781836146a1565b928097505f929550602091945001525f85525f6101208601525f60408601525f60808601525f60c08601525f60e08601525f6101008601525f6101408601525f6101608601525f6101808601526114e9576153bc926159f6565b6101a082015290565b6101208701525f615300565b5092509250949350615426925f60208701525f86525f6101208701525f60408701525f60808701525f60c08701525f60e08701525f6101008701525f6101408701525f6101608701525f6101808701526159f6565b6101a0830152565b5f6152dd565b5f52601260205260405f2060606040519261544e84614618565b8254808552600184015494856020820152600360ff6002870154161515958660408401520154938491015293929190565b9091939261548b61486e565b506101408301359360028510156114e9576101c0946155455760808401355b6154fb5750615333906154ca60806154c236876150ae565b950135615434565b509091506154ef575b50604051631de1ef6f60e21b81529586948594600486016151b5565b6101208501525f6154d3565b1561551357615333906154ca60806154c236876150ae565b916155226153339136906150ae565b925f6101208501526040519586948594631de1ef6f60e21b8652600486016151b5565b5f6154aa565b9190811015614ae75760051b8101359061019e19813603018212156114e9570190565b60405160208101907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f82527fcfa110dfb1be42028a1f97c0e7b75aea85f983e75bbcdb2b74ed224e07b238b360408201527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260a0815261560060c0826146a1565b5190209060405190602082019261190160f01b8452602283015260428201526042815261562e6062826146a1565b51902090565b6001600160a01b0316908115801561576b575b611bb5576001600160a01b03169080821461575c57815f52600f60205260ff60405f2054161561574d575f818152600b60205260409020546001600160a01b031661573e57815f52600a60205260405f2060ff815416158015615730575b611ba6576008906040516156b88161464e565b8481524260208083019182525f868152600b90915260409020915182546001600160a01b0319166001600160a01b039190911617825551600191909101550180546157029061478f565b90557f157322274a254e4f8bf1dda281061d8e5f24da79992ca7d2fae28cd8c9d010966020604051428152a3565b5060058101544210156156a5565b6315c6aa1d60e31b5f5260045ffd5b630938e63560e41b5f5260045ffd5b63dc33424560e01b5f5260045ffd5b506001600160a01b03811615615647565b9060207f200b84a0b2da981fe422e91bcde91702f71bb9efa43354b4bb8040e7566bddb2919493946157e060018060a01b03871696875f52601384528660405f20916040516157ca8161464e565b86600160ff865416958684520154910152615d46565b9360018060a01b031693845f52600c835260405f20615800828254614afb565b9055845f52600a8352615833600960405f20600a810162278d0081540462278d00420411615848575b5001918254614afb565b90556040519485526001600160a01b031693a4565b5f838301554290555f615829565b6001600160a01b038181165f908152600b60205260409020541680156158d557805f52600a60205260405f209160ff835416159182156158c6575b82156158b4575b5050614ecb576127109160026158b092015490614b08565b0490565b6158be9250614e56565b155f80615898565b60058401544210159250615891565b5050505f90565b81156158d55761491c926158ef91614b08565b614b1b565b6001600160a01b038181165f908152600b6020526040902054168015614ecb575f52600a60205260405f209060ff82541615908115615952575b8115615941575b50614ac8576001015490565b61594b9150614f2e565b155f615935565b6005830154421015915061592e565b906020915f526012825260405f206040519161597c83614618565b815483526001820154848401526060600360ff600285015416936040860194151585520154930192835260018060a01b03165f526013835260405f206001604051916159c78361464e565b60ff815416835201549384910152511515806159ec575b6159e6575090565b90505190565b50805115156159de565b90916002811015614a0d5715615a36578015614ecb57670de0b6b3a7640000820291808304670de0b6b3a7640000149015171561236b5761491c91614b1b565b908015614ecb57670de0b6b3a7640000820291808304670de0b6b3a7640000149015171561236b5761491c91614b1b565b9190615a8e9160018060a01b0382165f52601360205260405f2091604051614c3d8161464e565b9060018060a01b03165f52600d602052615aad60405f20918254614afb565b9055565b6040805163011d227b60e61b81526001600160a01b038084166004830181905260248301869052908616604483015295949093929184606481305afa958615611e0f575f945f97615b9a575b5084158781615b91575b50615b845791600791615b50935f52600b6020528760405f209160018060a01b038354165f52600a60205260405f209388615b74575b82615b56575b5050505001918254614afb565b90559190565b9254615b6b93906001600160a01b031661577c565b5f878180615b43565b615b7f898383615a67565b615b3d565b505050505090505f905f90565b9050155f615b07565b945095506040843d604011615bcd575b81615bb7604093836146a1565b810103126114e95760208451940151955f615afd565b3d9150615baa565b9060418151145f14615c0157615bfd91602082015190606060408401519301515f1a90615def565b9091565b50505f90600290565b6005811015614a0d5780615c1b5750565b60018103615c635760405162461bcd60e51b815260206004820152601860248201527745434453413a20696e76616c6964207369676e617475726560401b6044820152606490fd5b60028103615cb05760405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e677468006044820152606490fd5b600314615cb957565b60405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b6064820152608490fd5b908160209103126114e9575160ff811681036114e95790565b60ff166006039060ff821161236b57565b60ff6005199116019060ff821161236b57565b91909160ff821615615d8e575b5060ff81166006811115615d72575090612e53612e3061491c93615d33565b60061115615d8a5790612e35612e3061491c93615d22565b5090565b60405163313ce56760e01b81529150602090829060049082906001600160a01b03165afa5f9181615dce575b50615dc9575060125b5f615d53565b615dc3565b615de891925060203d602011612ec257612eb381836146a1565b905f615dba565b6fa2a8918ca85bafe22016d0b997e4df60600160ff1b038411615e55576020935f9360ff60809460405194855216868401526040830152606082015282805260015afa15611e0f575f516001600160a01b03811615615e4d57905f90565b505f90600190565b505050505f9060039056fe2c0795c946bf9fc2990be5d999989c8731f0852d4d27207d9dba34d96b78b7e6b96b70435fd2b7ae3ffc1de23c3b83da4d67758da2c5465211f7a526b3ff315d48d589dda401158269c9d1513b0ba3a30f014349b3cf156d5fd54652c092b550787a2e12f4a55b658b8f573c32432ee11a5e8b51677d1e1e937aaf6a0bb5776eed61fd7fbf88da1ed91a05df546e45243bc864eca2aba942fcb1fa4467cc326e1c15ac55c7ef7b31ba97b2f03c7a893b863e08011bc446a82d115450a9b1fadd03cd16f1ea4fdd4593cdc713e46d99e3765c519542ce4b05ae649a52b7211f3712146497b3b826918ec47f0cac7272a09ed06b30c16c030e99ec48ff5dd60b4798d1ebbe00ae92a5de96a0f49742a8afa89f42363592bc2e7cfaaed68b45e7a6f7262ed0443cc211121ceb1a80d69004f319245615a7488f951f1437fd91642ca164736f6c634300081b000a00000000000000000000000055d398326f99059ff775485246999027b3197955

Deployed Bytecode

0x6080806040526004361015610012575f80fd5b5f905f3560e01c90816302bca7cf146142f3575080630594fd04146142d657806306a743d914614253578063087374b114613ff95780630bd9878214613fd15780630e316ab714613f3e5780631096366f14613ee757806313e7c9d814613eaf5780631785f53c14613e435780631807683914613dcc5780631bcdeaa214613bc65780631ebb4fdc14613b4d57806322b1098c14613b055780632301306a14613ae057806324d7806c14613aa6578063261ecd1d146138c657806326fc511b1461376757806327f68850146137275780632fbc42a11461370a578063308a0380146136ae57806330cdf0551461368b57806331632e89146136535780633381d5d514611fc257806333a12804146135655780633492600d1461347757806334d429ac146133f857806335679806146133a45780633a78b9b9146133675780633d6d3598146133205780633fc0f619146132b3578063429b62e51461327c578063437e3763146131c2578063459e60041461210f57806346b3d9c31461317957806347489ec01461316057806349045e161461313d578063498c35fd146130395780634a2a11f51461301d5780634e043a2f14612f2b578063534ef88314612ec957806357f1915d14612db757806358c97e4b14612d905780635be0af4414612d315780635f8d833114612c995780636187da2314612b9457806361d027b314612b685780636471af8314612a2e578063649777fd14612a075780636639bf57146129705780636d70f7ae146129355780636e15adec146129095780636e663ab81461275a5780636f27bc311461273757806370480275146126cd5780637213bd3b1461266257806373561c3b14612627578063751cc57a146125ef57806377487eb7146125885780637787bdbc1461214c5780637df73e271461210f5780637e2cd926146120a75780637f0166dd146120275780637f1dc74814611fff5780637f82f03e14611fc257806383b8a5ae14611f6e57806383ba715e14611f2757806383db4fec14611ed25780638af8198c14611e5e5780638cf18e6414611e1a5780638de02b3414611d805780639040a55814611cea5780639870d7fe14611c6b5780639f89789c14611c47578063a412922914611867578063a5c23ddf14611837578063a975b6d7146117e3578063ac8a584a14611765578063ae16f525146115d4578063afd9c2c914611526578063b18fdd05146114ed578063b4c4fa501461148f578063b5f666f914611454578063bba1264e1461125a578063bc4f7c8214611219578063bf4bdd79146111d0578063bfb0f7a414611185578063c49d52b514611167578063c60d4fb4146110c6578063c905a74614611089578063ca5610701461101b578063ce7185c314610fc4578063d02829e214610f34578063d0c5a1d314610e60578063d283538614610d1d578063dbbd8e2d14610c59578063e5c8b03d14610bec578063e614573c146109f6578063eb12d61e1461093c578063eccdbdab1461089b578063ee95799614610836578063f0cb5df614610722578063f1899c9e146106e3578063f217ba6c146105f1578063f438cc391461059b578063f6a44b3814610537578063f978fd61146105085763fa34043a146104b7575f80fd5b34610505576104c5366144b6565b92919091338152600260205260016040822054036104f65760406104ea858585615ab1565b82519182526020820152f35b631f0853c160e21b8152600490fd5b80fd5b50346105055760203660031901126105055760ff60406020926004358152601084522054166040519015158152f35b50346105055760203660031901126105055760408091610555614376565b61055d614dc2565b506001600160a01b03168152601360205220815161057a8161464e565b6020600160ff84541693848452015491019081528251918252516020820152f35b5034610505576060366003190112610505576105b5614376565b6105bd61438c565b338352600260205260016040842054036105e257906105df9160443591615a67565b80f35b631f0853c160e21b8352600483fd5b5034610505576020366003190112610505576004356001600160401b0381116106df57610622903690600401614468565b90338352600660205260ff604084205416156106d05781156106c157825b82811061064b578380f35b6001906001600160a01b03610669610664838787614ad7565b614a2e565b16808652600760205260ff604087205416156106bb5780865260076020526040862060ff19815416905561069e600954614862565b60095533905f516020615f615f395f51905f528780a35b01610640565b506106b5565b63b4fa3fb360e01b8352600483fd5b631cb9b3c360e01b8352600483fd5b5080fd5b50346105055760203660031901126105055760209060ff906040906001600160a01b0361070e614376565b168152600f84522054166040519015158152f35b5034610505576020366003190112610505576004356001600160401b0381116106df57610753903690600401614468565b3383526001602052600160408420540361082757825b818110610774578380f35b6001600160a01b0361078a610664838587614ad7565b1615610818576001906001600160a01b036107a9610664838688614ad7565b16855260056020526040852054828060a01b036107ca610664848789614ad7565b16865260056020526107de60408720614931565b828060a01b036107f2610664848789614ad7565b166040519182528660208301525f516020615ea15f395f51905f5260403393a301610769565b63fd684c3b60e01b8452600484fd5b63384da73760e01b8352600483fd5b5034610505576040366003190112610505576108936020916040610858614376565b6001600160a01b03811683526013855291206040519091906108798161464e565b84600160ff85541694858452015491015260243590615d46565b604051908152f35b5034610505576020366003190112610505576108b5614376565b33825281602052600160408320540361092d576001600160a01b03168082526006602052604082205460ff161561091e5780825260066020526040822060ff198154169055610905600854614862565b60085533905f516020615e615f395f51905f528380a380f35b63e1f6d3ad60e01b8252600482fd5b637bfa4b9f60e01b8252600482fd5b503461050557602036600319011261050557610956614376565b338252600660205260ff604083205416156109e7576001600160a01b031680156109d857808252600760205260ff6040832054166109c957808252600760205260408220600160ff198254161790556109b060095461478f565b60095533905f516020615f415f395f51905f528380a380f35b630e1857b360e21b8252600482fd5b63e6c4247b60e01b8252600482fd5b631cb9b3c360e01b8252600482fd5b5034610505576040366003190112610505576004356001600160401b0381116106df57610a27903690600401614468565b6024356001600160401b038111610be857610a46903690600401614468565b909133855260016020526001604086205403610bd957818103610bca57845b818110610a70578580f35b6001600160a01b03610a86610664838589614ad7565b1615610bbb57610a97818486614ad7565b358652600460205260ff60408720541615610bac576001906001600160a01b03610ac561066483868a614ad7565b16875260056020526040872054610b65610ae0838789614ad7565b35848060a01b03610af561066486898d614ad7565b168a5260056020526003868b8b60406002818420015492815196610b1888614618565b87526020870192428452828801948552610b436106648c60608b01988f8a528f8060a01b0394614ad7565b16815260056020522094518555518885015551600284015551151591016147e2565b610b7361066483868a614ad7565b90610b7f838789614ad7565b3560405191825260208201525f516020615ea15f395f51905f5260403393868060a01b031692a301610a65565b632015bd1360e11b8652600486fd5b63fd684c3b60e01b8652600486fd5b63512509d360e11b8552600485fd5b63384da73760e01b8552600485fd5b8380fd5b5034610505578060031936011261050557338152600760205260ff60408220541615610c4a5733815260076020526040812060ff198154169055610c31600954614862565b60095533335f516020615f615f395f51905f528380a380f35b63143606b960e31b8152600490fd5b503461050557602036600319011261050557604061016091610c79614376565b610c81614dda565b5060018060a01b03168152600a60205220600a60405191610ca183614669565b60ff8154161515835260018101546020840152600281015460408401526003810154606084015260048101546080840152600581015460a0840152600681015460c0840152600781015460e0840152600881015461010084015260098101546101208401520154610140820152610d1b6040518092614508565bf35b503461050557608036600319011261050557610d37614376565b60243590604435610d466143b6565b9133855260026020526001604086205403610e51576001600160a01b0316928315610e11577f996199d7179aeb585815ed35e5fe33af482726603891755fe1a853634caf2a6792606092610dfd604051610d9f81614618565b878152602080820186815260408084018681529615158985018181528c8e52600e909452908c20935184546001600160a01b0319166001600160a01b03919091161784559051600184015594516002830155511515906003016147e2565b60405192835260208301526040820152a280f35b60405162461bcd60e51b815260206004820152601860248201527724b73b30b634b21031b7b63630ba32b930b6103a37b5b2b760411b6044820152606490fd5b631f0853c160e21b8552600485fd5b50346105055760203660031901126105055760043533825260026020526001604083205403610f25576014546001600160a01b03168015610f16578083526013602052610ecd60408420604051610eb68161464e565b6020600160ff84541693848452015491015261509d565b80600a0290600a820403610f02578211610ef35782526013602052600160408320015580f35b6307d8b17160e41b8352600483fd5b634e487b7160e01b84526011600452602484fd5b633adf0c5360e11b8352600483fd5b631f0853c160e21b8252600482fd5b5034610505576020366003190112610505576040608091610f53614376565b610f5b614f83565b506001600160a01b03168152600560205220604051610f7981614618565b815491828252600181015460208301908152606060ff600360028501549460408701958652015416930192151583526040519384525160208401525160408301525115156060820152f35b503461050557610fd336614578565b91610fdc61486e565b506014546001600160a01b031693841561100c576101c0610fff868686866152a6565b610d1b60405180926143d4565b633adf0c5360e11b8152600490fd5b503461050557602036600319011261050557611035614376565b33825281602052600160408320540361092d576001600160a01b031680825260016020819052604083205533907f8e82d295288aabe804a503407d755226d5683a4bde33edcd5849e2c51e197d858380a380f35b5034610505576020366003190112610505576020906001906040906001600160a01b036110b4614376565b16815260138452200154604051908152f35b5034610505576020366003190112610505576110e0614376565b33825260026020526001604083205403610f25576001600160a01b031680156111585780825260136020526040822060405161111b8161464e565b6020600160ff8454169384845201549101521561114957601480546001600160a01b03191691909117905580f35b6377ed3bad60e11b8252600482fd5b633a001e0560e11b8252600482fd5b50346105055780600319360112610505576020600854604051908152f35b50346105055780600319360112610505576014546001600160a01b031680156111c1576040826001926020945260138452200154604051908152f35b633adf0c5360e11b8252600482fd5b50346105055760203660031901126105055760409060043581526012602052208054611215600183015492600360ff6002830154169101549060405194859485614498565b0390f35b503461050557604036600319011261050557611233614376565b61123b61438c565b903383526001602052600160408420540361082757906105df91615634565b5034610505576040366003190112610505576004356024356001600160401b03811161145057806004019060a06003198236030112610be85733845283602052600160408520540361144157828452600460205260ff60408520541615611432576044810135612710811161142357838552600460205260408520928035801515810361141f576112eb90856147e2565b6112fd60018501916024850190614a42565b906001600160401b03821161140b576113208261131a85546145c5565b856147f3565b8790601f83116001146113a15782608495936004979593611356938c92611396575b50508160011b915f199060031b1c19161790565b90555b600285015560648101356003850155013591015533907ff73c3981f3b4778eed7762bb81f29d27251da4979f555ce3e34f380046a265fc8380a380f35b013590505f80611342565b8389526020892091601f1984168a5b8181106113f357509260019285926084989660049a9896106113da575b505050811b019055611359565b01355f19600384901b60f8161c191690555f80806113cd565b919360206001819287870135815501950192016113b0565b634e487b7160e01b88526041600452602488fd5b8680fd5b6314bba91760e11b8552600485fd5b63e142361760e01b8452600484fd5b637bfa4b9f60e01b8452600484fd5b8280fd5b50346105055760403660031901126105055761146e614376565b33825260026020526001604083205403610f25576105df906024359061503c565b5034610505576060366003190112610505576114a9614376565b90602435906001600160401b038211610505576101a06003198336030112610505575060443580151581036114e9576101c092610fff9260040190614fd6565b5f80fd5b5034610505576020366003190112610505576020906040906001600160a01b03611515614376565b168152600d83522054604051908152f35b503461050557602036600319011261050557610160906040906001600160a01b0361154f614376565b168152600a6020522060ff815416906001810154906002810154600382015460048301546005840154600685015491600786015493600887015495600a6009890154980154986040519a15158b5260208b015260408a01526060890152608088015260a087015260c086015260e0850152610100840152610120830152610140820152f35b5034610505576020366003190112610505576004356001600160401b0381116106df57611605903690600401614468565b61161181939293614e2b565b9161161f60405193846146a1565b81835261162b82614e2b565b6020840190601f190136823761164083614e2b565b9461164e60405196876146a1565b83865261165a84614e2b565b602087019490601f1901368637835b8181106116f65750505060405194859460408601906040875251809152606086019290845b8181106116d4575050506020908583038287015251918281520192915b8181106116b9575050500390f35b825115158452859450602093840193909201916001016116ab565b82516001600160a01b031685528897506020948501949092019160010161168e565b959694959294926001906001600160a01b03611716610664838688614ad7565b168652600b602052818060a01b03604087205416611734828b614e42565b5261174b611746610664838688614ad7565b614f2e565b6117558287614e42565b9015159052019695949296611669565b50346105055760203660031901126105055761177f614376565b338252600160205260016040832054036117d4576001600160a01b0316808252600260205260408220545f1901610f2557808252600260205281604081205533905f516020615f815f395f51905f528380a380f35b63384da73760e01b8252600482fd5b5034610505576020366003190112610505576060906040906001600160a01b0361180b614376565b168152600a602052206006810154906009600a8201549101549060405192835260208301526040820152f35b5034610505576040366003190112610505576040611859602435600435614fa7565b825191825215156020820152f35b346114e95760203660031901126114e9576004356001600160401b0381116114e9578060040161012060031983360301126114e9576001600160a01b036118ad82614a2e565b165f52600f60205260ff60405f20541615611be2575b6101048201916118dd6118d68484614a42565b369161479d565b6020815191012090815f52601060205260ff60405f205416611b975760e481013592834211611bd35760248201359161271083118015611bc4575b611bb55760a48101359142831115611ba65761193381614a2e565b9060448301359760648401359260848501359460c40135986040519060208201925f516020615f215f395f51905f528452600160a01b600190031660408301528860608301528b60808301528560a08301528660c08301528760e08301528a61010083015261012082015261012081526119af610140826146a1565b5190206119bb9061556e565b906119c69083614a42565b36906119d19261479d565b6119da91615bd5565b6119e390615c0a565b6119ec90614d6b565b15611b975787600a7fafa3ee9ff419401419ed31f5401fb2992206536c8c187144d59ca3755f718ad89860a0985f52601060205260405f20600160ff198254161790556001808a1b03611a3e85614a2e565b165f5281602052600760405f2001546001808b1b03611a5c86614a2e565b165f5282602052600860405f2001546001808c1b03611a7a87614a2e565b165f5283602052600960405f200154916001808d1b03611a9988614a2e565b165f52846020528460405f200154938a8a8d60405199611ab88b614669565b60018b5260208b0191825260408b0190815260608b018d815260808c0193845260a08c0194855260c08c0195865260e08c019687526101008c019788526101208c019889526101408c01998a52916001600160a01b03611b178e614a2e565b165f528a602052611b2f60405f209c5115158d6147e2565b5160018c01555160028b01555160038a015551600489015551600588015551600687015551600786015551600885015551600984015551910155611b78600180881b0391614a2e565b16966040519485526020850152604084015260608301526080820152a2005b630e479e9960e21b5f5260045ffd5b63a9cf949160e01b5f5260045ffd5b63d1107a7960e01b5f5260045ffd5b50612710604482013511611918565b63fd1d678360e01b5f5260045ffd5b6001600160a01b03611bf382614a2e565b165f908152600f60205260409020805460ff191660011790556001600160a01b03611c1d82614a2e565b167ffb681d60cd4ec9a3a53e878e0667916cd932d9df9f7743a139e58afa9a64259c5f80a26118c3565b346114e9575f3660031901126114e957604060085460095482519182526020820152f35b346114e95760203660031901126114e957611c84614376565b335f526001602052600160405f205403611cdb576001600160a01b03165f818152600260205260408120600190553391907ff1e04d73c4304b5ff164f9d10c7473e2a1593b740674a6107975e2a7001c1e5c9080a3005b63384da73760e01b5f5260045ffd5b346114e95760203660031901126114e957611d03614376565b611d0b614f83565b5060018060a01b03165f52600e602052608060405f20604051611d2d81614618565b60018060a01b0382541691828252600181015460208301908152606060ff600360028501549460408701958652015416930192151583526040519384525160208401525160408301525115156060820152f35b346114e95760203660031901126114e957611d99614376565b6040516330cdf05560e01b81526001600160a01b038216600482015290602082602481305afa918215611e0f575f92611dd9575b6040836104ea846158f4565b91506020823d602011611e07575b81611df4602093836146a1565b810103126114e9579051906104ea611dcd565b3d9150611de7565b6040513d5f823e3d90fd5b346114e95760203660031901126114e9576001600160a01b03611e3b614376565b165f5260136020526040805f20600160ff82541691015482519182526020820152f35b346114e95760203660031901126114e9576004355f52600460205260405f2060ff815416611e8e600183016146c4565b916002810154906004600382015491015490611ebe6040519586951515865260a0602087015260a0860190614312565b926040850152606084015260808301520390f35b346114e95760203660031901126114e957611eeb614376565b5f604051611ef881614633565b5260018060a01b03165f52600c602052602060405f2060405190611f1b82614633565b54809152604051908152f35b346114e9575f3660031901126114e957335f526001602052600160405f205403611cdb57335f5260016020525f604081205533335f516020615ee15f395f51905f525f80a3005b346114e9575f3660031901126114e957335f525f602052600160405f205403611fb357335f525f6020525f604081205533335f516020615ec15f395f51905f525f80a3005b637bfa4b9f60e01b5f5260045ffd5b346114e95760203660031901126114e9576001600160a01b03611fe3614376565b165f526006602052602060ff60405f2054166040519015158152f35b346114e95760203660031901126114e957602061201d611746614376565b6040519015158152f35b346114e95760203660031901126114e9576120406143c5565b335f526002602052600160405f2054036120985760207f05278e2faa68922bfef2c25501f766668736214ed2d748288e03a8d2133b0a7591151560115461ff008260081b169061ff00191617601155604051908152a1005b631f0853c160e21b5f5260045ffd5b346114e9576120b536614578565b916120be61486e565b506014546001600160a01b03168015612100576101c093836120e66080610fff960135615434565b5095919050856120f7575b5061547f565b159450876120f1565b633adf0c5360e11b5f5260045ffd5b346114e95760203660031901126114e9576001600160a01b03612130614376565b165f526007602052602060ff60405f2054166040519015158152f35b346114e95760803660031901126114e957612165614376565b602435906001600160401b0382116114e9576101a060031983360301126114e95760643591604435906001600160a01b03841684036114e9576121a661486e565b9260c4820135906121bc8260a4850135866158dc565b95911591610124840135831561240657619c406121d98288614b08565b0460208801525b6101808701526040516330cdf05560e01b81526001600160a01b038316600482018190529092602084602481305afa938415611e0f575f946123d0575b506122316122439160408a019586526158f4565b9260c089019384526084870135615961565b92836101408901526020880193845160405193630dccc75760e31b85526004850152602484015260448301525f6064830152606082608481305afa928315611e0f575f925f9461237f575b5082895261010089019384528451825161271003612710811161236b576122b491614b08565b9481516127100390612710821161236b576127106122f961230f958d61016084996305f5e1006122ea6123079961231f9f614b08565b04109101528351905190614b08565b04908160808d015251614b39565b905190614b08565b0460e08701528551905190614b39565b610120850152156123455750506101c091505f6101a0820152610d1b60405180926143d4565b610144013560028110156114e9576101c093612360926159f6565b6101a0820152610fff565b634e487b7160e01b5f52601160045260245ffd5b925092506060823d6060116123c8575b8161239c606093836146a1565b810103126114e957815160208301516040909301516001600160a01b038116036114e95791928a61228e565b3d915061238f565b9093506020813d6020116123fe575b816123ec602093836146a1565b810103126114e957519261223161221d565b3d91506123df565b61014485013560028110156114e9578061258257885b5f9183612430575b505060208801526121e0565b61243b908b8a6159f6565b9081151580612570575b15612424579091507b0119799812dea11197f27f0f6e885c8ba7eb31f476caf7411a86338781116125195761271083116124c9576124838284614b08565b91670de0b6b3a76400000391670de0b6b3a7640000831161236b576124b0926124ab91614b08565b614b08565b6b1d6329f1c35ca4bfabb9f56160281b90048980612424565b60405162461bcd60e51b815260206004820152602260248201527f43616c63756c61746f7248656c7065723a20696e76616c696420666565207261604482015261746560f01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602960248201527f43616c63756c61746f7248656c7065723a206f7574636f6d65546f6b656e7320604482015268746f6f206c6172676560b81b6064820152608490fd5b50670de0b6b3a7640000821115612445565b8661241c565b346114e95760203660031901126114e9576001600160a01b036125a9614376565b165f52600e602052608060405f2060018060a01b038154169060018101549060ff6003600283015492015416916040519384526020840152604083015215156060820152f35b346114e95760203660031901126114e9576001600160a01b03612610614376565b165f52600c602052602060405f2054604051908152f35b346114e95760203660031901126114e9576001600160a01b03612648614376565b165f5260016020526020600160405f205414604051908152f35b346114e95760203660031901126114e95761267b6143c5565b335f526002602052600160405f2054036120985760207f8828972f46c2f707f1ab9eb979e7832a17059cc5702692d7b4551a0324c7f11191151560ff196011541660ff821617601155604051908152a1005b346114e95760203660031901126114e9576126e6614376565b335f525f602052600160405f205403611fb35760018060a01b0316805f525f602052600160405f205533907ff9ffabca9c8276e99321725bcb43fb076a6c66a54b7f21c4e8146d8519b417dc5f80a3005b346114e95760203660031901126114e9576020610893612755614376565b614ed1565b346114e95760803660031901126114e957612773614376565b604435905f60243580606435806128da575b50506040516330cdf05560e01b81526001600160a01b0384166004820181905291908190602081602481305afa908115611e0f575f916128a8575b5080612879575b50506127d2846158f4565b80612838575b506127ef906060958082105f146128305794615856565b9081612811575b50604051928352602083015260018060a01b03166040820152f35b5f908152600b60205260409020546001600160a01b03169150846127f6565b508094615856565b612710811161286a576127100394612710861161236b576127106128616060976127ef94614b08565b049150946127d8565b6304cbf51b60e51b5f5260045ffd5b909150612710811161286a576127100390612710821161236b57612710916128a091614b08565b0485806127c7565b90506020813d6020116128d2575b816128c3602093836146a1565b810103126114e95751876127c0565b3d91506128b6565b909150612710811161286a576127100390612710821161236b576127109161290191614b08565b048480612785565b346114e95760403660031901126114e957602061201d612927614376565b61292f61438c565b90614e56565b346114e95760203660031901126114e9576001600160a01b03612956614376565b165f5260026020526020600160405f205414604051908152f35b346114e95760603660031901126114e957612989614376565b6024359060ff82168092036114e957335f526002602052600160405f205403612098576001600160a01b031680156129f857600190604051926129cb8461464e565b8352602083019060443582525f52601360205260ff60405f2093511660ff19845416178355519101555f80f35b633a001e0560e11b5f5260045ffd5b346114e9575f3660031901126114e95760206040515f516020615f015f395f51905f528152f35b346114e9575f3660031901126114e957600354612a4a81614e2b565b90612a5860405192836146a1565b808252601f19612a6782614e2b565b015f5b818110612b515750505f5b818110612ae357826040518091602082016020835281518091526040830190602060408260051b8601019301915f905b828210612ab457505050500390f35b91936001919395506020612ad38192603f198a82030186528851614336565b9601920192018594939192612aa5565b806001915f52600460205260405f20600460405191612b01836145fd565b60ff81541615158352612b158582016146c4565b6020840152600281015460408401526003810154606084015201546080820152612b3f8286614e42565b52612b4a8185614e42565b5001612a75565b602090612b5c614764565b82828701015201612a6a565b346114e9575f3660031901126114e95760115460405160109190911c6001600160a01b03168152602090f35b346114e95760203660031901126114e957610180612bb0614376565b612bb8614dda565b506001600160a01b03165f818152600a602052604090819020905191612bdd83614669565b60ff8254161515835260018201546020840152600282015460408401526003820154606084015260048201546080840152600a60058301549260a08501938452600681015460c0860152600781015460e08601526008810154610100860152600981015461012086015201546101408401525f52600f60205260ff60405f2054169081612c8e575b81612c83575b50612c796040518093614508565b1515610160820152f35b905051421083612c6b565b825115159150612c65565b346114e95760203660031901126114e9576060612cb4614376565b612cbc614dc2565b505f604051612cca81614633565b5260018060a01b0316805f52600b60205260405f2090600160405192612cef8461464e565b818060a01b038154168452015460208301525f52600d60205260405f2060405190612d1982614633565b548152612d2960405180936144f0565b516040820152f35b346114e95760203660031901126114e9576001600160a01b03612d52614376565b165f526005602052608060405f2080549060018101549060ff6003600283015492015416916040519384526020840152604083015215156060820152f35b346114e9575f3660031901126114e95760206040515f516020615f215f395f51905f528152f35b346114e95760403660031901126114e9576020612dd2614376565b6001600160a01b03165f8181526013835260409081902090516024359290612df98161464e565b84600160ff8554169485845201549101528115612e64575b5060ff81166006811115612e3b575090612e35612e3061089393615d33565b61509d565b90614b08565b60061115612e5e5790612e53612e30612e5993615d22565b90614b1b565b610893565b50610893565b60405163313ce56760e01b815291508390829060049082905afa5f9181612e9a575b50612e95575060125b83612e11565b612e8f565b612ebb919250843d8611612ec2575b612eb381836146a1565b810190615d09565b9084612e86565b503d612ea9565b346114e95760203660031901126114e957612ee2614376565b612eea614dc2565b5060018060a01b03165f52600b6020526040805f206001825191612f0d8361464e565b818060a01b03815416835201546020820152610d1b825180926144f0565b346114e95760203660031901126114e9576080612f46614376565b612f4e614dc2565b5060018060a01b0381165f52600b60205260405f2090612f90600160405193612f768561464e565b818060a01b03815416855201549160208401928352614f2e565b82519091906001600160a01b031680151580613016575b1561300d575f52600a602052600360405f20015480155f14612fe35750505f905b612fd560405180946144f0565b151560408301526060820152f35b612fed9151614afb565b4281111561300657613000904290614b39565b90612fc8565b505f613000565b50505f90612fc8565b5082612fa7565b346114e9575f3660031901126114e9576020604051610fa08152f35b346114e95760403660031901126114e957613052614376565b60243590335f526001602052600160405f205403611cdb576001600160a01b0316801561312e57815f52600460205260ff60405f2054161561311f57805f52600560205260405f205491815f5260056020526130ff600260405f2001546003604051916130be83614618565b848352602083019042825260408401908152606084019160018352875f52600560205260405f209451855551600185015551600284015551151591016147e2565b60405192835260208301525f516020615ea15f395f51905f5260403393a3005b632015bd1360e11b5f5260045ffd5b63fd684c3b60e01b5f5260045ffd5b346114e95760203660031901126114e957602061201d61315b614376565b614d6b565b346114e95760406104ea613173366144b6565b91614b46565b346114e95760203660031901126114e957613192614376565b5f60405161319f81614633565b5260018060a01b03165f52600d602052602060405f2060405190611f1b82614633565b346114e95760203660031901126114e9576131db614376565b335f525f602052600160405f205403611fb3576001600160a01b0316801561326d57805f52600660205260ff60405f20541661325e57805f52600660205260405f20600160ff1982541617905561323360085461478f565b60085533907f1b3bdd505031d7440a31317a144c6a332fd0894111458e9ccf17a35191261f765f80a3005b63032cd66d60e61b5f5260045ffd5b63e6c4247b60e01b5f5260045ffd5b346114e95760203660031901126114e9576001600160a01b0361329d614376565b165f525f602052602060405f2054604051908152f35b346114e95760203660031901126114e9576132cc614376565b335f525f602052600160405f205403611fb3576001600160a01b03165f818152600160205260409020545f1901611cdb57805f5260016020525f604081205533905f516020615ee15f395f51905f525f80a3005b346114e9575f3660031901126114e957335f526002602052600160405f20540361209857335f5260026020525f604081205533335f516020615f815f395f51905f525f80a3005b346114e95760203660031901126114e9576001600160a01b03613388614376565b165f52600f602052602060ff60405f2054166040519015158152f35b346114e95760203660031901126114e9576001600160a01b036133c5614376565b165f52600a602052602060405f2060ff81541690816133ea575b506040519015158152f35b6005915001544210826133df565b346114e95760a03660031901126114e957613411614376565b61341961438c565b90608435906001600160a01b03821682036114e957335f526002602052600160405f2054036120985761345091604435918461577c565b60018060a01b03165f52600a602052600760405f20016134736064358254614afb565b9055005b346114e95760203660031901126114e9576004356001600160401b0381116114e9576134a7903690600401614468565b90335f52600660205260ff60405f20541615613556578115613547575f5b8281106134ce57005b6001600160a01b036134e4610664838686614ad7565b1690811561326d57816001925f52600760205260ff60405f20541661354157805f52600760205260405f208360ff1982541617905561352460095461478f565b60095533905f516020615f415f395f51905f525f80a35b016134c5565b5061353b565b63b4fa3fb360e01b5f5260045ffd5b631cb9b3c360e01b5f5260045ffd5b346114e95760a03660031901126114e9576004356024356044356135876143b6565b9160843592335f526002602052600160405f20540361209857610fa082118015613648575b613639577f727c14dbdafd47c44927d04ba768ca9942afb2ecfa8917aec6d46504ac41f13093613634916040516135e281614618565b84815260036020820187815261362460408401851515815260608501928784528c5f52601260205260405f2095518655516001860155511515600285016147e2565b5191015560405194859485614498565b0390a2005b6301ce37c360e01b5f5260045ffd5b50610fa083116135ac565b346114e95760203660031901126114e9576001600160a01b03613674614376565b165f526001602052602060405f2054604051908152f35b346114e95760203660031901126114e95760206108936136a9614376565b614a74565b346114e9575f3660031901126114e957335f52600660205260ff60405f2054161561355657335f52600660205260405f2060ff1981541690556136f2600854614862565b60085533335f516020615e615f395f51905f525f80a3005b346114e9575f3660031901126114e9576020600954604051908152f35b346114e95760203660031901126114e9576014546001600160a01b03161561210057611215613757600435615434565b9060409492945194859485614498565b346114e95760203660031901126114e9576004356001600160401b0381116114e95780600401608060031983360301126114e9576001600160a01b036137ac82614a2e565b1633036138b75760648201906137c56118d68383614a42565b6020815191012092835f52601060205260ff60405f205416611b9757604481013592834211611bd35761387661315b916138706118d661386961387e96602461380d8a614a2e565b9101996138198b614a2e565b906040519160208301935f516020615f015f395f51905f52855260018060a01b0316604084015260018060a01b0316606083015260808201526080815261386160a0826146a1565b51902061556e565b9287614a42565b90615bd5565b919091615c0a565b15611b97576138a96138af916138b5945f52601060205260405f20600160ff19825416179055614a2e565b91614a2e565b90615634565b005b636ee77e2960e01b5f5260045ffd5b346114e95760a03660031901126114e9576138df614376565b602435906001600160401b0382116114e9576101a060031983360301126114e9576044356001600160401b0381116114e95761391f903690600401614468565b90916084356001600160401b0381116114e957613940903690600401614468565b93909461394b61486e565b506014546001600160a01b031692831561210057836139939261396c61486e565b5061397a6084820135615434565b509491905084613a9d575b50606435916004019061547f565b9461399d84614e2b565b946139ab60405196876146a1565b848652601f196139ba86614e2b565b015f5b818110613a865750505f5b858110613a305760405180886101e082016139e3838d6143d4565b6101e06101c08401528151809152602061020084019201905f5b818110613a0b575050500390f35b9193509160206101c082613a2260019488516143d4565b0194019101918493926139fd565b80613a6a86613a4d6020613a476001968c8b61554b565b01614a2e565b613a58848b8a61554b565b613a6385888a614ad7565b35916152a6565b613a74828a614e42565b52613a7f8189614e42565b50016139c8565b602090613a9161486e565b82828b010152016139bd565b1593508a613985565b346114e95760203660031901126114e9576001600160a01b03613ac7614376565b165f525f6020526020600160405f205414604051908152f35b346114e9575f3660031901126114e957602060ff60115460081c166040519015158152f35b346114e95760203660031901126114e9576001600160a01b03613b26614376565b165f52600b6020526040805f206001808060a01b0382541691015482519182526020820152f35b346114e95760403660031901126114e95760043560243590811515918281036114e957335f525f602052600160405f205403611fb357613b9890825f52600460205260405f206147e2565b6040519182527f3ba1e4aaeb9438f838c8418b7f4ba93a32aa7c37d79277b7ced11e47c20042b360203393a3005b346114e95760603660031901126114e957613bdf614376565b6024356001600160401b0381116114e9578036036101a06003198201126114e9576044356001600160a01b03811692908390036114e957613c1e61486e565b5060a48101359160405194631de1ef6f60e21b865260018060a01b03166004860152608060248601528160040135608486015260018060a01b03613c64602484016143a2565b1660a48601526001600160a01b03613c7e604484016143a2565b1660c48601526001600160a01b03613c98606484016143a2565b1660e486015260848201356101048601528261012486015260c482013561014486015260e48201356101648601526101048201356101848601526101248201356101a486015261014482013560028110156114e957613cfc906101c4870190614a00565b61016482013560038110156114e957613d1a906101e4870190614a21565b61018482013590602219018112156114e957016004810135916024909101906001600160401b0383116114e95782360382136114e9576101c093613d7086949385946101a0610204870152610224860191614842565b91604484015260648301520381305afa8015611e0f576101c0915f91613d9f575b50610d1b60405180926143d4565b613dbf9150823d8111613dc5575b613db781836146a1565b810190614948565b82613d91565b503d613dad565b346114e95760203660031901126114e957613de5614376565b335f526001602052600160405f205403611cdb576001600160a01b0316801561312e575f81815260056020526040902080549190613e2290614931565b6040519182525f60208301525f516020615ea15f395f51905f5260403393a3005b346114e95760203660031901126114e957613e5c614376565b335f525f602052600160405f205403611fb3576001600160a01b03165f818152602081905260409020545f1901611fb357805f525f6020525f604081205533905f516020615ec15f395f51905f525f80a3005b346114e95760203660031901126114e9576001600160a01b03613ed0614376565b165f526002602052602060405f2054604051908152f35b346114e95760803660031901126114e957613f00614376565b6024356001600160401b0381116114e9576101a060031982360301126114e9576101c091610fff91613f306143b6565b9160443591600401906148d4565b346114e95760203660031901126114e957613f57614376565b335f52600660205260ff60405f20541615613556576001600160a01b03165f8181526007602052604090205460ff1615613fc257805f52600760205260405f2060ff198154169055613faa600954614862565b60095533905f516020615f615f395f51905f525f80a3005b63bdb712ef60e01b5f5260045ffd5b346114e9575f3660031901126114e9576014546040516001600160a01b039091168152602090f35b346114e95760803660031901126114e9576004356001600160401b0381116114e957366023820112156114e9576004810135906001600160401b0382116114e957602481019060248336920101116114e95760243560443592335f525f602052600160405f205403611fb3576127108211614244576003549261407b8461478f565b6003556040519161408b836145fd565b6001835261409a36828461479d565b956020840196875260408401928584526060850182815260808601906064358252885f5260046020526140d460405f2097511515886147e2565b98518051999095600188016001600160401b038c11614230578b8b9861410060209e61131a85546145c5565b8d90601f83116001146141905792614148835f516020615e815f395f51905f529c9d97946004979461416d9b9a975f926141855750508160011b915f199060031b1c19161790565b90555b5160028501555160038401555191015560405193606085526060850191614842565b948783015260408201528033940390a3604051908152f35b015190505f80611342565b601f9b9695949392919b19821690835f528c5f20915f5b8181106141fc57509260049694925f516020615e815f395f51905f529d9e6001938361416d9d9c9b9997106141e4575b505050811b01905561414b565b01515f1960f88460031b161c191690555f80806141d7565b939597999b9d9496989a9c5090916020600181928786015181550195019301908f9c9a98969492919d9b999795939d6141a7565b634e487b7160e01b5f52604160045260245ffd5b6314bba91760e11b5f5260045ffd5b346114e95760203660031901126114e95761426c614764565b506004355f52600460205261121560405f2060046040519161428d836145fd565b60ff815416151583526142a2600182016146c4565b6020840152600281015460408401526003810154606084015201546080820152604051918291602083526020830190614336565b346114e9575f3660031901126114e9576020600354604051908152f35b346114e9575f3660031901126114e95760209060ff6011541615158152f35b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b90815115158152608080614359602085015160a0602086015260a0850190614312565b936040810151604085015260608101516060850152015191015290565b600435906001600160a01b03821682036114e957565b602435906001600160a01b03821682036114e957565b35906001600160a01b03821682036114e957565b6064359081151582036114e957565b6004359081151582036114e957565b6101a08091805184526020810151602085015260408101516040850152606081015160608501526080810151608085015260a081015160a085015260c081015160c085015260e081015160e085015261010081015161010085015261012081015161012085015261014081015161014085015261016081015115156101608501526101808101516101808501520151910152565b9181601f840112156114e9578235916001600160401b0383116114e9576020808501948460051b0101116114e957565b90949392606092608083019683526020830152151560408201520152565b60609060031901126114e9576004356001600160a01b03811681036114e95790602435906044356001600160a01b03811681036114e95790565b80516001600160a01b03168252602090810151910152565b61014080918051151584526020810151602085015260408101516040850152606081015160608501526080810151608085015260a081015160a085015260c081015160c085015260e081015160e08501526101008101516101008501526101208101516101208501520151910152565b9060606003198301126114e9576004356001600160a01b03811681036114e95791602435906001600160401b0382116114e9576101a09082900360031901126114e9576004019060443590565b90600182811c921680156145f3575b60208310146145df57565b634e487b7160e01b5f52602260045260245ffd5b91607f16916145d4565b60a081019081106001600160401b0382111761423057604052565b608081019081106001600160401b0382111761423057604052565b602081019081106001600160401b0382111761423057604052565b604081019081106001600160401b0382111761423057604052565b61016081019081106001600160401b0382111761423057604052565b6101c081019081106001600160401b0382111761423057604052565b601f909101601f19168101906001600160401b0382119082101761423057604052565b9060405191825f8254926146d7846145c5565b808452936001811690811561474257506001146146fe575b506146fc925003836146a1565b565b90505f9291925260205f20905f915b8183106147265750509060206146fc928201015f6146ef565b602091935080600191548385890101520191019091849261470d565b9050602092506146fc94915060ff191682840152151560051b8201015f6146ef565b60405190614771826145fd565b5f608083828152606060208201528260408201528260608201520152565b5f19811461236b5760010190565b9192916001600160401b03821161423057604051916147c6601f8201601f1916602001846146a1565b8294818452818301116114e9578281602093845f960137010152565b9060ff801983541691151516179055565b601f821161480057505050565b5f5260205f20906020601f840160051c83019310614838575b601f0160051c01905b81811061482d575050565b5f8155600101614822565b9091508190614819565b908060209392818452848401375f828201840152601f01601f1916010190565b801561236b575f190190565b6040519061487b82614685565b5f6101a0838281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e082015282610100820152826101208201528261014082015282610160820152826101808201520152565b9291906148df61486e565b506014546001600160a01b0316928315612100571561492857908161490c608061491c9695940135615434565b50959190508561491f575061547f565b90565b1594505f6120f1565b61491c936152a6565b60035f918281558260018201558260028201550155565b90816101c09103126114e9576040519061496182614685565b805182526020810151602083015260408101516040830152606081015160608301526080810151608083015260a081015160a083015260c081015160c083015260e081015160e08301526101008101516101008301526101208101516101208301526101408101516101408301526101608101519081151582036114e9576101a09161016084015261018081015161018084015201516101a082015290565b906002821015614a0d5752565b634e487b7160e01b5f52602160045260245ffd5b906003821015614a0d5752565b356001600160a01b03811681036114e95790565b903590601e19813603018212156114e957018035906001600160401b0382116114e9576020019181360383136114e957565b6001600160a01b03165f908152600560205260409020600381015460ff1615614ac857545f52600460205260405f2060ff81541615614ac85760038101548015159081614acd575b50614ac8576002015490565b505f90565b905042115f614abc565b9190811015614ae75760051b0190565b634e487b7160e01b5f52603260045260245ffd5b9190820180921161236b57565b8181029291811591840414171561236b57565b8115614b25570490565b634e487b7160e01b5f52601260045260245ffd5b9190820391821161236b57565b6001600160a01b038082165f908152600b6020526040902054929493919216908115614d6057815f52600a60205260405f209260ff8454161590811590818390614d52575b8015614d42575b15614d2657614ba25f8099614b39565b9291614d17575b8115614d05575b5015614bbe57505050505f90565b614bd2612710916002869896015490614b08565b04948515159081614cf7575b50614bea575b50509190565b60405191636f27bc3160e01b83526004830152602082602481305afa918215611e0f575f92614cc3575b5081614c5460018060a01b03831692835f5260136020528760405f2091604051614c3d8161464e565b6020600160ff865416958684520154910152615d46565b1115614be457909193505f52600e60205260405f2060ff60038201541680614cb6575b15614caf57600281015491604d831161236b57614c9d600191614ca694600a0a90614b08565b91015490614b1b565b915b5f80614be4565b5091614ca8565b5060018101541515614c77565b9091506020813d602011614cef575b81614cdf602093836146a1565b810103126114e95751905f614c14565b3d9150614cd2565b60069150015415155f614bde565b614d10915084614e56565b155f614bb0565b60058601544210159150614ba9565b614ba2612710614d3a60018901548b614b08565b048099614b39565b50614d4c81614f2e565b15614b92565b506005860154421015614b8b565b50505090505f905f90565b6001600160a01b03165f8181526007602052604090205460ff16908115614daa575b8115614d97575090565b90505f525f602052600160405f20541490565b8091505f52600660205260ff60405f20541690614d8d565b60405190614dcf8261464e565b5f6020838281520152565b60405190614de782614669565b5f610140838281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e082015282610100820152826101208201520152565b6001600160401b0381116142305760051b60200190565b8051821015614ae75760209160051b010190565b6001600160a01b039182165f908152600b60205260409020805490929182169116819003614ecb575f52600a60205260405f2060ff60115460081c16614eba576004015480158015614ec1575b614eba576001614eb4920154614afb565b42111590565b5050600190565b505f198114614ea3565b50505f90565b6001600160a01b03165f908152600a602052604090206006810154908115614f275762278d00600a8201540462278d004204115f14614f1e57505f5b81811015614ecb5761491c91614b39565b60090154614f0d565b50505f1990565b6001600160a01b039081165f908152600b6020526040902080549091168015614ecb575f52600a60205260405f2060ff60115416614eba576003015480158015614ec157614eba576001614eb4920154614afb565b60405190614f9082614618565b5f6060838281528260208201528260408201520152565b6014546001600160a01b0316801561210057614fc291615961565b808210614fcf5750905f90565b9160019150565b919091614fe161486e565b506014546001600160a01b0316918215612100571561502d5790829161500d608061491c950135615434565b509491905084615024575b5060a08101359161547f565b1593505f615018565b8260a061491c940135916152a6565b81156150995760407f2136f9ac419f7f8d55572b5e123f4b7c8021446b15168de5a6034589797bc1479160018060a01b031692835f5260056020526002825f200190615089818354614afb565b80925582519182526020820152a2565b5050565b60ff16604d811161236b57600a0a90565b91906101a0838203126114e957604051906101a082016001600160401b03811183821017614230576040528193803583526150eb602082016143a2565b60208401526150fc604082016143a2565b604084015261510d606082016143a2565b60608401526080810135608084015260a081013560a084015260c081013560c084015260e081013560e084015261010081013561010084015261012081013561012084015261014081013560028110156114e95761014084015261016081013560038110156114e957610160840152610180810135906001600160401b0382116114e9570181601f820112156114e957610180918160206151b09335910161479d565b910152565b929493906101806060936152909260018060a01b03168652608060208701528051608087015260018060a01b0360208201511660a087015260018060a01b0360408201511660c087015260018060a01b03858201511660e0870152608081015161010087015260a081015161012087015260c081015161014087015260e0810151610160870152610100810151828701526101208101516101a08701526152666101408201516101c0880190614a00565b61527a6101608201516101e0880190614a21565b01516101a0610200860152610220850190614312565b60408401959095526001600160a01b0316910152565b6152ae61486e565b6152c160c084013560a0850135866158dc565b6101408401359060028210156114e9575f8261542e5760808601355b6153d1576152f860806152f036896150ae565b970135615434565b5090506153c5575b506101208601511561536257505050506153336101c093946040519586948594631de1ef6f60e21b8652600486016151b5565b0381305afa908115611e0f575f91615349575090565b61491c91506101c03d8111613dc557613db781836146a1565b928097505f929550602091945001525f85525f6101208601525f60408601525f60808601525f60c08601525f60e08601525f6101008601525f6101408601525f6101608601525f6101808601526114e9576153bc926159f6565b6101a082015290565b6101208701525f615300565b5092509250949350615426925f60208701525f86525f6101208701525f60408701525f60808701525f60c08701525f60e08701525f6101008701525f6101408701525f6101608701525f6101808701526159f6565b6101a0830152565b5f6152dd565b5f52601260205260405f2060606040519261544e84614618565b8254808552600184015494856020820152600360ff6002870154161515958660408401520154938491015293929190565b9091939261548b61486e565b506101408301359360028510156114e9576101c0946155455760808401355b6154fb5750615333906154ca60806154c236876150ae565b950135615434565b509091506154ef575b50604051631de1ef6f60e21b81529586948594600486016151b5565b6101208501525f6154d3565b1561551357615333906154ca60806154c236876150ae565b916155226153339136906150ae565b925f6101208501526040519586948594631de1ef6f60e21b8652600486016151b5565b5f6154aa565b9190811015614ae75760051b8101359061019e19813603018212156114e9570190565b60405160208101907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f82527fcfa110dfb1be42028a1f97c0e7b75aea85f983e75bbcdb2b74ed224e07b238b360408201527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260a0815261560060c0826146a1565b5190209060405190602082019261190160f01b8452602283015260428201526042815261562e6062826146a1565b51902090565b6001600160a01b0316908115801561576b575b611bb5576001600160a01b03169080821461575c57815f52600f60205260ff60405f2054161561574d575f818152600b60205260409020546001600160a01b031661573e57815f52600a60205260405f2060ff815416158015615730575b611ba6576008906040516156b88161464e565b8481524260208083019182525f868152600b90915260409020915182546001600160a01b0319166001600160a01b039190911617825551600191909101550180546157029061478f565b90557f157322274a254e4f8bf1dda281061d8e5f24da79992ca7d2fae28cd8c9d010966020604051428152a3565b5060058101544210156156a5565b6315c6aa1d60e31b5f5260045ffd5b630938e63560e41b5f5260045ffd5b63dc33424560e01b5f5260045ffd5b506001600160a01b03811615615647565b9060207f200b84a0b2da981fe422e91bcde91702f71bb9efa43354b4bb8040e7566bddb2919493946157e060018060a01b03871696875f52601384528660405f20916040516157ca8161464e565b86600160ff865416958684520154910152615d46565b9360018060a01b031693845f52600c835260405f20615800828254614afb565b9055845f52600a8352615833600960405f20600a810162278d0081540462278d00420411615848575b5001918254614afb565b90556040519485526001600160a01b031693a4565b5f838301554290555f615829565b6001600160a01b038181165f908152600b60205260409020541680156158d557805f52600a60205260405f209160ff835416159182156158c6575b82156158b4575b5050614ecb576127109160026158b092015490614b08565b0490565b6158be9250614e56565b155f80615898565b60058401544210159250615891565b5050505f90565b81156158d55761491c926158ef91614b08565b614b1b565b6001600160a01b038181165f908152600b6020526040902054168015614ecb575f52600a60205260405f209060ff82541615908115615952575b8115615941575b50614ac8576001015490565b61594b9150614f2e565b155f615935565b6005830154421015915061592e565b906020915f526012825260405f206040519161597c83614618565b815483526001820154848401526060600360ff600285015416936040860194151585520154930192835260018060a01b03165f526013835260405f206001604051916159c78361464e565b60ff815416835201549384910152511515806159ec575b6159e6575090565b90505190565b50805115156159de565b90916002811015614a0d5715615a36578015614ecb57670de0b6b3a7640000820291808304670de0b6b3a7640000149015171561236b5761491c91614b1b565b908015614ecb57670de0b6b3a7640000820291808304670de0b6b3a7640000149015171561236b5761491c91614b1b565b9190615a8e9160018060a01b0382165f52601360205260405f2091604051614c3d8161464e565b9060018060a01b03165f52600d602052615aad60405f20918254614afb565b9055565b6040805163011d227b60e61b81526001600160a01b038084166004830181905260248301869052908616604483015295949093929184606481305afa958615611e0f575f945f97615b9a575b5084158781615b91575b50615b845791600791615b50935f52600b6020528760405f209160018060a01b038354165f52600a60205260405f209388615b74575b82615b56575b5050505001918254614afb565b90559190565b9254615b6b93906001600160a01b031661577c565b5f878180615b43565b615b7f898383615a67565b615b3d565b505050505090505f905f90565b9050155f615b07565b945095506040843d604011615bcd575b81615bb7604093836146a1565b810103126114e95760208451940151955f615afd565b3d9150615baa565b9060418151145f14615c0157615bfd91602082015190606060408401519301515f1a90615def565b9091565b50505f90600290565b6005811015614a0d5780615c1b5750565b60018103615c635760405162461bcd60e51b815260206004820152601860248201527745434453413a20696e76616c6964207369676e617475726560401b6044820152606490fd5b60028103615cb05760405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e677468006044820152606490fd5b600314615cb957565b60405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b6064820152608490fd5b908160209103126114e9575160ff811681036114e95790565b60ff166006039060ff821161236b57565b60ff6005199116019060ff821161236b57565b91909160ff821615615d8e575b5060ff81166006811115615d72575090612e53612e3061491c93615d33565b60061115615d8a5790612e35612e3061491c93615d22565b5090565b60405163313ce56760e01b81529150602090829060049082906001600160a01b03165afa5f9181615dce575b50615dc9575060125b5f615d53565b615dc3565b615de891925060203d602011612ec257612eb381836146a1565b905f615dba565b6fa2a8918ca85bafe22016d0b997e4df60600160ff1b038411615e55576020935f9360ff60809460405194855216868401526040830152606082015282805260015afa15611e0f575f516001600160a01b03811615615e4d57905f90565b505f90600190565b505050505f9060039056fe2c0795c946bf9fc2990be5d999989c8731f0852d4d27207d9dba34d96b78b7e6b96b70435fd2b7ae3ffc1de23c3b83da4d67758da2c5465211f7a526b3ff315d48d589dda401158269c9d1513b0ba3a30f014349b3cf156d5fd54652c092b550787a2e12f4a55b658b8f573c32432ee11a5e8b51677d1e1e937aaf6a0bb5776eed61fd7fbf88da1ed91a05df546e45243bc864eca2aba942fcb1fa4467cc326e1c15ac55c7ef7b31ba97b2f03c7a893b863e08011bc446a82d115450a9b1fadd03cd16f1ea4fdd4593cdc713e46d99e3765c519542ce4b05ae649a52b7211f3712146497b3b826918ec47f0cac7272a09ed06b30c16c030e99ec48ff5dd60b4798d1ebbe00ae92a5de96a0f49742a8afa89f42363592bc2e7cfaaed68b45e7a6f7262ed0443cc211121ceb1a80d69004f319245615a7488f951f1437fd91642ca164736f6c634300081b000a

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

00000000000000000000000055d398326f99059ff775485246999027b3197955

-----Decoded View---------------
Arg [0] : _defaultCollateralToken (address): 0x55d398326f99059fF775485246999027B3197955

-----Encoded View---------------
1 Constructor Arguments found :
Arg [0] : 00000000000000000000000055d398326f99059ff775485246999027b3197955


Block Transaction Gas Used Reward
view all blocks produced
Age Block Fee Address BC Fee Address Voting Power Jailed Incoming
View All Validatorset

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]

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