ETH Price: $2,842.98 (-3.26%)
 

Overview

ETH Balance

0 ETH

ETH Value

$0.00

Token Holdings

More Info

Private Name Tags

ContractCreator

Multichain Info

No addresses found
Transaction Hash
Block
From
To
Withdraw400287432025-12-27 14:47:1329 days ago1766846833IN
0xb773e4f5...185358779
0 ETH0.000001490.0015
Claim Merkl Rewa...400287362025-12-27 14:46:5929 days ago1766846819IN
0xb773e4f5...185358779
0 ETH0.000000240.00150161
Rebalance To Vau...396865372025-12-19 16:40:2137 days ago1766162421IN
0xb773e4f5...185358779
0 ETH0.00000390.00300581
User Deposit396741702025-12-19 9:48:0737 days ago1766137687IN
0xb773e4f5...185358779
0 ETH0.000000830.0015
Initial Deposit396732632025-12-19 9:17:5337 days ago1766135873IN
0xb773e4f5...185358779
0 ETH0.000001210.00159765

Latest 1 internal transaction

Parent Transaction Hash Block From To
396731962025-12-19 9:15:3937 days ago1766135739  Contract Creation0 ETH

Cross-Chain Transactions
Loading...
Loading

Similar Match Source Code
This contract matches the deployed Bytecode of the Source Code for Contract 0xBf4E6136...528Fa63DB
The constructor portion of the code might be different and could alter the actual behaviour of the contract

Contract Name:
UserVault_V4

Compiler Version
v0.8.30+commit.73712a01

Optimization Enabled:
Yes with 1 runs

Other Settings:
cancun EvmVersion

Contract Source Code (Solidity Standard Json-Input format)

File 1 of 1 : userVaultV4.sol
// File: @openzeppelin/contracts/token/ERC20/IERC20.sol


// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)

pragma solidity >=0.4.16;

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

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

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

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

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

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

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

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

// File: @openzeppelin/contracts/interfaces/IERC20.sol


// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC20.sol)

pragma solidity >=0.4.16;


// File: @openzeppelin/contracts/utils/introspection/IERC165.sol


// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)

pragma solidity >=0.4.16;

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

// File: @openzeppelin/contracts/interfaces/IERC165.sol


// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC165.sol)

pragma solidity >=0.4.16;


// File: @openzeppelin/contracts/interfaces/IERC1363.sol


// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1363.sol)

pragma solidity >=0.6.2;



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

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

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

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

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

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

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

// File: @openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol


// OpenZeppelin Contracts (last updated v5.5.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;



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

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

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        if (!_safeTransfer(token, to, value, true)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

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

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

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

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

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

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

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

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

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

    /**
     * @dev Imitates a Solidity `token.transfer(to, value)` call, relaxing the requirement on the return value: the
     * return value is optional (but if data is returned, it must not be false).
     *
     * @param token The token targeted by the call.
     * @param to The recipient of the tokens
     * @param value The amount of token to transfer
     * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
     */
    function _safeTransfer(IERC20 token, address to, uint256 value, bool bubble) private returns (bool success) {
        bytes4 selector = IERC20.transfer.selector;

        assembly ("memory-safe") {
            let fmp := mload(0x40)
            mstore(0x00, selector)
            mstore(0x04, and(to, shr(96, not(0))))
            mstore(0x24, value)
            success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)
            // if call success and return is true, all is good.
            // otherwise (not success or return is not true), we need to perform further checks
            if iszero(and(success, eq(mload(0x00), 1))) {
                // if the call was a failure and bubble is enabled, bubble the error
                if and(iszero(success), bubble) {
                    returndatacopy(fmp, 0x00, returndatasize())
                    revert(fmp, returndatasize())
                }
                // if the return value is not true, then the call is only successful if:
                // - the token address has code
                // - the returndata is empty
                success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))
            }
            mstore(0x40, fmp)
        }
    }

    /**
     * @dev Imitates a Solidity `token.transferFrom(from, to, value)` call, relaxing the requirement on the return
     * value: the return value is optional (but if data is returned, it must not be false).
     *
     * @param token The token targeted by the call.
     * @param from The sender of the tokens
     * @param to The recipient of the tokens
     * @param value The amount of token to transfer
     * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
     */
    function _safeTransferFrom(
        IERC20 token,
        address from,
        address to,
        uint256 value,
        bool bubble
    ) private returns (bool success) {
        bytes4 selector = IERC20.transferFrom.selector;

        assembly ("memory-safe") {
            let fmp := mload(0x40)
            mstore(0x00, selector)
            mstore(0x04, and(from, shr(96, not(0))))
            mstore(0x24, and(to, shr(96, not(0))))
            mstore(0x44, value)
            success := call(gas(), token, 0, 0x00, 0x64, 0x00, 0x20)
            // if call success and return is true, all is good.
            // otherwise (not success or return is not true), we need to perform further checks
            if iszero(and(success, eq(mload(0x00), 1))) {
                // if the call was a failure and bubble is enabled, bubble the error
                if and(iszero(success), bubble) {
                    returndatacopy(fmp, 0x00, returndatasize())
                    revert(fmp, returndatasize())
                }
                // if the return value is not true, then the call is only successful if:
                // - the token address has code
                // - the returndata is empty
                success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))
            }
            mstore(0x40, fmp)
            mstore(0x60, 0)
        }
    }

    /**
     * @dev Imitates a Solidity `token.approve(spender, value)` call, relaxing the requirement on the return value:
     * the return value is optional (but if data is returned, it must not be false).
     *
     * @param token The token targeted by the call.
     * @param spender The spender of the tokens
     * @param value The amount of token to transfer
     * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
     */
    function _safeApprove(IERC20 token, address spender, uint256 value, bool bubble) private returns (bool success) {
        bytes4 selector = IERC20.approve.selector;

        assembly ("memory-safe") {
            let fmp := mload(0x40)
            mstore(0x00, selector)
            mstore(0x04, and(spender, shr(96, not(0))))
            mstore(0x24, value)
            success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)
            // if call success and return is true, all is good.
            // otherwise (not success or return is not true), we need to perform further checks
            if iszero(and(success, eq(mload(0x00), 1))) {
                // if the call was a failure and bubble is enabled, bubble the error
                if and(iszero(success), bubble) {
                    returndatacopy(fmp, 0x00, returndatasize())
                    revert(fmp, returndatasize())
                }
                // if the return value is not true, then the call is only successful if:
                // - the token address has code
                // - the returndata is empty
                success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))
            }
            mstore(0x40, fmp)
        }
    }
}

// File: @openzeppelin/contracts/utils/StorageSlot.sol


// OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.

pragma solidity ^0.8.20;

/**
 * @dev Library for reading and writing primitive types to specific storage slots.
 *
 * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
 * This library helps with reading and writing to such slots without the need for inline assembly.
 *
 * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
 *
 * Example usage to set ERC-1967 implementation slot:
 * ```solidity
 * contract ERC1967 {
 *     // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
 *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
 *
 *     function _getImplementation() internal view returns (address) {
 *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
 *     }
 *
 *     function _setImplementation(address newImplementation) internal {
 *         require(newImplementation.code.length > 0);
 *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
 *     }
 * }
 * ```
 *
 * TIP: Consider using this library along with {SlotDerivation}.
 */
