LeverageToken

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "LeverageToken.sol": {
      "content": "// SPDX-License-Identifier: MIT

/*
Web: https://perpetual-x.com
X:   https://x.com/perpetualxeth
*/

pragma solidity ^0.8.20;

import {Ownable} from "solady/src/auth/Ownable.sol";
import {ERC20} from "solady/src/tokens/ERC20.sol";
import {SafeTransferLib} from "solady/src/utils/SafeTransferLib.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol";
import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {LiquidityAmounts} from "@uniswap/v4-periphery/src/libraries/LiquidityAmounts.sol";

struct ReserveData {
    uint256 configuration;
    uint128 liquidityIndex;
    uint128 currentLiquidityRate;
    uint128 variableBorrowIndex;
    uint128 currentVariableBorrowRate;
    uint128 currentStableBorrowRate;
    uint40 lastUpdateTimestamp;
    uint16 id;
    address aTokenAddress;
    address stableDebtTokenAddress;
    address variableDebtTokenAddress;
    address interestRateStrategyAddress;
    uint128 accruedToTreasury;
    uint128 unbacked;
    uint128 isolationModeTotalDebt;
}

interface IPool {
    function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external;
    function borrow(address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf) external;
    function repay(address asset, uint256 amount, uint256 rateMode, address onBehalfOf) external returns (uint256);
    function withdraw(address asset, uint256 amount, address to) external returns (uint256);
    function getUserAccountData(address user) external view returns (
        uint256 totalCollateralBase,
        uint256 totalDebtBase,
        uint256 availableBorrowsBase,
        uint256 currentLiquidationThreshold,
        uint256 ltv,
        uint256 healthFactor
    );
    function getReserveData(address asset) external view returns (ReserveData memory);
    function flashLoan(
        address receiverAddress,
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata modes,
        address onBehalfOf,
        bytes calldata params,
        uint16 referralCode
    ) external;
}

interface IFlashLoanReceiver {
    function executeOperation(
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata premiums,
        address initiator,
        bytes calldata params
    ) external returns (bool);
}

interface IWETH {
    function deposit() external payable;
    function withdraw(uint256) external;
    function approve(address, uint256) external returns (bool);
    function balanceOf(address) external view returns (uint256);
}

interface ISwapRouter {
    struct ExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
        uint160 sqrtPriceLimitX96;
    }
    function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);
}

