Source Code
Latest 25 from a total of 146,989 transactions
| Transaction Hash |
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
| Update Fee Rate ... | 92594643 | 14 mins ago | IN | 0 BNB | 0.00000858 | ||||
| Update Fee Rate ... | 92594643 | 14 mins ago | IN | 0 BNB | 0.00000858 | ||||
| Update Fee Rate ... | 92594635 | 14 mins ago | IN | 0 BNB | 0.00000858 | ||||
| Update Fee Rate ... | 92594634 | 14 mins ago | IN | 0 BNB | 0.00000858 | ||||
| Update Fee Rate ... | 92594624 | 14 mins ago | IN | 0 BNB | 0.00000858 | ||||
| Update Fee Rate ... | 92594624 | 14 mins ago | IN | 0 BNB | 0.00000858 | ||||
| Update Fee Rate ... | 92586650 | 1 hr ago | IN | 0 BNB | 0.00000858 | ||||
| Update Fee Rate ... | 92586649 | 1 hr ago | IN | 0 BNB | 0.00000858 | ||||
| Update Fee Rate ... | 92586641 | 1 hr ago | IN | 0 BNB | 0.00000858 | ||||
| Update Fee Rate ... | 92586640 | 1 hr ago | IN | 0 BNB | 0.00000858 | ||||
| Update Fee Rate ... | 92586630 | 1 hr ago | IN | 0 BNB | 0.00000858 | ||||
| Update Fee Rate ... | 92586629 | 1 hr ago | IN | 0 BNB | 0.00000858 | ||||
| Update Fee Rate ... | 92578656 | 2 hrs ago | IN | 0 BNB | 0.00000858 | ||||
| Update Fee Rate ... | 92578656 | 2 hrs ago | IN | 0 BNB | 0.00000858 | ||||
| Update Fee Rate ... | 92578647 | 2 hrs ago | IN | 0 BNB | 0.00000858 | ||||
| Update Fee Rate ... | 92578647 | 2 hrs ago | IN | 0 BNB | 0.00000858 | ||||
| Update Fee Rate ... | 92578638 | 2 hrs ago | IN | 0 BNB | 0.00000858 | ||||
| Update Fee Rate ... | 92578638 | 2 hrs ago | IN | 0 BNB | 0.00000858 | ||||
| Update Fee Rate ... | 92570659 | 3 hrs ago | IN | 0 BNB | 0.00000858 | ||||
| Update Fee Rate ... | 92570658 | 3 hrs ago | IN | 0 BNB | 0.00000858 | ||||
| Update Fee Rate ... | 92570650 | 3 hrs ago | IN | 0 BNB | 0.00000858 | ||||
| Update Fee Rate ... | 92570649 | 3 hrs ago | IN | 0 BNB | 0.00000858 | ||||
| Update Fee Rate ... | 92570640 | 3 hrs ago | IN | 0 BNB | 0.00000858 | ||||
| Update Fee Rate ... | 92570639 | 3 hrs ago | IN | 0 BNB | 0.00000858 | ||||
| Update Fee Rate ... | 92562664 | 4 hrs ago | IN | 0 BNB | 0.00000858 |
Cross-Chain Transactions
Loading...
Loading
Contract Name:
CTFExchangeFeeManager
Compiler Version
v0.8.27+commit.40a35a09
Optimization Enabled:
Yes with 1 runs
Other Settings:
cancun EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// 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);
}// 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);
}
}// 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;
}
}// 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);
}
}// 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);
}
}
}{
"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
- No Contract Security Audit Submitted- Submit Audit Here
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"}]Contract Creation Code
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
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in BNB
Multichain Portfolio | 32 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
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.