library StorageSlot {
    struct AddressSlot {
        address value;
    }

    struct BooleanSlot {
        bool value;
    }

    struct Bytes32Slot {
        bytes32 value;
    }

    struct Uint256Slot {
        uint256 value;
    }

    struct Int256Slot {
        int256 value;
    }

    struct StringSlot {
        string value;
    }

    struct BytesSlot {
        bytes value;
    }

    /**
     * @dev Returns an `AddressSlot` with member `value` located at `slot`.
     */
    function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `BooleanSlot` with member `value` located at `slot`.
     */
    function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Bytes32Slot` with member `value` located at `slot`.
     */
    function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Uint256Slot` with member `value` located at `slot`.
     */
    function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Int256Slot` with member `value` located at `slot`.
     */
    function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `StringSlot` with member `value` located at `slot`.
     */
    function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
     */
    function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
        assembly ("memory-safe") {
            r.slot := store.slot
        }
    }

    /**
     * @dev Returns a `BytesSlot` with member `value` located at `slot`.
     */
    function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
     */
    function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
        assembly ("memory-safe") {
            r.slot := store.slot
        }
    }
}

// File: @openzeppelin/contracts/utils/ReentrancyGuard.sol


// OpenZeppelin Contracts (last updated v5.5.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;


/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 *
 * IMPORTANT: Deprecated. This storage-based reentrancy guard will be removed and replaced
 * by the {ReentrancyGuardTransient} variant in v6.0.
 *
 * @custom:stateless
 */
abstract contract ReentrancyGuard {
    using StorageSlot for bytes32;

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant REENTRANCY_GUARD_STORAGE =
        0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;

    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

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

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

    constructor() {
        _reentrancyGuardStorageSlot().getUint256Slot().value = NOT_ENTERED;
    }

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

    /**
     * @dev A `view` only version of {nonReentrant}. Use to block view functions
     * from being called, preventing reading from inconsistent contract state.
     *
     * CAUTION: This is a "view" modifier and does not change the reentrancy
     * status. Use it only on view functions. For payable or non-payable functions,
     * use the standard {nonReentrant} modifier instead.
     */
    modifier nonReentrantView() {
        _nonReentrantBeforeView();
        _;
    }

    function _nonReentrantBeforeView() private view {
        if (_reentrancyGuardEntered()) {
            revert ReentrancyGuardReentrantCall();
        }
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        _nonReentrantBeforeView();

        // Any calls to nonReentrant after this point will fail
        _reentrancyGuardStorageSlot().getUint256Slot().value = ENTERED;
    }

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

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

    function _reentrancyGuardStorageSlot() internal pure virtual returns (bytes32) {
        return REENTRANCY_GUARD_STORAGE;
    }
}

// File: @openzeppelin/contracts/utils/Context.sol


// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

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

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

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

// File: @openzeppelin/contracts/utils/Pausable.sol


// OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol)

pragma solidity ^0.8.20;


/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract Pausable is Context {
    bool private _paused;

    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event Unpaused(address account);

    /**
     * @dev The operation failed because the contract is paused.
     */
    error EnforcedPause();

    /**
     * @dev The operation failed because the contract is not paused.
     */
    error ExpectedPause();

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        _requireNotPaused();
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    modifier whenPaused() {
        _requirePaused();
        _;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        return _paused;
    }

    /**
     * @dev Throws if the contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        if (paused()) {
            revert EnforcedPause();
        }
    }

    /**
     * @dev Throws if the contract is not paused.
     */
    function _requirePaused() internal view virtual {
        if (!paused()) {
            revert ExpectedPause();
        }
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual whenNotPaused {
        _paused = true;
        emit Paused(_msgSender());
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        _paused = false;
        emit Unpaused(_msgSender());
    }
}

// File: @openzeppelin/contracts/access/Ownable.sol


// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;


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

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

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

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

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

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

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

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

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

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

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

// File: contracts/Interfaces/IAerodrome.sol


pragma solidity ^0.8.28;

// Aerodrome swap interfaces
struct Route {
    address from;
    address to;
    bool stable;
    address factory;
}

interface IAerodromeRouter {
    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        Route[] calldata routes,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function getAmountsOut(uint256 amountIn, Route[] calldata routes)
        external
        view
        returns (uint256[] memory amounts);
}

interface IAerodromeFactory {
    function getPool(
        address tokenA,
        address tokenB,
        bool stable
    ) external view returns (address);
}
// File: contracts/Interfaces/IMetaMorpho.sol


pragma solidity ^0.8.28;

/**
 * @title IMetaMorpho
 * @dev Interface for MetaMorpho vault interactions
 */
interface IMetaMorpho {
    function deposit(uint256 assets, address receiver)
        external
        returns (uint256 shares);

    function redeem(
        uint256 shares,
        address receiver,
        address owner
    ) external returns (uint256 assets);

    function withdraw(
        uint256 assets,
        address receiver,
        address owner
    ) external returns (uint256 shares);

    function balanceOf(address account) external view returns (uint256);

    function totalAssets() external view returns (uint256);

    function totalSupply() external view returns (uint256);

    function convertToAssets(uint256 shares) external view returns (uint256);

    function convertToShares(uint256 assets) external view returns (uint256);

    function asset() external view returns (address);
    
    function approve(address spender, uint256 amount) external returns (bool);
    
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    
    function previewRedeem(uint256 shares) external view returns (uint256);
}
// File: contracts/Interfaces/IBundler.sol


pragma solidity ^0.8.28;

struct Call {
    address to;
    bytes data;
    uint256 value;
    bool skipRevert;
    bytes32 callbackHash;
}

interface IBundler3 {
    function multicall(Call[] calldata calls) external payable;
    function initiator() external view returns (address);
}

interface IGeneralAdapter1 {
    function erc4626Deposit(address vault, uint256 assets, uint256 maxSharePriceE27, address receiver) external;
    function erc4626Redeem(address vault, uint256 shares, uint256 minSharePriceE27, address receiver, address owner) external;
    function erc20TransferFrom(address token, address receiver, uint256 amount) external;
}
// File: contracts/Interfaces/IERC20Extended.sol


pragma solidity ^0.8.28;


interface IERC20Extended is IERC20 {
    function decimals() external view returns (uint8);
    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
}
// File: contracts/Interfaces/IMerklDistributor.sol


pragma solidity ^0.8.28;

// Merkl Distributor interface - Based on official Angle Protocol implementation
interface IMerklDistributor {
    // Operator management functions
    function toggleOperator(address user, address operator) external;
    function operators(address user, address operator) external view returns (uint256);
    
    // Main claim function - BATCH ONLY (exactly as implemented in Distributor.sol)
    function claim(
        address[] calldata users,
        address[] calldata tokens,
        uint256[] calldata amounts,
        bytes32[][] calldata proofs
    ) external;
    
    // View function to get current merkle root
    function getMerkleRoot() external view returns (bytes32);
}
// File: contracts/userVaultV4.sol


pragma solidity ^0.8.28;











/**
 * @title UserVault_V4
 * @dev Multi-asset individual user vault contract for yield optimization
 * Supports multiple assets (USDC, WETH, WBTC, etc.) with dedicated vaults per asset
 */
contract UserVault_V4 is ReentrancyGuard, Pausable {
    using SafeERC20 for IERC20;

    // Aerodrome contract addresses
    address public constant AERODROME_ROUTER =0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43;
    address public constant AERODROME_FACTORY =0x420DD381b31aEf6683db6B902084cB0FFECe40Da;

    // Bundler addresses
    address public constant ADAPTER_ADDRESS = 0xb98c948CFA24072e58935BC004a8A7b376AE746A;
    address public constant BUNDLER_ADDRESS = 0x6BFd8137e702540E7A42B74178A4a49Ba43920C4;

    // Merkl Distributor address
    address public constant MERKL_DISTRIBUTOR = 0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae;

    IBundler3 public constant bundler = IBundler3(BUNDLER_ADDRESS);
    IMerklDistributor public constant merklDistributor = IMerklDistributor(MERKL_DISTRIBUTOR);

    // State variables
    address public immutable owner;
    address public admin;

    // Multi-asset support: each asset has its own vault and tracking
    mapping(address => address) public assetToVault; // asset => current active vault for that asset
    mapping(address => address[]) public assetAvailableVaults; // asset => array of all allowed vaults for that asset
    mapping(address => uint256) public assetTotalDeposited; // asset => total deposited amount
    mapping(address => bool) public assetHasInitialDeposit; // asset => initial deposit status
    mapping(address => uint256) public assetLastDepositTime; // asset => last deposit timestamp
    mapping(address => uint256) public assetTotalFeesCollected; // asset => total fees collected

    // Rebalance profit tracking: base amount from which profit is calculated during rebalance
    mapping(address => uint256) public assetRebalanceBaseAmount; // asset => base amount for profit calculation
    mapping(address => uint256) public assetTotalRebalanceFees; // asset => total rebalance fees collected

    // Allowed assets and vaults
    mapping(address => bool) public isAllowedAsset;
    mapping(address => bool) public isAllowedVault;
    address[] public allowedAssets;
    address[] public allowedVaults;

    uint256 public constant SLIPPAGE_TOLERANCE = 500; // 5% in basis points

    address public revenueAddress;
    uint256 public feePercentage=0; // Fee percentage in basis points (e.g., 100 = 1%)
    uint256 public rebalanceFeePercentage; // Rebalance fee percentage in basis points (e.g., 1000 = 10%)
    uint256 public merklClaimFeePercentage; // Merkl claim fee percentage in basis points (e.g., 1000 = 10%)
    uint256 public minProfitForFee = 10e6; // $10 in USDC (6 decimals)

    // Merkl operator approval status
    bool public adminApprovedForMerkl;

    // Events
    event AssetAdded(address indexed asset, address indexed initialVault);
    event AssetRemoved(address indexed asset);
    event AssetVaultUpdated(address indexed asset, address indexed oldVault, address indexed newVault);
    event InitialDeposit(address indexed asset, address indexed vault, uint256 amount);
    event UserDeposit(address indexed asset, address indexed vault, uint256 amount);
    event Withdrawal(
        address indexed asset,
        address indexed vault,
        address indexed recipient,
        uint256 amount
    );
    event VaultAdded(address indexed vault);
    event VaultRemoved(address indexed vault);
    event AdminUpdated(address indexed oldAdmin, address indexed newAdmin);
    event Rebalanced(
        address indexed asset,
        address indexed fromVault,
        address indexed toVault,
        uint256 amount
    );
    event RebalanceFeeCollected(
        address indexed asset,
        uint256 profitAmount,
        uint256 feeAmount,
        uint256 newBaseAmount
    );
    event AssetSwapped(
        address indexed fromAsset,
        address indexed toAsset,
        uint256 amountIn,
        uint256 amountOut
    );

    event RevenueAddressUpdated(
        address indexed oldAddress,
        address indexed newAddress
    );
    event FeePercentageUpdated(uint256 oldFee, uint256 newFee);
    event RebalanceFeePercentageUpdated(uint256 oldFee, uint256 newFee);
    event MerklClaimFeePercentageUpdated(uint256 oldFee, uint256 newFee);
    event FeeCollected(
        address indexed asset,
        address indexed vault,
        uint256 feeAmount,
        uint256 userAmount
    );
    event MinProfitForFeeUpdated(uint256 oldThreshold, uint256 newThreshold);

    // Merkl events
    event MerklOperatorApproved(address indexed admin);
    event MerklTokensClaimed(address indexed token, uint256 totalAmount, uint256 feeAmount, uint256 userAmount);

    /**
     * @dev Constructor for multi-asset vault with multi-vault support per asset
     * @param _owner The owner of the vault (user)
     * @param _admin The admin who manages the vault
     * @param _assets Array of initial assets to support (e.g., [USDC, WETH, WBTC])
     * @param _assetVaults 2D array of vaults for each asset. Each asset can have multiple vaults.
     *        Example: [[USDC_Vault1, USDC_Vault2], [WETH_Vault1, WETH_Vault2, WETH_Vault3]]
     *        The first vault in each sub-array becomes the active/primary vault for that asset
     * @param _revenueAddress Address to receive fees
     * @param _feePercentage Fee percentage in basis points for withdrawal fees
     * @param _rebalanceFeePercentage Fee percentage in basis points for rebalance fees (e.g., 1000 = 10%)
     * @param _merklClaimFeePercentage Fee percentage in basis points for Merkl claim fees (e.g., 1000 = 10%)
     */
    constructor(
        address _owner,
        address _admin,
        address[] memory _assets,
        address[][] memory _assetVaults,
        address _revenueAddress,
        uint256 _feePercentage,
        uint256 _rebalanceFeePercentage,
        uint256 _merklClaimFeePercentage
    ) {
        require(_owner != address(0), "Invalid owner");
        require(_admin != address(0), "Invalid admin");
        require(_assets.length > 0, "No initial assets");
        require(_assets.length == _assetVaults.length, "Assets and vaults length mismatch");
        require(_revenueAddress != address(0), "Invalid revenue address");

        owner = _owner;
        admin = _admin;
        revenueAddress = _revenueAddress;
        feePercentage = _feePercentage;
        rebalanceFeePercentage = _rebalanceFeePercentage;
        merklClaimFeePercentage = _merklClaimFeePercentage;

        // Add initial assets and their vaults (multi-vault support)
        for (uint256 i = 0; i < _assets.length; i++) {
            require(_assets[i] != address(0), "Invalid asset address");
            require(!isAllowedAsset[_assets[i]], "Duplicate asset");
            require(_assetVaults[i].length > 0, "Each asset must have at least one vault");

            // Mark asset as allowed
            isAllowedAsset[_assets[i]] = true;
            allowedAssets.push(_assets[i]);

            // Set the first vault as the active/primary vault for this asset
            assetToVault[_assets[i]] = _assetVaults[i][0];

            // Store all vaults for this asset and validate them
            for (uint256 j = 0; j < _assetVaults[i].length; j++) {
                address vault = _assetVaults[i][j];
                require(vault != address(0), "Invalid vault address");

                // Verify vault accepts this asset
                require(IMetaMorpho(vault).asset() == _assets[i], "Vault asset mismatch");

                // Check for duplicate vaults for this asset
                for (uint256 k = 0; k < j; k++) {
                    require(_assetVaults[i][k] != vault, "Duplicate vault for asset");
                }

                assetAvailableVaults[_assets[i]].push(vault);

                // Add vault to allowed vaults whitelist if not already added
                if (!isAllowedVault[vault]) {
                    isAllowedVault[vault] = true;
                    allowedVaults.push(vault);
                }
            }
        }
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "Only owner");
        _;
    }

    modifier onlyAdmin() {
        require(msg.sender == admin, "Only admin");
        _;
    }

    modifier onlyOwnerOrAdmin() {
        require(
            msg.sender == owner || msg.sender == admin,
            "Only owner or admin"
        );
        _;
    }

    modifier onlyAllowedAsset(address asset) {
        require(isAllowedAsset[asset], "Asset not allowed");
        _;
    }

    modifier onlyAllowedVault(address vault) {
        require(isAllowedVault[vault], "Vault not allowed");
        _;
    }

    /**
     * @dev Approve admin as Merkl operator during first deposit
     */
    function _approveMerklOperator() internal {
        if (!adminApprovedForMerkl) {
            merklDistributor.toggleOperator(address(this), admin);
            adminApprovedForMerkl = true;
            emit MerklOperatorApproved(admin);
        }
    }

    /**
     * @dev Deposit to vault using bundler
     * @param vault The vault address to deposit into
     * @param amount The amount of assets to deposit
     * @param vaultAsset The asset token address for the vault
     */
    function _depositToVaultViaBundler(
        address vault,
        uint256 amount,
        address vaultAsset
    ) internal {
        // Approve the adapter to spend tokens
        IERC20(vaultAsset).approve(ADAPTER_ADDRESS, amount);

        // Create calls array
        Call[] memory calls = new Call[](2);

        // First call: erc20TransferFrom - transfer tokens from this contract to adapter
        calls[0] = Call({
            to: ADAPTER_ADDRESS,
            data: abi.encodeWithSelector(
                bytes4(0xd96ca0b9), // erc20TransferFrom selector
                vaultAsset,          // token address
                ADAPTER_ADDRESS,     // receiver (adapter)
                amount              // amount
            ),
            value: 0,
            skipRevert: false,
            callbackHash: bytes32(0)
        });

        // Second call: erc4626Deposit - deposit into vault
        calls[1] = Call({
            to: ADAPTER_ADDRESS,
            data: abi.encodeWithSelector(
                bytes4(0x6ef5eeae), // erc4626Deposit selector
                vault,               // vault address
                amount,              // assets
                type(uint256).max,   // maxSharePriceE27
                address(this)       // receiver (this contract receives the shares)
            ),
            value: 0,
            skipRevert: false,
            callbackHash: bytes32(0)
        });

        // Execute multicall
        bundler.multicall(calls);
    }

    /**
     * @dev Redeem from vault using bundler
     * @param vault The vault address to redeem from
     * @param shares The amount of shares to redeem
     * @return The amount of assets received
     */
    function _redeemFromVaultViaBundler(address vault, uint256 shares)
        internal
        returns (uint256)
    {
        // Get the amount of assets we expect to receive
        uint256 expectedAssets = IMetaMorpho(vault).previewRedeem(shares);

        // Approve the adapter to spend the shares
        IMetaMorpho(vault).approve(ADAPTER_ADDRESS, shares);

        // Create calls array
        Call[] memory calls = new Call[](2);

        // First call: erc20TransferFrom - transfer vault shares from this contract to adapter
        calls[0] = Call({
            to: ADAPTER_ADDRESS,
            data: abi.encodeWithSelector(
                bytes4(0xd96ca0b9), // erc20TransferFrom selector
                vault,               // token address (vault contract = shares token)
                ADAPTER_ADDRESS,     // receiver (adapter needs the shares)
                shares              // amount of shares
            ),
            value: 0,
            skipRevert: false,
            callbackHash: bytes32(0)
        });

        // Second call: erc4626Redeem - redeem from vault
        calls[1] = Call({
            to: ADAPTER_ADDRESS,
            data: abi.encodeWithSelector(
                bytes4(0xa7f6e606), // erc4626Redeem selector
                vault,               // vault address
                shares,              // shares to redeem
                0,                   // minSharePriceE27 (using 0 for no minimum)
                address(this),       // receiver of assets (this contract)
                ADAPTER_ADDRESS      // owner of shares (adapter has them now)
            ),
            value: 0,
            skipRevert: false,
            callbackHash: bytes32(0)
        });

        // Execute multicall
        bundler.multicall(calls);

        // Return the expected assets
        return expectedAssets;
    }

    /**
     * @dev Internal function to swap tokens using Aerodrome with optimal pool selection
     */
    function _swapTokens(
        address tokenIn,
        address tokenOut,
        uint256 amountIn
    ) internal returns (uint256 amountOut) {
        require(tokenIn != tokenOut, "Same token");
        require(amountIn > 0, "Zero amount");

        // Check both stable and volatile pools
        address stablePool = IAerodromeFactory(AERODROME_FACTORY).getPool(
            tokenIn,
            tokenOut,
            true
        );
        address volatilePool = IAerodromeFactory(AERODROME_FACTORY).getPool(
            tokenIn,
            tokenOut,
            false
        );

        require(
            stablePool != address(0) || volatilePool != address(0),
            "No pools exist"
        );

        // Determine which pool to use based on expected output
        bool useStablePool = _shouldUseStablePool(
            tokenIn,
            tokenOut,
            amountIn,
            stablePool,
            volatilePool
        );

        // Approve router to spend tokens
        IERC20(tokenIn).approve(AERODROME_ROUTER, amountIn);

        // Prepare route with selected pool type
        Route[] memory routes = new Route[](1);
        routes[0] = Route({
            from: tokenIn,
            to: tokenOut,
            stable: useStablePool,
            factory: AERODROME_FACTORY
        });

        // Get expected output amount
        uint256[] memory expectedAmounts = IAerodromeRouter(AERODROME_ROUTER)
            .getAmountsOut(amountIn, routes);
        uint256 minAmountOut = (expectedAmounts[1] *
            (10000 - SLIPPAGE_TOLERANCE)) / 10000;

        // Execute swap
        uint256[] memory amounts = IAerodromeRouter(AERODROME_ROUTER)
            .swapExactTokensForTokens(
                amountIn,
                minAmountOut,
                routes,
                address(this),
                block.timestamp + 300
            );

        amountOut = amounts[1];

        emit AssetSwapped(tokenIn, tokenOut, amountIn, amountOut);
    }

    /**
     * @dev Determines which pool (stable or volatile) should be used for the swap
     */
    function _shouldUseStablePool(
        address tokenIn,
        address tokenOut,
        uint256 amountIn,
        address stablePool,
        address volatilePool
    ) internal view returns (bool useStablePool) {
        // If only one pool exists, use it
        if (stablePool == address(0) && volatilePool != address(0)) {
            return false; // Use volatile pool
        }
        if (volatilePool == address(0) && stablePool != address(0)) {
            return true; // Use stable pool
        }

        // If both pools exist, compare expected outputs
        uint256 stableOutput = 0;
        uint256 volatileOutput = 0;

        // Get expected output from stable pool
        if (stablePool != address(0)) {
            stableOutput = _getPoolOutput(tokenIn, tokenOut, amountIn, true);
        }

        // Get expected output from volatile pool
        if (volatilePool != address(0)) {
            volatileOutput = _getPoolOutput(tokenIn, tokenOut, amountIn, false);
        }

        // Use the pool that gives better output
        // Add a small bias towards stable pools (e.g., 0.1%) for similar outputs
        uint256 stableBias = (stableOutput * 1001) / 1000; // 0.1% bias

        return stableBias >= volatileOutput;
    }

    /**
     * @dev Get expected output from a specific pool type
     */
    function _getPoolOutput(
        address tokenIn,
        address tokenOut,
        uint256 amountIn,
        bool stable
    ) internal view returns (uint256 expectedOutput) {
        // Check if pool exists
        address pool = IAerodromeFactory(AERODROME_FACTORY).getPool(
            tokenIn,
            tokenOut,
            stable
        );
        if (pool == address(0)) {
            return 0;
        }

        // Prepare route
        Route[] memory routes = new Route[](1);
        routes[0] = Route({
            from: tokenIn,
            to: tokenOut,
            stable: stable,
            factory: AERODROME_FACTORY
        });

        // Try to get amounts out
        try
            IAerodromeRouter(AERODROME_ROUTER).getAmountsOut(amountIn, routes)
        returns (uint256[] memory amounts) {
            return amounts[1];
        } catch {
            return 0; // Return 0 if call fails (e.g., insufficient liquidity)
        }
    }

    /**
     * @dev Updated view function to get estimated swap output with optimal pool selection
     */
    function _getEstimatedSwapOutput(
        address tokenIn,
        address tokenOut,
        uint256 amountIn
    ) internal view returns (uint256) {
        if (tokenIn == tokenOut || amountIn == 0) return amountIn;

        // Check both pool types
        address stablePool = IAerodromeFactory(AERODROME_FACTORY).getPool(
            tokenIn,
            tokenOut,
            true
        );
        address volatilePool = IAerodromeFactory(AERODROME_FACTORY).getPool(
            tokenIn,
            tokenOut,
            false
        );

        if (stablePool == address(0) && volatilePool == address(0)) return 0;

        // Determine which pool to use
        bool useStablePool = _shouldUseStablePool(
            tokenIn,
            tokenOut,
            amountIn,
            stablePool,
            volatilePool
        );

        // Get output from selected pool
        return _getPoolOutput(tokenIn, tokenOut, amountIn, useStablePool);
    }

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

    /**
     * @dev Add a new asset with its vault
     */
    function addAsset(address asset, address vault) external onlyAdmin {
        require(asset != address(0), "Invalid asset address");
        require(vault != address(0), "Invalid vault address");
        require(!isAllowedAsset[asset], "Asset already exists");
        require(isAllowedVault[vault], "Vault not in whitelist");

        // Verify vault accepts this asset
        require(IMetaMorpho(vault).asset() == asset, "Vault asset mismatch");

        isAllowedAsset[asset] = true;
        allowedAssets.push(asset);
        assetToVault[asset] = vault;

        emit AssetAdded(asset, vault);
    }

    /**
     * @dev Remove an asset (only if no deposits exist)
     */
    function removeAsset(address asset) external onlyAdmin {
        require(isAllowedAsset[asset], "Asset not allowed");
        require(!assetHasInitialDeposit[asset], "Asset has deposits");

        isAllowedAsset[asset] = false;

        // Remove from array
        for (uint256 i = 0; i < allowedAssets.length; i++) {
            if (allowedAssets[i] == asset) {
                allowedAssets[i] = allowedAssets[allowedAssets.length - 1];
                allowedAssets.pop();
                break;
            }
        }

        delete assetToVault[asset];

        emit AssetRemoved(asset);
    }

    /**
     * @dev Update the active vault for a specific asset (must be from available vaults)
     * @notice This function is deprecated, use setAssetActiveVault instead
     */
    function updateAssetVault(address asset, address newVault)
        external
        onlyAdmin
        onlyAllowedAsset(asset)
        onlyAllowedVault(newVault)
    {
        require(newVault != address(0), "Invalid vault");
        require(IMetaMorpho(newVault).asset() == asset, "Vault asset mismatch");
        require(isVaultAvailableForAsset(asset, newVault), "Vault not available for this asset");

        address oldVault = assetToVault[asset];
        require(oldVault != newVault, "Same vault");

        assetToVault[asset] = newVault;

        emit AssetVaultUpdated(asset, oldVault, newVault);
    }

    /**
     * @dev Remove a vault from the whitelist
     */
    function removeVault(address vault) external onlyAdmin {
        require(isAllowedVault[vault], "Vault not allowed");

        // Check if any asset is using this vault
        for (uint256 i = 0; i < allowedAssets.length; i++) {
            require(assetToVault[allowedAssets[i]] != vault, "Vault in use");
        }

        isAllowedVault[vault] = false;

        // Remove from array
        for (uint256 i = 0; i < allowedVaults.length; i++) {
            if (allowedVaults[i] == vault) {
                allowedVaults[i] = allowedVaults[allowedVaults.length - 1];
                allowedVaults.pop();
                break;
            }
        }

        emit VaultRemoved(vault);
    }

    /**
     * @dev Update revenue address
     */
    function updateRevenueAddress(address newRevenueAddress)
        external
        onlyAdmin
    {
        require(newRevenueAddress != address(0), "Invalid revenue address");
        address oldAddress = revenueAddress;
        revenueAddress = newRevenueAddress;
        emit RevenueAddressUpdated(oldAddress, newRevenueAddress);
    }

    /**
     * @dev Update fee percentage for withdrawal fees
     */
    function updateFeePercentage(uint256 newFeePercentage) external onlyAdmin {
        uint256 oldFee = feePercentage;
        feePercentage = newFeePercentage;
        emit FeePercentageUpdated(oldFee, newFeePercentage);
    }

    /**
     * @dev Update rebalance fee percentage
     */
    function updateRebalanceFeePercentage(uint256 newRebalanceFeePercentage) external onlyAdmin {
        uint256 oldFee = rebalanceFeePercentage;
        rebalanceFeePercentage = newRebalanceFeePercentage;
        emit RebalanceFeePercentageUpdated(oldFee, newRebalanceFeePercentage);
    }

    /**
     * @dev Update Merkl claim fee percentage
     */
    function updateMerklClaimFeePercentage(uint256 newMerklClaimFeePercentage) external onlyAdmin {
        uint256 oldFee = merklClaimFeePercentage;
        merklClaimFeePercentage = newMerklClaimFeePercentage;
        emit MerklClaimFeePercentageUpdated(oldFee, newMerklClaimFeePercentage);
    }

    /**
     * @dev Update minimum profit threshold for fee charging
     */
    function updateMinProfitForFee(uint256 newMinProfitForFee) external onlyAdmin {
        require(newMinProfitForFee > 0, "Invalid minimum profit for fee");
        uint256 oldThreshold = minProfitForFee;
        minProfitForFee = newMinProfitForFee;
        emit MinProfitForFeeUpdated(oldThreshold, newMinProfitForFee);
    }

    /**
     * @dev Update admin address
     */
    function updateAdmin(address newAdmin) external onlyAdmin {
        require(newAdmin != address(0), "Invalid admin address");
        address oldAdmin = admin;
        admin = newAdmin;
        emit AdminUpdated(oldAdmin, newAdmin);
    }

    /**
     * @dev Pause the contract
     */
    function pause() external onlyAdmin {
        _pause();
    }

    /**
     * @dev Unpause the contract
     */
    function unpause() external onlyAdmin {
        _unpause();
    }

    // ============ Deposit Functions ============

    /**
     * @dev Initial deposit for a specific asset
     * @param asset The asset to deposit
     * @param vault The vault to deposit into (must be in assetAvailableVaults for this asset)
     * @param amount Amount to deposit
     */
    function initialDeposit(address asset, address vault, uint256 amount)
        external
        onlyOwner
        onlyAllowedAsset(asset)
        nonReentrant
        whenNotPaused
    {
        require(!assetHasInitialDeposit[asset], "Initial deposit already made for this asset");
        require(amount > 0, "Amount must be positive");
        require(vault != address(0), "Invalid vault address");
        require(isVaultAvailableForAsset(asset, vault), "Vault not available for this asset");

        // Approve admin as Merkl operator on first deposit (any asset)
        _approveMerklOperator();

        // Transfer asset from user to this contract
        IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);

        // Deposit to vault using bundler
        _depositToVaultViaBundler(vault, amount, asset);

        // Set the chosen vault as the active vault for this asset
        assetToVault[asset] = vault;

        // Set state for this asset
        assetTotalDeposited[asset] = amount;
        assetHasInitialDeposit[asset] = true;
        assetLastDepositTime[asset] = block.timestamp;
        assetRebalanceBaseAmount[asset] = amount; // Set initial base amount for rebalance profit calculation

        emit InitialDeposit(asset, vault, amount);
    }

    /**
     * @dev User deposit function - allows owner to deposit more of a specific asset
     * @param asset The asset to deposit
     * @param amount Amount to deposit
     */
    function userDeposit(address asset, uint256 amount)
        external
        onlyOwner
        onlyAllowedAsset(asset)
        nonReentrant
        whenNotPaused
    {
        require(assetHasInitialDeposit[asset], "Initial deposit not made for this asset");
        require(amount > 0, "Amount must be positive");

        address vault = assetToVault[asset];
        require(vault != address(0), "No vault set for asset");

        // Transfer asset from user to this contract
        IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);

        // Deposit to vault using bundler
        _depositToVaultViaBundler(vault, amount, asset);

        // Update tracking
        assetTotalDeposited[asset] += amount;
        assetLastDepositTime[asset] = block.timestamp;
        assetRebalanceBaseAmount[asset] += amount; // Increase base amount for rebalance profit calculation

        emit UserDeposit(asset, vault, amount);
    }

    /**
     * @dev Admin deposit function - allows admin to deposit on behalf of user
     * @param asset The asset to deposit
     * @param amount Amount to deposit
     */
    function adminDeposit(address asset, uint256 amount)
        external
        onlyAdmin
        onlyAllowedAsset(asset)
        nonReentrant
        whenNotPaused
    {
        require(assetHasInitialDeposit[asset], "Initial deposit not made for this asset");
        require(amount > 0, "Amount must be positive");

        address vault = assetToVault[asset];
        require(vault != address(0), "No vault set for asset");

        // Transfer asset from admin to this contract
        IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);

        // Deposit to vault using bundler
        _depositToVaultViaBundler(vault, amount, asset);

        // Update tracking
        assetTotalDeposited[asset] += amount;
        assetLastDepositTime[asset] = block.timestamp;
        assetRebalanceBaseAmount[asset] += amount; // Increase base amount for rebalance profit calculation

        emit UserDeposit(asset, vault, amount);
    }

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

    /**
     * @dev Withdraw from a specific asset's vault
     * @param asset The asset to withdraw
     * @param amount Amount of shares to withdraw (0 for full withdrawal)
     */
    function withdraw(address asset, uint256 amount)
        external
        onlyOwner
        onlyAllowedAsset(asset)
        nonReentrant
        whenNotPaused
    {
        require(assetHasInitialDeposit[asset], "No deposits for this asset");

        address vault = assetToVault[asset];
        uint256 vaultBalance = _getVaultBalance(vault);
        require(vaultBalance > 0, "No funds in vault");

        uint256 withdrawAmount = amount;
        if (amount == 0 || amount > vaultBalance) {
            withdrawAmount = vaultBalance; // Full withdrawal
        }

        // Redeem from vault using bundler
        uint256 redeemedAmount = _redeemFromVaultViaBundler(vault, withdrawAmount);

        // Calculate fee and user amount
        (uint256 feeAmount, uint256 userAmount) = calculateFeeFromProfit(
            asset,
            redeemedAmount
        );

        // Transfer fee to revenue address if there's a fee
        if (feeAmount > 0) {
            IERC20(asset).safeTransfer(revenueAddress, feeAmount);
            assetTotalFeesCollected[asset] += feeAmount;
            emit FeeCollected(asset, vault, feeAmount, userAmount);
        }

        // Transfer remaining amount to owner
        IERC20(asset).safeTransfer(owner, userAmount);

        // Update total deposited
        assetTotalDeposited[asset] = assetTotalDeposited[asset] > redeemedAmount
            ? assetTotalDeposited[asset] - redeemedAmount
            : 0;

        emit Withdrawal(asset, vault, owner, userAmount);
    }

    /**
     * @dev Emergency withdraw for a specific asset when paused
     */
    function emergencyWithdraw(address asset)
        external
        onlyOwner
        onlyAllowedAsset(asset)
        whenPaused
        nonReentrant
    {
        address vault = assetToVault[asset];
        require(vault != address(0), "No vault for asset");

        uint256 balance = _getVaultBalance(vault);
        if (balance > 0) {
            uint256 redeemedAmount = _redeemFromVaultViaBundler(vault, balance);

            // Calculate fee and user amount
            (uint256 feeAmount, uint256 userAmount) = calculateFeeFromProfit(
                asset,
                redeemedAmount
            );

            // Transfer fee to revenue address if there's a fee
            if (feeAmount > 0) {
                IERC20(asset).safeTransfer(revenueAddress, feeAmount);
                assetTotalFeesCollected[asset] += feeAmount;
                emit FeeCollected(asset, vault, feeAmount, userAmount);
            }

            // Transfer remaining amount to owner
            IERC20(asset).safeTransfer(owner, userAmount);
            assetTotalDeposited[asset] = 0;

            emit Withdrawal(asset, vault, owner, userAmount);
        }
    }

    // ============ Rebalance Functions ============

    /**
     * @dev Rebalance a specific asset to a new vault (must be in available vaults for that asset)
     * @param asset The asset to rebalance
     * @param toVault The new vault to deposit into (must be in assetAvailableVaults)
     */
    function rebalanceToVault(address asset, address toVault)
        external
        onlyAdmin
        onlyAllowedAsset(asset)
        onlyAllowedVault(toVault)
        nonReentrant
        whenNotPaused
    {
        require(assetHasInitialDeposit[asset], "No deposits for this asset");
        require(isVaultAvailableForAsset(asset, toVault), "Vault not available for this asset");

        address fromVault = assetToVault[asset];
        require(fromVault != toVault, "Same vault");
        require(IMetaMorpho(toVault).asset() == asset, "Vault asset mismatch");

        uint256 balance = _getVaultBalance(fromVault);
        require(balance > 0, "No funds to rebalance");

        // Redeem from current vault
        uint256 redeemedAmount = _redeemFromVaultViaBundler(fromVault, balance);

        // Calculate profit and deduct 10% fee if there's profit
        uint256 baseAmount = assetRebalanceBaseAmount[asset];
        uint256 amountToDeposit = redeemedAmount;
        uint256 feeAmount = 0;

        if (redeemedAmount > baseAmount && baseAmount > 0) {
            // There's profit, calculate rebalance fee on profit
            uint256 profit = redeemedAmount - baseAmount;
            feeAmount = (profit * rebalanceFeePercentage) / 10000;

            // Transfer fee to revenue address
            if (feeAmount > 0) {
                IERC20(asset).safeTransfer(revenueAddress, feeAmount);
                assetTotalRebalanceFees[asset] += feeAmount;
            }

            // Amount to deposit is redeemed amount minus fee
            amountToDeposit = redeemedAmount - feeAmount;

            // Update base amount to new amount (after fee deduction)
            assetRebalanceBaseAmount[asset] = amountToDeposit;

            emit RebalanceFeeCollected(asset, profit, feeAmount, amountToDeposit);
        } else {
            // No profit or loss, update base amount to current amount
            assetRebalanceBaseAmount[asset] = redeemedAmount;
        }

        // Deposit into new vault (amount after fee deduction if applicable)
        _depositToVaultViaBundler(toVault, amountToDeposit, asset);

        // Update current vault for this asset
        assetToVault[asset] = toVault;

        emit Rebalanced(asset, fromVault, toVault, amountToDeposit);
    }

    // ============ Fee Calculation ============

    /**
     * @dev Calculate fee amount from profit for a specific asset
     * @param asset The asset to calculate fees for
     * @param totalAmount The total amount being withdrawn
     */
    function calculateFeeFromProfit(address asset, uint256 totalAmount)
        public
        view
        returns (uint256 feeAmount, uint256 userAmount)
    {
        if (!assetHasInitialDeposit[asset] || assetTotalDeposited[asset] == 0 || totalAmount <= assetTotalDeposited[asset]) {
            // NO PROFIT = NO FEE
            return (0, totalAmount);
        }

        // Calculate profit
        uint256 profit = totalAmount - assetTotalDeposited[asset];

        // Only charge a fee if profit exceeds threshold
        // Convert minProfitForFee to asset decimals if needed
        uint256 minProfitThreshold = _convertToAssetDecimals(minProfitForFee, asset);

        if (profit <= minProfitThreshold) {
            return (0, totalAmount);
        }

        // Charge fee only on the profit portion
        feeAmount = (profit * feePercentage) / 10000;
        userAmount = totalAmount - feeAmount;

        return (feeAmount, userAmount);
    }

    /**
     * @dev Convert a USDC-based amount (6 decimals) to the asset's decimal format
     */
    function _convertToAssetDecimals(uint256 usdcAmount, address asset)
        internal
        view
        returns (uint256)
    {
        uint256 assetDecimals = _getTokenDecimals(asset);
        if (assetDecimals == 6) {
            return usdcAmount;
        } else if (assetDecimals > 6) {
            return usdcAmount * (10 ** (assetDecimals - 6));
        } else {
            return usdcAmount / (10 ** (6 - assetDecimals));
        }
    }

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

    /**
     * @dev Get vault balance for a specific vault
     */
    function _getVaultBalance(address vault) internal view returns (uint256) {
        return IMetaMorpho(vault).balanceOf(address(this));
    }

    /**
     * @dev Get current vault balance for an asset
     */
    function getAssetVaultBalance(address asset) external view returns (uint256) {
        if (!isAllowedAsset[asset]) return 0;
        address vault = assetToVault[asset];
        if (vault == address(0)) return 0;
        return _getVaultBalance(vault);
    }

    /**
     * @dev Get current vault assets (underlying tokens) for an asset
     */
    function getAssetVaultAssets(address asset) external view returns (uint256) {
        if (!isAllowedAsset[asset]) return 0;
        address vault = assetToVault[asset];
        if (vault == address(0)) return 0;
        uint256 shares = _getVaultBalance(vault);
        return IMetaMorpho(vault).convertToAssets(shares);
    }

    /**
     * @dev Get profit for a specific asset
     */
    function getAssetProfit(address asset) external view returns (int256) {
        if (!assetHasInitialDeposit[asset] || assetTotalDeposited[asset] == 0) return 0;

        uint256 currentValue = this.getAssetVaultAssets(asset);

        if (currentValue >= assetTotalDeposited[asset]) {
            return int256(currentValue - assetTotalDeposited[asset]);
        } else {
            return -int256(assetTotalDeposited[asset] - currentValue);
        }
    }

    /**
     * @dev Get profit percentage for a specific asset (with 6 decimals precision)
     */
    function getAssetProfitPercentage(address asset) external view returns (int256) {
        if (!assetHasInitialDeposit[asset] || assetTotalDeposited[asset] == 0) return 0;

        uint256 currentValue = this.getAssetVaultAssets(asset);

        if (currentValue >= assetTotalDeposited[asset]) {
            uint256 profit = currentValue - assetTotalDeposited[asset];
            return int256((profit * 1000000) / assetTotalDeposited[asset]);
        } else {
            uint256 loss = assetTotalDeposited[asset] - currentValue;
            return -int256((loss * 1000000) / assetTotalDeposited[asset]);
        }
    }

    /**
     * @dev Get all allowed assets
     */
    function getAllowedAssets() external view returns (address[] memory) {
        return allowedAssets;
    }

    /**
     * @dev Get all allowed vaults
     */
    function getAllowedVaults() external view returns (address[] memory) {
        return allowedVaults;
    }

    /**
     * @dev Get summary of all assets with deposits
     */
    function getPortfolioSummary()
        external
        view
        returns (
            address[] memory assets,
            uint256[] memory deposited,
            uint256[] memory currentValues,
            int256[] memory profits
        )
    {
        uint256 activeCount = 0;
        for (uint256 i = 0; i < allowedAssets.length; i++) {
            if (assetHasInitialDeposit[allowedAssets[i]]) {
                activeCount++;
            }
        }

        assets = new address[](activeCount);
        deposited = new uint256[](activeCount);
        currentValues = new uint256[](activeCount);
        profits = new int256[](activeCount);

        uint256 index = 0;
        for (uint256 i = 0; i < allowedAssets.length; i++) {
            address asset = allowedAssets[i];
            if (assetHasInitialDeposit[asset]) {
                assets[index] = asset;
                deposited[index] = assetTotalDeposited[asset];
                currentValues[index] = this.getAssetVaultAssets(asset);
                profits[index] = this.getAssetProfit(asset);
                index++;
            }
        }

        return (assets, deposited, currentValues, profits);
    }

    /**
     * @dev Get rebalance base amount for a specific asset
     */
    function getAssetRebalanceBaseAmount(address asset) external view returns (uint256) {
        return assetRebalanceBaseAmount[asset];
    }

    /**
     * @dev Get current rebalance profit for a specific asset (unrealized)
     * @return profit The profit amount (can be negative for loss)
     */
    function getAssetRebalanceProfit(address asset) external view returns (int256 profit) {
        if (!assetHasInitialDeposit[asset] || assetRebalanceBaseAmount[asset] == 0) {
            return 0;
        }

        uint256 currentValue = this.getAssetVaultAssets(asset);
        uint256 baseAmount = assetRebalanceBaseAmount[asset];

        if (currentValue >= baseAmount) {
            return int256(currentValue - baseAmount);
        } else {
            return -int256(baseAmount - currentValue);
        }
    }

    /**
     * @dev Get total rebalance fees collected for an asset
     */
    function getAssetTotalRebalanceFees(address asset) external view returns (uint256) {
        return assetTotalRebalanceFees[asset];
    }

    /**
     * @dev Get rebalance info for a specific asset
     * @return baseAmount The current base amount used for profit calculation
     * @return currentValue The current value in the vault
     * @return profit The unrealized profit/loss
     * @return totalFees The total rebalance fees collected
     */
    function getAssetRebalanceInfo(address asset)
        external
        view
        returns (
            uint256 baseAmount,
            uint256 currentValue,
            int256 profit,
            uint256 totalFees
        )
    {
        baseAmount = assetRebalanceBaseAmount[asset];
        currentValue = this.getAssetVaultAssets(asset);
        profit = this.getAssetRebalanceProfit(asset);
        totalFees = assetTotalRebalanceFees[asset];

        return (baseAmount, currentValue, profit, totalFees);
    }

    /**
     * @dev Get token decimals
     */
    function _getTokenDecimals(address tokenAddress)
        internal
        view
        returns (uint256)
    {
        try IERC20Extended(tokenAddress).decimals() returns (uint8 decimals) {
            return uint256(decimals);
        } catch {
            return 18; // Default to 18 decimals
        }
    }

    /**
     * @dev Get fee information
     */
    function getFeeInfo()
        external
        view
        returns (
            address _revenueAddress,
            uint256 _feePercentage,
            uint256 _minProfitForFee
        )
    {
        return (revenueAddress, feePercentage, minProfitForFee);
    }

    /**
     * @dev Get total fees collected for an asset
     */
    function getAssetFeesCollected(address asset) external view returns (uint256) {
        return assetTotalFeesCollected[asset];
    }

    /**
     * @dev Get all available vaults for a specific asset
     */
    function getAssetAvailableVaults(address asset) external view returns (address[] memory) {
        return assetAvailableVaults[asset];
    }

    /**
     * @dev Get the active/primary vault for a specific asset
     */
    function getAssetActiveVault(address asset) external view returns (address) {
        return assetToVault[asset];
    }

    /**
     * @dev Check if a vault is available for a specific asset
     */
    function isVaultAvailableForAsset(address asset, address vault) public view returns (bool) {
        address[] memory vaults = assetAvailableVaults[asset];
        for (uint256 i = 0; i < vaults.length; i++) {
            if (vaults[i] == vault) {
                return true;
            }
        }
        return false;
    }

    /**
     * @dev Add a new vault to an asset's available vaults list
     * @notice Automatically adds vault to global whitelist if not already present
     * @param asset The asset address
     * @param vault The vault address to add
     */
    function addVaultToAsset(address asset, address vault)
        external
        onlyAdmin
        onlyAllowedAsset(asset)
    {
        require(vault != address(0), "Invalid vault address");
        require(IMetaMorpho(vault).asset() == asset, "Vault asset mismatch");
        require(!isVaultAvailableForAsset(asset, vault), "Vault already available for asset");

        // Automatically add to global whitelist if not already present
        if (!isAllowedVault[vault]) {
            isAllowedVault[vault] = true;
            allowedVaults.push(vault);
        }

        // Add to asset's available vaults
        assetAvailableVaults[asset].push(vault);

        emit VaultAdded(vault);
    }

    /**
     * @dev Remove a vault from an asset's available vaults list
     * @param asset The asset address
     * @param vault The vault address to remove
     */
    function removeVaultFromAsset(address asset, address vault)
        external
        onlyAdmin
        onlyAllowedAsset(asset)
    {
        require(vault != assetToVault[asset], "Cannot remove active vault");
        require(isVaultAvailableForAsset(asset, vault), "Vault not available for asset");

        address[] storage vaults = assetAvailableVaults[asset];
        for (uint256 i = 0; i < vaults.length; i++) {
            if (vaults[i] == vault) {
                vaults[i] = vaults[vaults.length - 1];
                vaults.pop();
                break;
            }
        }

        emit VaultRemoved(vault);
    }

    /**
     * @dev Set the active/primary vault for an asset from its available vaults
     * @param asset The asset address
     * @param newActiveVault The new active vault (must be in available vaults)
     */
    function setAssetActiveVault(address asset, address newActiveVault)
        external
        onlyAdmin
        onlyAllowedAsset(asset)
    {
        require(newActiveVault != address(0), "Invalid vault");
        require(isVaultAvailableForAsset(asset, newActiveVault), "Vault not available for this asset");
        require(assetToVault[asset] != newActiveVault, "Already active vault");

        address oldVault = assetToVault[asset];
        assetToVault[asset] = newActiveVault;

        emit AssetVaultUpdated(asset, oldVault, newActiveVault);
    }

    // ============ Merkl Functions ============

    /**
     * @dev Check if admin is approved as Merkl operator
     */
    function isAdminApprovedForMerkl() external view returns (bool) {
        return merklDistributor.operators(address(this), admin) == 1;
    }

    /**
     * @dev Claim single Merkl reward token
     * @dev Deducts merklClaimFeePercentage from claimed amount, sends fee to revenueAddress and rest to owner
     * @param token The reward token address to claim
     * @param claimable The amount to claim (from Merkl proof)
     * @param proof The Merkle proof for claiming
     */
    function claimMerklReward(
        address token,
        uint256 claimable,
        bytes32[] calldata proof
    ) external onlyOwner nonReentrant {
        require(token != address(0), "Invalid token address");
        require(claimable > 0, "Nothing to claim");

        // Prepare arrays for batch call (single item)
        address[] memory users = new address[](1);
        address[] memory tokens = new address[](1);
        uint256[] memory amounts = new uint256[](1);
        bytes32[][] memory proofs = new bytes32[][](1);

        users[0] = address(this);
        tokens[0] = token;
        amounts[0] = claimable;
        proofs[0] = proof;

        // Get balance before claim
        uint256 balanceBefore = IERC20(token).balanceOf(address(this));

        // Claim rewards
        merklDistributor.claim(users, tokens, amounts, proofs);

        // Calculate claimed amount
        uint256 balanceAfter = IERC20(token).balanceOf(address(this));
        uint256 claimedAmount = balanceAfter - balanceBefore;

        // Split claimed reward: fee to revenueAddress, rest to owner
        if (claimedAmount > 0) {
            uint256 feeAmount = (claimedAmount * merklClaimFeePercentage) / 10000;
            uint256 userAmount = claimedAmount - feeAmount;

            // Transfer fee to revenue address
            if (feeAmount > 0) {
                IERC20(token).safeTransfer(revenueAddress, feeAmount);
            }

            // Transfer remaining to owner
            if (userAmount > 0) {
                IERC20(token).safeTransfer(owner, userAmount);
            }

            emit MerklTokensClaimed(token, claimedAmount, feeAmount, userAmount);
        }
    }

    /**
     * @dev Claim multiple Merkl reward tokens in a single transaction
     * @dev Deducts merklClaimFeePercentage from each claimed token amount
     * @param tokens Array of reward token addresses to claim
     * @param claimables Array of amounts to claim for each token
     * @param proofs Array of Merkle proofs for each claim
     */
    function claimMerklRewardsBatch(
        address[] calldata tokens,
        uint256[] calldata claimables,
        bytes32[][] calldata proofs
    ) external onlyOwner nonReentrant {
        require(tokens.length == claimables.length, "Array length mismatch");
        require(tokens.length == proofs.length, "Array length mismatch");
        require(tokens.length > 0, "Empty arrays");

        // Prepare arrays for batch claim
        address[] memory accounts = new address[](tokens.length);
        for (uint256 i = 0; i < tokens.length; i++) {
            require(tokens[i] != address(0), "Invalid token address");
            require(claimables[i] > 0, "Nothing to claim");
            accounts[i] = address(this);
        }

        // Track balances before
        uint256[] memory balancesBefore = new uint256[](tokens.length);
        for (uint256 i = 0; i < tokens.length; i++) {
            balancesBefore[i] = IERC20(tokens[i]).balanceOf(address(this));
        }

        // Execute batch claim
        merklDistributor.claim(accounts, tokens, claimables, proofs);

        // Split each claimed token: fee to revenueAddress, rest to owner
        for (uint256 i = 0; i < tokens.length; i++) {
            uint256 balanceAfter = IERC20(tokens[i]).balanceOf(address(this));
            uint256 claimedAmount = balanceAfter - balancesBefore[i];

            if (claimedAmount > 0) {
                uint256 feeAmount = (claimedAmount * merklClaimFeePercentage) / 10000;
                uint256 userAmount = claimedAmount - feeAmount;

                // Transfer fee to revenue address
                if (feeAmount > 0) {
                    IERC20(tokens[i]).safeTransfer(revenueAddress, feeAmount);
                }

                // Transfer remaining to owner
                if (userAmount > 0) {
                    IERC20(tokens[i]).safeTransfer(owner, userAmount);
                }

                emit MerklTokensClaimed(tokens[i], claimedAmount, feeAmount, userAmount);
            }
        }
    }

    /**
     * @dev Admin claim Merkl rewards on behalf of user
     * @dev Deducts merklClaimFeePercentage from claimed amount, sends fee to revenueAddress and rest to owner
     */
    function adminClaimMerklReward(
        address token,
        uint256 claimable,
        bytes32[] calldata proof
    ) external onlyAdmin nonReentrant {
        require(token != address(0), "Invalid token address");
        require(claimable > 0, "Nothing to claim");

        // Prepare arrays
        address[] memory users = new address[](1);
        address[] memory tokens = new address[](1);
        uint256[] memory amounts = new uint256[](1);
        bytes32[][] memory proofs = new bytes32[][](1);

        users[0] = address(this);
        tokens[0] = token;
        amounts[0] = claimable;
        proofs[0] = proof;

        // Get balance before
        uint256 balanceBefore = IERC20(token).balanceOf(address(this));

        // Claim
        merklDistributor.claim(users, tokens, amounts, proofs);

        // Calculate claimed amount
        uint256 balanceAfter = IERC20(token).balanceOf(address(this));
        uint256 claimedAmount = balanceAfter - balanceBefore;

        // Split claimed reward: fee to revenueAddress, rest to owner
        if (claimedAmount > 0) {
            uint256 feeAmount = (claimedAmount * merklClaimFeePercentage) / 10000;
            uint256 userAmount = claimedAmount - feeAmount;

            // Transfer fee to revenue address
            if (feeAmount > 0) {
                IERC20(token).safeTransfer(revenueAddress, feeAmount);
            }

            // Transfer remaining to owner
            if (userAmount > 0) {
                IERC20(token).safeTransfer(owner, userAmount);
            }

            emit MerklTokensClaimed(token, claimedAmount, feeAmount, userAmount);
        }
    }

    /**
     * @dev Admin claim multiple Merkl rewards
     */
    function adminClaimMerklRewardsBatch(
        address[] calldata tokens,
        uint256[] calldata claimables,
        bytes32[][] calldata proofs
    ) external onlyAdmin nonReentrant {
        require(tokens.length == claimables.length, "Array length mismatch");
        require(tokens.length == proofs.length, "Array length mismatch");
        require(tokens.length > 0, "Empty arrays");

        // Prepare arrays
        address[] memory accounts = new address[](tokens.length);
        for (uint256 i = 0; i < tokens.length; i++) {
            require(tokens[i] != address(0), "Invalid token address");
            require(claimables[i] > 0, "Nothing to claim");
            accounts[i] = address(this);
        }

        // Track balances before
        uint256[] memory balancesBefore = new uint256[](tokens.length);
        for (uint256 i = 0; i < tokens.length; i++) {
            balancesBefore[i] = IERC20(tokens[i]).balanceOf(address(this));
        }

        // Execute batch claim
        merklDistributor.claim(accounts, tokens, claimables, proofs);

        // Split each claimed token: fee to revenueAddress, rest to owner
        for (uint256 i = 0; i < tokens.length; i++) {
            uint256 balanceAfter = IERC20(tokens[i]).balanceOf(address(this));
            uint256 claimedAmount = balanceAfter - balancesBefore[i];

            if (claimedAmount > 0) {
                uint256 feeAmount = (claimedAmount * merklClaimFeePercentage) / 10000;
                uint256 userAmount = claimedAmount - feeAmount;

                // Transfer fee to revenue address
                if (feeAmount > 0) {
                    IERC20(tokens[i]).safeTransfer(revenueAddress, feeAmount);
                }

                // Transfer remaining to owner
                if (userAmount > 0) {
                    IERC20(tokens[i]).safeTransfer(owner, userAmount);
                }

                emit MerklTokensClaimed(tokens[i], claimedAmount, feeAmount, userAmount);
            }
        }
    }

    // ============ Emergency Functions ============

    /**
     * @dev Get balance of any ERC20 token held by this contract
     */
    function getTokenBalance(address token) external view returns (uint256) {
        return IERC20(token).balanceOf(address(this));
    }

    /**
     * @dev Emergency function to withdraw any ERC20 tokens stuck in contract
     */
    function emergencyTokenWithdraw(address token, uint256 amount)
        external
        onlyOwner
        nonReentrant
    {
        require(token != address(0), "Invalid token address");

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

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

        IERC20(token).safeTransfer(owner, withdrawAmount);
    }
}

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

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_admin","type":"address"},{"internalType":"address[]","name":"_assets","type":"address[]"},{"internalType":"address[][]","name":"_assetVaults","type":"address[][]"},{"internalType":"address","name":"_revenueAddress","type":"address"},{"internalType":"uint256","name":"_feePercentage","type":"uint256"},{"internalType":"uint256","name":"_rebalanceFeePercentage","type":"uint256"},{"internalType":"uint256","name":"_merklClaimFeePercentage","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"EnforcedPause","type":"error"},{"inputs":[],"name":"ExpectedPause","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldAdmin","type":"address"},{"indexed":true,"internalType":"address","name":"newAdmin","type":"address"}],"name":"AdminUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"indexed":true,"internalType":"address","name":"initialVault","type":"address"}],"name":"AssetAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"asset","type":"address"}],"name":"AssetRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"fromAsset","type":"address"},{"indexed":true,"internalType":"address","name":"toAsset","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOut","type":"uint256"}],"name":"AssetSwapped","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"indexed":true,"internalType":"address","name":"oldVault","type":"address"},{"indexed":true,"internalType":"address","name":"newVault","type":"address"}],"name":"AssetVaultUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"indexed":true,"internalType":"address","name":"vault","type":"address"},{"indexed":false,"internalType":"uint256","name":"feeAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"userAmount","type":"uint256"}],"name":"FeeCollected","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newFee","type":"uint256"}],"name":"FeePercentageUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"indexed":true,"internalType":"address","name":"vault","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"InitialDeposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newFee","type":"uint256"}],"name":"MerklClaimFeePercentageUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"admin","type":"address"}],"name":"MerklOperatorApproved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"totalAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"feeAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"userAmount","type":"uint256"}],"name":"MerklTokensClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldThreshold","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newThreshold","type":"uint256"}],"name":"MinProfitForFeeUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"indexed":false,"internalType":"uint256","name":"profitAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"feeAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newBaseAmount","type":"uint256"}],"name":"RebalanceFeeCollected","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newFee","type":"uint256"}],"name":"RebalanceFeePercentageUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"indexed":true,"internalType":"address","name":"fromVault","type":"address"},{"indexed":true,"internalType":"address","name":"toVault","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Rebalanced","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldAddress","type":"address"},{"indexed":true,"internalType":"address","name":"newAddress","type":"address"}],"name":"RevenueAddressUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"indexed":true,"internalType":"address","name":"vault","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"UserDeposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"vault","type":"address"}],"name":"VaultAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"vault","type":"address"}],"name":"VaultRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"indexed":true,"internalType":"address","name":"vault","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Withdrawal","type":"event"},{"inputs":[],"name":"ADAPTER_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"AERODROME_FACTORY","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"AERODROME_ROUTER","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BUNDLER_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MERKL_DISTRIBUTOR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SLIPPAGE_TOLERANCE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"address","name":"vault","type":"address"}],"name":"addAsset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"address","name":"vault","type":"address"}],"name":"addVaultToAsset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"adminApprovedForMerkl","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"claimable","type":"uint256"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"}],"name":"adminClaimMerklReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"tokens","type":"address[]"},{"internalType":"uint256[]","name":"claimables","type":"uint256[]"},{"internalType":"bytes32[][]","name":"proofs","type":"bytes32[][]"}],"name":"adminClaimMerklRewardsBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"adminDeposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"allowedAssets","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"allowedVaults","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"assetAvailableVaults","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"assetHasInitialDeposit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"assetLastDepositTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"assetRebalanceBaseAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"assetToVault","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"assetTotalDeposited","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"assetTotalFeesCollected","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"assetTotalRebalanceFees","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bundler","outputs":[{"internalType":"contract IBundler3","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"totalAmount","type":"uint256"}],"name":"calculateFeeFromProfit","outputs":[{"internalType":"uint256","name":"feeAmount","type":"uint256"},{"internalType":"uint256","name":"userAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"claimable","type":"uint256"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"}],"name":"claimMerklReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"tokens","type":"address[]"},{"internalType":"uint256[]","name":"claimables","type":"uint256[]"},{"internalType":"bytes32[][]","name":"proofs","type":"bytes32[][]"}],"name":"claimMerklRewardsBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"emergencyTokenWithdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"emergencyWithdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"feePercentage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllowedAssets","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllowedVaults","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"getAssetActiveVault","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"getAssetAvailableVaults","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"getAssetFeesCollected","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"getAssetProfit","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"getAssetProfitPercentage","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"getAssetRebalanceBaseAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"getAssetRebalanceInfo","outputs":[{"internalType":"uint256","name":"baseAmount","type":"uint256"},{"internalType":"uint256","name":"currentValue","type":"uint256"},{"internalType":"int256","name":"profit","type":"int256"},{"internalType":"uint256","name":"totalFees","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"getAssetRebalanceProfit","outputs":[{"internalType":"int256","name":"profit","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"getAssetTotalRebalanceFees","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"getAssetVaultAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"getAssetVaultBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getFeeInfo","outputs":[{"internalType":"address","name":"_revenueAddress","type":"address"},{"internalType":"uint256","name":"_feePercentage","type":"uint256"},{"internalType":"uint256","name":"_minProfitForFee","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPortfolioSummary","outputs":[{"internalType":"address[]","name":"assets","type":"address[]"},{"internalType":"uint256[]","name":"deposited","type":"uint256[]"},{"internalType":"uint256[]","name":"currentValues","type":"uint256[]"},{"internalType":"int256[]","name":"profits","type":"int256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"getTokenBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"address","name":"vault","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"initialDeposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isAdminApprovedForMerkl","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"isAllowedAsset","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"isAllowedVault","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"address","name":"vault","type":"address"}],"name":"isVaultAvailableForAsset","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"merklClaimFeePercentage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"merklDistributor","outputs":[{"internalType":"contract IMerklDistributor","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minProfitForFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rebalanceFeePercentage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"address","name":"toVault","type":"address"}],"name":"rebalanceToVault","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"removeAsset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"}],"name":"removeVault","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"address","name":"vault","type":"address"}],"name":"removeVaultFromAsset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"revenueAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"address","name":"newActiveVault","type":"address"}],"name":"setAssetActiveVault","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newAdmin","type":"address"}],"name":"updateAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"address","name":"newVault","type":"address"}],"name":"updateAssetVault","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newFeePercentage","type":"uint256"}],"name":"updateFeePercentage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newMerklClaimFeePercentage","type":"uint256"}],"name":"updateMerklClaimFeePercentage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newMinProfitForFee","type":"uint256"}],"name":"updateMinProfitForFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newRebalanceFeePercentage","type":"uint256"}],"name":"updateRebalanceFeePercentage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newRevenueAddress","type":"address"}],"name":"updateRevenueAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"userDeposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]

0x60a080604052346104aa576148dd803803809161001c8285610581565b8339810190610100818303126104aa57610035816105a4565b610041602083016105a4565b60408301519092906001600160401b0381116104aa57846100639183016105cf565b60608201519093906001600160401b0381116104aa5782019480601f870112156104aa578551610092816105b8565b966100a06040519889610581565b81885260208089019260051b820101918383116104aa5760208201905b8382106105545750505050506100d5608083016105a4565b60a083015160c084015160e09094015160017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00556298968060115594909290916001600160a01b038316156104aa576001600160a01b038216156104aa57865180156104aa578851036104aa576001600160a01b03169182156104aa576080525f5490610100600160a81b039060081b1690610100600160a81b031916175f5560018060a01b0319600d541617600d55600e55600f556010555f905b80518210156104fd576001600160a01b036101ac8383610634565b5116156104aa576001600160a01b036101c58383610634565b51165f52600960205260ff60405f2054166104aa576101e48284610634565b5151156104aa576001600160a01b036101fd8383610634565b51165f908152600960205260409020805460ff191660011790556001600160a01b036102298383610634565b5116600b5468010000000000000000811015610466576001810180600b5581101561045257600b5f527f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db90180546001600160a01b031916909117905561028f8284610634565b5180511561045257602001516001600160a01b03908116906102b18484610634565b51165f90815260016020526040812080546001600160a01b0319169092179091555b6102dd8385610634565b51518110156104f2576001600160a01b03610302826102fc8688610634565b51610634565b51169081156104aa576040516338d52e0f60e01b8152602081600481865afa9081156104e7575f916104ae575b506001600160a01b036103428686610634565b51166001600160a01b03909116036104aa575f5b81811061047a57506001600160a01b036103708585610634565b51165f52600260205260405f20805490680100000000000000008210156104665760018201808255821015610452575f5260205f20018260018060a01b0319825416179055815f52600a60205260ff60405f205416156103d5575b60019150016102d3565b815f52600a60205260405f20600160ff19825416179055600c549168010000000000000000831015610466576001830180600c5583101561045257600c5f527fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c790920180546001600160a01b0319169092179091556001906103cb565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52604160045260245ffd5b9194909391856001600160a01b03610496836102fc8787610634565b5116146104aa576001019491939094610356565b5f80fd5b90506020813d82116104df575b816104c860209383610581565b810103126104aa576104d9906105a4565b5f61032f565b3d91506104bb565b6040513d5f823e3d90fd5b509060010190610191565b60405161429490816106498239608051818181610497015281816110ab015281816115cb0152818161162201528181611c480152818161202701528181612135015281816123720152818161303e015261345c0152f35b81516001600160401b0381116104aa57602091610576878480948801016105cf565b8152019101906100bd565b601f909101601f19168101906001600160401b0382119082101761046657604052565b51906001600160a01b03821682036104aa57565b6001600160401b0381116104665760051b60200190565b9080601f830112156104aa5781516105e6816105b8565b926105f46040519485610581565b81845260208085019260051b8201019283116104aa57602001905b82821061061c5750505090565b60208091610629846105a4565b81520191019061060f565b80518210156104525760209160051b01019056fe6080806040526004361015610012575f80fd5b5f905f3560e01c90816202eab7146127c757508063014b6cf0146127a457806301cf857714612670578063060b9c57146126495780630ab503c5146126265780630ccd3ef9146125fd5780630d8e15ad146125bd57806312df392f14611e00578063155438f11461243657806315cb51ce146124085780631746a22b146123df5780631fdee44b146123ac578063209caa0014612361578063219461ed146122045780632eff17641461228d5780632f5fd0401461222b5780632fa4abea14612204578063317cb1511461211a5780633a8fcfe6146120e45780633aecd0e3146120c15780633f4ba83a1461206157806344cb903b146120135780634a5e42b114611ece5780634c20378614611eb25780635323ed3814611e595780635c975abb14611e385780635f8f398814611e0057806368152b7c14611bf55780636cad3fb014611d9e5780636ff1c9bc14611c2d578063714cbe4e14611bf5578063735594bf14611ae057806374c9b63c14611abd5780637de1ac4e14611a855780637f77d821146119245780637fb921ba146118c25780638456cb591461185e5780638ae3f947146115fa5780638da5cb5b146115b55780638e87b7851461113d578063923ceb6b146115065780639beba3fe146114cd5780639f54d54b146114af578063a001ecdd14611491578063a0e09ae71461146d578063a1e157081461144f578063a3c1eb8b146111ce578063a414266c146111ab578063a57174af1461116c578063ad0ffd8b1461113d578063b410908d14611114578063b52d73431461108f578063b53e02d81461102b578063bbf4a50d14610d95578063bfdda9f014610fb0578063c13fae1b14610f92578063c537bed014610f53578063ceb68c2314610dce578063d2d6618514610d95578063d4db3c5514610d5e578063d4dcc9a214610d0c578063d856d02114610ce0578063da48999714610b37578063db37bab414610b08578063e2f273bd14610a79578063e44fef95146109fb578063e87f025e14610754578063f066eea0146106be578063f19dbf2414610654578063f3fef3a31461047b578063f851a44014610450578063fc509cab1461040f578063fd812094146103d05763fdd288591461034b575f80fd5b346103cd5760203660031901126103cd576103646127fc565b815460081c6001600160a01b031633036103cb576001600160a01b031680156103cb57600d80546001600160a01b0319811683179091556001600160a01b03167f9c18ec49f4aa2c93ab2db0f2f75da25811971a22646a4da0409e945bd48e8af78380a380f35b505b80fd5b50346103cd5760203660031901126103cd5760209060ff906040906001600160a01b036103fb6127fc565b168152600a84522054166040519015158152f35b50346103cd5760203660031901126103cd576020906001600160a01b036104346127fc565b16815260018252604060018060a01b0391205416604051908152f35b50346103cd57806003193601126103cd575460405160089190911c6001600160a01b03168152602090f35b50346103cd5760403660031901126103cd576104956127fc565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0381169160243533849003610650576001600160a01b0382168086526009602052604086205490929060ff161561064c576104f66139f7565b6104fe613ab0565b828652600460205260ff6040872054161561064c57828652600160205260408620546001600160a01b0316936105338561319e565b9182156106485761058e86869361057a610573886020985f5160206141df5f395f51905f529a9181811591821561063e575b5050610636575b5084613e73565b8095612c9f565b95818d8895936105e5575b50509050613a2f565b84885260038352806040892054115f146105de576105b790858952600384526040892054612a12565b848852600383526040882055604051908152a460015f51602061423f5f395f51905f525580f35b50866105b7565b816040809261060d5f51602061413f5f395f51905f529560018060a01b03600d541688613a2f565b85815260068d5220610620828254613668565b90558151908152868b820152a388885f8d610585565b90505f61056c565b119050815f610565565b8780fd5b8580fd5b8480fd5b50346103cd5760203660031901126103cd5780546004359060081c6001600160a01b031633036103cb5780156103cb5760407f5cdfd4e6875894063a0c220faf9b9c9176a915c2dffee5f72267d1a5d55bcbe891601154908060115582519182526020820152a180f35b50346103cd57806003193601126103cd57604051600b8054808352908352909160208301917f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9915b818110610735576107318561071d818703826129ef565b604051918291602083526020830190612965565b0390f35b82546001600160a01b0316845260209093019260019283019201610706565b50346103cd57806003193601126103cd578080600b54905b8181106109a8575061077d83612d61565b9161078784612d61565b61079085612d61565b906107b361079d87612d4a565b966107ab60405198896129ef565b808852612d4a565b602087019490601f19013686378390845b8181106108445750505061080b6107f096926107fd602093604051998a9960808b5260808b0190612965565b90898203868b01526129a1565b9087820360408901526129a1565b91858303606087015251918281520192915b81811061082b575050500390f35b825184528594506020938401939092019160010161081d565b6108518199979899612828565b905460039190911b1c6001600160a01b03168087526004602052604087205460ff16610885575b50600101979695976107c4565b80610893858c969496612db0565b52808752600360205260408720546108ab8387612db0565b5260405163d856d02160e01b815260048101829052602081602481305afa90811561099d57889161096c575b506108e28388612db0565b52604051906214b6cf60e41b82526004820152602081602481305afa90811561096157879161092b575b50816109249161091e6001948b612db0565b5261397e565b9290610878565b90506020813d8211610959575b81610945602093836129ef565b810103126109555751600161090c565b5f80fd5b3d9150610938565b6040513d89823e3d90fd5b90506020813d8211610995575b81610986602093836129ef565b8101031261095557515f6108d7565b3d9150610979565b6040513d8a823e3d90fd5b6109b58194929394612828565b905460039190911b1c6001600160a01b031684526004602052604084205460ff166109e7575b6001019291909261076c565b906109f360019161397e565b9190506109db565b50346103cd57806003193601126103cd57604051600c8054808352908352909160208301917fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7915b818110610a5a576107318561071d818703826129ef565b82546001600160a01b0316845260209093019260019283019201610a43565b50346103cd5760203660031901126103cd57610a936127fc565b8154600881901c6001600160a01b03169033829003610b04576001600160a01b03831692831561065057610100600160a81b031990911660089190911b610100600160a81b03161783557f101b8081ff3b56bbf45deb824d86a3b0fd38b7e3dd42421105cf8abe9106db0b8380a380f35b8380fd5b50346103cd57806003193601126103cd57602060405173cf77a3ba9a5ca399b7c97c74d54e5b1beb874e438152f35b50346103cd5760403660031901126103cd57610b516127fc565b610b59612812565b825460081c6001600160a01b03163303610cdc576001600160a01b038216908115610b04576001600160a01b0316918215610b0457818452600960205260ff604085205416610b0457828452600a60205260ff60408520541615610b04576040516338d52e0f60e01b8152602081600481875afa8015610cd15783918691610ca2575b506001600160a01b031603610b0457818452600960205260408420805460ff19166001179055600b54600160401b811015610c8e5790610c27826001610c4b9401600b55600b61286c565b9080546001600160a01b0360039390931b83811b199091169290931690921b179055565b80835260016020526040832080546001600160a01b031916831790557f0bb5715f0f217c2fe9a0c877ea87d474380c641102f3440ee2a4c8b9d97909188380a380f35b634e487b7160e01b85526041600452602485fd5b610cc4915060203d602011610cca575b610cbc81836129ef565b810190612b17565b5f610bdc565b503d610cb2565b6040513d87823e3d90fd5b8280fd5b50346103cd5760203660031901126103cd576020610d04610cff6127fc565b61390c565b604051908152f35b50346103cd57610d1b366128f7565b865490949193919060081c6001600160a01b03163303610d5a57610d4695610d416139f7565b61324f565b60015f51602061423f5f395f51905f525580f35b8680fd5b50346103cd5760403660031901126103cd576020610d8b610d7d6127fc565b610d85612812565b90613863565b6040519015158152f35b50346103cd5760203660031901126103cd576020906040906001600160a01b03610dbd6127fc565b168152600783522054604051908152f35b50346103cd5760203660031901126103cd57610de86127fc565b815460081c6001600160a01b031633036103cb576001600160a01b0316808252600a602052604082205460ff16156103cb57600b54825b818110610f1c575050808252600a60205260408220805460ff19169055815b600c5480821015610f155782610e5383612854565b905460039190911b1c6001600160a01b031614610e735750600101610e3e565b5f198101908111610f015790610c27610e8e610ea693612854565b905460039190911b1c6001600160a01b031691612854565b600c548015610eed575f1901610ed5610ec082600c61286c565b81549060018060a01b039060031b1b19169055565b600c555b5f5160206141ff5f395f51905f528280a280f35b634e487b7160e01b83526031600452602483fd5b634e487b7160e01b84526011600452602484fd5b5050610ed9565b610f2581612828565b90546001600160a01b0360039290921b1c8116855260016020526040852054168314610b0457600101610e1f565b50346103cd5760203660031901126103cd5760209060ff906040906001600160a01b03610f7e6127fc565b168152600984522054166040519015158152f35b50346103cd57806003193601126103cd576020601054604051908152f35b50346103cd5760203660031901126103cd576001600160a01b03610fd26127fc565b168152600260205260408120604051918260208354918281520192825260208220915b81811061100c576107318561071d818703826129ef565b82546001600160a01b0316845260209093019260019283019201610ff5565b50346103cd5760203660031901126103cd5780546004359060081c6001600160a01b031633036103cb5760407f57c018c0d9a10ed2a22cca11b501912832324d423f47b1246a60c5f73b52289191600f549080600f5582519182526020820152a180f35b50346103cd5760403660031901126103cd576110a96127fc565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036103cb576001600160a01b03811682526009602052604082205460ff16156103cb57610d46906111036139f7565b61110b613ab0565b60243590613675565b50346103cd57806003193601126103cd57600d546040516001600160a01b039091168152602090f35b50346103cd57806003193601126103cd576020604051736bfd8137e702540e7a42b74178a4a49ba43920c48152f35b50346103cd5760203660031901126103cd5760209060ff906040906001600160a01b036111976127fc565b168152600484522054166040519015158152f35b50346103cd57806003193601126103cd57602060ff601254166040519015158152f35b50346103cd5760403660031901126103cd576111e86127fc565b6111f0612812565b825490919060081c6001600160a01b03163303610cdc576001600160a01b0381168084526009602052604084205460ff1615610b04576001600160a01b038316808552600a602052604085205490939060ff1615610650576112506139f7565b611258613ab0565b818552600460205260ff60408620541615610650576112778184613863565b1561065057818552600160205260408520546001600160a01b03169284841461064c576040516338d52e0f60e01b8152602081600481895afa80156109615784918891611430575b506001600160a01b03160361064c57856112d88561319e565b9182156103cb577fe26365f32dd2a1a1a322c5fba39361fcd955999bdabd2121b6864894790cded4938561130e60209589613e73565b8185526007865260408520548181831180611427575b1561140c5750917fa96e8cd4545e1d2f0f6bc0877047d3a207236888264e7be5513e888779a85fe28261135f604098956113a3989795612a12565b61137e612710611371600f5484612b36565b048093816113d857612a12565b8099878683995260078d52205561139b8660405193849384612dc4565b0390a2613c58565b83875260018252604080882080546001600160a01b0319168817905551908152a460015f51602061423f5f395f51905f525580f35b600d546113f09083906001600160a01b031689613a2f565b86895260088d528b8920611405838254613668565b9055612a12565b956113a39594929150928693526007875260408c2055613c58565b50811515611324565b611449915060203d602011610cca57610cbc81836129ef565b5f6112bf565b50346103cd57806003193601126103cd576020600f54604051908152f35b50346103cd5760203660031901126103cd576020610d0461148c6127fc565b61381e565b50346103cd57806003193601126103cd576020600e54604051908152f35b50346103cd57806003193601126103cd576020601154604051908152f35b50346103cd5760203660031901126103cd576020906040906001600160a01b036114f56127fc565b168152600583522054604051908152f35b50346103cd57806003193601126103cd57805460405163131bac4760e11b81529190602090839081906115499060081c6001600160a01b03163060048401613804565b03815f51602061415f5f395f51905f525afa9081156115a95790611576575b602090600160405191148152f35b506020813d6020116115a1575b81611590602093836129ef565b810103126109555760209051611568565b3d9150611583565b604051903d90823e3d90fd5b50346103cd57806003193601126103cd576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b5034610955576060366003190112610955576116146127fc565b61161c612812565b906044357f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163303610955576001600160a01b0382165f8181526009602052604090205490929060ff16156109555761167b6139f7565b611683613ab0565b825f52600460205260ff60405f205416610955578115610955576001600160a01b038416938415610955576116b88183613863565b156109555760ff6012541615611763575b916116f1602092825f51602061421f5f395f51905f52956116ec8230338b613aca565b613c58565b83865260018252604086208560018060a01b0319825416179055838652600382528060408720558386526004825260408620600160ff198254161790558386526005825242604087205583865260078252806040872055604051908152a360015f51602061423f5f395f51905f525580f35b5f5460081c6001600160a01b03165f51602061415f5f395f51905f523b15610955575f6117a5916040518093819263bdac7ca360e01b83523060048401613804565b0381835f51602061415f5f395f51905f525af1801561185357611825575b50916116f1602092825f51602061421f5f395f51905f5295600160ff19601254161760125560018060a01b038a5460081c167f08ea92db3a4bb72aee66d7d65a7c80ba70489af194b0544456e6f35702e25f648b80a2935094509250506116c9565b5f51602061421f5f395f51905f52939196506020926118475f6116f1936129ef565b5f9792945092506117c3565b6040513d5f823e3d90fd5b34610955575f366003190112610955575f54600881901c6001600160a01b031633036109555760019061188f613ab0565b60ff1916175f557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586020604051338152a1005b34610955576020366003190112610955575f546004359060081c6001600160a01b031633036109555760407f7c7f0e4cdf50c1655dd67dca80b07b6a91e0d9ffe2fe40eb7552b1feb028744891601054908060105582519182526020820152a1005b346109555760403660031901126109555761193d6127fc565b611945612812565b5f5490919060081c6001600160a01b03163303610955576001600160a01b0381165f8181526009602052604090205490919060ff1615610955575f828152600160205260409020546001600160a01b038481169491168414610955576119aa91613863565b15610955575f52600260205260405f205f5b81549081811015611a7d57836119d2828561286c565b905460039190911b1c6001600160a01b0316146119f35760019150016119bc565b5f198201918211611a6957610c27611a0e611a27938561286c565b905460039190911b1c6001600160a01b0316918461286c565b80548015611a55575f190190611a40610ec0838361286c565b555b5f5160206141ff5f395f51905f525f80a2005b634e487b7160e01b5f52603160045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b505050611a42565b34610955576020366003190112610955576001600160a01b03611aa66127fc565b165f526003602052602060405f2054604051908152f35b34610955576020366003190112610955576020610d04611adb6127fc565b61373e565b34610955576020366003190112610955576001600160a01b03611b016127fc565b16805f52600760205260405f205460405163d856d02160e01b8152826004820152602081602481305afa908115611853575f91611bc3575b50604051631d326d8f60e21b81526004810184905292602084602481305afa908115611853575f91611b8e575b608094505f52600860205260405f205491604051938452602084015260408301526060820152f35b90506020843d602011611bbb575b81611ba9602093836129ef565b81010312610955576080935190611b66565b3d9150611b9c565b90506020813d602011611bed575b81611bde602093836129ef565b81010312610955575183611b39565b3d9150611bd1565b34610955576020366003190112610955576001600160a01b03611c166127fc565b165f526008602052602060405f2054604051908152f35b3461095557602036600319011261095557611c466127fc565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b038116919033839003610955576001600160a01b0382165f8181526009602052604090205490919060ff161561095557611ca6613e58565b611cae6139f7565b5f828152600160205260409020546001600160a01b031692831561095557611cd58461319e565b9182611cef575b60015f51602061423f5f395f51905f5255005b611d2b858592611d19602095611d135f5160206141df5f395f51905f529885613e73565b90612c9f565b9481869492611d4a575b509050613a2f565b835f52600382525f6040812055604051908152a4808080808080611cdc565b600d545f51602061413f5f395f51905f5291604091611d749082906001600160a01b031686613a2f565b835f5260068a52815f20611d89828254613668565b90558151908152868a820152a387878b611d23565b34610955576020366003190112610955575f546004359060081c6001600160a01b031633036109555760407fb27c12a91635e11c22bffa7bd8e0a8735da52b94aaefd7f249776c7590ba789491600e549080600e5582519182526020820152a1005b34610955576020366003190112610955576001600160a01b03611e216127fc565b165f526006602052602060405f2054604051908152f35b34610955575f36600319011261095557602060ff5f54166040519015158152f35b3461095557604036600319011261095557611e726127fc565b5f5460081c6001600160a01b03163303610955576001600160a01b0381165f9081526009602052604090205460ff161561095557611cdc906111036139f7565b34610955575f3660031901126109555760206040516101f48152f35b3461095557602036600319011261095557611ee76127fc565b5f5460081c6001600160a01b03163303610955576001600160a01b03165f8181526009602052604090205460ff161561095557805f52600460205260ff60405f205416610955575f818152600960205260408120805460ff191690555b600b54908181101561200c5782611f5a82612828565b905460039190911b1c6001600160a01b031614611f7b576001915001611f44565b5f198201918211611a6957610c27611f95611fad93612828565b905460039190911b1c6001600160a01b031691612828565b600b548015611a55575f1901611fc7610ec082600b61286c565b600b555b5f81815260016020526040812080546001600160a01b03191690557f37803e2125c48ee96c38ddf04e826daf335b0e1603579040fd275aba6d06b6fc9080a2005b5050611fcb565b3461095557612021366128f7565b939092907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316330361095557611cdc95610d416139f7565b34610955575f366003190112610955575f54600881901c6001600160a01b031633036109555761208f613e58565b60ff19165f557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa6020604051338152a1005b34610955576020366003190112610955576020610d046120df6127fc565b61319e565b34610955576120f2366128b1565b5f5490929060081c6001600160a01b0316330361095557611cdc936121156139f7565b612dda565b34610955576040366003190112610955576121336127fc565b7f000000000000000000000000000000000000000000000000000000000000000090602435906001600160a01b0383163303610955576121716139f7565b6001600160a01b0316918215610955576040516370a0823160e01b815230600482015291602083602481875afa928315611853575f936121d0575b50821561095557806121ca5750815b821161095557611cdc92613a2f565b916121bb565b9092506020813d6020116121fc575b816121ec602093836129ef565b81010312610955575191846121ac565b3d91506121df565b34610955575f3660031901126109555760206040515f51602061415f5f395f51905f528152f35b34610955576040366003190112610955576122446127fc565b6001600160a01b03165f90815260026020526040902080546024359190821015610955576020916122749161286c565b905460405160039290921b1c6001600160a01b03168152f35b34610955576040366003190112610955576122a66127fc565b6122ae612812565b5f5490919060081c6001600160a01b03163303610955576001600160a01b0381165f8181526009602052604090205490919060ff1615610955576001600160a01b0383169283156109555761230291613863565b15610955575f818152600160205260409020546001600160a01b03168214610955575f81815260016020526040812080546001600160a01b0319811685179091556001600160a01b031691905f51602061417f5f395f51905f529080a4005b346109555761236f366128b1565b917f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316330361095557611cdc936121156139f7565b346109555760403660031901126109555760406123d36123ca6127fc565b60243590612c9f565b82519182526020820152f35b3461095557602036600319011261095557600435600c5481101561095557612274602091612854565b34610955575f36600319011261095557602060405173420dd381b31aef6683db6b902084cb0ffece40da8152f35b346109555760403660031901126109555761244f6127fc565b612457612812565b5f5460081c6001600160a01b03163303610955576001600160a01b0382165f8181526009602052604090205460ff1615610955576001600160a01b038216928315610955576040516338d52e0f60e01b8152602081600481885afa80156118535783915f9161259e575b506001600160a01b03160361095557826124da91613863565b61095557825f52600a60205260ff60405f2054161561255c575b5f52600260205260405f2090815491600160401b8310156125485782610c279160016125229501815561286c565b7f7b7ef7a864d96a85497a1ed846adb39940dd6ccef678ff6ac8d55505e09b8cc45f80a2005b634e487b7160e01b5f52604160045260245ffd5b5f838152600a60205260409020805460ff19166001179055600c54600160401b8110156125485782610c278260016125999401600c55600c61286c565b6124f4565b6125b7915060203d602011610cca57610cbc81836129ef565b866124c1565b34610955576020366003190112610955576001600160a01b036125de6127fc565b165f526001602052602060018060a01b0360405f205416604051908152f35b3461095557602036600319011261095557600435600b5481101561095557612274602091612828565b34610955576020366003190112610955576020610d046126446127fc565b612b67565b34610955575f3660031901126109555760206040515f51602061419f5f395f51905f528152f35b34610955576040366003190112610955576126896127fc565b612691612812565b5f5490919060081c6001600160a01b03163303610955576001600160a01b0381165f8181526009602052604090205490919060ff1615610955576001600160a01b0383165f818152600a602052604090205490939060ff1615610955578315610955576040516338d52e0f60e01b8152602081600481885afa80156118535784915f91612785575b506001600160a01b0316036109555761273191613863565b15610955575f818152600160205260409020546001600160a01b031690828214610955575f81815260016020526040812080546001600160a01b031916851790555f51602061417f5f395f51905f529080a4005b61279e915060203d602011610cca57610cbc81836129ef565b86612719565b34610955576020366003190112610955576020610d046127c26127fc565b612a2f565b34610955575f36600319011261095557600d54600e546011546001600160a01b03909216835260208301526040820152606090f35b600435906001600160a01b038216820361095557565b602435906001600160a01b038216820361095557565b600b5481101561284057600b5f5260205f2001905f90565b634e487b7160e01b5f52603260045260245ffd5b600c5481101561284057600c5f5260205f2001905f90565b8054821015612840575f5260205f2001905f90565b9181601f84011215610955578235916001600160401b038311610955576020808501948460051b01011161095557565b6060600319820112610955576004356001600160a01b0381168103610955579160243591604435906001600160401b038211610955576128f391600401612881565b9091565b6060600319820112610955576004356001600160401b038111610955578161292191600401612881565b909290916024356001600160401b038111610955578161294391600401612881565b90929091604435906001600160401b038211610955576128f391600401612881565b90602080835192838152019201905f5b8181106129825750505090565b82516001600160a01b0316845260209384019390920191600101612975565b90602080835192838152019201905f5b8181106129be5750505090565b82518452602093840193909201916001016129b1565b60a081019081106001600160401b0382111761254857604052565b601f909101601f19168101906001600160401b0382119082101761254857604052565b91908203918211611a6957565b600160ff1b8114611a69575f0390565b6001600160a01b03165f8181526004602052604090205460ff16158015612b03575b612afe5760405163d856d02160e01b815260048101829052602081602481305afa908115611853575f91612acc575b505f828152600360205260409020548110612aae57612aab915f52600360205260405f205490612a12565b90565b612ac790612aab925f52600360205260405f2054612a12565b612a1f565b90506020813d602011612af6575b81612ae7602093836129ef565b8101031261095557515f612a80565b3d9150612ada565b505f90565b50805f52600360205260405f205415612a51565b9081602091031261095557516001600160a01b03811681036109555790565b81810292918115918404141715611a6957565b8115612b53570490565b634e487b7160e01b5f52601260045260245ffd5b6001600160a01b03165f8181526004602052604090205460ff16158015612c8b575b612afe5760405163d856d02160e01b815260048101829052602081602481305afa908115611853575f91612c59575b505f828152600360205260409020548110612c1157612be490825f52600360205260405f205490612a12565b620f4240810290808204620f42401490151715611a6957612aab915f52600360205260405f205490612b49565b612c2790825f52600360205260405f2054612a12565b90620f4240820291808304620f42401490151715611a6957612aab91612ac7915f52600360205260405f205490612b49565b90506020813d602011612c83575b81612c74602093836129ef565b8101031261095557515f612bb8565b3d9150612c67565b50805f52600360205260405f205415612b89565b6001600160a01b0381165f8181526004602052604090205492939260ff16158015612d36575b8015612d20575b612d19575f526003602052612cf1612ce860405f205485612a12565b9160115461399a565b811115612d1357612710612d0b612aab92600e5490612b36565b048093612a12565b505f9190565b50505f9190565b50805f52600360205260405f2054841115612ccc565b50805f52600360205260405f205415612cc5565b6001600160401b0381116125485760051b60200190565b90612d6b82612d4a565b612d7860405191826129ef565b8281528092612d89601f1991612d4a565b0190602036910137565b8051156128405760200190565b8051600110156128405760400190565b80518210156128405760209160051b010190565b6040919493926060820195825260208201520152565b6001600160a01b031692915f9190841561095557811561095557604093845191612e0486846129ef565b60018352601f1986019182366020860137865191612e2288846129ef565b6001835283366020850137875195612e3a89886129ef565b6001875284366020890137885194612e528a876129ef565b600186525f5b81811061318d57505030612e6b87612d93565b5289612e7685612d93565b52612e8087612d93565b52612e8a82612d4a565b91612e97895193846129ef565b808352602083019060051b82019136831161095557905b82821061317d57505050612ec183612d93565b52612ecb82612d93565b5085516370a0823160e01b8152306004820152936020856024818b5afa948515613173575f9561313f575b505f51602061415f5f395f51905f523b1561095557612f5c90612f4a612f3895949389519687966301c7ba5760e61b8852608060048901526084880190612965565b86810360031901602488015290612965565b848103600319016044860152906129a1565b600319838203016064840152815180825260208201916020808360051b8301019401925f915b8383106130e357505050505090805f920381835f51602061415f5f395f51905f525af180156130d9576130c4575b5082516370a0823160e01b815230600482015291602083602481885afa9081156130b95790613085575b612fe49250612a12565b9081612fef57505050565b5f5160206141bf5f395f51905f529161303361271061301060105484612b36565b049261301c8484612a12565b9084613068575b81613038575b5193849384612dc4565b0390a2565b613063827f000000000000000000000000000000000000000000000000000000000000000089613a2f565b613029565b600d546130809086906001600160a01b031689613a2f565b613023565b506020823d6020116130b1575b8161309f602093836129ef565b8101031261095557612fe49151612fda565b3d9150613092565b8451903d90823e3d90fd5b6130d19192505f906129ef565b5f905f612fb0565b84513d5f823e3d90fd5b919395509193601f19828203018352855190602080835192838152019201905f905b8082106131275750505060208060019297019301930190928695949293612f82565b90919260208060019286518152019401920190613105565b9094506020813d60201161316b575b8161315b602093836129ef565b810103126109555751935f612ef6565b3d915061314e565b87513d5f823e3d90fd5b8135815260209182019101612eae565b806060602080938a01015201612e58565b6040516370a0823160e01b815230600482015290602090829060249082906001600160a01b03165afa908115611853575f916131d8575090565b90506020813d6020116131ff575b816131f3602093836129ef565b81010312610955575190565b3d91506131e6565b91908110156128405760051b0190565b356001600160a01b03811681036109555790565b81835290916001600160fb1b0383116109555760209260051b809284830137010190565b919092949385840361095557848403610955575f9484156109555761327385612d61565b925f5b868110613626575061328786612d61565b975f5b87811061359d57505f51602061415f5f395f51905f523b15610955576040516301c7ba5760e61b8152608060048201529485949291906020906132d1906084880190612965565b8681036003190160248801528981520191875f5b8a811061355e57505085830360031901604487015261330592919061322b565b91600319848403016064850152808352602083019060208160051b85010193835f91601e1982360301905b8484106134fe575050505050505090805f920381835f51602061415f5f395f51905f525af18015611853576134e9575b50825b828110613371575050505050565b60249060206001600160a01b0361339161338c848888613207565b613217565b16604051938480926370a0823160e01b82523060048301525afa918215610cd15785926134b4575b506133d16001926133ca8389612db0565b5190612a12565b806133de575b5001613363565b5f5160206141bf5f395f51905f526127106133fb60105484612b36565b046134068184612a12565b9281613486575b83613441575b613438868060a01b0361342a61338c888c8c613207565b169460405193849384612dc4565b0390a25f6133d7565b61348184878060a01b0361345961338c898d8d613207565b167f000000000000000000000000000000000000000000000000000000000000000090613a2f565b613413565b6134af82878060a01b0361349e61338c898d8d613207565b16888060a01b03600d541690613a2f565b61340d565b91506020823d82116134e1575b816134ce602093836129ef565b81010312610955579051906133d16133b9565b3d91506134c1565b6134f69193505f906129ef565b5f915f613360565b9193959750919395601f19828203018752873583811215610955578401602081019190356001600160401b038111610955578060051b360383136109555761354c602092839260019561322b565b99019701940191889796959391613330565b9496509391929091908435906001600160a01b0382168203610955576001600160a01b03909116815287969460209182019493929101906001016132e5565b60249060206001600160a01b036135b861338c848d8d613207565b16604051938480926370a0823160e01b82523060048301525afa8015611853575f906135f4575b600192506135ed828d612db0565b520161328a565b506020823d821161361e575b8161360d602093836129ef565b8101031261095557600191516135df565b3d9150613600565b6001600160a01b0361363c61338c838a8a613207565b16156109555761364d818a84613207565b351561095557600190306136618288612db0565b5201613276565b91908201809211611a6957565b6001600160a01b0381165f818152600460205260409020549192909160ff1615610955578015610955575f828152600160205260409020546001600160a01b031692831561095557816136f86020926136f17f3bc57f469ad6d10d7723ea226cd22bd2b9e527def2b529f6ab44645a1668958295303389613aca565b8287613c58565b835f526003825260405f2061370e828254613668565b9055835f52600582524260405f2055835f526007825260405f20613733828254613668565b9055604051908152a3565b6001600160a01b03165f8181526004602052604090205460ff161580156137f0575b612afe5760405163d856d02160e01b81526004810182905290602082602481305afa918215611853575f926137bc575b505f908152600760205260409020548082106137af57612aab91612a12565b612aab91612ac791612a12565b9091506020813d6020116137e8575b816137d8602093836129ef565b810103126109555751905f613790565b3d91506137cb565b50805f52600760205260405f205415613760565b6001600160a01b0391821681529116602082015260400190565b6001600160a01b03165f8181526009602052604090205460ff1615612afe575f908152600160205260409020546001600160a01b03168015612afe57612aab9061319e565b60018060a01b03165f52600260205260405f2090604051808360208295549384815201905f5260205f20925f5b8181106138ea5750506138a5925003836129ef565b5f5b82518110156138e3576001600160a01b036138c28285612db0565b51166001600160a01b038316146138db576001016138a7565b505050600190565b5050505f90565b84546001600160a01b0316835260019485019487945060209093019201613890565b6001600160a01b03165f8181526009602052604090205460ff1615612afe575f908152600160205260409020546001600160a01b03168015612afe5760206139538261319e565b6024604051809481936303d1689d60e11b835260048301525afa908115611853575f916131d8575090565b5f198114611a695760010190565b604d8111611a6957600a0a90565b906139a4906140c4565b600681036139b0575090565b9060068211156139d8576005198201918211611a69576139d2612aab9261398c565b90612b36565b906006039060068211611a69576139f1612aab9261398c565b90612b49565b60025f51602061423f5f395f51905f525414613a205760025f51602061423f5f395f51905f5255565b633ee5aeb560e01b5f5260045ffd5b916040519163a9059cbb60e01b5f5260018060a01b031660045260245260205f60448180865af19060015f5114821615613a8f575b60405215613a6f5750565b635274afe760e01b5f9081526001600160a01b0391909116600452602490fd5b906001811516613aa757823b15153d15161690613a64565b503d5f823e3d90fd5b60ff5f5416613abb57565b63d93c066560e01b5f5260045ffd5b6040516323b872dd60e01b5f9081526001600160a01b039384166004529290931660245260449390935260209060648180865af19060015f5114821615613b1b575b6040525f60605215613a6f5750565b906001811516613aa757823b15153d15161690613b0c565b90816020910312610955575180151581036109555790565b60405160609190613b5c83826129ef565b6002815291601f1901825f5b828110613b7457505050565b602090604051613b83816129d4565b5f81526060838201525f60408201525f60608201525f608082015282828501015201613b68565b602081016020825282518091526040820191602060408360051b8301019401925f915b838310613bdc57505050505090565b909192939460208060c0600193603f198682030187528260808b51878060a01b0381511684528281015160a08486015280519384918260a0880152018686015e5f8584860101526040810151604085015260608101511515606085015201516080830152601f8019910116010197019301930191939290613bcd565b60405163095ea7b360e01b81525f51602061419f5f395f51905f526004820152602481018390526001600160a01b03909316929091906020816044815f885af1801561185357613e2b575b50613cac613b4b565b926040519063d96ca0b960e01b602083015260248201525f51602061419f5f395f51905f52604482015281606482015260648152613ceb6084826129ef565b60405190613cf8826129d4565b5f51602061419f5f395f51905f52825260208201525f60408201525f60608201525f6080820152613d2884612d93565b52613d3283612d93565b5060405163377af75760e11b60208201526001600160a01b03909216602483015260448201525f196064820152306084808301919091528152613d7660a4826129ef565b60405190613d83826129d4565b5f51602061419f5f395f51905f52825260208201525f60408201525f60608201525f6080820152613db382612da0565b52613dbd81612da0565b50736bfd8137e702540e7a42b74178a4a49ba43920c43b15610955575f613df8916040518093819263374f435d60e01b835260048301613baa565b038183736bfd8137e702540e7a42b74178a4a49ba43920c45af1801561185357613e1f5750565b5f613e29916129ef565b565b613e4c9060203d602011613e51575b613e4481836129ef565b810190613b33565b613ca3565b503d613e3a565b60ff5f541615613e6457565b638dfc202b60e01b5f5260045ffd5b60405163266d6a8360e11b81526004810183905291906001600160a01b0316602083602481845afa928315611853575f93614090575b5060405163095ea7b360e01b81525f51602061419f5f395f51905f526004820152602481018390526020816044815f865af1801561185357614073575b50613eef613b4b565b9160405163d96ca0b960e01b60208201528260248201525f51602061419f5f395f51905f52604482015281606482015260648152613f2e6084826129ef565b60405190613f3b826129d4565b5f51602061419f5f395f51905f52825260208201525f60408201525f60608201525f6080820152613f6b84612d93565b52613f7583612d93565b50604051916353fb730360e11b6020840152602483015260448201525f60648201523060848201525f51602061419f5f395f51905f5260a482015260a48152613fbf60c4826129ef565b60405190613fcc826129d4565b5f51602061419f5f395f51905f52825260208201525f60408201525f60608201525f6080820152613ffc82612da0565b5261400681612da0565b50736bfd8137e702540e7a42b74178a4a49ba43920c43b15610955575f614041916040518093819263374f435d60e01b835260048301613baa565b038183736bfd8137e702540e7a42b74178a4a49ba43920c45af1801561185357614069575090565b5f612aab916129ef565b61408b9060203d602011613e5157613e4481836129ef565b613ee6565b9092506020813d6020116140bc575b816140ac602093836129ef565b810103126109555751915f613ea9565b3d915061409f565b60405163313ce56760e01b815290602090829060049082906001600160a01b03165afa5f9181614100575b506140fa5750601290565b60ff1690565b9091506020813d602011614136575b8161411c602093836129ef565b81010312610955575160ff8116810361095557905f6140ef565b3d915061410f56fe205442d60b70af1203d43cab62352c3b69b94f091be32fe683198057282b5c920000000000000000000000003ef3d8ba38ebe18db133cec108f4d14ce00dd9ae6e04d857bfe8fb4a5708cc80d2e83427508e17ca97c58ed3ea93ff3e8386782c000000000000000000000000b98c948cfa24072e58935bc004a8a7b376ae746a6b93c712f8760f33773ec46c38ea20e57ef035bcd5b8a46488e317251744d39d342e7ff505a8a0364cd0dc2ff195c315e43bce86b204846ecd36913e117b109ee71f3a50e5ad81964f352c411f1d45e35438ecd1acecef59ac81d9fbbf6cbc0a268c2c6d010a7b0a0c1bfa0c35fd165097626036f72cd52f416fbf0f842369409b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00a2646970667358221220937a045ce22337a851e0472222b7c242aefa56df9e4e2494d9e83d0a72bca53464736f6c634300081e0033000000000000000000000000485333f8b80c3d054b1ada1417a055c3442f3170000000000000000000000000eeee7d713adf6f408dd3637987191b35e3a872b000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000eeee7d713adf6f408dd3637987191b35e3a872b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000003000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000004200000000000000000000000000000000000006000000000000000000000000cbb7c0000ab88b473b1f5afd9ef808440eed33bf00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000004600000000000000000000000000000000000000000000000000000000000000017000000000000000000000000cddcdd18a16ed441f6cb10c3909e5e7ec2b9e8f30000000000000000000000001d3b1cd0a0f242d598834b3f2d126dc6bd7746570000000000000000000000005435bc53f2c61298167cdb11cdf0db2bfa259ca000000000000000000000000023479229e52ab6aad312d0b03df9f33b46753b5e000000000000000000000000ef417a2512c5a41f69ae4e021648b69a7cde5d0300000000000000000000000012afdefb2237a5963e7bab3e2d46ad0eee70406e000000000000000000000000e74c499fa461af1844fca84204490877787ced5600000000000000000000000075e1a1f9535c01cdce25e51ea4aff0d171337e1f000000000000000000000000beefe94c8ad530842bfe7d8b397938ffc1cb83b2000000000000000000000000ee8f4ec5672f09119b96ab6fb59c27e1b7e44b61000000000000000000000000236919f11ff9ea9550a4287696c2fc9e18e6e89000000000000000000000000043e623ff7d14d5b105f7be9c488f36dbf11d1f46000000000000000000000000beefa7b88064feef0cee02aaebbd95d30df3878f000000000000000000000000beef010f9cb27031ad51e3333f9af9c6b1228183000000000000000000000000616a4e1db48e22028f6bbf20444cd3b8e32737380000000000000000000000001401d1271c47648ac70cbcdfa3776d4a87ce006b000000000000000000000000c0c5689e6f4d256e861f65465b691aeecc0deb120000000000000000000000007bfa7c4f149e7415b73bdedfe609237e29cbf34a000000000000000000000000c1256ae5ff1cf2719d4937adb3bbccab2e00a2ca000000000000000000000000cd347c1e7d600a9a3e403497562edd0a7bc3ef21000000000000000000000000cbeef01994e24a60f7dcb8de98e75ad8bd4ad60d000000000000000000000000b7890cee6cf4792cdcc13489d36d9d42726ab8630000000000000000000000008773447e6369472d9b72f064ea62e405216e908400000000000000000000000000000000000000000000000000000000000000070000000000000000000000005a32099837d89e3a794a44fb131cbbad41f87a8c00000000000000000000000009832347586e238841f49149c84d121bc2191c5300000000000000000000000027d8c7273fd3fcc6956a0b370ce5fd4a7fc65c18000000000000000000000000a0e430870c4604ccfc7b38ca7845b1ff653d0ff1000000000000000000000000beef050a7485865a7a8d8ca0cc5f7536b7a3443e0000000000000000000000001d795e29044a62da42d927c4b179269139a28a6b0000000000000000000000006b13c060f13af1fdb319f52315bbbf3fb1d888440000000000000000000000000000000000000000000000000000000000000003000000000000000000000000543257ef2161176d7c8cd90ba65c2d4caef5a7960000000000000000000000006770216ac60f634483ec073cbabc4011c94307cb0000000000000000000000005a47c803488fe2bb0a0eaaf346b420e4df22f3c7

Deployed Bytecode

0x6080806040526004361015610012575f80fd5b5f905f3560e01c90816202eab7146127c757508063014b6cf0146127a457806301cf857714612670578063060b9c57146126495780630ab503c5146126265780630ccd3ef9146125fd5780630d8e15ad146125bd57806312df392f14611e00578063155438f11461243657806315cb51ce146124085780631746a22b146123df5780631fdee44b146123ac578063209caa0014612361578063219461ed146122045780632eff17641461228d5780632f5fd0401461222b5780632fa4abea14612204578063317cb1511461211a5780633a8fcfe6146120e45780633aecd0e3146120c15780633f4ba83a1461206157806344cb903b146120135780634a5e42b114611ece5780634c20378614611eb25780635323ed3814611e595780635c975abb14611e385780635f8f398814611e0057806368152b7c14611bf55780636cad3fb014611d9e5780636ff1c9bc14611c2d578063714cbe4e14611bf5578063735594bf14611ae057806374c9b63c14611abd5780637de1ac4e14611a855780637f77d821146119245780637fb921ba146118c25780638456cb591461185e5780638ae3f947146115fa5780638da5cb5b146115b55780638e87b7851461113d578063923ceb6b146115065780639beba3fe146114cd5780639f54d54b146114af578063a001ecdd14611491578063a0e09ae71461146d578063a1e157081461144f578063a3c1eb8b146111ce578063a414266c146111ab578063a57174af1461116c578063ad0ffd8b1461113d578063b410908d14611114578063b52d73431461108f578063b53e02d81461102b578063bbf4a50d14610d95578063bfdda9f014610fb0578063c13fae1b14610f92578063c537bed014610f53578063ceb68c2314610dce578063d2d6618514610d95578063d4db3c5514610d5e578063d4dcc9a214610d0c578063d856d02114610ce0578063da48999714610b37578063db37bab414610b08578063e2f273bd14610a79578063e44fef95146109fb578063e87f025e14610754578063f066eea0146106be578063f19dbf2414610654578063f3fef3a31461047b578063f851a44014610450578063fc509cab1461040f578063fd812094146103d05763fdd288591461034b575f80fd5b346103cd5760203660031901126103cd576103646127fc565b815460081c6001600160a01b031633036103cb576001600160a01b031680156103cb57600d80546001600160a01b0319811683179091556001600160a01b03167f9c18ec49f4aa2c93ab2db0f2f75da25811971a22646a4da0409e945bd48e8af78380a380f35b505b80fd5b50346103cd5760203660031901126103cd5760209060ff906040906001600160a01b036103fb6127fc565b168152600a84522054166040519015158152f35b50346103cd5760203660031901126103cd576020906001600160a01b036104346127fc565b16815260018252604060018060a01b0391205416604051908152f35b50346103cd57806003193601126103cd575460405160089190911c6001600160a01b03168152602090f35b50346103cd5760403660031901126103cd576104956127fc565b7f000000000000000000000000485333f8b80c3d054b1ada1417a055c3442f31706001600160a01b0381169160243533849003610650576001600160a01b0382168086526009602052604086205490929060ff161561064c576104f66139f7565b6104fe613ab0565b828652600460205260ff6040872054161561064c57828652600160205260408620546001600160a01b0316936105338561319e565b9182156106485761058e86869361057a610573886020985f5160206141df5f395f51905f529a9181811591821561063e575b5050610636575b5084613e73565b8095612c9f565b95818d8895936105e5575b50509050613a2f565b84885260038352806040892054115f146105de576105b790858952600384526040892054612a12565b848852600383526040882055604051908152a460015f51602061423f5f395f51905f525580f35b50866105b7565b816040809261060d5f51602061413f5f395f51905f529560018060a01b03600d541688613a2f565b85815260068d5220610620828254613668565b90558151908152868b820152a388885f8d610585565b90505f61056c565b119050815f610565565b8780fd5b8580fd5b8480fd5b50346103cd5760203660031901126103cd5780546004359060081c6001600160a01b031633036103cb5780156103cb5760407f5cdfd4e6875894063a0c220faf9b9c9176a915c2dffee5f72267d1a5d55bcbe891601154908060115582519182526020820152a180f35b50346103cd57806003193601126103cd57604051600b8054808352908352909160208301917f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9915b818110610735576107318561071d818703826129ef565b604051918291602083526020830190612965565b0390f35b82546001600160a01b0316845260209093019260019283019201610706565b50346103cd57806003193601126103cd578080600b54905b8181106109a8575061077d83612d61565b9161078784612d61565b61079085612d61565b906107b361079d87612d4a565b966107ab60405198896129ef565b808852612d4a565b602087019490601f19013686378390845b8181106108445750505061080b6107f096926107fd602093604051998a9960808b5260808b0190612965565b90898203868b01526129a1565b9087820360408901526129a1565b91858303606087015251918281520192915b81811061082b575050500390f35b825184528594506020938401939092019160010161081d565b6108518199979899612828565b905460039190911b1c6001600160a01b03168087526004602052604087205460ff16610885575b50600101979695976107c4565b80610893858c969496612db0565b52808752600360205260408720546108ab8387612db0565b5260405163d856d02160e01b815260048101829052602081602481305afa90811561099d57889161096c575b506108e28388612db0565b52604051906214b6cf60e41b82526004820152602081602481305afa90811561096157879161092b575b50816109249161091e6001948b612db0565b5261397e565b9290610878565b90506020813d8211610959575b81610945602093836129ef565b810103126109555751600161090c565b5f80fd5b3d9150610938565b6040513d89823e3d90fd5b90506020813d8211610995575b81610986602093836129ef565b8101031261095557515f6108d7565b3d9150610979565b6040513d8a823e3d90fd5b6109b58194929394612828565b905460039190911b1c6001600160a01b031684526004602052604084205460ff166109e7575b6001019291909261076c565b906109f360019161397e565b9190506109db565b50346103cd57806003193601126103cd57604051600c8054808352908352909160208301917fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7915b818110610a5a576107318561071d818703826129ef565b82546001600160a01b0316845260209093019260019283019201610a43565b50346103cd5760203660031901126103cd57610a936127fc565b8154600881901c6001600160a01b03169033829003610b04576001600160a01b03831692831561065057610100600160a81b031990911660089190911b610100600160a81b03161783557f101b8081ff3b56bbf45deb824d86a3b0fd38b7e3dd42421105cf8abe9106db0b8380a380f35b8380fd5b50346103cd57806003193601126103cd57602060405173cf77a3ba9a5ca399b7c97c74d54e5b1beb874e438152f35b50346103cd5760403660031901126103cd57610b516127fc565b610b59612812565b825460081c6001600160a01b03163303610cdc576001600160a01b038216908115610b04576001600160a01b0316918215610b0457818452600960205260ff604085205416610b0457828452600a60205260ff60408520541615610b04576040516338d52e0f60e01b8152602081600481875afa8015610cd15783918691610ca2575b506001600160a01b031603610b0457818452600960205260408420805460ff19166001179055600b54600160401b811015610c8e5790610c27826001610c4b9401600b55600b61286c565b9080546001600160a01b0360039390931b83811b199091169290931690921b179055565b80835260016020526040832080546001600160a01b031916831790557f0bb5715f0f217c2fe9a0c877ea87d474380c641102f3440ee2a4c8b9d97909188380a380f35b634e487b7160e01b85526041600452602485fd5b610cc4915060203d602011610cca575b610cbc81836129ef565b810190612b17565b5f610bdc565b503d610cb2565b6040513d87823e3d90fd5b8280fd5b50346103cd5760203660031901126103cd576020610d04610cff6127fc565b61390c565b604051908152f35b50346103cd57610d1b366128f7565b865490949193919060081c6001600160a01b03163303610d5a57610d4695610d416139f7565b61324f565b60015f51602061423f5f395f51905f525580f35b8680fd5b50346103cd5760403660031901126103cd576020610d8b610d7d6127fc565b610d85612812565b90613863565b6040519015158152f35b50346103cd5760203660031901126103cd576020906040906001600160a01b03610dbd6127fc565b168152600783522054604051908152f35b50346103cd5760203660031901126103cd57610de86127fc565b815460081c6001600160a01b031633036103cb576001600160a01b0316808252600a602052604082205460ff16156103cb57600b54825b818110610f1c575050808252600a60205260408220805460ff19169055815b600c5480821015610f155782610e5383612854565b905460039190911b1c6001600160a01b031614610e735750600101610e3e565b5f198101908111610f015790610c27610e8e610ea693612854565b905460039190911b1c6001600160a01b031691612854565b600c548015610eed575f1901610ed5610ec082600c61286c565b81549060018060a01b039060031b1b19169055565b600c555b5f5160206141ff5f395f51905f528280a280f35b634e487b7160e01b83526031600452602483fd5b634e487b7160e01b84526011600452602484fd5b5050610ed9565b610f2581612828565b90546001600160a01b0360039290921b1c8116855260016020526040852054168314610b0457600101610e1f565b50346103cd5760203660031901126103cd5760209060ff906040906001600160a01b03610f7e6127fc565b168152600984522054166040519015158152f35b50346103cd57806003193601126103cd576020601054604051908152f35b50346103cd5760203660031901126103cd576001600160a01b03610fd26127fc565b168152600260205260408120604051918260208354918281520192825260208220915b81811061100c576107318561071d818703826129ef565b82546001600160a01b0316845260209093019260019283019201610ff5565b50346103cd5760203660031901126103cd5780546004359060081c6001600160a01b031633036103cb5760407f57c018c0d9a10ed2a22cca11b501912832324d423f47b1246a60c5f73b52289191600f549080600f5582519182526020820152a180f35b50346103cd5760403660031901126103cd576110a96127fc565b7f000000000000000000000000485333f8b80c3d054b1ada1417a055c3442f31706001600160a01b031633036103cb576001600160a01b03811682526009602052604082205460ff16156103cb57610d46906111036139f7565b61110b613ab0565b60243590613675565b50346103cd57806003193601126103cd57600d546040516001600160a01b039091168152602090f35b50346103cd57806003193601126103cd576020604051736bfd8137e702540e7a42b74178a4a49ba43920c48152f35b50346103cd5760203660031901126103cd5760209060ff906040906001600160a01b036111976127fc565b168152600484522054166040519015158152f35b50346103cd57806003193601126103cd57602060ff601254166040519015158152f35b50346103cd5760403660031901126103cd576111e86127fc565b6111f0612812565b825490919060081c6001600160a01b03163303610cdc576001600160a01b0381168084526009602052604084205460ff1615610b04576001600160a01b038316808552600a602052604085205490939060ff1615610650576112506139f7565b611258613ab0565b818552600460205260ff60408620541615610650576112778184613863565b1561065057818552600160205260408520546001600160a01b03169284841461064c576040516338d52e0f60e01b8152602081600481895afa80156109615784918891611430575b506001600160a01b03160361064c57856112d88561319e565b9182156103cb577fe26365f32dd2a1a1a322c5fba39361fcd955999bdabd2121b6864894790cded4938561130e60209589613e73565b8185526007865260408520548181831180611427575b1561140c5750917fa96e8cd4545e1d2f0f6bc0877047d3a207236888264e7be5513e888779a85fe28261135f604098956113a3989795612a12565b61137e612710611371600f5484612b36565b048093816113d857612a12565b8099878683995260078d52205561139b8660405193849384612dc4565b0390a2613c58565b83875260018252604080882080546001600160a01b0319168817905551908152a460015f51602061423f5f395f51905f525580f35b600d546113f09083906001600160a01b031689613a2f565b86895260088d528b8920611405838254613668565b9055612a12565b956113a39594929150928693526007875260408c2055613c58565b50811515611324565b611449915060203d602011610cca57610cbc81836129ef565b5f6112bf565b50346103cd57806003193601126103cd576020600f54604051908152f35b50346103cd5760203660031901126103cd576020610d0461148c6127fc565b61381e565b50346103cd57806003193601126103cd576020600e54604051908152f35b50346103cd57806003193601126103cd576020601154604051908152f35b50346103cd5760203660031901126103cd576020906040906001600160a01b036114f56127fc565b168152600583522054604051908152f35b50346103cd57806003193601126103cd57805460405163131bac4760e11b81529190602090839081906115499060081c6001600160a01b03163060048401613804565b03815f51602061415f5f395f51905f525afa9081156115a95790611576575b602090600160405191148152f35b506020813d6020116115a1575b81611590602093836129ef565b810103126109555760209051611568565b3d9150611583565b604051903d90823e3d90fd5b50346103cd57806003193601126103cd576040517f000000000000000000000000485333f8b80c3d054b1ada1417a055c3442f31706001600160a01b03168152602090f35b5034610955576060366003190112610955576116146127fc565b61161c612812565b906044357f000000000000000000000000485333f8b80c3d054b1ada1417a055c3442f31706001600160a01b03163303610955576001600160a01b0382165f8181526009602052604090205490929060ff16156109555761167b6139f7565b611683613ab0565b825f52600460205260ff60405f205416610955578115610955576001600160a01b038416938415610955576116b88183613863565b156109555760ff6012541615611763575b916116f1602092825f51602061421f5f395f51905f52956116ec8230338b613aca565b613c58565b83865260018252604086208560018060a01b0319825416179055838652600382528060408720558386526004825260408620600160ff198254161790558386526005825242604087205583865260078252806040872055604051908152a360015f51602061423f5f395f51905f525580f35b5f5460081c6001600160a01b03165f51602061415f5f395f51905f523b15610955575f6117a5916040518093819263bdac7ca360e01b83523060048401613804565b0381835f51602061415f5f395f51905f525af1801561185357611825575b50916116f1602092825f51602061421f5f395f51905f5295600160ff19601254161760125560018060a01b038a5460081c167f08ea92db3a4bb72aee66d7d65a7c80ba70489af194b0544456e6f35702e25f648b80a2935094509250506116c9565b5f51602061421f5f395f51905f52939196506020926118475f6116f1936129ef565b5f9792945092506117c3565b6040513d5f823e3d90fd5b34610955575f366003190112610955575f54600881901c6001600160a01b031633036109555760019061188f613ab0565b60ff1916175f557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586020604051338152a1005b34610955576020366003190112610955575f546004359060081c6001600160a01b031633036109555760407f7c7f0e4cdf50c1655dd67dca80b07b6a91e0d9ffe2fe40eb7552b1feb028744891601054908060105582519182526020820152a1005b346109555760403660031901126109555761193d6127fc565b611945612812565b5f5490919060081c6001600160a01b03163303610955576001600160a01b0381165f8181526009602052604090205490919060ff1615610955575f828152600160205260409020546001600160a01b038481169491168414610955576119aa91613863565b15610955575f52600260205260405f205f5b81549081811015611a7d57836119d2828561286c565b905460039190911b1c6001600160a01b0316146119f35760019150016119bc565b5f198201918211611a6957610c27611a0e611a27938561286c565b905460039190911b1c6001600160a01b0316918461286c565b80548015611a55575f190190611a40610ec0838361286c565b555b5f5160206141ff5f395f51905f525f80a2005b634e487b7160e01b5f52603160045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b505050611a42565b34610955576020366003190112610955576001600160a01b03611aa66127fc565b165f526003602052602060405f2054604051908152f35b34610955576020366003190112610955576020610d04611adb6127fc565b61373e565b34610955576020366003190112610955576001600160a01b03611b016127fc565b16805f52600760205260405f205460405163d856d02160e01b8152826004820152602081602481305afa908115611853575f91611bc3575b50604051631d326d8f60e21b81526004810184905292602084602481305afa908115611853575f91611b8e575b608094505f52600860205260405f205491604051938452602084015260408301526060820152f35b90506020843d602011611bbb575b81611ba9602093836129ef565b81010312610955576080935190611b66565b3d9150611b9c565b90506020813d602011611bed575b81611bde602093836129ef565b81010312610955575183611b39565b3d9150611bd1565b34610955576020366003190112610955576001600160a01b03611c166127fc565b165f526008602052602060405f2054604051908152f35b3461095557602036600319011261095557611c466127fc565b7f000000000000000000000000485333f8b80c3d054b1ada1417a055c3442f31706001600160a01b038116919033839003610955576001600160a01b0382165f8181526009602052604090205490919060ff161561095557611ca6613e58565b611cae6139f7565b5f828152600160205260409020546001600160a01b031692831561095557611cd58461319e565b9182611cef575b60015f51602061423f5f395f51905f5255005b611d2b858592611d19602095611d135f5160206141df5f395f51905f529885613e73565b90612c9f565b9481869492611d4a575b509050613a2f565b835f52600382525f6040812055604051908152a4808080808080611cdc565b600d545f51602061413f5f395f51905f5291604091611d749082906001600160a01b031686613a2f565b835f5260068a52815f20611d89828254613668565b90558151908152868a820152a387878b611d23565b34610955576020366003190112610955575f546004359060081c6001600160a01b031633036109555760407fb27c12a91635e11c22bffa7bd8e0a8735da52b94aaefd7f249776c7590ba789491600e549080600e5582519182526020820152a1005b34610955576020366003190112610955576001600160a01b03611e216127fc565b165f526006602052602060405f2054604051908152f35b34610955575f36600319011261095557602060ff5f54166040519015158152f35b3461095557604036600319011261095557611e726127fc565b5f5460081c6001600160a01b03163303610955576001600160a01b0381165f9081526009602052604090205460ff161561095557611cdc906111036139f7565b34610955575f3660031901126109555760206040516101f48152f35b3461095557602036600319011261095557611ee76127fc565b5f5460081c6001600160a01b03163303610955576001600160a01b03165f8181526009602052604090205460ff161561095557805f52600460205260ff60405f205416610955575f818152600960205260408120805460ff191690555b600b54908181101561200c5782611f5a82612828565b905460039190911b1c6001600160a01b031614611f7b576001915001611f44565b5f198201918211611a6957610c27611f95611fad93612828565b905460039190911b1c6001600160a01b031691612828565b600b548015611a55575f1901611fc7610ec082600b61286c565b600b555b5f81815260016020526040812080546001600160a01b03191690557f37803e2125c48ee96c38ddf04e826daf335b0e1603579040fd275aba6d06b6fc9080a2005b5050611fcb565b3461095557612021366128f7565b939092907f000000000000000000000000485333f8b80c3d054b1ada1417a055c3442f31706001600160a01b0316330361095557611cdc95610d416139f7565b34610955575f366003190112610955575f54600881901c6001600160a01b031633036109555761208f613e58565b60ff19165f557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa6020604051338152a1005b34610955576020366003190112610955576020610d046120df6127fc565b61319e565b34610955576120f2366128b1565b5f5490929060081c6001600160a01b0316330361095557611cdc936121156139f7565b612dda565b34610955576040366003190112610955576121336127fc565b7f000000000000000000000000485333f8b80c3d054b1ada1417a055c3442f317090602435906001600160a01b0383163303610955576121716139f7565b6001600160a01b0316918215610955576040516370a0823160e01b815230600482015291602083602481875afa928315611853575f936121d0575b50821561095557806121ca5750815b821161095557611cdc92613a2f565b916121bb565b9092506020813d6020116121fc575b816121ec602093836129ef565b81010312610955575191846121ac565b3d91506121df565b34610955575f3660031901126109555760206040515f51602061415f5f395f51905f528152f35b34610955576040366003190112610955576122446127fc565b6001600160a01b03165f90815260026020526040902080546024359190821015610955576020916122749161286c565b905460405160039290921b1c6001600160a01b03168152f35b34610955576040366003190112610955576122a66127fc565b6122ae612812565b5f5490919060081c6001600160a01b03163303610955576001600160a01b0381165f8181526009602052604090205490919060ff1615610955576001600160a01b0383169283156109555761230291613863565b15610955575f818152600160205260409020546001600160a01b03168214610955575f81815260016020526040812080546001600160a01b0319811685179091556001600160a01b031691905f51602061417f5f395f51905f529080a4005b346109555761236f366128b1565b917f000000000000000000000000485333f8b80c3d054b1ada1417a055c3442f31706001600160a01b0316330361095557611cdc936121156139f7565b346109555760403660031901126109555760406123d36123ca6127fc565b60243590612c9f565b82519182526020820152f35b3461095557602036600319011261095557600435600c5481101561095557612274602091612854565b34610955575f36600319011261095557602060405173420dd381b31aef6683db6b902084cb0ffece40da8152f35b346109555760403660031901126109555761244f6127fc565b612457612812565b5f5460081c6001600160a01b03163303610955576001600160a01b0382165f8181526009602052604090205460ff1615610955576001600160a01b038216928315610955576040516338d52e0f60e01b8152602081600481885afa80156118535783915f9161259e575b506001600160a01b03160361095557826124da91613863565b61095557825f52600a60205260ff60405f2054161561255c575b5f52600260205260405f2090815491600160401b8310156125485782610c279160016125229501815561286c565b7f7b7ef7a864d96a85497a1ed846adb39940dd6ccef678ff6ac8d55505e09b8cc45f80a2005b634e487b7160e01b5f52604160045260245ffd5b5f838152600a60205260409020805460ff19166001179055600c54600160401b8110156125485782610c278260016125999401600c55600c61286c565b6124f4565b6125b7915060203d602011610cca57610cbc81836129ef565b866124c1565b34610955576020366003190112610955576001600160a01b036125de6127fc565b165f526001602052602060018060a01b0360405f205416604051908152f35b3461095557602036600319011261095557600435600b5481101561095557612274602091612828565b34610955576020366003190112610955576020610d046126446127fc565b612b67565b34610955575f3660031901126109555760206040515f51602061419f5f395f51905f528152f35b34610955576040366003190112610955576126896127fc565b612691612812565b5f5490919060081c6001600160a01b03163303610955576001600160a01b0381165f8181526009602052604090205490919060ff1615610955576001600160a01b0383165f818152600a602052604090205490939060ff1615610955578315610955576040516338d52e0f60e01b8152602081600481885afa80156118535784915f91612785575b506001600160a01b0316036109555761273191613863565b15610955575f818152600160205260409020546001600160a01b031690828214610955575f81815260016020526040812080546001600160a01b031916851790555f51602061417f5f395f51905f529080a4005b61279e915060203d602011610cca57610cbc81836129ef565b86612719565b34610955576020366003190112610955576020610d046127c26127fc565b612a2f565b34610955575f36600319011261095557600d54600e546011546001600160a01b03909216835260208301526040820152606090f35b600435906001600160a01b038216820361095557565b602435906001600160a01b038216820361095557565b600b5481101561284057600b5f5260205f2001905f90565b634e487b7160e01b5f52603260045260245ffd5b600c5481101561284057600c5f5260205f2001905f90565b8054821015612840575f5260205f2001905f90565b9181601f84011215610955578235916001600160401b038311610955576020808501948460051b01011161095557565b6060600319820112610955576004356001600160a01b0381168103610955579160243591604435906001600160401b038211610955576128f391600401612881565b9091565b6060600319820112610955576004356001600160401b038111610955578161292191600401612881565b909290916024356001600160401b038111610955578161294391600401612881565b90929091604435906001600160401b038211610955576128f391600401612881565b90602080835192838152019201905f5b8181106129825750505090565b82516001600160a01b0316845260209384019390920191600101612975565b90602080835192838152019201905f5b8181106129be5750505090565b82518452602093840193909201916001016129b1565b60a081019081106001600160401b0382111761254857604052565b601f909101601f19168101906001600160401b0382119082101761254857604052565b91908203918211611a6957565b600160ff1b8114611a69575f0390565b6001600160a01b03165f8181526004602052604090205460ff16158015612b03575b612afe5760405163d856d02160e01b815260048101829052602081602481305afa908115611853575f91612acc575b505f828152600360205260409020548110612aae57612aab915f52600360205260405f205490612a12565b90565b612ac790612aab925f52600360205260405f2054612a12565b612a1f565b90506020813d602011612af6575b81612ae7602093836129ef565b8101031261095557515f612a80565b3d9150612ada565b505f90565b50805f52600360205260405f205415612a51565b9081602091031261095557516001600160a01b03811681036109555790565b81810292918115918404141715611a6957565b8115612b53570490565b634e487b7160e01b5f52601260045260245ffd5b6001600160a01b03165f8181526004602052604090205460ff16158015612c8b575b612afe5760405163d856d02160e01b815260048101829052602081602481305afa908115611853575f91612c59575b505f828152600360205260409020548110612c1157612be490825f52600360205260405f205490612a12565b620f4240810290808204620f42401490151715611a6957612aab915f52600360205260405f205490612b49565b612c2790825f52600360205260405f2054612a12565b90620f4240820291808304620f42401490151715611a6957612aab91612ac7915f52600360205260405f205490612b49565b90506020813d602011612c83575b81612c74602093836129ef565b8101031261095557515f612bb8565b3d9150612c67565b50805f52600360205260405f205415612b89565b6001600160a01b0381165f8181526004602052604090205492939260ff16158015612d36575b8015612d20575b612d19575f526003602052612cf1612ce860405f205485612a12565b9160115461399a565b811115612d1357612710612d0b612aab92600e5490612b36565b048093612a12565b505f9190565b50505f9190565b50805f52600360205260405f2054841115612ccc565b50805f52600360205260405f205415612cc5565b6001600160401b0381116125485760051b60200190565b90612d6b82612d4a565b612d7860405191826129ef565b8281528092612d89601f1991612d4a565b0190602036910137565b8051156128405760200190565b8051600110156128405760400190565b80518210156128405760209160051b010190565b6040919493926060820195825260208201520152565b6001600160a01b031692915f9190841561095557811561095557604093845191612e0486846129ef565b60018352601f1986019182366020860137865191612e2288846129ef565b6001835283366020850137875195612e3a89886129ef565b6001875284366020890137885194612e528a876129ef565b600186525f5b81811061318d57505030612e6b87612d93565b5289612e7685612d93565b52612e8087612d93565b52612e8a82612d4a565b91612e97895193846129ef565b808352602083019060051b82019136831161095557905b82821061317d57505050612ec183612d93565b52612ecb82612d93565b5085516370a0823160e01b8152306004820152936020856024818b5afa948515613173575f9561313f575b505f51602061415f5f395f51905f523b1561095557612f5c90612f4a612f3895949389519687966301c7ba5760e61b8852608060048901526084880190612965565b86810360031901602488015290612965565b848103600319016044860152906129a1565b600319838203016064840152815180825260208201916020808360051b8301019401925f915b8383106130e357505050505090805f920381835f51602061415f5f395f51905f525af180156130d9576130c4575b5082516370a0823160e01b815230600482015291602083602481885afa9081156130b95790613085575b612fe49250612a12565b9081612fef57505050565b5f5160206141bf5f395f51905f529161303361271061301060105484612b36565b049261301c8484612a12565b9084613068575b81613038575b5193849384612dc4565b0390a2565b613063827f000000000000000000000000485333f8b80c3d054b1ada1417a055c3442f317089613a2f565b613029565b600d546130809086906001600160a01b031689613a2f565b613023565b506020823d6020116130b1575b8161309f602093836129ef565b8101031261095557612fe49151612fda565b3d9150613092565b8451903d90823e3d90fd5b6130d19192505f906129ef565b5f905f612fb0565b84513d5f823e3d90fd5b919395509193601f19828203018352855190602080835192838152019201905f905b8082106131275750505060208060019297019301930190928695949293612f82565b90919260208060019286518152019401920190613105565b9094506020813d60201161316b575b8161315b602093836129ef565b810103126109555751935f612ef6565b3d915061314e565b87513d5f823e3d90fd5b8135815260209182019101612eae565b806060602080938a01015201612e58565b6040516370a0823160e01b815230600482015290602090829060249082906001600160a01b03165afa908115611853575f916131d8575090565b90506020813d6020116131ff575b816131f3602093836129ef565b81010312610955575190565b3d91506131e6565b91908110156128405760051b0190565b356001600160a01b03811681036109555790565b81835290916001600160fb1b0383116109555760209260051b809284830137010190565b919092949385840361095557848403610955575f9484156109555761327385612d61565b925f5b868110613626575061328786612d61565b975f5b87811061359d57505f51602061415f5f395f51905f523b15610955576040516301c7ba5760e61b8152608060048201529485949291906020906132d1906084880190612965565b8681036003190160248801528981520191875f5b8a811061355e57505085830360031901604487015261330592919061322b565b91600319848403016064850152808352602083019060208160051b85010193835f91601e1982360301905b8484106134fe575050505050505090805f920381835f51602061415f5f395f51905f525af18015611853576134e9575b50825b828110613371575050505050565b60249060206001600160a01b0361339161338c848888613207565b613217565b16604051938480926370a0823160e01b82523060048301525afa918215610cd15785926134b4575b506133d16001926133ca8389612db0565b5190612a12565b806133de575b5001613363565b5f5160206141bf5f395f51905f526127106133fb60105484612b36565b046134068184612a12565b9281613486575b83613441575b613438868060a01b0361342a61338c888c8c613207565b169460405193849384612dc4565b0390a25f6133d7565b61348184878060a01b0361345961338c898d8d613207565b167f000000000000000000000000485333f8b80c3d054b1ada1417a055c3442f317090613a2f565b613413565b6134af82878060a01b0361349e61338c898d8d613207565b16888060a01b03600d541690613a2f565b61340d565b91506020823d82116134e1575b816134ce602093836129ef565b81010312610955579051906133d16133b9565b3d91506134c1565b6134f69193505f906129ef565b5f915f613360565b9193959750919395601f19828203018752873583811215610955578401602081019190356001600160401b038111610955578060051b360383136109555761354c602092839260019561322b565b99019701940191889796959391613330565b9496509391929091908435906001600160a01b0382168203610955576001600160a01b03909116815287969460209182019493929101906001016132e5565b60249060206001600160a01b036135b861338c848d8d613207565b16604051938480926370a0823160e01b82523060048301525afa8015611853575f906135f4575b600192506135ed828d612db0565b520161328a565b506020823d821161361e575b8161360d602093836129ef565b8101031261095557600191516135df565b3d9150613600565b6001600160a01b0361363c61338c838a8a613207565b16156109555761364d818a84613207565b351561095557600190306136618288612db0565b5201613276565b91908201809211611a6957565b6001600160a01b0381165f818152600460205260409020549192909160ff1615610955578015610955575f828152600160205260409020546001600160a01b031692831561095557816136f86020926136f17f3bc57f469ad6d10d7723ea226cd22bd2b9e527def2b529f6ab44645a1668958295303389613aca565b8287613c58565b835f526003825260405f2061370e828254613668565b9055835f52600582524260405f2055835f526007825260405f20613733828254613668565b9055604051908152a3565b6001600160a01b03165f8181526004602052604090205460ff161580156137f0575b612afe5760405163d856d02160e01b81526004810182905290602082602481305afa918215611853575f926137bc575b505f908152600760205260409020548082106137af57612aab91612a12565b612aab91612ac791612a12565b9091506020813d6020116137e8575b816137d8602093836129ef565b810103126109555751905f613790565b3d91506137cb565b50805f52600760205260405f205415613760565b6001600160a01b0391821681529116602082015260400190565b6001600160a01b03165f8181526009602052604090205460ff1615612afe575f908152600160205260409020546001600160a01b03168015612afe57612aab9061319e565b60018060a01b03165f52600260205260405f2090604051808360208295549384815201905f5260205f20925f5b8181106138ea5750506138a5925003836129ef565b5f5b82518110156138e3576001600160a01b036138c28285612db0565b51166001600160a01b038316146138db576001016138a7565b505050600190565b5050505f90565b84546001600160a01b0316835260019485019487945060209093019201613890565b6001600160a01b03165f8181526009602052604090205460ff1615612afe575f908152600160205260409020546001600160a01b03168015612afe5760206139538261319e565b6024604051809481936303d1689d60e11b835260048301525afa908115611853575f916131d8575090565b5f198114611a695760010190565b604d8111611a6957600a0a90565b906139a4906140c4565b600681036139b0575090565b9060068211156139d8576005198201918211611a69576139d2612aab9261398c565b90612b36565b906006039060068211611a69576139f1612aab9261398c565b90612b49565b60025f51602061423f5f395f51905f525414613a205760025f51602061423f5f395f51905f5255565b633ee5aeb560e01b5f5260045ffd5b916040519163a9059cbb60e01b5f5260018060a01b031660045260245260205f60448180865af19060015f5114821615613a8f575b60405215613a6f5750565b635274afe760e01b5f9081526001600160a01b0391909116600452602490fd5b906001811516613aa757823b15153d15161690613a64565b503d5f823e3d90fd5b60ff5f5416613abb57565b63d93c066560e01b5f5260045ffd5b6040516323b872dd60e01b5f9081526001600160a01b039384166004529290931660245260449390935260209060648180865af19060015f5114821615613b1b575b6040525f60605215613a6f5750565b906001811516613aa757823b15153d15161690613b0c565b90816020910312610955575180151581036109555790565b60405160609190613b5c83826129ef565b6002815291601f1901825f5b828110613b7457505050565b602090604051613b83816129d4565b5f81526060838201525f60408201525f60608201525f608082015282828501015201613b68565b602081016020825282518091526040820191602060408360051b8301019401925f915b838310613bdc57505050505090565b909192939460208060c0600193603f198682030187528260808b51878060a01b0381511684528281015160a08486015280519384918260a0880152018686015e5f8584860101526040810151604085015260608101511515606085015201516080830152601f8019910116010197019301930191939290613bcd565b60405163095ea7b360e01b81525f51602061419f5f395f51905f526004820152602481018390526001600160a01b03909316929091906020816044815f885af1801561185357613e2b575b50613cac613b4b565b926040519063d96ca0b960e01b602083015260248201525f51602061419f5f395f51905f52604482015281606482015260648152613ceb6084826129ef565b60405190613cf8826129d4565b5f51602061419f5f395f51905f52825260208201525f60408201525f60608201525f6080820152613d2884612d93565b52613d3283612d93565b5060405163377af75760e11b60208201526001600160a01b03909216602483015260448201525f196064820152306084808301919091528152613d7660a4826129ef565b60405190613d83826129d4565b5f51602061419f5f395f51905f52825260208201525f60408201525f60608201525f6080820152613db382612da0565b52613dbd81612da0565b50736bfd8137e702540e7a42b74178a4a49ba43920c43b15610955575f613df8916040518093819263374f435d60e01b835260048301613baa565b038183736bfd8137e702540e7a42b74178a4a49ba43920c45af1801561185357613e1f5750565b5f613e29916129ef565b565b613e4c9060203d602011613e51575b613e4481836129ef565b810190613b33565b613ca3565b503d613e3a565b60ff5f541615613e6457565b638dfc202b60e01b5f5260045ffd5b60405163266d6a8360e11b81526004810183905291906001600160a01b0316602083602481845afa928315611853575f93614090575b5060405163095ea7b360e01b81525f51602061419f5f395f51905f526004820152602481018390526020816044815f865af1801561185357614073575b50613eef613b4b565b9160405163d96ca0b960e01b60208201528260248201525f51602061419f5f395f51905f52604482015281606482015260648152613f2e6084826129ef565b60405190613f3b826129d4565b5f51602061419f5f395f51905f52825260208201525f60408201525f60608201525f6080820152613f6b84612d93565b52613f7583612d93565b50604051916353fb730360e11b6020840152602483015260448201525f60648201523060848201525f51602061419f5f395f51905f5260a482015260a48152613fbf60c4826129ef565b60405190613fcc826129d4565b5f51602061419f5f395f51905f52825260208201525f60408201525f60608201525f6080820152613ffc82612da0565b5261400681612da0565b50736bfd8137e702540e7a42b74178a4a49ba43920c43b15610955575f614041916040518093819263374f435d60e01b835260048301613baa565b038183736bfd8137e702540e7a42b74178a4a49ba43920c45af1801561185357614069575090565b5f612aab916129ef565b61408b9060203d602011613e5157613e4481836129ef565b613ee6565b9092506020813d6020116140bc575b816140ac602093836129ef565b810103126109555751915f613ea9565b3d915061409f565b60405163313ce56760e01b815290602090829060049082906001600160a01b03165afa5f9181614100575b506140fa5750601290565b60ff1690565b9091506020813d602011614136575b8161411c602093836129ef565b81010312610955575160ff8116810361095557905f6140ef565b3d915061410f56fe205442d60b70af1203d43cab62352c3b69b94f091be32fe683198057282b5c920000000000000000000000003ef3d8ba38ebe18db133cec108f4d14ce00dd9ae6e04d857bfe8fb4a5708cc80d2e83427508e17ca97c58ed3ea93ff3e8386782c000000000000000000000000b98c948cfa24072e58935bc004a8a7b376ae746a6b93c712f8760f33773ec46c38ea20e57ef035bcd5b8a46488e317251744d39d342e7ff505a8a0364cd0dc2ff195c315e43bce86b204846ecd36913e117b109ee71f3a50e5ad81964f352c411f1d45e35438ecd1acecef59ac81d9fbbf6cbc0a268c2c6d010a7b0a0c1bfa0c35fd165097626036f72cd52f416fbf0f842369409b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00a2646970667358221220937a045ce22337a851e0472222b7c242aefa56df9e4e2494d9e83d0a72bca53464736f6c634300081e0033

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

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

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]
[ 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.