interface IERC20 {
    function approve(address spender, uint256 amount) external returns (bool);
    function transfer(address to, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

interface AggregatorV3Interface {
    function decimals() external view returns (uint8);
    function latestRoundData() external view returns (
        uint80 roundId,
        int256 answer,
        uint256 startedAt,
        uint256 updatedAt,
        uint80 answeredInRound
    );
}

/// @title TaxToken - Token with Built-in Aave Leverage System
/// @notice All-in-one: Token + Tax Collection + Leveraged ETH Strategy
contract LeverageToken is ERC20, Ownable, ReentrancyGuard, IFlashLoanReceiver {
    using PoolIdLibrary for PoolKey;

    /* ══════════════════════════════════════════════════════════════════════════════ */
    /*                                   CONSTANTS                                    */
    /* ══════════════════════════════════════════════════════════════════════════════ */
    
    IPositionManager public immutable positionManager;
    IAllowanceTransfer public immutable permit2;
    IPoolManager public immutable poolManager;
    
    // Aave v3 Pool (Ethereum Mainnet)
    IPool public constant AAVE_POOL = IPool(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2);
    IWETH public constant WETH = IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
    address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
    ISwapRouter public constant SWAP_ROUTER = ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564);

    // Chainlink Price Feeds (Mainnet)
    AggregatorV3Interface public constant ETH_USD_FEED = AggregatorV3Interface(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419);
    AggregatorV3Interface public constant USDC_USD_FEED = AggregatorV3Interface(0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6);

    uint256 public constant MAX_SUPPLY = 1_000_000_000 * 1e18;
    uint256 public constant BASIS_POINTS = 10000;
    uint256 public constant MIN_HEALTH_FACTOR = 1.1e18; // 1.1 = 110% (10% buffer above liquidation)
    uint256 public constant ETH_STALENESS_THRESHOLD = 3900; // 65 minutes (ETH/USD heartbeat is 1 hour)
    uint256 public constant USDC_STALENESS_THRESHOLD = 90000; // 25 hours (USDC/USD heartbeat is 24 hours)

    /* ══════════════════════════════════════════════════════════════════════════════ */
    /*                                STATE VARIABLES                                 */
    /* ══════════════════════════════════════════════════════════════════════════════ */

    address public hookAddress;
    bool public midSwap;
    
    // Leverage parameters
    uint256 public leverageThreshold = 1 ether;
    uint256 public targetLeverage = 3;
    uint256 public callerRewardBps = 200; // 2% caller reward
    uint256 public maxSlippageBps = 400; // 4% slippage tolerance
    uint256 public emergencySlippageBps = 1000; // 10% for emergency closes
    uint256 public takeProfitThresholdBps = 2000;
    uint256 public compoundCallerRewardBps = 50;
    uint256 public leverageAllocationBps = 5000; // 50% to leverage
    
    uint256 public accumulatedLeverageFees;
    bool public leverageEnabled = true;
    uint256 public maxPriceDeviationBps = 500; // 5% max deviation from oracle
    bool private isExecuting; // Reentrancy guard for leverage execution
    uint256 public totalDeposited; // Track actual ETH deposited for accurate profit calculation

    /* ══════════════════════════════════════════════════════════════════════════════ */
    /*                                    EVENTS                                      */
    /* ══════════════════════════════════════════════════════════════════════════════ */

    event FeesDeposited(uint256 amount, uint256 toLeverage, uint256 toContract);
    event LeverageExecuted(address indexed caller, uint256 ethAmount, uint256 callerReward);
    event PositionClosed(uint256 ethReturned, uint256 profit);
    event PositionCompounded(address indexed caller, uint256 profit, uint256 callerReward);
    event ETHWithdrawn(address indexed to, uint256 amount);
    event DebugFlashLoan(string step, uint256 value);
    event DebugSwap(uint256 amountIn, uint256 amountOut);

    /* ══════════════════════════════════════════════════════════════════════════════ */
    /*                                   ERRORS                                       */
    /* ══════════════════════════════════════════════════════════════════════════════ */

    error OnlyHook();
    error BelowThreshold();
    error NoPosition();
    error InvalidParameters();
    error StalePrice();
    error PriceDeviation();
    error UnhealthyPosition();
    error AlreadyExecuting();
    error InsufficientOutput();

    /* ══════════════════════════════════════════════════════════════════════════════ */
    /*                                 CONSTRUCTOR                                    */
    /* ══════════════════════════════════════════════════════════════════════════════ */
    
    constructor(
        address _owner,
        address _positionManager,
        address _permit2,
        address _poolManager
    ) {
        positionManager = IPositionManager(_positionManager);
        permit2 = IAllowanceTransfer(_permit2);
        poolManager = IPoolManager(_poolManager);

        _initializeOwner(_owner);
        _mint(address(this), MAX_SUPPLY);
    }

    function name() public pure override returns (string memory) { 
        return "PerpetualX"; 
    }

    function symbol() public pure override returns (string memory) { 
        return "PERPX";     
    }

    /* ══════════════════════════════════════════════════════════════════════════════ */
    /*                              UNISWAP V4 FUNCTIONS                              */
    /* ══════════════════════════════════════════════════════════════════════════════ */

    function loadLiquidity(address _hook) external payable onlyOwner returns (uint256 tokenId) {
        hookAddress = _hook;
        address tokenA = address(this);
        uint160 sqrtPriceX96 = 3014831488601586337191090825970934;
        int24 tickLower = -887200;
        int24 tickUpper = 210800;

        this.approve(address(permit2), type(uint256).max);
        permit2.approve(tokenA, address(positionManager), type(uint160).max, type(uint48).max);
        permit2.approve(tokenA, address(poolManager), type(uint160).max, type(uint48).max);

        PoolKey memory pool = PoolKey({
            currency0: Currency.wrap(address(0)),
            currency1: Currency.wrap(address(tokenA)),
            fee: 0,
            tickSpacing: 200,
            hooks: IHooks(hookAddress)
        });

        poolManager.initialize(pool, sqrtPriceX96);

        uint128 liquidity = _calculateLiquidity(
            sqrtPriceX96,
            tickLower,
            tickUpper,
            0,
            balanceOf(address(this))
        );

        uint256 nextId = positionManager.nextTokenId();
        bytes memory actions = abi.encodePacked(uint8(Actions.MINT_POSITION), uint8(Actions.SETTLE_PAIR));
        bytes memory hookData = new bytes(0);

        bytes[] memory params = new bytes[](2);
        params[0] = abi.encode(pool, tickLower, tickUpper, liquidity, 0, balanceOf(address(this)), address(this), hookData);
        params[1] = abi.encode(pool.currency0, pool.currency1);

        positionManager.modifyLiquidities(abi.encode(actions, params), block.timestamp + 120);
        
        return nextId;
    }

    function setMidSwap(bool value) external {
        if (msg.sender != hookAddress) revert OnlyHook();
        midSwap = value;
    }

    function depositTaxes() external payable {
        if (msg.sender != hookAddress && msg.sender != owner()) revert OnlyHook();

        // If called by hook, it has already split the taxes - add everything to leverage
        // If called by owner (for testing), do the split here
        if (msg.sender == hookAddress) {
            // Hook already split - add all to leverage fund
            if (msg.value > 0) {
                accumulatedLeverageFees += msg.value;
            }
            emit FeesDeposited(msg.value, msg.value, 0);
        } else {
            // Owner calling directly (testing) - do split here
            if (!leverageEnabled || leverageAllocationBps == 0) {
                emit FeesDeposited(msg.value, 0, msg.value);
                return;
            }

            uint256 toLeverage = (msg.value * leverageAllocationBps) / BASIS_POINTS;
            uint256 toContract = msg.value - toLeverage;

            if (toLeverage > 0) {
                accumulatedLeverageFees += toLeverage;
            }

            emit FeesDeposited(msg.value, toLeverage, toContract);
        }
    }

    /* ══════════════════════════════════════════════════════════════════════════════ */
    /*                            LEVERAGE EXECUTION (PUBLIC)                         */
    /* ══════════════════════════════════════════════════════════════════════════════ */

    function executeLeverage() external {
        if (isExecuting) revert AlreadyExecuting();
        if (accumulatedLeverageFees < leverageThreshold) revert BelowThreshold();

        isExecuting = true;

        uint256 ethToLeverage = accumulatedLeverageFees;
        accumulatedLeverageFees = 0;

        uint256 callerReward = (ethToLeverage * callerRewardBps) / BASIS_POINTS;
        uint256 amountToLeverage = ethToLeverage - callerReward;

        // Track actual deposited amount for profit calculation
        totalDeposited += amountToLeverage;

        _executeLeverageInternal(amountToLeverage);

        emit LeverageExecuted(msg.sender, amountToLeverage, callerReward);

        isExecuting = false;

        // CEI pattern: external transfer last
        SafeTransferLib.forceSafeTransferETH(msg.sender, callerReward);
    }

    function compoundPosition() external {
        (uint256 totalCollateralBase, uint256 totalDebtBase, , , , ) =
            AAVE_POOL.getUserAccountData(address(this));

        if (totalDebtBase == 0) revert NoPosition();

        // Prevent underflow: ensure collateral > debt
        if (totalCollateralBase <= totalDebtBase) revert NoPosition();

        uint256 netValue = totalCollateralBase - totalDebtBase;

        // Use actual deposited amount (in USD terms via oracle)
        if (totalDeposited == 0) revert NoPosition();

        // Get ETH price to convert deposited ETH to USD (both in 8 decimals)
        (uint256 ethPrice, ) = _getOraclePrices();
        // totalDeposited is 18 decimals, ethPrice is 8 decimals
        // Result should be 8 decimals to match Aave's totalCollateralBase
        uint256 depositedValueUSD = (totalDeposited * ethPrice) / 1e10;

        // Prevent underflow: ensure there's actual profit
        if (netValue <= depositedValueUSD) revert BelowThreshold();

        uint256 profitBps = ((netValue - depositedValueUSD) * BASIS_POINTS) / depositedValueUSD;

        if (profitBps < takeProfitThresholdBps) revert BelowThreshold();

        // Use normal slippage for auto-compound
        uint256 totalProfit = _closePositionInternal(maxSlippageBps);
        totalDeposited = 0; // Reset since position is closed

        uint256 callerReward = (totalProfit * compoundCallerRewardBps) / BASIS_POINTS;

        SafeTransferLib.forceSafeTransferETH(msg.sender, callerReward);

        uint256 remainingProfit = totalProfit - callerReward;

        if (remainingProfit >= leverageThreshold) {
            totalDeposited = remainingProfit; // Track new deposit
            _executeLeverageInternal(remainingProfit);
        } else {
            accumulatedLeverageFees += remainingProfit;
        }
        
        emit PositionCompounded(msg.sender, totalProfit, callerReward);
    }

    function closePosition() external onlyOwner {
        uint256 profit = _closePositionInternal(emergencySlippageBps);
        totalDeposited = 0; // Reset for next position
        SafeTransferLib.forceSafeTransferETH(owner(), profit);
        emit PositionClosed(profit, profit);
    }

    /* ══════════════════════════════════════════════════════════════════════════════ */
    /*                            FLASH LOAN CALLBACK                                 */
    /* ══════════════════════════════════════════════════════════════════════════════ */

    function executeOperation(
    address[] calldata /* assets */,
    uint256[] calldata amounts,
    uint256[] calldata premiums,
    address initiator,
    bytes calldata params
    ) external override returns (bool) {
    require(msg.sender == address(AAVE_POOL), "Not Aave");
    require(initiator == address(this), "Not initiated by this");

    uint256 flashLoanAmount = amounts[0];
    uint256 premium = premiums[0];

    // Check if this is a close position operation
    (uint256 slippageBpsOrEth, bool isClosing) = abi.decode(params, (uint256, bool));

    if (isClosing) {
        // Handle close position: assets[0] is USDC
        return _handleClosePosition(flashLoanAmount, premium, slippageBpsOrEth);
    }

    // Handle open leverage position: assets[0] is WETH
    uint256 originalEth = slippageBpsOrEth;
    uint256 totalWeth = originalEth + flashLoanAmount;
    
    emit DebugFlashLoan("Flash loan received", flashLoanAmount);
    
    WETH.approve(address(AAVE_POOL), totalWeth);
    AAVE_POOL.supply(address(WETH), totalWeth, address(this), 0);
    
    emit DebugFlashLoan("Supplied to Aave", totalWeth);

    (, , uint256 availableBorrow, , , uint256 healthFactor) = AAVE_POOL.getUserAccountData(address(this));

    emit DebugFlashLoan("Available borrow USD", availableBorrow);
    emit DebugFlashLoan("Health factor after supply", healthFactor);

    // Try to borrow - 65% of available for MAXIMUM leverage (10 loop stacking with 1.1 HF)
    // availableBorrow is in 8 decimals (USD), USDC is 6 decimals
    // So we need to divide by 100 to convert from 8 decimals to 6 decimals
    uint256 usdcToBorrow = (availableBorrow * 6500) / BASIS_POINTS / 100; // 65% of available, converted to 6 decimals

    emit DebugFlashLoan("Attempting to borrow USDC", usdcToBorrow);

    // Try the borrow
    try AAVE_POOL.borrow(USDC, usdcToBorrow, 2, 0, address(this)) {
        emit DebugFlashLoan("Borrow successful", usdcToBorrow);

        // Approve USDC for swap
        IERC20(USDC).approve(address(SWAP_ROUTER), usdcToBorrow);

        // Swap USDC to WETH - set minimum to 0 for testing
        ISwapRouter.ExactInputSingleParams memory swapParams = ISwapRouter.ExactInputSingleParams({
            tokenIn: USDC,
            tokenOut: address(WETH),
            fee: 3000,
            recipient: address(this),
            deadline: block.timestamp,
            amountIn: usdcToBorrow,
            amountOutMinimum: 0, // No minimum for testing
            sqrtPriceLimitX96: 0
        });

        uint256 wethReceived = SWAP_ROUTER.exactInputSingle(swapParams);
        emit DebugSwap(usdcToBorrow, wethReceived);

        // Supply swapped WETH back to Aave
        WETH.approve(address(AAVE_POOL), wethReceived);
        AAVE_POOL.supply(address(WETH), wethReceived, address(this), 0);

        emit DebugFlashLoan("Re-deposited WETH", wethReceived);
    } catch {
        // Borrow failed - continue without it
        emit DebugFlashLoan("Borrow failed - continuing without leverage", 0);
    }

    // Calculate flash loan repayment needed
    uint256 totalDebt = flashLoanAmount + premium;

    // Withdraw ONLY the flash loan repayment amount
    // This leaves the leveraged position (swapped WETH) on Aave
    uint256 withdrawn = AAVE_POOL.withdraw(address(WETH), totalDebt, address(this));

    emit DebugFlashLoan("Withdrawn from Aave for repayment", withdrawn);

    // Approve the flash loan repayment
    WETH.approve(address(AAVE_POOL), totalDebt);

    emit DebugFlashLoan("Repaying flash loan", totalDebt);

    // Validate health factor after all operations
    (, , , , , uint256 finalHealthFactor) = AAVE_POOL.getUserAccountData(address(this));
    if (finalHealthFactor < MIN_HEALTH_FACTOR) revert UnhealthyPosition();

    emit DebugFlashLoan("Final health factor", finalHealthFactor);

    return true;
    }

    /* ══════════════════════════════════════════════════════════════════════════════ */
    /*                               VIEW FUNCTIONS                                   */
    /* ══════════════════════════════════════════════════════════════════════════════ */

    function canExecute() external view returns (bool) {
        return accumulatedLeverageFees >= leverageThreshold && !isExecuting;
    }

    function canCompound() external view returns (bool, uint256 currentProfitBps) {
        (uint256 totalCollateralBase, uint256 totalDebtBase, , , , ) =
            AAVE_POOL.getUserAccountData(address(this));

        if (totalDebtBase == 0) return (false, 0);
        if (totalDeposited == 0) return (false, 0);

        uint256 netValue = totalCollateralBase - totalDebtBase;

        // Get ETH price to convert deposited ETH to USD (both in 8 decimals)
        (uint256 ethPrice, ) = _getOraclePrices();
        // totalDeposited is 18 decimals, ethPrice is 8 decimals
        // Result should be 8 decimals to match Aave's totalCollateralBase
        uint256 depositedValueUSD = (totalDeposited * ethPrice) / 1e10;

        if (netValue <= depositedValueUSD) return (false, 0);

        currentProfitBps = ((netValue - depositedValueUSD) * BASIS_POINTS) / depositedValueUSD;

        return (currentProfitBps >= takeProfitThresholdBps, currentProfitBps);
    }

    function getPositionData() external view returns (
        uint256 totalCollateral,
        uint256 totalDebt,
        uint256 availableBorrow,
        uint256 healthFactor
    ) {
        (totalCollateral, totalDebt, availableBorrow, , , healthFactor) = 
            AAVE_POOL.getUserAccountData(address(this));
    }

    function getExpectedReward() external view returns (uint256) {
        if (accumulatedLeverageFees < leverageThreshold) return 0;
        return (accumulatedLeverageFees * callerRewardBps) / BASIS_POINTS;
    }

    /* ══════════════════════════════════════════════════════════════════════════════ */
    /*                               ADMIN FUNCTIONS                                  */
    /* ══════════════════════════════════════════════════════════════════════════════ */

    function setLeverageParameters(
        uint256 _threshold,
        uint256 _leverage,
        uint256 _callerRewardBps,
        uint256 _maxSlippageBps,
        uint256 _takeProfitThresholdBps,
        uint256 _compoundCallerRewardBps
    ) external onlyOwner {
        if (_leverage < 1 || _leverage > 5) revert InvalidParameters();
        if (_callerRewardBps > 500) revert InvalidParameters();
        if (_maxSlippageBps > 1000) revert InvalidParameters();
        if (_compoundCallerRewardBps > 200) revert InvalidParameters();
        
        leverageThreshold = _threshold;
        targetLeverage = _leverage;
        callerRewardBps = _callerRewardBps;
        maxSlippageBps = _maxSlippageBps;
        takeProfitThresholdBps = _takeProfitThresholdBps;
        compoundCallerRewardBps = _compoundCallerRewardBps;
    }

    function setLeverageAllocation(uint256 _bps) external onlyOwner {
        require(_bps <= BASIS_POINTS, "Invalid BPS");
        leverageAllocationBps = _bps;
    }

    /// @notice Set leverage allocation to 100% after taxes normalize to 10/10
    /// @dev Call this function once taxes have settled to 10% buy/sell (after 35 min)
    /// @dev Sniper sell taxes still go 100% to owner wallet (handled in TaxHook)
    function enableFullLeverageAllocation() external onlyOwner {
        leverageAllocationBps = 10000; // 100% to leverage fund
    }

    function toggleLeverage(bool _enabled) external onlyOwner {
        leverageEnabled = _enabled;
    }

    function emergencyWithdrawETH(address _to, uint256 _amount) external onlyOwner {
        SafeTransferLib.forceSafeTransferETH(_to, _amount);
        emit ETHWithdrawn(_to, _amount);
    }

    function emergencyWithdrawTokens(address _token) external onlyOwner {
        uint256 balance = ERC20(_token).balanceOf(address(this));
        if (balance > 0) {
            SafeTransferLib.safeTransfer(_token, owner(), balance);
        }
    }

    /* ══════════════════════════════════════════════════════════════════════════════ */
    /*                              INTERNAL FUNCTIONS                                */
    /* ══════════════════════════════════════════════════════════════════════════════ */

    function _closePositionInternal(uint256 slippageBps) internal returns (uint256) {
        // Get actual USDC debt amount
        ReserveData memory usdcReserve = AAVE_POOL.getReserveData(USDC);
        uint256 usdcDebt = IERC20(usdcReserve.variableDebtTokenAddress).balanceOf(address(this));

        if (usdcDebt == 0) revert NoPosition();

        // Use flash loan to close position safely
        // Flash loan USDC to repay debt, then withdraw all WETH and sell to repay flash loan
        address[] memory assets = new address[](1);
        assets[0] = USDC;

        uint256[] memory amounts = new uint256[](1);
        amounts[0] = usdcDebt;

        uint256[] memory modes = new uint256[](1);
        modes[0] = 0; // 0 = no debt, just flash loan

        bytes memory params = abi.encode(slippageBps, true); // true = closing position

        AAVE_POOL.flashLoan(
            address(this),
            assets,
            amounts,
            modes,
            address(this),
            params,
            0
        );

        return address(this).balance;
    }

    function _handleClosePosition(uint256 flashLoanAmount, uint256 premium, uint256 slippageBps) internal returns (bool) {
        // We received USDC flash loan, use it to repay the debt
        IERC20(USDC).approve(address(AAVE_POOL), flashLoanAmount);
        AAVE_POOL.repay(USDC, flashLoanAmount, 2, address(this));

        // Now withdraw ALL WETH collateral (debt is repaid, so we can withdraw everything)
        uint256 wethWithdrawn = AAVE_POOL.withdraw(address(WETH), type(uint256).max, address(this));

        // Swap WETH → USDC to repay flash loan + premium
        uint256 usdcNeeded = flashLoanAmount + premium;

        WETH.approve(address(SWAP_ROUTER), wethWithdrawn);

        // Get oracle prices for minimum output calculation
        (uint256 ethPrice, uint256 usdcPrice) = _getOraclePrices();

        // Calculate minimum USDC we expect
        uint256 expectedUsdcOut = (wethWithdrawn * ethPrice * 1e6) / (usdcPrice * 1e18);
        uint256 minUsdcOut = (expectedUsdcOut * (BASIS_POINTS - slippageBps)) / BASIS_POINTS;

        // Make sure we get at least enough to repay flash loan
        if (minUsdcOut < usdcNeeded) {
            minUsdcOut = usdcNeeded;
        }

        ISwapRouter.ExactInputSingleParams memory swapParams = ISwapRouter.ExactInputSingleParams({
            tokenIn: address(WETH),
            tokenOut: USDC,
            fee: 500, // 0.05% pool
            recipient: address(this),
            deadline: block.timestamp,
            amountIn: wethWithdrawn,
            amountOutMinimum: minUsdcOut,
            sqrtPriceLimitX96: 0
        });

        uint256 usdcReceived = SWAP_ROUTER.exactInputSingle(swapParams);

        // Approve flash loan repayment
        IERC20(USDC).approve(address(AAVE_POOL), usdcNeeded);

        // Any remaining USDC is profit, convert to ETH
        if (usdcReceived > usdcNeeded) {
            uint256 profitUsdc = usdcReceived - usdcNeeded;

            // Swap profit USDC → WETH
            IERC20(USDC).approve(address(SWAP_ROUTER), profitUsdc);

            ISwapRouter.ExactInputSingleParams memory profitSwap = ISwapRouter.ExactInputSingleParams({
                tokenIn: USDC,
                tokenOut: address(WETH),
                fee: 500,
                recipient: address(this),
                deadline: block.timestamp,
                amountIn: profitUsdc,
                amountOutMinimum: 0,
                sqrtPriceLimitX96: 0
            });

            uint256 profitWeth = SWAP_ROUTER.exactInputSingle(profitSwap);

            // Convert to ETH
            WETH.withdraw(profitWeth);
        }

        return true;
    }

    function _executeLeverageInternal(uint256 ethAmount) internal {
        WETH.deposit{value: ethAmount}();
        
        uint256 flashLoanAmount = ethAmount * (targetLeverage - 1);
        
        address[] memory assets = new address[](1);
        assets[0] = address(WETH);
        
        uint256[] memory amounts = new uint256[](1);
        amounts[0] = flashLoanAmount;
        
        uint256[] memory modes = new uint256[](1);
        modes[0] = 0;
        
        bytes memory params = abi.encode(ethAmount, false); // false = opening position

        AAVE_POOL.flashLoan(address(this), assets, amounts, modes, address(this), params, 0);
    }

    function _calculateLiquidity(
        uint160 sqrtPriceX96,
        int24 tickLower,
        int24 tickUpper,
        uint256 amount0Desired,
        uint256 amount1Desired
    ) private pure returns (uint128) {
        return LiquidityAmounts.getLiquidityForAmounts(
            sqrtPriceX96,
            TickMath.getSqrtPriceAtTick(tickLower),
            TickMath.getSqrtPriceAtTick(tickUpper),
            amount0Desired,
            amount1Desired
        );
    }

    /**
     * @dev Get validated prices from Chainlink oracles
     * @return ethPrice ETH/USD price (8 decimals)
     * @return usdcPrice USDC/USD price (8 decimals)
     */
    function _getOraclePrices() private view returns (uint256 ethPrice, uint256 usdcPrice) {
        // Get ETH/USD price
        (
            uint80 ethRoundId,
            int256 ethAnswer,
            ,
            uint256 ethUpdatedAt,
            uint80 ethAnsweredInRound
        ) = ETH_USD_FEED.latestRoundData();

        // Validate ETH price feed
        if (ethAnswer <= 0) revert PriceDeviation();
        if (ethUpdatedAt == 0 || block.timestamp - ethUpdatedAt > ETH_STALENESS_THRESHOLD) revert StalePrice();
        if (ethAnsweredInRound < ethRoundId) revert StalePrice();

        // Get USDC/USD price
        (
            uint80 usdcRoundId,
            int256 usdcAnswer,
            ,
            uint256 usdcUpdatedAt,
            uint80 usdcAnsweredInRound
        ) = USDC_USD_FEED.latestRoundData();

        // Validate USDC price feed
        if (usdcAnswer <= 0) revert PriceDeviation();
        if (usdcUpdatedAt == 0 || block.timestamp - usdcUpdatedAt > USDC_STALENESS_THRESHOLD) revert StalePrice();
        if (usdcAnsweredInRound < usdcRoundId) revert StalePrice();

        ethPrice = uint256(ethAnswer);
        usdcPrice = uint256(usdcAnswer);

        // Sanity check: USDC should be close to $1 (between $0.95 and $1.05)
        if (usdcPrice < 0.95e8 || usdcPrice > 1.05e8) revert PriceDeviation();
    }

    receive() external payable {}
}"
    },
    "@uniswap/v4-periphery/src/libraries/LiquidityAmounts.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol";
import {FixedPoint96} from "@uniswap/v4-core/src/libraries/FixedPoint96.sol";
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";

/// @notice Provides functions for computing liquidity amounts from token amounts and prices
library LiquidityAmounts {
    using SafeCast for uint256;

    /// @notice Computes the amount of liquidity received for a given amount of token0 and price range
    /// @dev Calculates amount0 * (sqrt(upper) * sqrt(lower)) / (sqrt(upper) - sqrt(lower))
    /// @param sqrtPriceAX96 A sqrt price representing the first tick boundary
    /// @param sqrtPriceBX96 A sqrt price representing the second tick boundary
    /// @param amount0 The amount0 being sent in
    /// @return liquidity The amount of returned liquidity
    function getLiquidityForAmount0(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint256 amount0)
        internal
        pure
        returns (uint128 liquidity)
    {
        unchecked {
            if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
            uint256 intermediate = FullMath.mulDiv(sqrtPriceAX96, sqrtPriceBX96, FixedPoint96.Q96);
            return FullMath.mulDiv(amount0, intermediate, sqrtPriceBX96 - sqrtPriceAX96).toUint128();
        }
    }

    /// @notice Computes the amount of liquidity received for a given amount of token1 and price range
    /// @dev Calculates amount1 / (sqrt(upper) - sqrt(lower)).
    /// @param sqrtPriceAX96 A sqrt price representing the first tick boundary
    /// @param sqrtPriceBX96 A sqrt price representing the second tick boundary
    /// @param amount1 The amount1 being sent in
    /// @return liquidity The amount of returned liquidity
    function getLiquidityForAmount1(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint256 amount1)
        internal
        pure
        returns (uint128 liquidity)
    {
        unchecked {
            if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
            return FullMath.mulDiv(amount1, FixedPoint96.Q96, sqrtPriceBX96 - sqrtPriceAX96).toUint128();
        }
    }

    /// @notice Computes the maximum amount of liquidity received for a given amount of token0, token1, the current
    /// pool prices and the prices at the tick boundaries
    /// @param sqrtPriceX96 A sqrt price representing the current pool prices
    /// @param sqrtPriceAX96 A sqrt price representing the first tick boundary
    /// @param sqrtPriceBX96 A sqrt price representing the second tick boundary
    /// @param amount0 The amount of token0 being sent in
    /// @param amount1 The amount of token1 being sent in
    /// @return liquidity The maximum amount of liquidity received
    function getLiquidityForAmounts(
        uint160 sqrtPriceX96,
        uint160 sqrtPriceAX96,
        uint160 sqrtPriceBX96,
        uint256 amount0,
        uint256 amount1
    ) internal pure returns (uint128 liquidity) {
        if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);

        if (sqrtPriceX96 <= sqrtPriceAX96) {
            liquidity = getLiquidityForAmount0(sqrtPriceAX96, sqrtPriceBX96, amount0);
        } else if (sqrtPriceX96 < sqrtPriceBX96) {
            uint128 liquidity0 = getLiquidityForAmount0(sqrtPriceX96, sqrtPriceBX96, amount0);
            uint128 liquidity1 = getLiquidityForAmount1(sqrtPriceAX96, sqrtPriceX96, amount1);

            liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1;
        } else {
            liquidity = getLiquidityForAmount1(sqrtPriceAX96, sqrtPriceBX96, amount1);
        }
    }
}
"
    },
    "@uniswap/v4-core/src/libraries/TickMath.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {BitMath} from "./BitMath.sol";
import {CustomRevert} from "./CustomRevert.sol";

/// @title Math library for computing sqrt prices from ticks and vice versa
/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports
/// prices between 2**-128 and 2**128
library TickMath {
    using CustomRevert for bytes4;

    /// @notice Thrown when the tick passed to #getSqrtPriceAtTick is not between MIN_TICK and MAX_TICK
    error InvalidTick(int24 tick);
    /// @notice Thrown when the price passed to #getTickAtSqrtPrice does not correspond to a price between MIN_TICK and MAX_TICK
    error InvalidSqrtPrice(uint160 sqrtPriceX96);

    /// @dev The minimum tick that may be passed to #getSqrtPriceAtTick computed from log base 1.0001 of 2**-128
    /// @dev If ever MIN_TICK and MAX_TICK are not centered around 0, the absTick logic in getSqrtPriceAtTick cannot be used
    int24 internal constant MIN_TICK = -887272;
    /// @dev The maximum tick that may be passed to #getSqrtPriceAtTick computed from log base 1.0001 of 2**128
    /// @dev If ever MIN_TICK and MAX_TICK are not centered around 0, the absTick logic in getSqrtPriceAtTick cannot be used
    int24 internal constant MAX_TICK = 887272;

    /// @dev The minimum tick spacing value drawn from the range of type int16 that is greater than 0, i.e. min from the range [1, 32767]
    int24 internal constant MIN_TICK_SPACING = 1;
    /// @dev The maximum tick spacing value drawn from the range of type int16, i.e. max from the range [1, 32767]
    int24 internal constant MAX_TICK_SPACING = type(int16).max;

    /// @dev The minimum value that can be returned from #getSqrtPriceAtTick. Equivalent to getSqrtPriceAtTick(MIN_TICK)
    uint160 internal constant MIN_SQRT_PRICE = 4295128739;
    /// @dev The maximum value that can be returned from #getSqrtPriceAtTick. Equivalent to getSqrtPriceAtTick(MAX_TICK)
    uint160 internal constant MAX_SQRT_PRICE = 1461446703485210103287273052203988822378723970342;
    /// @dev A threshold used for optimized bounds check, equals `MAX_SQRT_PRICE - MIN_SQRT_PRICE - 1`
    uint160 internal constant MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE =
        1461446703485210103287273052203988822378723970342 - 4295128739 - 1;

    /// @notice Given a tickSpacing, compute the maximum usable tick
    function maxUsableTick(int24 tickSpacing) internal pure returns (int24) {
        unchecked {
            return (MAX_TICK / tickSpacing) * tickSpacing;
        }
    }

    /// @notice Given a tickSpacing, compute the minimum usable tick
    function minUsableTick(int24 tickSpacing) internal pure returns (int24) {
        unchecked {
            return (MIN_TICK / tickSpacing) * tickSpacing;
        }
    }

    /// @notice Calculates sqrt(1.0001^tick) * 2^96
    /// @dev Throws if |tick| > max tick
    /// @param tick The input tick for the above formula
    /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the price of the two assets (currency1/currency0)
    /// at the given tick
    function getSqrtPriceAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
        unchecked {
            uint256 absTick;
            assembly ("memory-safe") {
                tick := signextend(2, tick)
                // mask = 0 if tick >= 0 else -1 (all 1s)
                let mask := sar(255, tick)
                // if tick >= 0, |tick| = tick = 0 ^ tick
                // if tick < 0, |tick| = ~~|tick| = ~(-|tick| - 1) = ~(tick - 1) = (-1) ^ (tick - 1)
                // either way, |tick| = mask ^ (tick + mask)
                absTick := xor(mask, add(mask, tick))
            }

            if (absTick > uint256(int256(MAX_TICK))) InvalidTick.selector.revertWith(tick);

            // The tick is decomposed into bits, and for each bit with index i that is set, the product of 1/sqrt(1.0001^(2^i))
            // is calculated (using Q128.128). The constants used for this calculation are rounded to the nearest integer

            // Equivalent to:
            //     price = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
            //     or price = int(2**128 / sqrt(1.0001)) if (absTick & 0x1) else 1 << 128
            uint256 price;
            assembly ("memory-safe") {
                price := xor(shl(128, 1), mul(xor(shl(128, 1), 0xfffcb933bd6fad37aa2d162d1a594001), and(absTick, 0x1)))
            }
            if (absTick & 0x2 != 0) price = (price * 0xfff97272373d413259a46990580e213a) >> 128;
            if (absTick & 0x4 != 0) price = (price * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
            if (absTick & 0x8 != 0) price = (price * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
            if (absTick & 0x10 != 0) price = (price * 0xffcb9843d60f6159c9db58835c926644) >> 128;
            if (absTick & 0x20 != 0) price = (price * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
            if (absTick & 0x40 != 0) price = (price * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
            if (absTick & 0x80 != 0) price = (price * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
            if (absTick & 0x100 != 0) price = (price * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
            if (absTick & 0x200 != 0) price = (price * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
            if (absTick & 0x400 != 0) price = (price * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
            if (absTick & 0x800 != 0) price = (price * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
            if (absTick & 0x1000 != 0) price = (price * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
            if (absTick & 0x2000 != 0) price = (price * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
            if (absTick & 0x4000 != 0) price = (price * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
            if (absTick & 0x8000 != 0) price = (price * 0x31be135f97d08fd981231505542fcfa6) >> 128;
            if (absTick & 0x10000 != 0) price = (price * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
            if (absTick & 0x20000 != 0) price = (price * 0x5d6af8dedb81196699c329225ee604) >> 128;
            if (absTick & 0x40000 != 0) price = (price * 0x2216e584f5fa1ea926041bedfe98) >> 128;
            if (absTick & 0x80000 != 0) price = (price * 0x48a170391f7dc42444e8fa2) >> 128;

            assembly ("memory-safe") {
                // if (tick > 0) price = type(uint256).max / price;
                if sgt(tick, 0) { price := div(not(0), price) }

                // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96.
                // we then downcast because we know the result always fits within 160 bits due to our tick input constraint
                // we round up in the division so getTickAtSqrtPrice of the output price is always consistent
                // `sub(shl(32, 1), 1)` is `type(uint32).max`
                // `price + type(uint32).max` will not overflow because `price` fits in 192 bits
                sqrtPriceX96 := shr(32, add(price, sub(shl(32, 1), 1)))
            }
        }
    }

    /// @notice Calculates the greatest tick value such that getSqrtPriceAtTick(tick) <= sqrtPriceX96
    /// @dev Throws in case sqrtPriceX96 < MIN_SQRT_PRICE, as MIN_SQRT_PRICE is the lowest value getSqrtPriceAtTick may
    /// ever return.
    /// @param sqrtPriceX96 The sqrt price for which to compute the tick as a Q64.96
    /// @return tick The greatest tick for which the getSqrtPriceAtTick(tick) is less than or equal to the input sqrtPriceX96
    function getTickAtSqrtPrice(uint160 sqrtPriceX96) internal pure returns (int24 tick) {
        unchecked {
            // Equivalent: if (sqrtPriceX96 < MIN_SQRT_PRICE || sqrtPriceX96 >= MAX_SQRT_PRICE) revert InvalidSqrtPrice();
            // second inequality must be >= because the price can never reach the price at the max tick
            // if sqrtPriceX96 < MIN_SQRT_PRICE, the `sub` underflows and `gt` is true
            // if sqrtPriceX96 >= MAX_SQRT_PRICE, sqrtPriceX96 - MIN_SQRT_PRICE > MAX_SQRT_PRICE - MIN_SQRT_PRICE - 1
            if ((sqrtPriceX96 - MIN_SQRT_PRICE) > MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE) {
                InvalidSqrtPrice.selector.revertWith(sqrtPriceX96);
            }

            uint256 price = uint256(sqrtPriceX96) << 32;

            uint256 r = price;
            uint256 msb = BitMath.mostSignificantBit(r);

            if (msb >= 128) r = price >> (msb - 127);
            else r = price << (127 - msb);

            int256 log_2 = (int256(msb) - 128) << 64;

            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(63, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(62, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(61, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(60, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(59, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(58, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(57, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(56, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(55, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(54, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(53, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(52, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(51, f))
                r := shr(f, r)
            }
            assembly ("memory-safe") {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(50, f))
            }

            int256 log_sqrt10001 = log_2 * 255738958999603826347141; // Q22.128 number

            // Magic number represents the ceiling of the maximum value of the error when approximating log_sqrt10001(x)
            int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128);

            // Magic number represents the minimum value of the error when approximating log_sqrt10001(x), when
            // sqrtPrice is from the range (2^-64, 2^64). This is safe as MIN_SQRT_PRICE is more than 2^-64. If MIN_SQRT_PRICE
            // is changed, this may need to be changed too
            int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128);

            tick = tickLow == tickHi ? tickLow : getSqrtPriceAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow;
        }
    }
}
"
    },
    "@uniswap/v4-periphery/src/libraries/Actions.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @notice Library to define different pool actions.
/// @dev These are suggested common commands, however additional commands should be defined as required
/// Some of these actions are not supported in the Router contracts or Position Manager contracts, but are left as they may be helpful commands for other peripheral contracts.
library Actions {
    // pool actions
    // liquidity actions
    uint256 internal constant INCREASE_LIQUIDITY = 0x00;
    uint256 internal constant DECREASE_LIQUIDITY = 0x01;
    uint256 internal constant MINT_POSITION = 0x02;
    uint256 internal constant BURN_POSITION = 0x03;
    uint256 internal constant INCREASE_LIQUIDITY_FROM_DELTAS = 0x04;
    uint256 internal constant MINT_POSITION_FROM_DELTAS = 0x05;

    // swapping
    uint256 internal constant SWAP_EXACT_IN_SINGLE = 0x06;
    uint256 internal constant SWAP_EXACT_IN = 0x07;
    uint256 internal constant SWAP_EXACT_OUT_SINGLE = 0x08;
    uint256 internal constant SWAP_EXACT_OUT = 0x09;

    // donate
    // note this is not supported in the position manager or router
    uint256 internal constant DONATE = 0x0a;

    // closing deltas on the pool manager
    // settling
    uint256 internal constant SETTLE = 0x0b;
    uint256 internal constant SETTLE_ALL = 0x0c;
    uint256 internal constant SETTLE_PAIR = 0x0d;
    // taking
    uint256 internal constant TAKE = 0x0e;
    uint256 internal constant TAKE_ALL = 0x0f;
    uint256 internal constant TAKE_PORTION = 0x10;
    uint256 internal constant TAKE_PAIR = 0x11;

    uint256 internal constant CLOSE_CURRENCY = 0x12;
    uint256 internal constant CLEAR_OR_TAKE = 0x13;
    uint256 internal constant SWEEP = 0x14;

    uint256 internal constant WRAP = 0x15;
    uint256 internal constant UNWRAP = 0x16;

    // minting/burning 6909s to close deltas
    // note this is not supported in the position manager or router
    uint256 internal constant MINT_6909 = 0x17;
    uint256 internal constant BURN_6909 = 0x18;
}
"
    },
    "@uniswap/v4-core/src/types/PoolId.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

type PoolId is bytes32;

/// @notice Library for computing the ID of a pool
library PoolIdLibrary {
    /// @notice Returns value equal to keccak256(abi.encode(poolKey))
    function toId(PoolKey memory poolKey) internal pure returns (PoolId poolId) {
        assembly ("memory-safe") {
            // 0xa0 represents the total size of the poolKey struct (5 slots of 32 bytes)
            poolId := keccak256(poolKey, 0xa0)
        }
    }
}
"
    },
    "@uniswap/v4-core/src/types/PoolKey.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Currency} from "./Currency.sol";
import {IHooks} from "../interfaces/IHooks.sol";
import {PoolIdLibrary} from "./PoolId.sol";

using PoolIdLibrary for PoolKey global;

/// @notice Returns the key for identifying a pool
struct PoolKey {
    /// @notice The lower currency of the pool, sorted numerically
    Currency currency0;
    /// @notice The higher currency of the pool, sorted numerically
    Currency currency1;
    /// @notice The pool LP fee, capped at 1_000_000. If the highest bit is 1, the pool has a dynamic fee and must be exactly equal to 0x800000
    uint24 fee;
    /// @notice Ticks that involve positions must be a multiple of tick spacing
    int24 tickSpacing;
    /// @notice The hooks of the pool
    IHooks hooks;
}
"
    },
    "@uniswap/v4-core/src/types/Currency.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IERC20Minimal} from "../interfaces/external/IERC20Minimal.sol";
import {CustomRevert} from "../libraries/CustomRevert.sol";

type Currency is address;

using {greaterThan as >, lessThan as <, greaterThanOrEqualTo as >=, equals as ==} for Currency global;
using CurrencyLibrary for Currency global;

function equals(Currency currency, Currency other) pure returns (bool) {
    return Currency.unwrap(currency) == Currency.unwrap(other);
}

function greaterThan(Currency currency, Currency other) pure returns (bool) {
    return Currency.unwrap(currency) > Currency.unwrap(other);
}

function lessThan(Currency currency, Currency other) pure returns (bool) {
    return Currency.unwrap(currency) < Currency.unwrap(other);
}

function greaterThanOrEqualTo(Currency currency, Currency other) pure returns (bool) {
    return Currency.unwrap(currency) >= Currency.unwrap(other);
}

/// @title CurrencyLibrary
/// @dev This library allows for transferring and holding native tokens and ERC20 tokens
library CurrencyLibrary {
    /// @notice Additional context for ERC-7751 wrapped error when a native transfer fails
    error NativeTransferFailed();

    /// @notice Additional context for ERC-7751 wrapped error when an ERC20 transfer fails
    error ERC20TransferFailed();

    /// @notice A constant to represent the native currency
    Currency public constant ADDRESS_ZERO = Currency.wrap(address(0));

    function transfer(Currency currency, address to, uint256 amount) internal {
        // altered from https://github.com/transmissions11/solmate/blob/44a9963d4c78111f77caa0e65d677b8b46d6f2e6/src/utils/SafeTransferLib.sol
        // modified custom error selectors

        bool success;
        if (currency.isAddressZero()) {
            assembly ("memory-safe") {
                // Transfer the ETH and revert if it fails.
                success := call(gas(), to, amount, 0, 0, 0, 0)
            }
            // revert with NativeTransferFailed, containing the bubbled up error as an argument
            if (!success) {
                CustomRevert.bubbleUpAndRevertWith(to, bytes4(0), NativeTransferFailed.selector);
            }
        } else {
            assembly ("memory-safe") {
                // Get a pointer to some free memory.
                let fmp := mload(0x40)

                // Write the abi-encoded calldata into memory, beginning with the function selector.
                mstore(fmp, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
                mstore(add(fmp, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
                mstore(add(fmp, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

                success :=
                    and(
                        // Set success to whether the call reverted, if not we check it either
                        // returned exactly 1 (can't just be non-zero data), or had no return data.
                        or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                        // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                        // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                        // Counterintuitively, this call must be positioned second to the or() call in the
                        // surrounding and() call or else returndatasize() will be zero during the computation.
                        call(gas(), currency, 0, fmp, 68, 0, 32)
                    )

                // Now clean the memory we used
                mstore(fmp, 0) // 4 byte `selector` and 28 bytes of `to` were stored here
                mstore(add(fmp, 0x20), 0) // 4 bytes of `to` and 28 bytes of `amount` were stored here
                mstore(add(fmp, 0x40), 0) // 4 bytes of `amount` were stored here
            }
            // revert with ERC20TransferFailed, containing the bubbled up error as an argument
            if (!success) {
                CustomRevert.bubbleUpAndRevertWith(
                    Currency.unwrap(currency), IERC20Minimal.transfer.selector, ERC20TransferFailed.selector
                );
            }
        }
    }

    function balanceOfSelf(Currency currency) internal view returns (uint256) {
        if (currency.isAddressZero()) {
            return address(this).balance;
        } else {
            return IERC20Minimal(Currency.unwrap(currency)).balanceOf(address(this));
        }
    }

    function balanceOf(Currency currency, address owner) internal view returns (uint256) {
        if (currency.isAddressZero()) {
            return owner.balance;
        } else {
            return IERC20Minimal(Currency.unwrap(currency)).balanceOf(owner);
        }
    }

    function isAddressZero(Currency currency) internal pure returns (bool) {
        return Currency.unwrap(currency) == Currency.unwrap(ADDRESS_ZERO);
    }

    function toId(Currency currency) internal pure returns (uint256) {
        return uint160(Currency.unwrap(currency));
    }

    // If the upper 12 bytes are non-zero, they will be zero-ed out
    // Therefore, fromId() and toId() are not inverses of each other
    function fromId(uint256 id) internal pure returns (Currency) {
        return Currency.wrap(address(uint160(id)));
    }
}
"
    },
    "@uniswap/v4-core/src/interfaces/IHooks.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {PoolKey} from "../types/PoolKey.sol";
import {BalanceDelta} from "../types/BalanceDelta.sol";
import {IPoolManager} from "./IPoolManager.sol";
import {BeforeSwapDelta} from "../types/BeforeSwapDelta.sol";

/// @notice V4 decides whether to invoke specific hooks by inspecting the least significant bits
/// of the address that the hooks contract is deployed to.
/// For example, a hooks contract deployed to address: 0x0000000000000000000000000000000000002400
/// has the lowest bits '10 0100 0000 0000' which would cause the 'before initialize' and 'after add liquidity' hooks to be used.
/// See the Hooks library for the full spec.
/// @dev Should only be callable by the v4 PoolManager.
interface IHooks {
    /// @notice The hook called before the state of a pool is initialized
    /// @param sender The initial msg.sender for the initialize call
    /// @param key The key for the pool being initialized
    /// @param sqrtPriceX96 The sqrt(price) of the pool as a Q64.96
    /// @return bytes4 The function selector for the hook
    function beforeInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96) external returns (bytes4);

    /// @notice The hook called after the state of a pool is initialized
    /// @param sender The initial msg.sender for the initialize call
    /// @param key The key for the pool being initialized
    /// @param sqrtPriceX96 The sqrt(price) of the pool as a Q64.96
    /// @param tick The current tick after the state of a pool is initialized
    /// @return bytes4 The function selector for the hook
    function afterInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96, int24 tick)
        external
        returns (bytes4);

    /// @notice The hook called before liquidity is added
    /// @param sender The initial msg.sender for the add liquidity call
    /// @param key The key for the pool
    /// @param params The parameters for adding liquidity
    /// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be passed on to the hook
    /// @return bytes4 The function selector for the hook
    function beforeAddLiquidity(
        address sender,
        PoolKey calldata key,
        IPoolManager.ModifyLiquidityParams calldata params,
        bytes calldata hookData
    ) external returns (bytes4);

    /// @notice The hook called after liquidity is added
    /// @param sender The initial msg.sender for the add liquidity call
    /// @param key The key for the pool
    /// @param params The parameters for adding liquidity
    /// @param delta The caller's balance delta after adding liquidity; the sum of principal delta, fees accrued, and hook delta
    /// @param feesAccrued The fees accrued since the last time fees were collected from this position
    /// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be passed on to the hook
    /// @return bytes4 The function selector for the hook
    /// @return BalanceDelta The hook's delta in token0 and token1. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
    function afterAddLiquidity(
        address sender,
        PoolKey calldata key,
        IPoolManager.ModifyLiquidityParams calldata params,
        BalanceDelta delta,
        BalanceDelta feesAccrued,
        bytes calldata hookData
    ) external returns (bytes4, BalanceDelta);

    /// @notice The hook called before liquidity is removed
    /// @param sender The initial msg.sender for the remove liquidity call
    /// @param key The key for the pool
    /// @param params The parameters for removing liquidity
    /// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be be passed on to the hook
    /// @return bytes4 The function selector for the hook
    function beforeRemoveLiquidity(
        address sender,
        PoolKey calldata key,
        IPoolManager.ModifyLiquidityParams calldata params,
        bytes calldata hookData
    ) external returns (bytes4);

    /// @notice The hook called after liquidity is removed
    /// @param sender The initial msg.sender for the remove liquidity 

Tags:
ERC20, Multisig, Mintable, Burnable, Swap, Liquidity, Upgradeable, Multi-Signature, Factory, Oracle|addr:0x71d865673e6c2e112cded381b38f7a958ea22d39|verified:true|block:23571729|tx:0xb4412005634057265b5a71c72fc16b0c142fa5c1dfa95b8281834a14b2bd837b|first_check:1760428249

Submitted on: 2025-10-14 09:50:49

Comments

Log in to comment.

No comments yet.