ETH Price: $2,843.29 (-3.25%)
 

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Block
From
To
Withdraw401409412025-12-30 5:07:0926 days ago1767071229IN
0xDd23dF0D...14De67988
0 ETH0.000000720.00150604
Collect And Conv...401409312025-12-30 5:06:4926 days ago1767071209IN
0xDd23dF0D...14De67988
0 ETH0.000000790.00154457
Withdraw368090262025-10-14 2:03:19104 days ago1760407399IN
0xDd23dF0D...14De67988
0 ETH0.000002630.0050769
Withdraw368089872025-10-14 2:02:01104 days ago1760407321IN
0xDd23dF0D...14De67988
0 ETH0.000003130.00495212
Deposit ETH368089482025-10-14 2:00:43104 days ago1760407243IN
0xDd23dF0D...14De67988
0.00003937 ETH0.00000150.00469552
Collect And Conv...365692312025-10-08 12:50:09109 days ago1759927809IN
0xDd23dF0D...14De67988
0 ETH0.000003470.00673908
Withdraw365521302025-10-08 3:20:07110 days ago1759893607IN
0xDd23dF0D...14De67988
0 ETH0.000002040.00431027
Withdraw365521242025-10-08 3:19:55110 days ago1759893595IN
0xDd23dF0D...14De67988
0 ETH0.00000270.00432977
Deposit ETH365519952025-10-08 3:15:37110 days ago1759893337IN
0xDd23dF0D...14De67988
0.00029803 ETH0.000001270.0042184
Collect And Conv...364303322025-10-05 7:40:11112 days ago1759650011IN
0xDd23dF0D...14De67988
0 ETH0.000001140.00240528
Withdraw364285892025-10-05 6:42:05112 days ago1759646525IN
0xDd23dF0D...14De67988
0 ETH0.000000970.00205549
Withdraw364285802025-10-05 6:41:47112 days ago1759646507IN
0xDd23dF0D...14De67988
0 ETH0.000001290.00208135
Deposit ETH364285732025-10-05 6:41:33112 days ago1759646493IN
0xDd23dF0D...14De67988
0.00010061 ETH0.000000660.00209499
Collect And Conv...364210282025-10-05 2:30:03113 days ago1759631403IN
0xDd23dF0D...14De67988
0 ETH0.000000810.0017597
Withdraw364169902025-10-05 0:15:27113 days ago1759623327IN
0xDd23dF0D...14De67988
0 ETH0.000001610.002615
Collect And Conv...363944202025-10-04 11:43:07113 days ago1759578187IN
0xDd23dF0D...14De67988
0 ETH0.000001470.00294625
Collect And Conv...363929622025-10-04 10:54:31113 days ago1759575271IN
0xDd23dF0D...14De67988
0 ETH0.000001210.00226055
Collect And Conv...362911932025-10-02 2:22:13116 days ago1759371733IN
0xDd23dF0D...14De67988
0 ETH0.000001920.00386865
Withdraw362602302025-10-01 9:10:07116 days ago1759309807IN
0xDd23dF0D...14De67988
0 ETH0.000004370.00891218
Collect And Conv...362601962025-10-01 9:08:59116 days ago1759309739IN
0xDd23dF0D...14De67988
0 ETH0.000005010.00933122
Collect And Conv...361447552025-09-28 17:00:57119 days ago1759078857IN
0xDd23dF0D...14De67988
0 ETH0.000001110.00226717
Withdraw361424232025-09-28 15:43:13119 days ago1759074193IN
0xDd23dF0D...14De67988
0 ETH0.000000850.00138675
Withdraw361421302025-09-28 15:33:27119 days ago1759073607IN
0xDd23dF0D...14De67988
0 ETH0.000000720.00136528
Update Global Fe...361419662025-09-28 15:27:59119 days ago1759073279IN
0xDd23dF0D...14De67988
0 ETH0.000000160.00137811
Withdraw361419462025-09-28 15:27:19119 days ago1759073239IN
0xDd23dF0D...14De67988
0 ETH0.000000950.0015829
View all transactions

Latest 25 internal transactions (View All)

Parent Transaction Hash Block From To
401409412025-12-30 5:07:0926 days ago1767071229
0xDd23dF0D...14De67988
0.00970794 ETH
401409412025-12-30 5:07:0926 days ago1767071229
0xDd23dF0D...14De67988
0.00970794 ETH
368090262025-10-14 2:03:19104 days ago1760407399
0xDd23dF0D...14De67988
0.1483767 ETH
368090262025-10-14 2:03:19104 days ago1760407399
0xDd23dF0D...14De67988
0.1483767 ETH
368089872025-10-14 2:02:01104 days ago1760407321
0xDd23dF0D...14De67988
0.00003936 ETH
368089872025-10-14 2:02:01104 days ago1760407321
0xDd23dF0D...14De67988
0.00003936 ETH
368089482025-10-14 2:00:43104 days ago1760407243
0xDd23dF0D...14De67988
0 ETH
368089482025-10-14 2:00:43104 days ago1760407243
0xDd23dF0D...14De67988
0 ETH
368089482025-10-14 2:00:43104 days ago1760407243
0xDd23dF0D...14De67988
0.00003937 ETH
365521302025-10-08 3:20:07110 days ago1759893607
0xDd23dF0D...14De67988
0.0001443 ETH
365521302025-10-08 3:20:07110 days ago1759893607
0xDd23dF0D...14De67988
0.0001443 ETH
365521242025-10-08 3:19:55110 days ago1759893595
0xDd23dF0D...14De67988
0.00029803 ETH
365521242025-10-08 3:19:55110 days ago1759893595
0xDd23dF0D...14De67988
0.00029803 ETH
365519952025-10-08 3:15:37110 days ago1759893337
0xDd23dF0D...14De67988
0.00029803 ETH
364285892025-10-05 6:42:05112 days ago1759646525
0xDd23dF0D...14De67988
0.00128795 ETH
364285892025-10-05 6:42:05112 days ago1759646525
0xDd23dF0D...14De67988
0.00128795 ETH
364285802025-10-05 6:41:47112 days ago1759646507
0xDd23dF0D...14De67988
0.00010061 ETH
364285802025-10-05 6:41:47112 days ago1759646507
0xDd23dF0D...14De67988
0.00010061 ETH
364285732025-10-05 6:41:33112 days ago1759646493
0xDd23dF0D...14De67988
0 ETH
364285732025-10-05 6:41:33112 days ago1759646493
0xDd23dF0D...14De67988
0 ETH
364285732025-10-05 6:41:33112 days ago1759646493
0xDd23dF0D...14De67988
0.00010061 ETH
364169902025-10-05 0:15:27113 days ago1759623327
0xDd23dF0D...14De67988
0.0087705 ETH
364169902025-10-05 0:15:27113 days ago1759623327
0xDd23dF0D...14De67988
0.0087705 ETH
362602302025-10-01 9:10:07116 days ago1759309807
0xDd23dF0D...14De67988
0.01025863 ETH
362602302025-10-01 9:10:07116 days ago1759309807
0xDd23dF0D...14De67988
0.01025863 ETH
View All Internal Transactions

Cross-Chain Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
DackieVault

Compiler Version
v0.8.28+commit.7893614a

Optimization Enabled:
Yes with 200 runs

Other Settings:
cancun EvmVersion
File 1 of 15 : DackieVault.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";

import "./interfaces/IDackieVault.sol";
import "./interfaces/IUniswapV3.sol";
import "./interfaces/IWETH.sol";

/**
 * @title DackieVault
 * @notice A vault for managing full-range V3 liquidity positions on WETH/DACKIE pair
 * with automatic fee collection and conversion to cbBTC
 */
contract DackieVault is IDackieVault, Ownable, ReentrancyGuard, Pausable, IERC721Receiver {
    using SafeERC20 for IERC20;

    /*//////////////////////////////////////////////////////////////
                               CONSTANTS
    //////////////////////////////////////////////////////////////*/

    /// @notice WETH token address on Base
    address public constant WETH = 0x4200000000000000000000000000000000000006;
    /// @notice DACKIE token address on Base
    address public constant DACKIE = 0x73326b4d0225c429bed050c11C4422d91470AaF4;
    /// @notice cbBTC token address on Base
    address public constant cbBTC = 0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf;
    /// @notice V3 fee tier (1%)
    uint24 public constant FEE_TIER = 10000;
    /// @notice Precision for fee accumulator calculations
    uint256 public constant PRECISION = 1e18;
    /// @notice Maximum treasury fee in basis points (50%)
    uint16 public constant MAX_TREASURY_FEE_BPS = 5000;
    /// @notice Maximum lock duration (~2 years)
    uint32 public constant MAX_LOCK_DURATION = 730 days;

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

    /// @notice V3 Position Manager
    INonfungiblePositionManager public immutable positionManager;
    /// @notice V3 Swap Router
    ISwapRouter public swapRouter;
    /// @notice V3 Factory
    IUniswapV3Factory public immutable factory;

    /// @notice Token ID of the shared V3 position (0 if no position exists)
    uint256 public tokenId;
    /// @notice Total liquidity in the shared position
    uint128 public totalLiquidity;

    /// @notice User position information including normal and locked liquidity
    struct UserPosition {
        uint128 normalLiquidity; // withdrawable anytime
        uint128 lockedLiquidity; // withdrawable only after lockedUntil
        uint64 lockedUntil; // unix timestamp when locked liquidity becomes withdrawable
        uint256 feeDebt0; // for per-user fee accounting
        uint256 feeDebt1; // for per-user fee accounting
        uint256 rewardDebt; // for DACKIE reward accounting
        uint256 lockMultiplier; // Q1e18 multiplier applied to locked liquidity (default 1e18)
    }

    /// @notice Track user positions
    mapping(address => UserPosition) private userPositions;

    /// @notice Fee accumulator for token0 (per unit of liquidity)
    uint256 public accFee0PerLiquidity;
    /// @notice Fee accumulator for token1 (per unit of liquidity)
    uint256 public accFee1PerLiquidity;

    /// @notice Swap path for WETH to cbBTC
    bytes public pathWETHToCbBTC;
    /// @notice Swap path for DACKIE to cbBTC
    bytes public pathDACKIEToCbBTC;

    /// @notice Tick spacing for the fee tier
    int24 public immutable tickSpacing;
    /// @notice Lower tick for full-range position
    int24 public immutable tickLower;
    /// @notice Upper tick for full-range position
    int24 public immutable tickUpper;

    /// @notice Whether token0 is WETH (true) or DACKIE (false)
    bool public immutable isWETHToken0;

    /// @notice Treasury address to receive fee split
    address public treasuryAddress;
    /// @notice Treasury fee in basis points (e.g., 2000 = 20%)
    uint16 public treasuryFeeBps;

    /*//////////////////////////////////////////////////////////////
                        DACKIE REWARD STATE VARIABLES
    //////////////////////////////////////////////////////////////*/

    /// @notice Latest reward period number
    uint256 public latestPeriodNumber;
    /// @notice Latest reward period start time
    uint256 public latestPeriodStartTime;
    /// @notice Latest reward period end time
    uint256 public latestPeriodEndTime;
    /// @notice DACKIE emission rate per second for current period
    uint256 public latestPeriodDACKIEPerSecond;
    /// @notice Accumulated DACKIE rewards per unit of reward weight
    uint256 public accDACKIERewardPerLiquidity;

    /// @notice Total reward weight across all users (normal + locked with multiplier)
    uint256 public totalRewardWeight;

    /// @notice Receiver contract for DACKIE harvesting
    address public receiver;

    /// @notice Precision for reward calculations
    uint256 public constant REWARD_PRECISION = 1e18;
    /// @notice Max lock multiplier (5.0x)
    uint256 public constant MAX_LOCK_MULTIPLIER = 5e18;
    /// @notice Minimum reward duration
    uint256 public constant MIN_REWARD_DURATION = 1 days;
    /// @notice Maximum reward duration
    uint256 public constant MAX_REWARD_DURATION = 30 days;

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Initialize the DackieVault
     * @param _positionManager Address of V3 Position Manager
     * @param _swapRouter Address of V3 Swap Router
     * @param _treasuryAddress Address to receive treasury fee split
     * @param _receiver Address of the receiver contract for DACKIE rewards
     */
    constructor(
        address _positionManager,
        address _swapRouter,
        address _treasuryAddress,
        address _receiver
    ) Ownable(msg.sender) {
        if (_positionManager == address(0) || _swapRouter == address(0)) {
            revert InvalidRouter();
        }

        positionManager = INonfungiblePositionManager(_positionManager);
        swapRouter = ISwapRouter(_swapRouter);
        factory = IUniswapV3Factory(positionManager.factory());

        // Get tick spacing for the fee tier
        tickSpacing = factory.feeAmountTickSpacing(FEE_TIER);

        // Ensure we have a valid tick spacing
        if (tickSpacing == 0) {
            tickSpacing = 200; // Default for 1% fee tier
        }

        // Calculate full-range ticks
        tickLower = (TickMath.MIN_TICK / tickSpacing) * tickSpacing;
        tickUpper = (TickMath.MAX_TICK / tickSpacing) * tickSpacing;

        // Determine token order
        isWETHToken0 = WETH < DACKIE;

        // Set default swap paths (direct routes for Base)
        // WETH -> cbBTC (assuming 0.3% fee pool exists)
        pathWETHToCbBTC = abi.encodePacked(WETH, uint24(500), cbBTC);
        // DACKIE -> WETH -> cbBTC (via 1% and 0.3% pools)
        pathDACKIEToCbBTC = abi.encodePacked(DACKIE, FEE_TIER, WETH, uint24(500), cbBTC);

        // Set treasury configuration
        if (_treasuryAddress == address(0)) {
            revert InvalidRouter(); // Reuse existing error for simplicity
        }
        treasuryAddress = _treasuryAddress;
        treasuryFeeBps = 2000; // Default 20%

        // Set receiver address
        if (_receiver == address(0)) {
            revert InvalidReceiver();
        }
        receiver = _receiver;

        // Approve tokens for position manager and swap router
        IERC20(WETH).approve(_positionManager, type(uint256).max);
        IERC20(DACKIE).approve(_positionManager, type(uint256).max);
        IERC20(WETH).approve(_swapRouter, type(uint256).max);
        IERC20(DACKIE).approve(_swapRouter, type(uint256).max);
    }

    /*//////////////////////////////////////////////////////////////
                               MODIFIERS
    //////////////////////////////////////////////////////////////*/

    modifier onlyReceiver() {
        if (msg.sender != receiver) revert InvalidReceiver();
        _;
    }

    /*//////////////////////////////////////////////////////////////
                            MAIN FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /**
     * @inheritdoc IDackieVault
     */
    function deposit(
        uint256 amountWETHDesired,
        uint256 amountDACKIEDesired,
        uint256 amountWETHMin,
        uint256 amountDACKIEMin,
        uint256 deadline
    ) external nonReentrant whenNotPaused {
        if (block.timestamp > deadline) revert DeadlineExpired();
        if (amountWETHDesired == 0 || amountDACKIEDesired == 0) revert ZeroAmount();

        // Transfer tokens from user
        IERC20(WETH).safeTransferFrom(msg.sender, address(this), amountWETHDesired);
        IERC20(DACKIE).safeTransferFrom(msg.sender, address(this), amountDACKIEDesired);

        (uint256 actualWETH, uint256 actualDACKIE) = _depositInternalNoRefund(
            amountWETHDesired,
            amountDACKIEDesired,
            amountWETHMin,
            amountDACKIEMin,
            deadline,
            false
        );

        // Refund unused tokens for regular deposit
        uint256 refundWETH = amountWETHDesired - actualWETH;
        uint256 refundDACKIE = amountDACKIEDesired - actualDACKIE;

        if (refundWETH > 0) {
            IERC20(WETH).safeTransfer(msg.sender, refundWETH);
        }
        if (refundDACKIE > 0) {
            IERC20(DACKIE).safeTransfer(msg.sender, refundDACKIE);
        }
    }

    /**
     * @notice Deposit ETH and DACKIE tokens to the vault (ETH version)
     * @param amountDACKIEDesired Amount of DACKIE tokens desired
     * @param amountETHMin Minimum ETH amount to use
     * @param amountDACKIEMin Minimum DACKIE amount to use
     * @param deadline Transaction deadline
     */
    function depositETH(
        uint256 amountDACKIEDesired,
        uint256 amountETHMin,
        uint256 amountDACKIEMin,
        uint256 deadline
    ) external payable nonReentrant whenNotPaused {
        if (block.timestamp > deadline) revert DeadlineExpired();
        if (msg.value == 0 || amountDACKIEDesired == 0) revert ZeroAmount();

        // Wrap ETH to WETH
        _wrapETH(msg.value);

        // Transfer DACKIE from user
        IERC20(DACKIE).safeTransferFrom(msg.sender, address(this), amountDACKIEDesired);

        // Call the internal deposit function (no refunds in internal)
        (uint256 actualWETH, uint256 actualDACKIE) = _depositInternalNoRefund(
            msg.value,
            amountDACKIEDesired,
            amountETHMin,
            amountDACKIEMin,
            deadline,
            false
        );

        // Refund excess ETH if any
        uint256 excessETH = msg.value - actualWETH;
        if (excessETH > 0) {
            // Unwrap excess WETH to ETH and send to user
            IWETH(WETH).withdraw(excessETH);
            (bool success, ) = payable(msg.sender).call{ value: excessETH }("");
            require(success, "ETH refund failed");
        }

        // Refund excess DACKIE if any
        uint256 excessDACKIE = amountDACKIEDesired - actualDACKIE;
        if (excessDACKIE > 0) {
            IERC20(DACKIE).safeTransfer(msg.sender, excessDACKIE);
        }
    }

    /**
     * @notice Internal deposit function shared by deposit and depositETH
     * @param amountWETHDesired Amount of WETH tokens desired
     * @param amountDACKIEDesired Amount of DACKIE tokens desired
     * @param amountWETHMin Minimum WETH amount to use
     * @param amountDACKIEMin Minimum DACKIE amount to use
     * @param deadline Transaction deadline
     * @param isLocked Whether this is a locked deposit
     */
    function _depositInternalNoRefund(
        uint256 amountWETHDesired,
        uint256 amountDACKIEDesired,
        uint256 amountWETHMin,
        uint256 amountDACKIEMin,
        uint256 deadline,
        bool isLocked
    ) internal returns (uint256 actualWETH, uint256 actualDACKIE) {
        // Update global fees and rewards before any state changes
        _updateGlobalFees();
        _updateDACKIERewards();

        uint128 liquidityAdded;
        uint256 amount0;
        uint256 amount1;

        if (tokenId == 0) {
            // First deposit - mint new position
            (uint256 amount0Desired, uint256 amount1Desired) = isWETHToken0
                ? (amountWETHDesired, amountDACKIEDesired)
                : (amountDACKIEDesired, amountWETHDesired);

            (uint256 amount0Min, uint256 amount1Min) = isWETHToken0
                ? (amountWETHMin, amountDACKIEMin)
                : (amountDACKIEMin, amountWETHMin);

            (tokenId, liquidityAdded, amount0, amount1) = positionManager.mint(
                INonfungiblePositionManager.MintParams({
                    token0: isWETHToken0 ? WETH : DACKIE,
                    token1: isWETHToken0 ? DACKIE : WETH,
                    fee: FEE_TIER,
                    tickLower: tickLower,
                    tickUpper: tickUpper,
                    amount0Desired: amount0Desired,
                    amount1Desired: amount1Desired,
                    amount0Min: amount0Min,
                    amount1Min: amount1Min,
                    recipient: address(this),
                    deadline: deadline
                })
            );
        } else {
            // Add to existing position
            (uint256 amount0Desired, uint256 amount1Desired) = isWETHToken0
                ? (amountWETHDesired, amountDACKIEDesired)
                : (amountDACKIEDesired, amountWETHDesired);

            (uint256 amount0Min, uint256 amount1Min) = isWETHToken0
                ? (amountWETHMin, amountDACKIEMin)
                : (amountDACKIEMin, amountWETHMin);

            (liquidityAdded, amount0, amount1) = positionManager.increaseLiquidity(
                INonfungiblePositionManager.IncreaseLiquidityParams({
                    tokenId: tokenId,
                    amount0Desired: amount0Desired,
                    amount1Desired: amount1Desired,
                    amount0Min: amount0Min,
                    amount1Min: amount1Min,
                    deadline: deadline
                })
            );
        }

        // Update user and total liquidity
        totalLiquidity += liquidityAdded;
        if (isLocked) {
            userPositions[msg.sender].lockedLiquidity += liquidityAdded;
        } else {
            userPositions[msg.sender].normalLiquidity += liquidityAdded;
        }

        // Update user fee debt to prevent fee crowding
        userPositions[msg.sender].feeDebt0 += (liquidityAdded * accFee0PerLiquidity) / PRECISION;
        userPositions[msg.sender].feeDebt1 += (liquidityAdded * accFee1PerLiquidity) / PRECISION;

        // Update reward weights and debt based on locked or normal
        if (isLocked) {
            // For locked path, weight is updated in _depositLockedInternal; do nothing here
        } else {
            // Normal deposit: add 1x weight
            uint256 weightDelta = liquidityAdded;
            totalRewardWeight += weightDelta;
            _updateUserRewardDebt(msg.sender, int256(weightDelta));
        }

        // Calculate actual amounts used
        (actualWETH, actualDACKIE) = isWETHToken0 ? (amount0, amount1) : (amount1, amount0);

        emit Deposit(msg.sender, liquidityAdded, actualWETH, actualDACKIE);
    }

    /**
     * @inheritdoc IDackieVault
     */
    function withdraw(
        uint256 liquidityToRemove,
        uint256 amountWETHMin,
        uint256 amountDACKIEMin,
        uint256 deadline
    ) external nonReentrant whenNotPaused {
        if (block.timestamp > deadline) revert DeadlineExpired();
        if (liquidityToRemove == 0) revert ZeroAmount();
        // Auto-unlock expired liquidity
        _autoUnlockExpired(msg.sender);

        if (liquidityToRemove > userPositions[msg.sender].normalLiquidity) revert InsufficientNormalLiquidity();

        // Update global fees and rewards before any state changes
        _updateGlobalFees();
        _updateDACKIERewards();

        // Auto-collect and convert pending fees to cbBTC before withdrawal
        (uint256 pendingWETH, uint256 pendingDACKIE) = pendingFees(msg.sender);
        if (pendingWETH > 0 || pendingDACKIE > 0) {
            _collectAndConvertFeesInternal(msg.sender, pendingWETH, pendingDACKIE, deadline);
        }

        // Decrease liquidity from position
        (uint256 amount0Min, uint256 amount1Min) = isWETHToken0
            ? (amountWETHMin, amountDACKIEMin)
            : (amountDACKIEMin, amountWETHMin);

        (uint256 amount0, uint256 amount1) = positionManager.decreaseLiquidity(
            INonfungiblePositionManager.DecreaseLiquidityParams({
                tokenId: tokenId,
                liquidity: uint128(liquidityToRemove),
                amount0Min: amount0Min,
                amount1Min: amount1Min,
                deadline: deadline
            })
        );

        // Collect the principal amounts
        (uint256 collected0, uint256 collected1) = positionManager.collect(
            INonfungiblePositionManager.CollectParams({
                tokenId: tokenId,
                recipient: address(this),
                amount0Max: uint128(amount0),
                amount1Max: uint128(amount1)
            })
        );

        // Update state
        totalLiquidity -= uint128(liquidityToRemove);
        userPositions[msg.sender].normalLiquidity -= uint128(liquidityToRemove);

        // Update user fee debt
        uint256 feeDebtReduction0 = (liquidityToRemove * accFee0PerLiquidity) / PRECISION;
        uint256 feeDebtReduction1 = (liquidityToRemove * accFee1PerLiquidity) / PRECISION;

        // Handle underflow protection
        if (userPositions[msg.sender].feeDebt0 > feeDebtReduction0) {
            userPositions[msg.sender].feeDebt0 -= feeDebtReduction0;
        } else {
            userPositions[msg.sender].feeDebt0 = 0;
        }

        if (userPositions[msg.sender].feeDebt1 > feeDebtReduction1) {
            userPositions[msg.sender].feeDebt1 -= feeDebtReduction1;
        } else {
            userPositions[msg.sender].feeDebt1 = 0;
        }

        // Update reward weight and reward debt after liquidity removal (normal = 1x)
        if (liquidityToRemove > 0) {
            uint256 weightDeltaNeg = liquidityToRemove; // 1x
            if (totalRewardWeight >= weightDeltaNeg) totalRewardWeight -= weightDeltaNeg;
            else totalRewardWeight = 0;
            _updateUserRewardDebt(msg.sender, -int256(weightDeltaNeg));
        }

        // Transfer principal back to user
        (uint256 withdrawnWETH, uint256 withdrawnDACKIE) = isWETHToken0
            ? (collected0, collected1)
            : (collected1, collected0);

        // Unwrap WETH to ETH and send to user
        _unwrapAndSendETH(msg.sender, withdrawnWETH);
        IERC20(DACKIE).safeTransfer(msg.sender, withdrawnDACKIE);

        emit Withdraw(msg.sender, uint128(liquidityToRemove), withdrawnWETH, withdrawnDACKIE);
    }

    /**
     * @notice Emergency withdraw without fee collection (faster, gas-efficient)
     * @param liquidityToRemove Amount of liquidity to remove
     * @param amountWETHMin Minimum WETH to receive
     * @param amountDACKIEMin Minimum DACKIE to receive
     * @param deadline Transaction deadline
     */
    function withdrawEmergency(
        uint256 liquidityToRemove,
        uint256 amountWETHMin,
        uint256 amountDACKIEMin,
        uint256 deadline
    ) external nonReentrant {
        if (block.timestamp > deadline) revert DeadlineExpired();
        if (liquidityToRemove == 0) revert ZeroAmount();
        // Auto-unlock expired liquidity
        _autoUnlockExpired(msg.sender);

        if (liquidityToRemove > userPositions[msg.sender].normalLiquidity) revert InsufficientNormalLiquidity();

        // Update global fees before any state changes (but don't collect)
        _updateGlobalFees();
        _updateDACKIERewards();

        // Decrease liquidity from position
        (uint256 amount0Min, uint256 amount1Min) = isWETHToken0
            ? (amountWETHMin, amountDACKIEMin)
            : (amountDACKIEMin, amountWETHMin);

        (uint256 amount0, uint256 amount1) = positionManager.decreaseLiquidity(
            INonfungiblePositionManager.DecreaseLiquidityParams({
                tokenId: tokenId,
                liquidity: uint128(liquidityToRemove),
                amount0Min: amount0Min,
                amount1Min: amount1Min,
                deadline: deadline
            })
        );

        // Collect the principal amounts
        (uint256 collected0, uint256 collected1) = positionManager.collect(
            INonfungiblePositionManager.CollectParams({
                tokenId: tokenId,
                recipient: address(this),
                amount0Max: uint128(amount0),
                amount1Max: uint128(amount1)
            })
        );

        // Update state
        totalLiquidity -= uint128(liquidityToRemove);
        userPositions[msg.sender].normalLiquidity -= uint128(liquidityToRemove);

        // Update user fee debt
        uint256 feeDebtReduction0 = (liquidityToRemove * accFee0PerLiquidity) / PRECISION;
        uint256 feeDebtReduction1 = (liquidityToRemove * accFee1PerLiquidity) / PRECISION;

        // Handle underflow protection
        if (userPositions[msg.sender].feeDebt0 > feeDebtReduction0) {
            userPositions[msg.sender].feeDebt0 -= feeDebtReduction0;
        } else {
            userPositions[msg.sender].feeDebt0 = 0;
        }

        if (userPositions[msg.sender].feeDebt1 > feeDebtReduction1) {
            userPositions[msg.sender].feeDebt1 -= feeDebtReduction1;
        } else {
            userPositions[msg.sender].feeDebt1 = 0;
        }

        // Update reward weight and reward debt after liquidity removal (normal = 1x)
        if (liquidityToRemove > 0) {
            uint256 weightDeltaNeg = liquidityToRemove; // 1x
            if (totalRewardWeight >= weightDeltaNeg) totalRewardWeight -= weightDeltaNeg;
            else totalRewardWeight = 0;
            _updateUserRewardDebt(msg.sender, -int256(weightDeltaNeg));
        }

        // Transfer principal back to user
        (uint256 withdrawnWETH, uint256 withdrawnDACKIE) = isWETHToken0
            ? (collected0, collected1)
            : (collected1, collected0);

        // Unwrap WETH to ETH and send to user
        _unwrapAndSendETH(msg.sender, withdrawnWETH);
        IERC20(DACKIE).safeTransfer(msg.sender, withdrawnDACKIE);

        emit Withdraw(msg.sender, uint128(liquidityToRemove), withdrawnWETH, withdrawnDACKIE);
    }

    /**
     * @inheritdoc IDackieVault
     */
    function collectAndConvertFees(
        uint256 minWETH,
        uint256 minDACKIE,
        uint256 minCbBTC,
        uint256 deadline
    ) external nonReentrant whenNotPaused returns (uint256 cbBTCSentToUser, uint256 cbBTCSentToTreasury) {
        if (block.timestamp > deadline) revert DeadlineExpired();

        // Ensure expired locks are unlocked before reward/fee calculations
        _autoUnlockExpired(msg.sender);

        // Update global fees and rewards to get latest
        _updateGlobalFees();
        _updateDACKIERewards();

        // Try to distribute DACKIE rewards (with rollback protection)
        _distributeDACKIEReward(msg.sender);

        // Calculate user's pending fees
        (uint256 pendingWETH, uint256 pendingDACKIE) = pendingFees(msg.sender);

        // Check minimum fee requirements
        if (minWETH > 0 && pendingWETH < minWETH) revert InsufficientFees();
        if (minDACKIE > 0 && pendingDACKIE < minDACKIE) revert InsufficientFees();

        if (pendingWETH == 0 && pendingDACKIE == 0) {
            return (0, 0);
        }

        // Update user fee debt
        uint128 userLiq = userPositions[msg.sender].normalLiquidity + userPositions[msg.sender].lockedLiquidity;
        userPositions[msg.sender].feeDebt0 = (userLiq * accFee0PerLiquidity) / PRECISION;
        userPositions[msg.sender].feeDebt1 = (userLiq * accFee1PerLiquidity) / PRECISION;

        // Get actual fee amounts based on token order
        (uint256 feeWETH, uint256 feeDACKIE) = isWETHToken0
            ? (pendingWETH, pendingDACKIE)
            : (pendingDACKIE, pendingWETH);

        uint256 totalCbBTC = 0;

        // Swap WETH fees to cbBTC
        if (feeWETH > 0) {
            totalCbBTC += _swapToCbBTC(WETH, feeWETH, pathWETHToCbBTC, deadline);
        }

        // Swap DACKIE fees to cbBTC
        if (feeDACKIE > 0) {
            totalCbBTC += _swapToCbBTC(DACKIE, feeDACKIE, pathDACKIEToCbBTC, deadline);
        }

        if (totalCbBTC < minCbBTC) revert SlippageExceeded();

        // Calculate treasury split
        cbBTCSentToTreasury = (treasuryAddress != address(0) && treasuryFeeBps > 0)
            ? (totalCbBTC * treasuryFeeBps) / 10000
            : 0;
        cbBTCSentToUser = totalCbBTC - cbBTCSentToTreasury;

        // Transfer cbBTC to treasury and user
        if (cbBTCSentToTreasury > 0) {
            IERC20(cbBTC).safeTransfer(treasuryAddress, cbBTCSentToTreasury);
        }
        if (cbBTCSentToUser > 0) {
            IERC20(cbBTC).safeTransfer(msg.sender, cbBTCSentToUser);
        }

        emit FeesCollected(msg.sender, feeWETH, feeDACKIE);
        emit FeesConverted(msg.sender, feeWETH, feeDACKIE, cbBTCSentToUser, cbBTCSentToTreasury);

        return (cbBTCSentToUser, cbBTCSentToTreasury);
    }

    /**
     * @inheritdoc IDackieVault
     */
    function depositLocked(
        uint256 amountWETHDesired,
        uint256 amountDACKIEDesired,
        uint256 amountWETHMin,
        uint256 amountDACKIEMin,
        uint256 deadline,
        uint32 lockDuration
    ) external nonReentrant whenNotPaused {
        if (block.timestamp > deadline) revert DeadlineExpired();
        if (amountWETHDesired == 0 || amountDACKIEDesired == 0) revert ZeroAmount();

        // Calculate lock expiry
        uint64 newLockedUntil = _calculateLockExpiry(userPositions[msg.sender].lockedUntil, lockDuration);

        // Transfer tokens from user
        IERC20(WETH).safeTransferFrom(msg.sender, address(this), amountWETHDesired);
        IERC20(DACKIE).safeTransferFrom(msg.sender, address(this), amountDACKIEDesired);

        (uint256 actualWETH, uint256 actualDACKIE) = _depositLockedInternal(
            amountWETHDesired,
            amountDACKIEDesired,
            amountWETHMin,
            amountDACKIEMin,
            deadline,
            newLockedUntil
        );

        // Refund unused tokens for regular locked deposit
        uint256 refundWETH = amountWETHDesired - actualWETH;
        uint256 refundDACKIE = amountDACKIEDesired - actualDACKIE;

        if (refundWETH > 0) {
            IERC20(WETH).safeTransfer(msg.sender, refundWETH);
        }
        if (refundDACKIE > 0) {
            IERC20(DACKIE).safeTransfer(msg.sender, refundDACKIE);
        }
    }

    /**
     * @notice Deposit ETH and DACKIE tokens to the vault with lock (ETH version)
     * @param amountDACKIEDesired Amount of DACKIE tokens desired
     * @param amountETHMin Minimum ETH amount to use
     * @param amountDACKIEMin Minimum DACKIE amount to use
     * @param deadline Transaction deadline
     * @param lockDuration Lock duration in seconds
     */
    function depositLockedETH(
        uint256 amountDACKIEDesired,
        uint256 amountETHMin,
        uint256 amountDACKIEMin,
        uint256 deadline,
        uint32 lockDuration
    ) external payable nonReentrant whenNotPaused {
        if (block.timestamp > deadline) revert DeadlineExpired();
        if (msg.value == 0 || amountDACKIEDesired == 0) revert ZeroAmount();

        // Calculate lock expiry
        uint64 newLockedUntil = _calculateLockExpiry(userPositions[msg.sender].lockedUntil, lockDuration);

        // Wrap ETH to WETH
        _wrapETH(msg.value);

        // Transfer DACKIE from user
        IERC20(DACKIE).safeTransferFrom(msg.sender, address(this), amountDACKIEDesired);

        (uint256 actualWETH, uint256 actualDACKIE) = _depositLockedInternal(
            msg.value,
            amountDACKIEDesired,
            amountETHMin,
            amountDACKIEMin,
            deadline,
            newLockedUntil
        );

        // Refund excess ETH if any
        uint256 excessETH = msg.value - actualWETH;
        if (excessETH > 0) {
            // Unwrap excess WETH to ETH and send to user
            IWETH(WETH).withdraw(excessETH);
            (bool success, ) = payable(msg.sender).call{ value: excessETH }("");
            require(success, "ETH refund failed");
        }

        // Refund excess DACKIE if any
        uint256 excessDACKIE = amountDACKIEDesired - actualDACKIE;
        if (excessDACKIE > 0) {
            IERC20(DACKIE).safeTransfer(msg.sender, excessDACKIE);
        }
    }

    /**
     * @notice Internal locked deposit function
     * @param amountWETHDesired Amount of WETH tokens desired
     * @param amountDACKIEDesired Amount of DACKIE tokens desired
     * @param amountWETHMin Minimum WETH amount to use
     * @param amountDACKIEMin Minimum DACKIE amount to use
     * @param deadline Transaction deadline
     * @param newLockedUntil New lock expiry timestamp
     */
    function _depositLockedInternal(
        uint256 amountWETHDesired,
        uint256 amountDACKIEDesired,
        uint256 amountWETHMin,
        uint256 amountDACKIEMin,
        uint256 deadline,
        uint64 newLockedUntil
    ) internal returns (uint256 actualWETH, uint256 actualDACKIE) {
        // Update global fees before any state changes
        _updateGlobalFees();
        _updateDACKIERewards();

        uint128 liquidityAdded;
        uint256 amount0;
        uint256 amount1;

        // Snapshot existing locked state and multiplier BEFORE mutation
        UserPosition storage u = userPositions[msg.sender];
        uint128 prevLocked = u.lockedLiquidity;
        uint256 oldMultiplier = u.lockMultiplier == 0 ? REWARD_PRECISION : u.lockMultiplier;

        if (tokenId == 0) {
            // First deposit - mint new position
            (uint256 amount0Desired, uint256 amount1Desired) = isWETHToken0
                ? (amountWETHDesired, amountDACKIEDesired)
                : (amountDACKIEDesired, amountWETHDesired);

            (uint256 amount0Min, uint256 amount1Min) = isWETHToken0
                ? (amountWETHMin, amountDACKIEMin)
                : (amountDACKIEMin, amountWETHMin);

            (tokenId, liquidityAdded, amount0, amount1) = positionManager.mint(
                INonfungiblePositionManager.MintParams({
                    token0: isWETHToken0 ? WETH : DACKIE,
                    token1: isWETHToken0 ? DACKIE : WETH,
                    fee: FEE_TIER,
                    tickLower: tickLower,
                    tickUpper: tickUpper,
                    amount0Desired: amount0Desired,
                    amount1Desired: amount1Desired,
                    amount0Min: amount0Min,
                    amount1Min: amount1Min,
                    recipient: address(this),
                    deadline: deadline
                })
            );
        } else {
            // Add to existing position
            (uint256 amount0Desired, uint256 amount1Desired) = isWETHToken0
                ? (amountWETHDesired, amountDACKIEDesired)
                : (amountDACKIEDesired, amountWETHDesired);

            (uint256 amount0Min, uint256 amount1Min) = isWETHToken0
                ? (amountWETHMin, amountDACKIEMin)
                : (amountDACKIEMin, amountWETHMin);

            (liquidityAdded, amount0, amount1) = positionManager.increaseLiquidity(
                INonfungiblePositionManager.IncreaseLiquidityParams({
                    tokenId: tokenId,
                    amount0Desired: amount0Desired,
                    amount1Desired: amount1Desired,
                    amount0Min: amount0Min,
                    amount1Min: amount1Min,
                    deadline: deadline
                })
            );
        }

        // Update user and total liquidity
        totalLiquidity += liquidityAdded;
        u.lockedLiquidity = uint128(uint256(u.lockedLiquidity) + liquidityAdded);
        u.lockedUntil = newLockedUntil;

        // Update user fee debt to prevent fee crowding
        userPositions[msg.sender].feeDebt0 += (liquidityAdded * accFee0PerLiquidity) / PRECISION;
        userPositions[msg.sender].feeDebt1 += (liquidityAdded * accFee1PerLiquidity) / PRECISION;

        // Calculate multiplier and update totalRewardWeight/rewardDebt by weight delta
        uint256 newMultiplier = _calcLockMultiplier(uint32(newLockedUntil - uint64(block.timestamp)));
        // Increase weight for EXISTING locked position (exclude newly added) if multiplier grows
        if (prevLocked > 0 && newMultiplier > oldMultiplier) {
            uint256 bonusDelta = (uint256(prevLocked) * (newMultiplier - oldMultiplier)) / REWARD_PRECISION;
            if (bonusDelta > 0) {
                totalRewardWeight += bonusDelta;
                _updateUserRewardDebt(msg.sender, int256(bonusDelta));
            }
        }
        // Add weight for newly added liquidity with newMultiplier
        uint256 weightDeltaNew = (uint256(liquidityAdded) * newMultiplier) / REWARD_PRECISION;
        if (weightDeltaNew > 0) {
            totalRewardWeight += weightDeltaNew;
            _updateUserRewardDebt(msg.sender, int256(weightDeltaNew));
        }
        u.lockMultiplier = newMultiplier;

        // Calculate actual amounts used
        (actualWETH, actualDACKIE) = isWETHToken0 ? (amount0, amount1) : (amount1, amount0);

        emit Deposit(msg.sender, liquidityAdded, actualWETH, actualDACKIE);
        emit LiquidityLocked(msg.sender, liquidityAdded, newLockedUntil);
    }

    /**
     * @inheritdoc IDackieVault
     */
    function convertToLocked(uint128 liquidityToConvert, uint32 lockDuration) external nonReentrant whenNotPaused {
        if (liquidityToConvert == 0) revert ZeroAmount();
        if (liquidityToConvert > userPositions[msg.sender].normalLiquidity) revert InsufficientNormalLiquidity();

        // Update global fees before any state changes
        _updateGlobalFees();
        _updateDACKIERewards();

        // Calculate new lock expiry (use current state before mutation)
        uint64 oldLockedUntilSnap = userPositions[msg.sender].lockedUntil;
        uint64 newLockedUntil = _calculateLockExpiry(oldLockedUntilSnap, lockDuration);

        // Move liquidity from normal to locked
        userPositions[msg.sender].normalLiquidity -= liquidityToConvert;
        userPositions[msg.sender].lockedLiquidity += liquidityToConvert;
        userPositions[msg.sender].lockedUntil = newLockedUntil;

        // Adjust weights: remove normal weight, add locked weight with multiplier
        UserPosition storage u = userPositions[msg.sender];
        uint256 newMultiplier = _calcLockMultiplier(uint32(newLockedUntil - uint64(block.timestamp)));
        uint256 multiplier = newMultiplier;
        uint256 removeWeight = uint256(liquidityToConvert); // normal = 1x
        uint256 addWeight = (uint256(liquidityToConvert) * multiplier) / REWARD_PRECISION;
        if (addWeight >= removeWeight) {
            uint256 delta = addWeight - removeWeight;
            if (delta > 0) {
                totalRewardWeight += delta;
                _updateUserRewardDebt(msg.sender, int256(delta));
            }
        } else {
            uint256 deltaNeg = removeWeight - addWeight;
            if (deltaNeg > 0) {
                if (totalRewardWeight >= deltaNeg) totalRewardWeight -= deltaNeg;
                else totalRewardWeight = 0;
                _updateUserRewardDebt(msg.sender, -int256(deltaNeg));
            }
        }
        u.lockMultiplier = multiplier;

        emit LiquidityConvertedToLocked(msg.sender, liquidityToConvert, newLockedUntil);
    }

    /**
     * @inheritdoc IDackieVault
     */
    function increaseLockedLiquidity(
        uint256 amountWETHDesired,
        uint256 amountDACKIEDesired,
        uint256 amountWETHMin,
        uint256 amountDACKIEMin,
        uint256 deadline
    ) external nonReentrant whenNotPaused {
        if (block.timestamp > deadline) revert DeadlineExpired();
        if (amountWETHDesired == 0 || amountDACKIEDesired == 0) revert ZeroAmount();
        // Check if user has locked liquidity and lock is not expired
        UserPosition storage u = userPositions[msg.sender];
        if (u.lockedLiquidity == 0) revert ZeroAmount();
        if (block.timestamp >= u.lockedUntil) revert LockExpired();

        // Update global fees before any state changes
        _updateGlobalFees();
        _updateDACKIERewards();

        // Transfer tokens from user
        IERC20(WETH).safeTransferFrom(msg.sender, address(this), amountWETHDesired);
        IERC20(DACKIE).safeTransferFrom(msg.sender, address(this), amountDACKIEDesired);

        // Add to existing position (similar to regular deposit)
        (uint256 amount0Desired, uint256 amount1Desired) = isWETHToken0
            ? (amountWETHDesired, amountDACKIEDesired)
            : (amountDACKIEDesired, amountWETHDesired);

        (uint256 amount0Min, uint256 amount1Min) = isWETHToken0
            ? (amountWETHMin, amountDACKIEMin)
            : (amountDACKIEMin, amountWETHMin);

        (uint128 liquidityAdded, uint256 amount0, uint256 amount1) = positionManager.increaseLiquidity(
            INonfungiblePositionManager.IncreaseLiquidityParams({
                tokenId: tokenId,
                amount0Desired: amount0Desired,
                amount1Desired: amount1Desired,
                amount0Min: amount0Min,
                amount1Min: amount1Min,
                deadline: deadline
            })
        );

        // Update user and total liquidity (do not change lockedUntil)
        totalLiquidity += liquidityAdded;
        userPositions[msg.sender].lockedLiquidity += liquidityAdded;

        // Update user fee debt to prevent fee crowding
        userPositions[msg.sender].feeDebt0 += (liquidityAdded * accFee0PerLiquidity) / PRECISION;
        userPositions[msg.sender].feeDebt1 += (liquidityAdded * accFee1PerLiquidity) / PRECISION;

        // Update totalRewardWeight and user reward debt by weight delta at current multiplier
        uint256 multiplier = u.lockMultiplier == 0 ? REWARD_PRECISION : u.lockMultiplier;
        uint256 weightDelta = (uint256(liquidityAdded) * multiplier) / REWARD_PRECISION;
        if (weightDelta > 0) {
            totalRewardWeight += weightDelta;
            _updateUserRewardDebt(msg.sender, int256(weightDelta));
        }

        // Calculate actual amounts used
        (uint256 actualWETH, uint256 actualDACKIE) = isWETHToken0 ? (amount0, amount1) : (amount1, amount0);

        // Refund unused tokens
        uint256 refundWETH = amountWETHDesired - actualWETH;
        uint256 refundDACKIE = amountDACKIEDesired - actualDACKIE;

        if (refundWETH > 0) {
            IERC20(WETH).safeTransfer(msg.sender, refundWETH);
        }
        if (refundDACKIE > 0) {
            IERC20(DACKIE).safeTransfer(msg.sender, refundDACKIE);
        }

        emit Deposit(msg.sender, liquidityAdded, actualWETH, actualDACKIE);
        emit LockedLiquidityIncreased(msg.sender, liquidityAdded, userPositions[msg.sender].lockedUntil);
    }

    /**
     * @inheritdoc IDackieVault
     */
    function extendLockDuration(uint32 lockDuration) external nonReentrant whenNotPaused {
        _extendLockDurationMerged(lockDuration);
    }

    function _extendLockDurationMerged(uint32 lockDuration) internal {
        UserPosition storage u = userPositions[msg.sender];
        if (u.lockedLiquidity == 0) revert ZeroAmount();

        // Update global fees before any state changes
        _updateGlobalFees();
        _updateDACKIERewards();

        uint64 oldLockedUntil = u.lockedUntil;

        if (block.timestamp >= oldLockedUntil) {
            // Expired path: behave like previous extendExpiredLock
            if (lockDuration == 0 || lockDuration > MAX_LOCK_DURATION) {
                revert InvalidLockDuration();
            }

            uint64 newLockedUntil = uint64(block.timestamp) + uint64(lockDuration);
            if (newLockedUntil > uint64(block.timestamp) + uint64(MAX_LOCK_DURATION)) {
                newLockedUntil = uint64(block.timestamp) + uint64(MAX_LOCK_DURATION);
            }

            // Remove any old bonus weight that might remain after expiry (multiplier > 1e18)
            uint256 oldMultiplier = u.lockMultiplier == 0 ? REWARD_PRECISION : u.lockMultiplier;
            if (oldMultiplier > REWARD_PRECISION && u.lockedLiquidity > 0) {
                uint256 oldBonus = (uint256(u.lockedLiquidity) * (oldMultiplier - REWARD_PRECISION)) / REWARD_PRECISION;
                if (oldBonus > 0) {
                    if (totalRewardWeight >= oldBonus) totalRewardWeight -= oldBonus;
                    else totalRewardWeight = 0;
                    _updateUserRewardDebt(msg.sender, -int256(oldBonus));
                }
            }

            // Compute and apply new multiplier/bonus for the renewed lock
            uint256 newMultiplier = _calcLockMultiplier(uint32(newLockedUntil - uint64(block.timestamp)));
            if (u.lockedLiquidity > 0) {
                uint256 newBonus = (uint256(u.lockedLiquidity) * (newMultiplier - REWARD_PRECISION)) / REWARD_PRECISION;
                if (newBonus > 0) {
                    totalRewardWeight += newBonus;
                    _updateUserRewardDebt(msg.sender, int256(newBonus));
                }
            }

            u.lockMultiplier = newMultiplier;
            u.lockedUntil = newLockedUntil;

            emit ExpiredLockExtended(msg.sender, newLockedUntil);
        } else {
            // Active lock path: behave like previous extendLockDuration
            uint64 newLockedUntil = _calculateLockExpiry(oldLockedUntil, lockDuration);

            uint256 oldMultiplier2 = u.lockMultiplier == 0 ? REWARD_PRECISION : u.lockMultiplier;
            uint256 newMultiplier2 = _calcLockMultiplier(uint32(newLockedUntil - uint64(block.timestamp)));
            if (u.lockedLiquidity > 0 && newMultiplier2 > oldMultiplier2) {
                uint256 bonusDelta = (uint256(u.lockedLiquidity) * (newMultiplier2 - oldMultiplier2)) /
                    REWARD_PRECISION;
                if (bonusDelta > 0) {
                    totalRewardWeight += bonusDelta;
                    _updateUserRewardDebt(msg.sender, int256(bonusDelta));
                }
            }
            u.lockMultiplier = newMultiplier2;
            u.lockedUntil = newLockedUntil;

            emit LockDurationExtended(msg.sender, oldLockedUntil, newLockedUntil);
        }
    }

    /**
     * @inheritdoc IDackieVault
     */
    function increaseLockedLiquidityAndExtend(
        uint256 amountWETHDesired,
        uint256 amountDACKIEDesired,
        uint256 amountWETHMin,
        uint256 amountDACKIEMin,
        uint256 deadline,
        uint32 lockDuration
    ) external nonReentrant whenNotPaused {
        if (block.timestamp > deadline) revert DeadlineExpired();
        if (amountWETHDesired == 0 || amountDACKIEDesired == 0) revert ZeroAmount();
        if (userPositions[msg.sender].lockedLiquidity == 0) revert ZeroAmount();

        // Calculate new lock expiry (use current state before mutation)
        uint64 oldLockedUntilSnap = userPositions[msg.sender].lockedUntil;
        uint64 newLockedUntil = _calculateLockExpiry(oldLockedUntilSnap, lockDuration);

        // Update global fees before any state changes
        _updateGlobalFees();
        _updateDACKIERewards();

        // Transfer tokens from user
        IERC20(WETH).safeTransferFrom(msg.sender, address(this), amountWETHDesired);
        IERC20(DACKIE).safeTransferFrom(msg.sender, address(this), amountDACKIEDesired);

        // Add to existing position
        (uint256 amount0Desired, uint256 amount1Desired) = isWETHToken0
            ? (amountWETHDesired, amountDACKIEDesired)
            : (amountDACKIEDesired, amountWETHDesired);

        (uint256 amount0Min, uint256 amount1Min) = isWETHToken0
            ? (amountWETHMin, amountDACKIEMin)
            : (amountDACKIEMin, amountWETHMin);

        (uint128 liquidityAdded, uint256 amount0, uint256 amount1) = positionManager.increaseLiquidity(
            INonfungiblePositionManager.IncreaseLiquidityParams({
                tokenId: tokenId,
                amount0Desired: amount0Desired,
                amount1Desired: amount1Desired,
                amount0Min: amount0Min,
                amount1Min: amount1Min,
                deadline: deadline
            })
        );

        // Snapshot existing locked state and multiplier BEFORE mutation
        UserPosition storage u = userPositions[msg.sender];
        uint128 prevLocked = u.lockedLiquidity;
        uint256 oldMultiplier = u.lockMultiplier == 0 ? REWARD_PRECISION : u.lockMultiplier;

        // Update user and total liquidity
        totalLiquidity += liquidityAdded;
        u.lockedLiquidity += liquidityAdded;

        // Update user fee debt to prevent fee crowding
        userPositions[msg.sender].feeDebt0 += (liquidityAdded * accFee0PerLiquidity) / PRECISION;
        userPositions[msg.sender].feeDebt1 += (liquidityAdded * accFee1PerLiquidity) / PRECISION;

        // Compute new multiplier
        uint256 newMultiplier = _calcLockMultiplier(uint32(newLockedUntil - uint64(block.timestamp)));

        // Handle expired vs active lock for EXISTING locked portion
        if (block.timestamp >= oldLockedUntilSnap) {
            // Expired: remove old bonus entirely then add new bonus from base 1e18
            if (oldMultiplier > REWARD_PRECISION && prevLocked > 0) {
                uint256 oldBonus = (uint256(prevLocked) * (oldMultiplier - REWARD_PRECISION)) / REWARD_PRECISION;
                if (oldBonus > 0) {
                    if (totalRewardWeight >= oldBonus) totalRewardWeight -= oldBonus;
                    else totalRewardWeight = 0;
                    _updateUserRewardDebt(msg.sender, -int256(oldBonus));
                }
            }
            if (prevLocked > 0 && newMultiplier > REWARD_PRECISION) {
                uint256 newBonus = (uint256(prevLocked) * (newMultiplier - REWARD_PRECISION)) / REWARD_PRECISION;
                if (newBonus > 0) {
                    totalRewardWeight += newBonus;
                    _updateUserRewardDebt(msg.sender, int256(newBonus));
                }
            }
        } else {
            // Active: only add delta when multiplier increases
            if (prevLocked > 0 && newMultiplier > oldMultiplier) {
                uint256 bonusDelta = (uint256(prevLocked) * (newMultiplier - oldMultiplier)) / REWARD_PRECISION;
                if (bonusDelta > 0) {
                    totalRewardWeight += bonusDelta;
                    _updateUserRewardDebt(msg.sender, int256(bonusDelta));
                }
            }
        }

        // Add weight for newly added liquidity with new multiplier
        uint256 weightDeltaNew = (uint256(liquidityAdded) * newMultiplier) / REWARD_PRECISION;
        if (weightDeltaNew > 0) {
            totalRewardWeight += weightDeltaNew;
            _updateUserRewardDebt(msg.sender, int256(weightDeltaNew));
        }

        // Update lock params at the end
        u.lockMultiplier = newMultiplier;
        u.lockedUntil = newLockedUntil;

        // Calculate actual amounts used
        (uint256 actualWETH, uint256 actualDACKIE) = isWETHToken0 ? (amount0, amount1) : (amount1, amount0);

        // Refund unused tokens
        uint256 refundWETH = amountWETHDesired - actualWETH;
        uint256 refundDACKIE = amountDACKIEDesired - actualDACKIE;

        if (refundWETH > 0) {
            IERC20(WETH).safeTransfer(msg.sender, refundWETH);
        }
        if (refundDACKIE > 0) {
            IERC20(DACKIE).safeTransfer(msg.sender, refundDACKIE);
        }

        emit Deposit(msg.sender, liquidityAdded, actualWETH, actualDACKIE);
        emit LockedLiquidityIncreasedAndExtended(msg.sender, liquidityAdded, newLockedUntil);
    }

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

    /**
     * @inheritdoc IDackieVault
     */
    function pendingFees(address user) public view returns (uint256 pendingWETH, uint256 pendingDACKIE) {
        UserPosition storage userPos = userPositions[user];
        uint256 userLiq = uint256(userPos.normalLiquidity) + uint256(userPos.lockedLiquidity);
        if (userLiq == 0) return (0, 0);

        uint256 accrued0 = (userLiq * accFee0PerLiquidity) / PRECISION;
        uint256 accrued1 = (userLiq * accFee1PerLiquidity) / PRECISION;

        uint256 pending0 = accrued0 >= userPos.feeDebt0 ? accrued0 - userPos.feeDebt0 : 0;
        uint256 pending1 = accrued1 >= userPos.feeDebt1 ? accrued1 - userPos.feeDebt1 : 0;

        (pendingWETH, pendingDACKIE) = isWETHToken0 ? (pending0, pending1) : (pending1, pending0);
    }

    /**
     * @inheritdoc IDackieVault
     */
    function position() external view returns (uint256, uint128) {
        return (tokenId, totalLiquidity);
    }

    /**
     * @inheritdoc IDackieVault
     */
    function userInfo(
        address user
    )
        external
        view
        returns (
            uint128 normalLiquidity,
            uint128 lockedLiquidity,
            uint64 lockedUntil,
            uint256 pendingWETH,
            uint256 pendingDACKIE
        )
    {
        UserPosition storage userPos = userPositions[user];
        (pendingWETH, pendingDACKIE) = pendingFees(user);
        return (userPos.normalLiquidity, userPos.lockedLiquidity, userPos.lockedUntil, pendingWETH, pendingDACKIE);
    }

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

    /**
     * @inheritdoc IDackieVault
     */
    function setSwapPaths(bytes calldata pathWETHToCbBTC_, bytes calldata pathDACKIEToCbBTC_) external onlyOwner {
        if (pathWETHToCbBTC_.length == 0 || pathDACKIEToCbBTC_.length == 0) {
            revert InvalidPath();
        }
        pathWETHToCbBTC = pathWETHToCbBTC_;
        pathDACKIEToCbBTC = pathDACKIEToCbBTC_;

        emit SwapPathsUpdated(pathWETHToCbBTC_, pathDACKIEToCbBTC_);
    }

    /**
     * @inheritdoc IDackieVault
     */
    function setSwapRouter(address swapRouter_) external onlyOwner {
        if (swapRouter_ == address(0)) revert InvalidRouter();

        // Revoke old approvals
        IERC20(WETH).approve(address(swapRouter), 0);
        IERC20(DACKIE).approve(address(swapRouter), 0);

        swapRouter = ISwapRouter(swapRouter_);

        // Approve new router
        IERC20(WETH).approve(swapRouter_, type(uint256).max);
        IERC20(DACKIE).approve(swapRouter_, type(uint256).max);

        emit SwapRouterUpdated(swapRouter_);
    }

    /**
     * @notice Pause the contract (emergency function)
     */
    function pause() external onlyOwner {
        _pause();
    }

    /**
     * @notice Unpause the contract
     */
    function unpause() external onlyOwner {
        _unpause();
    }

    /**
     * @notice Set treasury address for fee splitting
     * @param _treasury New treasury address
     */
    function setTreasuryAddress(address _treasury) external onlyOwner {
        if (_treasury == address(0)) {
            revert InvalidRouter(); // Reuse existing error
        }
        treasuryAddress = _treasury;
        emit TreasuryUpdated(treasuryAddress, treasuryFeeBps);
    }

    /**
     * @notice Set treasury fee in basis points
     * @param _bps New treasury fee in basis points
     */
    function setTreasuryFeeBps(uint16 _bps) external onlyOwner {
        if (_bps > MAX_TREASURY_FEE_BPS) {
            revert InvalidRouter(); // Reuse existing error
        }
        treasuryFeeBps = _bps;
        emit TreasuryUpdated(treasuryAddress, treasuryFeeBps);
    }

    /**
     * @notice Emergency function to rescue tokens (not core tokens if user shares exist)
     * @param token Token address to rescue
     * @param recipient Address to send tokens to
     */
    function rescueTokens(address token, address recipient) external onlyOwner {
        // Don't allow rescuing core tokens if users have shares
        if (totalLiquidity > 0) {
            require(token != WETH && token != DACKIE && token != cbBTC, "Cannot rescue core tokens");
        }

        IERC20(token).safeTransfer(recipient, IERC20(token).balanceOf(address(this)));
    }

    /*//////////////////////////////////////////////////////////////
                        LOCK EXPIRY FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /**
     * @inheritdoc IDackieVault
     */
    function unlockExpired() external {
        _autoUnlockExpired(msg.sender);
    }

    /**
     * @notice Update global fee accumulators by syncing with V3 position
     * @dev Can be called by anyone to ensure fees are up-to-date
     */
    function updateGlobalFees() external {
        _updateGlobalFees();
    }

    /**
     * @inheritdoc IDackieVault
     */
    function convertExpiredLockedToNormal() external nonReentrant {
        UserPosition storage userPos = userPositions[msg.sender];
        if (userPos.lockedLiquidity == 0) revert ZeroAmount();
        if (block.timestamp < userPos.lockedUntil) revert LiquidityStillLocked();

        // Update global fees before any state changes
        _updateGlobalFees();
        _updateDACKIERewards();

        // Auto-collect and convert pending fees to cbBTC before conversion
        (uint256 pendingWETH, uint256 pendingDACKIE) = pendingFees(msg.sender);
        if (pendingWETH > 0 || pendingDACKIE > 0) {
            _collectAndConvertFeesInternal(msg.sender, pendingWETH, pendingDACKIE, block.timestamp + 600);
        }

        uint128 liquidityToConvert = userPos.lockedLiquidity;
        // Adjust totalRewardWeight by removing bonus weight of locked portion
        uint256 multiplier = userPos.lockMultiplier == 0 ? REWARD_PRECISION : userPos.lockMultiplier;
        if (multiplier > REWARD_PRECISION) {
            uint256 bonusWeight = (uint256(liquidityToConvert) * (multiplier - REWARD_PRECISION)) / REWARD_PRECISION;
            if (bonusWeight > 0) {
                if (totalRewardWeight >= bonusWeight) {
                    totalRewardWeight -= bonusWeight;
                } else {
                    totalRewardWeight = 0;
                }
                _updateUserRewardDebt(msg.sender, -int256(bonusWeight));
            }
        }
        userPos.normalLiquidity += liquidityToConvert;
        userPos.lockedLiquidity = 0;
        userPos.lockedUntil = 0;
        userPos.lockMultiplier = REWARD_PRECISION;

        emit ExpiredLockedConvertedToNormal(msg.sender, liquidityToConvert);
    }

    /**
     * @inheritdoc IDackieVault
     */
    // extendExpiredLock removed. Use extendLockDuration for both expired and active locks.

    /*//////////////////////////////////////////////////////////////
                        DACKIE REWARD FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /**
     * @inheritdoc IDackieVault
     */
    function upkeep(uint256 amount, uint256 duration) external onlyReceiver {
        if (amount == 0) revert ZeroAmount();
        if (duration < MIN_REWARD_DURATION || duration > MAX_REWARD_DURATION) {
            revert InvalidRewardDuration();
        }

        // Pull DACKIE from the receiver (receiver must approve the vault beforehand)
        IERC20(DACKIE).safeTransferFrom(msg.sender, address(this), amount);

        // Update reward state before any calculations
        _updateDACKIERewards();

        uint256 currentTime = block.timestamp;
        uint256 endTime = currentTime + duration;
        uint256 dackiePerSecond;
        uint256 totalAmount = amount;

        // Handle remaining rewards from previous period
        if (latestPeriodEndTime > currentTime) {
            uint256 remainingTime = latestPeriodEndTime - currentTime;
            uint256 remainingRewards = (remainingTime * latestPeriodDACKIEPerSecond) / REWARD_PRECISION;
            totalAmount += remainingRewards;
        }

        // Calculate new emission rate
        dackiePerSecond = (totalAmount * REWARD_PRECISION) / duration;

        // Update period state
        latestPeriodNumber++;
        latestPeriodStartTime = currentTime;
        latestPeriodEndTime = endTime;
        latestPeriodDACKIEPerSecond = dackiePerSecond;

        emit DACKIERewardPeriodStarted(latestPeriodNumber, currentTime, endTime, dackiePerSecond, totalAmount);
    }

    /**
     * @inheritdoc IDackieVault
     */
    function pendingDACKIEReward(address user) external view returns (uint256 pending) {
        UserPosition storage userPos = userPositions[user];
        uint256 userWeight = _userRewardWeight(user);

        if (userWeight == 0) return 0;

        // Calculate what the accumulated reward per liquidity would be if updated now
        uint256 currentAccDACKIERewardPerLiquidity = accDACKIERewardPerLiquidity;

        // Add rewards that would be accumulated if _updateDACKIERewards was called
        if (totalRewardWeight > 0 && latestPeriodStartTime < block.timestamp && latestPeriodStartTime > 0) {
            uint256 effectiveEndTime = block.timestamp > latestPeriodEndTime ? latestPeriodEndTime : block.timestamp;
            uint256 timeElapsed = effectiveEndTime - latestPeriodStartTime;
            uint256 rewardToDistribute = (timeElapsed * latestPeriodDACKIEPerSecond) / REWARD_PRECISION;
            currentAccDACKIERewardPerLiquidity += (rewardToDistribute * REWARD_PRECISION) / totalRewardWeight;
        }

        // Calculate pending rewards based on liquidity proportion
        uint256 accumulatedReward = (userWeight * currentAccDACKIERewardPerLiquidity) / REWARD_PRECISION;
        if (accumulatedReward >= userPos.rewardDebt) {
            pending = accumulatedReward - userPos.rewardDebt;
        } else {
            pending = 0;
        }
    }

    /**
     * @inheritdoc IDackieVault
     */
    function getLatestRewardPeriod()
        external
        view
        returns (uint256 periodNumber, uint256 startTime, uint256 endTime, uint256 dackiePerSecond)
    {
        return (latestPeriodNumber, latestPeriodStartTime, latestPeriodEndTime, latestPeriodDACKIEPerSecond);
    }

    /**
     * @inheritdoc IDackieVault
     */
    function setReceiver(address _receiver) external onlyOwner {
        if (_receiver == address(0)) revert InvalidReceiver();
        receiver = _receiver;
        emit ReceiverUpdated(_receiver);
    }

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

    /**
     * @notice Update global fee accumulators by collecting all fees from the position
     */
    function _updateGlobalFees() internal {
        if (tokenId == 0 || totalLiquidity == 0) return;

        // Collect all fees from the position
        (uint256 collected0, uint256 collected1) = positionManager.collect(
            INonfungiblePositionManager.CollectParams({
                tokenId: tokenId,
                recipient: address(this),
                amount0Max: type(uint128).max,
                amount1Max: type(uint128).max
            })
        );

        // Update accumulators with newly collected fees
        if (collected0 > 0) {
            accFee0PerLiquidity += (collected0 * PRECISION) / totalLiquidity;
        }
        if (collected1 > 0) {
            accFee1PerLiquidity += (collected1 * PRECISION) / totalLiquidity;
        }
    }

    /**
     * @notice Swap tokens to cbBTC using the configured path
     * @param amountIn Amount of input token
     * @param path Swap path
     * @return amountOut Amount of cbBTC received
     */
    function _swapToCbBTC(
        address /* tokenIn */,
        uint256 amountIn,
        bytes memory path,
        uint256 /* deadline */
    ) internal returns (uint256 amountOut) {
        try
            swapRouter.exactInput(
                ISwapRouter.ExactInputParams({
                    path: path,
                    recipient: address(this),
                    amountIn: amountIn,
                    amountOutMinimum: 0 // We handle slippage at the total level
                })
            )
        returns (uint256 output) {
            return output;
        } catch {
            revert SwapFailed();
        }
    }

    /**
     * @notice Auto-unlock expired locked liquidity for a user
     * @param user Address of the user
     */
    function _autoUnlockExpired(address user) internal {
        UserPosition storage userPos = userPositions[user];
        if (userPos.lockedLiquidity > 0 && block.timestamp >= userPos.lockedUntil) {
            // Before changing weights, update rewards
            _updateDACKIERewards();
            // Adjust totalRewardWeight by removing bonus weight of locked portion
            uint256 multiplier = userPos.lockMultiplier == 0 ? REWARD_PRECISION : userPos.lockMultiplier;
            if (multiplier > REWARD_PRECISION) {
                uint256 bonusWeight = (uint256(userPos.lockedLiquidity) * (multiplier - REWARD_PRECISION)) /
                    REWARD_PRECISION;
                if (bonusWeight > 0) {
                    // Reduce totalRewardWeight and rewardDebt accordingly
                    if (totalRewardWeight >= bonusWeight) {
                        totalRewardWeight -= bonusWeight;
                    } else {
                        totalRewardWeight = 0;
                    }
                    _updateUserRewardDebt(user, -int256(bonusWeight));
                }
            }
            uint128 liquidityToUnlock = userPos.lockedLiquidity;
            userPos.normalLiquidity += liquidityToUnlock;
            userPos.lockedLiquidity = 0;
            userPos.lockedUntil = 0;
            userPos.lockMultiplier = REWARD_PRECISION;

            emit LockedLiquidityUnlocked(user, liquidityToUnlock);
        }
    }

    /**
     * @notice Calculate new lock expiry timestamp
     * @param currentExpiry Current lock expiry (0 if no lock)
     * @param lockDuration Additional lock duration in seconds
     * @return newExpiry New lock expiry timestamp
     */
    function _calculateLockExpiry(uint64 currentExpiry, uint32 lockDuration) internal view returns (uint64 newExpiry) {
        if (lockDuration == 0 || lockDuration > MAX_LOCK_DURATION) {
            revert InvalidLockDuration();
        }

        uint64 newFromNow = uint64(block.timestamp) + uint64(lockDuration);
        if (newFromNow > uint64(block.timestamp) + uint64(MAX_LOCK_DURATION)) {
            newFromNow = uint64(block.timestamp) + uint64(MAX_LOCK_DURATION);
        }

        return currentExpiry > newFromNow ? currentExpiry : newFromNow;
    }

    /**
     * @notice Calculate lock multiplier based on duration (linear 1.0x → 5.0x)
     */
    function _calcLockMultiplier(uint32 lockDuration) internal pure returns (uint256) {
        uint256 base = REWARD_PRECISION; // 1e18
        if (lockDuration == 0) return base;
        if (lockDuration >= MAX_LOCK_DURATION) return MAX_LOCK_MULTIPLIER;
        uint256 bonus = ((MAX_LOCK_MULTIPLIER - base) * lockDuration) / MAX_LOCK_DURATION;
        return base + bonus;
    }

    /**
     * @notice Compute user's reward weight (normal 1x + locked * multiplier)
     */
    function _userRewardWeight(address user) internal view returns (uint256) {
        UserPosition storage u = userPositions[user];
        uint256 multiplier = u.lockMultiplier == 0 ? REWARD_PRECISION : u.lockMultiplier;
        return uint256(u.normalLiquidity) + (uint256(u.lockedLiquidity) * multiplier) / REWARD_PRECISION;
    }

    /**
     * @notice Update DACKIE reward accumulators based on time elapsed
     */
    function _updateDACKIERewards() internal {
        if (latestPeriodStartTime == 0) return;

        uint256 currentTime = block.timestamp;
        if (currentTime <= latestPeriodStartTime) return;

        uint256 effectiveEndTime = currentTime > latestPeriodEndTime ? latestPeriodEndTime : currentTime;
        uint256 effectiveTimeElapsed = effectiveEndTime - latestPeriodStartTime;

        if (effectiveTimeElapsed > 0) {
            // Only update accumulator if there's reward weight to distribute to
            if (totalRewardWeight > 0) {
                uint256 rewardToDistribute = (effectiveTimeElapsed * latestPeriodDACKIEPerSecond) / REWARD_PRECISION;
                accDACKIERewardPerLiquidity += (rewardToDistribute * REWARD_PRECISION) / totalRewardWeight;
            }
            // Always update start time to track elapsed time, even if no liquidity
            latestPeriodStartTime = effectiveEndTime;
        }
    }

    /**
     * @notice Distribute DACKIE rewards to user with rollback protection
     * @param user User address
     * @return success Whether the reward distribution was successful
     */
    function _distributeDACKIEReward(address user) internal returns (bool success) {
        UserPosition storage userPos = userPositions[user];
        uint256 userWeight = _userRewardWeight(user);

        if (userWeight == 0) {
            return true;
        }

        // Calculate pending rewards with underflow clamp
        uint256 accumulatedReward = (userWeight * accDACKIERewardPerLiquidity) / REWARD_PRECISION;
        uint256 pending;
        if (accumulatedReward >= userPos.rewardDebt) {
            pending = accumulatedReward - userPos.rewardDebt;
        } else {
            pending = 0;
        }

        if (pending > 0) {
            // Update reward debt first
            userPos.rewardDebt = accumulatedReward;
            IERC20(DACKIE).safeTransfer(user, pending);
            emit DACKIERewardDistributed(user, pending);
            return true;
        }

        // Update reward debt even if no pending rewards
        userPos.rewardDebt = accumulatedReward;
        return true;
    }

    /**
     * @notice Update user reward debt after liquidity changes
     * @param user User address
     * @param liquidityDelta Change in liquidity (positive for increase, negative for decrease)
     */
    function _updateUserRewardDebt(address user, int256 liquidityDelta) internal {
        UserPosition storage userPos = userPositions[user];

        if (liquidityDelta > 0) {
            // Increase weight - add to reward debt to prevent reward farming
            uint256 increase = (uint256(liquidityDelta) * accDACKIERewardPerLiquidity) / REWARD_PRECISION;
            userPos.rewardDebt += increase;
        } else if (liquidityDelta < 0) {
            // Decrease weight - reduce reward debt proportionally
            uint256 rewardDebtReduction = (uint256(-liquidityDelta) * accDACKIERewardPerLiquidity) / REWARD_PRECISION;
            if (userPos.rewardDebt > rewardDebtReduction) {
                userPos.rewardDebt -= rewardDebtReduction;
            } else {
                userPos.rewardDebt = 0;
            }
        }
    }

    /**
     * @notice Internal function to collect and convert fees to cbBTC
     * @param user User address
     * @param pendingWETH Amount of pending WETH fees
     * @param pendingDACKIE Amount of pending DACKIE fees
     * @param deadline Transaction deadline
     */
    function _collectAndConvertFeesInternal(
        address user,
        uint256 pendingWETH,
        uint256 pendingDACKIE,
        uint256 deadline
    ) internal {
        if (pendingWETH == 0 && pendingDACKIE == 0) return;

        // Try to distribute DACKIE rewards (with rollback protection)
        _distributeDACKIEReward(user);

        // Update user fee debt
        uint128 userLiq = userPositions[user].normalLiquidity + userPositions[user].lockedLiquidity;
        userPositions[user].feeDebt0 = (userLiq * accFee0PerLiquidity) / PRECISION;
        userPositions[user].feeDebt1 = (userLiq * accFee1PerLiquidity) / PRECISION;

        // Get actual fee amounts based on token order
        (uint256 feeWETH, uint256 feeDACKIE) = isWETHToken0
            ? (pendingWETH, pendingDACKIE)
            : (pendingDACKIE, pendingWETH);

        uint256 totalCbBTC = 0;

        // Swap WETH fees to cbBTC
        if (feeWETH > 0) {
            totalCbBTC += _swapToCbBTC(WETH, feeWETH, pathWETHToCbBTC, deadline);
        }

        // Swap DACKIE fees to cbBTC
        if (feeDACKIE > 0) {
            totalCbBTC += _swapToCbBTC(DACKIE, feeDACKIE, pathDACKIEToCbBTC, deadline);
        }

        if (totalCbBTC > 0) {
            // Calculate treasury split
            uint256 cbBTCSentToTreasury = (treasuryAddress != address(0) && treasuryFeeBps > 0)
                ? (totalCbBTC * treasuryFeeBps) / 10000
                : 0;
            uint256 cbBTCSentToUser = totalCbBTC - cbBTCSentToTreasury;

            // Transfer cbBTC to treasury and user
            if (cbBTCSentToTreasury > 0) {
                IERC20(cbBTC).safeTransfer(treasuryAddress, cbBTCSentToTreasury);
            }
            if (cbBTCSentToUser > 0) {
                IERC20(cbBTC).safeTransfer(user, cbBTCSentToUser);
            }

            emit FeesCollected(user, feeWETH, feeDACKIE);
            emit FeesConverted(user, feeWETH, feeDACKIE, cbBTCSentToUser, cbBTCSentToTreasury);
        }
    }

    /**
     * @notice Handle receipt of NFT from position manager
     */
    function onERC721Received(address, address, uint256, bytes calldata) external pure override returns (bytes4) {
        return IERC721Receiver.onERC721Received.selector;
    }

    /**
     * @notice Receive ETH function to accept ETH deposits
     */
    receive() external payable {
        // Allow receiving ETH for wrapping purposes
    }

    /**
     * @notice Wrap ETH to WETH
     * @param amount Amount of ETH to wrap
     */
    function _wrapETH(uint256 amount) internal {
        if (amount > 0) {
            IWETH(WETH).deposit{ value: amount }();
        }
    }

    /**
     * @notice Unwrap WETH to ETH and send to recipient
     * @param recipient Address to receive ETH
     * @param amount Amount of WETH to unwrap
     */
    function _unwrapAndSendETH(address recipient, uint256 amount) internal {
        if (amount > 0) {
            IWETH(WETH).withdraw(amount);
            (bool success, ) = payable(recipient).call{ value: amount }("");
            require(success, "ETH transfer failed");
        }
    }
}

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

pragma solidity ^0.8.20;

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

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

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

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

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

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

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

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

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

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

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

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

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

pragma solidity ^0.8.20;

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

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

    uint256 private _status;

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

    constructor() {
        _status = NOT_ENTERED;
    }

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

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

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

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

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

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

pragma solidity ^0.8.20;

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

/**
 * @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());
    }
}

// SPDX-License-Identifier: MIT
// 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);
}

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

pragma solidity ^0.8.20;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

File 7 of 15 : IERC721Receiver.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC721/IERC721Receiver.sol)

pragma solidity >=0.5.0;

/**
 * @title ERC-721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC-721 asset contracts.
 */
interface IERC721Receiver {
    /**
     * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
     * by `operator` from `from`, this function is called.
     *
     * It must return its Solidity selector to confirm the token transfer.
     * If any other value is returned or the interface is not implemented by the recipient, the transfer will be
     * reverted.
     *
     * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
     */
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
}

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

/**
 * @title IDackieVault
 * @notice Interface for DackieVault - a vault for managing full-range Uniswap V3 liquidity
 * positions on WETH/DACKIE pair with automatic fee collection and conversion to cbBTC
 */
interface IDackieVault {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Emitted when a user deposits liquidity into the vault
     * @param user The address of the user making the deposit
     * @param liquidity The amount of liquidity added
     * @param amountWETH The amount of WETH deposited
     * @param amountDACKIE The amount of DACKIE deposited
     */
    event Deposit(address indexed user, uint128 liquidity, uint256 amountWETH, uint256 amountDACKIE);

    /**
     * @notice Emitted when a user withdraws liquidity from the vault
     * @param user The address of the user making the withdrawal
     * @param liquidity The amount of liquidity removed
     * @param amountWETH The amount of WETH withdrawn
     * @param amountDACKIE The amount of DACKIE withdrawn
     */
    event Withdraw(address indexed user, uint128 liquidity, uint256 amountWETH, uint256 amountDACKIE);

    /**
     * @notice Emitted when fees are collected for a user
     * @param user The address of the user collecting fees
     * @param amountWETH The amount of WETH fees collected
     * @param amountDACKIE The amount of DACKIE fees collected
     */
    event FeesCollected(address indexed user, uint256 amountWETH, uint256 amountDACKIE);

    /**
     * @notice Emitted when fees are converted to cbBTC and split
     * @param user The address of the user for whom fees were converted
     * @param amountWETHIn The amount of WETH converted
     * @param amountDACKIEIn The amount of DACKIE converted
     * @param amountCbBTCUser The amount of cbBTC sent to user
     * @param amountCbBTCTreasury The amount of cbBTC sent to treasury
     */
    event FeesConverted(
        address indexed user,
        uint256 amountWETHIn,
        uint256 amountDACKIEIn,
        uint256 amountCbBTCUser,
        uint256 amountCbBTCTreasury
    );

    /**
     * @notice Emitted when treasury configuration is updated
     * @param treasury The new treasury address
     * @param feeBps The new treasury fee in basis points
     */
    event TreasuryUpdated(address indexed treasury, uint16 feeBps);

    /**
     * @notice Emitted when swap paths are updated
     * @param pathWETH New path for WETH to cbBTC swaps
     * @param pathDACKIE New path for DACKIE to cbBTC swaps
     */
    event SwapPathsUpdated(bytes pathWETH, bytes pathDACKIE);

    /**
     * @notice Emitted when the swap router is updated
     * @param router New swap router address
     */
    event SwapRouterUpdated(address indexed router);

    /**
     * @notice Emitted when liquidity is locked
     * @param user The address of the user locking liquidity
     * @param liquidityAdded The amount of liquidity locked
     * @param lockedUntil The timestamp when the liquidity can be unlocked
     */
    event LiquidityLocked(address indexed user, uint128 liquidityAdded, uint64 lockedUntil);

    /**
     * @notice Emitted when normal liquidity is converted to locked
     * @param user The address of the user converting liquidity
     * @param liquidityConverted The amount of liquidity converted
     * @param lockedUntil The timestamp when the liquidity can be unlocked
     */
    event LiquidityConvertedToLocked(address indexed user, uint128 liquidityConverted, uint64 lockedUntil);

    /**
     * @notice Emitted when locked liquidity is unlocked
     * @param user The address of the user whose liquidity was unlocked
     * @param liquidityUnlocked The amount of liquidity unlocked
     */
    event LockedLiquidityUnlocked(address indexed user, uint128 liquidityUnlocked);

    /**
     * @notice Emitted when locked liquidity is increased
     * @param user The address of the user increasing locked liquidity
     * @param liquidityAdded The amount of liquidity added
     * @param lockedUntil The timestamp when the liquidity can be unlocked
     */
    event LockedLiquidityIncreased(address indexed user, uint128 liquidityAdded, uint64 lockedUntil);

    /**
     * @notice Emitted when lock duration is extended
     * @param user The address of the user extending the lock
     * @param oldLockedUntil The previous lock expiry timestamp
     * @param newLockedUntil The new lock expiry timestamp
     */
    event LockDurationExtended(address indexed user, uint64 oldLockedUntil, uint64 newLockedUntil);

    /**
     * @notice Emitted when locked liquidity is increased and duration extended
     * @param user The address of the user
     * @param liquidityAdded The amount of liquidity added
     * @param newLockedUntil The new lock expiry timestamp
     */
    event LockedLiquidityIncreasedAndExtended(address indexed user, uint128 liquidityAdded, uint64 newLockedUntil);

    /**
     * @notice Emitted when expired locked liquidity is converted to normal
     * @param user The address of the user
     * @param liquidityConverted The amount of liquidity converted
     */
    event ExpiredLockedConvertedToNormal(address indexed user, uint128 liquidityConverted);

    /**
     * @notice Emitted when expired lock is extended
     * @param user The address of the user
     * @param newLockedUntil The new lock expiry timestamp
     */
    event ExpiredLockExtended(address indexed user, uint64 newLockedUntil);

    /**
     * @notice Emitted when a new DACKIE reward period starts
     * @param periodNumber The period number
     * @param startTime Period start timestamp
     * @param endTime Period end timestamp
     * @param dackiePerSecond DACKIE emission rate per second
     * @param totalAmount Total DACKIE amount for the period
     */
    event DACKIERewardPeriodStarted(
        uint256 indexed periodNumber,
        uint256 startTime,
        uint256 endTime,
        uint256 dackiePerSecond,
        uint256 totalAmount
    );

    /**
     * @notice Emitted when DACKIE rewards are distributed to a user
     * @param user The address of the user receiving rewards
     * @param amount The amount of DACKIE rewards distributed
     */
    event DACKIERewardDistributed(address indexed user, uint256 amount);

    /**
     * @notice Emitted when the receiver address is updated
     * @param receiver The new receiver address
     */
    event ReceiverUpdated(address indexed receiver);

    /**
     * @notice Emitted during emergency DACKIE withdrawal
     * @param recipient The address receiving the emergency withdrawal
     * @param amount The amount of DACKIE withdrawn
     */
    event DACKIEEmergencyWithdraw(address indexed recipient, uint256 amount);

    /*//////////////////////////////////////////////////////////////
                                STRUCTS
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Information about a user's position in the vault
     * @param normalLiquidity The user's normal liquidity (withdrawable anytime)
     * @param lockedLiquidity The user's locked liquidity (withdrawable after lockedUntil)
     * @param lockedUntil Timestamp when locked liquidity becomes withdrawable
     * @param pendingWETH Pending WETH fees to be collected
     * @param pendingDACKIE Pending DACKIE fees to be collected
     */
    struct UserInfo {
        uint128 normalLiquidity;
        uint128 lockedLiquidity;
        uint64 lockedUntil;
        uint256 pendingWETH;
        uint256 pendingDACKIE;
    }

    /**
     * @notice Information about the vault's shared position
     * @param tokenId The Uniswap V3 NFT token ID (0 if no position exists)
     * @param liquidity Total liquidity in the shared position
     */
    struct PositionInfo {
        uint256 tokenId;
        uint128 liquidity;
    }

    /*//////////////////////////////////////////////////////////////
                                ERRORS
    //////////////////////////////////////////////////////////////*/

    error InsufficientLiquidity();
    error SlippageExceeded();
    error DeadlineExpired();
    error InsufficientFees();
    error SwapFailed();
    error InvalidPath();
    error ZeroAmount();
    error InvalidRouter();
    error InvalidLockDuration();
    error InsufficientNormalLiquidity();
    error LiquidityStillLocked();
    error LockExpired();
    error InvalidRewardDuration();
    error InvalidReceiver();
    error NoRewardsAvailable();
    error RewardPeriodExpired();

    /*//////////////////////////////////////////////////////////////
                            MAIN FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Deposit tokens to add liquidity to the vault
     * @param amountWETHDesired Desired amount of WETH to deposit
     * @param amountDACKIEDesired Desired amount of DACKIE to deposit
     * @param amountWETHMin Minimum amount of WETH to deposit (slippage protection)
     * @param amountDACKIEMin Minimum amount of DACKIE to deposit (slippage protection)
     * @param deadline Transaction deadline
     */
    function deposit(
        uint256 amountWETHDesired,
        uint256 amountDACKIEDesired,
        uint256 amountWETHMin,
        uint256 amountDACKIEMin,
        uint256 deadline
    ) external;

    /**
     * @notice Deposit tokens as locked liquidity to the vault
     * @param amountWETHDesired Desired amount of WETH to deposit
     * @param amountDACKIEDesired Desired amount of DACKIE to deposit
     * @param amountWETHMin Minimum amount of WETH to deposit (slippage protection)
     * @param amountDACKIEMin Minimum amount of DACKIE to deposit (slippage protection)
     * @param deadline Transaction deadline
     * @param lockDuration Duration to lock the liquidity in seconds
     */
    function depositLocked(
        uint256 amountWETHDesired,
        uint256 amountDACKIEDesired,
        uint256 amountWETHMin,
        uint256 amountDACKIEMin,
        uint256 deadline,
        uint32 lockDuration
    ) external;

    /**
     * @notice Convert normal liquidity to locked liquidity
     * @param liquidityToConvert Amount of normal liquidity to convert
     * @param lockDuration Duration to lock the liquidity in seconds
     */
    function convertToLocked(uint128 liquidityToConvert, uint32 lockDuration) external;

    /**
     * @notice Increase locked liquidity without changing lock duration
     * @param amountWETHDesired Desired amount of WETH to deposit
     * @param amountDACKIEDesired Desired amount of DACKIE to deposit
     * @param amountWETHMin Minimum amount of WETH to deposit (slippage protection)
     * @param amountDACKIEMin Minimum amount of DACKIE to deposit (slippage protection)
     * @param deadline Transaction deadline
     */
    function increaseLockedLiquidity(
        uint256 amountWETHDesired,
        uint256 amountDACKIEDesired,
        uint256 amountWETHMin,
        uint256 amountDACKIEMin,
        uint256 deadline
    ) external;

    /**
     * @notice Extend lock duration without changing liquidity
     * @param lockDuration Additional duration to extend the lock in seconds
     */
    function extendLockDuration(uint32 lockDuration) external;

    /**
     * @notice Increase locked liquidity and extend lock duration
     * @param amountWETHDesired Desired amount of WETH to deposit
     * @param amountDACKIEDesired Desired amount of DACKIE to deposit
     * @param amountWETHMin Minimum amount of WETH to deposit (slippage protection)
     * @param amountDACKIEMin Minimum amount of DACKIE to deposit (slippage protection)
     * @param deadline Transaction deadline
     * @param lockDuration Duration to extend the lock in seconds
     */
    function increaseLockedLiquidityAndExtend(
        uint256 amountWETHDesired,
        uint256 amountDACKIEDesired,
        uint256 amountWETHMin,
        uint256 amountDACKIEMin,
        uint256 deadline,
        uint32 lockDuration
    ) external;

    /**
     * @notice Unlock expired locked liquidity
     */
    function unlockExpired() external;

    /**
     * @notice Convert expired locked liquidity to normal liquidity
     */
    function convertExpiredLockedToNormal() external;

    /**
     * @notice Withdraw liquidity from the vault
     * @param liquidityToRemove Amount of liquidity to remove
     * @param amountWETHMin Minimum amount of WETH to receive (slippage protection)
     * @param amountDACKIEMin Minimum amount of DACKIE to receive (slippage protection)
     * @param deadline Transaction deadline
     */
    function withdraw(
        uint256 liquidityToRemove,
        uint256 amountWETHMin,
        uint256 amountDACKIEMin,
        uint256 deadline
    ) external;

    /**
     * @notice Emergency withdraw without fee collection (faster, gas-efficient)
     * @param liquidityToRemove Amount of liquidity to remove
     * @param amountWETHMin Minimum WETH to receive
     * @param amountDACKIEMin Minimum DACKIE to receive
     * @param deadline Transaction deadline
     */
    function withdrawEmergency(
        uint256 liquidityToRemove,
        uint256 amountWETHMin,
        uint256 amountDACKIEMin,
        uint256 deadline
    ) external;

    /**
     * @notice Collect accrued fees and convert them to cbBTC with treasury split
     * @param minWETH Minimum WETH fees expected (0 to skip check)
     * @param minDACKIE Minimum DACKIE fees expected (0 to skip check)
     * @param minCbBTC Minimum cbBTC to receive from conversion (before treasury split)
     * @param deadline Transaction deadline
     * @return cbBTCSentToUser Amount of cbBTC sent to the user
     * @return cbBTCSentToTreasury Amount of cbBTC sent to treasury
     */
    function collectAndConvertFees(
        uint256 minWETH,
        uint256 minDACKIE,
        uint256 minCbBTC,
        uint256 deadline
    ) external returns (uint256 cbBTCSentToUser, uint256 cbBTCSentToTreasury);

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

    /**
     * @notice Get pending fees for a user
     * @param user Address of the user
     * @return pendingWETH Amount of pending WETH fees
     * @return pendingDACKIE Amount of pending DACKIE fees
     */
    function pendingFees(address user) external view returns (uint256 pendingWETH, uint256 pendingDACKIE);

    /**
     * @notice Get pending DACKIE rewards for a user
     * @param user Address of the user
     * @return pending Amount of pending DACKIE rewards
     */
    function pendingDACKIEReward(address user) external view returns (uint256 pending);

    /**
     * @notice Get information about the vault's shared position
     * @return tokenId The Uniswap V3 NFT token ID (0 if no position)
     * @return liquidity Total liquidity in the position
     */
    function position() external view returns (uint256 tokenId, uint128 liquidity);

    /**
     * @notice Get comprehensive information about a user's position
     * @param user Address of the user
     * @return normalLiquidity User's normal liquidity
     * @return lockedLiquidity User's locked liquidity
     * @return lockedUntil Timestamp when locked liquidity becomes withdrawable
     * @return pendingWETH Pending WETH fees
     * @return pendingDACKIE Pending DACKIE fees
     */
    function userInfo(
        address user
    )
        external
        view
        returns (
            uint128 normalLiquidity,
            uint128 lockedLiquidity,
            uint64 lockedUntil,
            uint256 pendingWETH,
            uint256 pendingDACKIE
        );

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

    /**
     * @notice Set the swap paths for converting fees to cbBTC
     * @param pathWETHToCbBTC Path for WETH to cbBTC swaps
     * @param pathDACKIEToCbBTC Path for DACKIE to cbBTC swaps
     */
    function setSwapPaths(bytes calldata pathWETHToCbBTC, bytes calldata pathDACKIEToCbBTC) external;

    /**
     * @notice Set the swap router address
     * @param swapRouter Address of the new swap router
     */
    function setSwapRouter(address swapRouter) external;

    /**
     * @notice Set treasury address for fee splitting
     * @param treasury New treasury address
     */
    function setTreasuryAddress(address treasury) external;

    /**
     * @notice Set treasury fee in basis points
     * @param bps New treasury fee in basis points
     */
    function setTreasuryFeeBps(uint16 bps) external;

    /**
     * @notice Update global fee accumulators by syncing with Uniswap V3 position
     * @dev Can be called by anyone to ensure fees are up-to-date
     */
    function updateGlobalFees() external;

    /**
     * @notice Update DACKIE reward period (called by receiver)
     * @param amount Amount of DACKIE to distribute
     * @param duration Period duration in seconds
     */
    function upkeep(uint256 amount, uint256 duration) external;

    /**
     * @notice Set receiver contract address
     * @param receiver Receiver contract address
     */
    function setReceiver(address receiver) external;

    /**
     * @notice Get latest reward period information
     * @return periodNumber Current period number
     * @return startTime Period start timestamp
     * @return endTime Period end timestamp
     * @return dackiePerSecond DACKIE emission rate per second
     */
    function getLatestRewardPeriod()
        external
        view
        returns (uint256 periodNumber, uint256 startTime, uint256 endTime, uint256 dackiePerSecond);
}

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

import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";

/**
 * @title Uniswap V3 Interfaces
 * @notice Minimal interfaces for Uniswap V3 core and periphery contracts
 */

interface INonfungiblePositionManager is IERC721Receiver {
    struct MintParams {
        address token0;
        address token1;
        uint24 fee;
        int24 tickLower;
        int24 tickUpper;
        uint256 amount0Desired;
        uint256 amount1Desired;
        uint256 amount0Min;
        uint256 amount1Min;
        address recipient;
        uint256 deadline;
    }

    struct IncreaseLiquidityParams {
        uint256 tokenId;
        uint256 amount0Desired;
        uint256 amount1Desired;
        uint256 amount0Min;
        uint256 amount1Min;
        uint256 deadline;
    }

    struct DecreaseLiquidityParams {
        uint256 tokenId;
        uint128 liquidity;
        uint256 amount0Min;
        uint256 amount1Min;
        uint256 deadline;
    }

    struct CollectParams {
        uint256 tokenId;
        address recipient;
        uint128 amount0Max;
        uint128 amount1Max;
    }

    function mint(
        MintParams calldata params
    ) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);

    function increaseLiquidity(
        IncreaseLiquidityParams calldata params
    ) external payable returns (uint128 liquidity, uint256 amount0, uint256 amount1);

    function decreaseLiquidity(
        DecreaseLiquidityParams calldata params
    ) external payable returns (uint256 amount0, uint256 amount1);

    function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1);

    function positions(
        uint256 tokenId
    )
        external
        view
        returns (
            uint96 nonce,
            address operator,
            address token0,
            address token1,
            uint24 fee,
            int24 tickLower,
            int24 tickUpper,
            uint128 liquidity,
            uint256 feeGrowthInside0LastX128,
            uint256 feeGrowthInside1LastX128,
            uint128 tokensOwed0,
            uint128 tokensOwed1
        );

    function factory() external view returns (address);

    function WETH9() external view returns (address);
}

interface ISwapRouter {
    struct ExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 amountIn;
        uint256 amountOutMinimum;
        uint160 sqrtPriceLimitX96;
    }

    struct ExactInputParams {
        bytes path;
        address recipient;
        uint256 amountIn;
        uint256 amountOutMinimum;
    }

    function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);

    function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);
}

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

    function liquidity() external view returns (uint128);

    function tickSpacing() external view returns (int24);

    function fee() external view returns (uint24);

    function token0() external view returns (address);

    function token1() external view returns (address);
}

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

    function feeAmountTickSpacing(uint24 fee) external view returns (int24);
}

/**
 * @title TickMath
 * @notice Computes sqrt price for ticks and vice versa
 */
library TickMath {
    /// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128
    int24 internal constant MIN_TICK = -887272;
    /// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**127
    int24 internal constant MAX_TICK = -MIN_TICK;

    /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK)
    uint160 internal constant MIN_SQRT_RATIO = 4295128739;
    /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK)
    uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;

    /// @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 ratio of the two assets (token1/token0)
    /// at the given tick
    function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
        uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));
        require(absTick <= uint256(int256(MAX_TICK)), "T");

        uint256 ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
        if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;
        if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
        if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
        if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128;
        if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
        if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
        if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
        if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
        if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
        if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
        if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
        if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
        if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
        if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
        if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128;
        if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
        if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128;
        if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;
        if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;

        if (tick > 0) ratio = type(uint256).max / ratio;

        // 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 getTickAtSqrtRatio of the output price is always consistent
        sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1));
    }
}

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

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IWETH is IERC20 {
    function deposit() external payable;

    function withdraw(uint256 wad) external;
}

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

pragma solidity ^0.8.20;

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

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

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

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

pragma solidity >=0.6.2;

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

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

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

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

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

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

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

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

File 13 of 15 : IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC20.sol)

pragma solidity >=0.4.16;

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

File 14 of 15 : IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC165.sol)

pragma solidity >=0.4.16;

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

// SPDX-License-Identifier: MIT
// 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);
}

Settings
{
  "remappings": [
    "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
    "@uniswap/v3-core/=lib/v3-core/",
    "@uniswap/v3-periphery/=lib/v3-periphery/",
    "@forge-std/=lib/forge-std/src/",
    "@ds-test/=lib/ds-test/src/",
    "@dackievault/=src/",
    "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
    "forge-std/=lib/forge-std/src/",
    "halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/",
    "openzeppelin-contracts/=lib/openzeppelin-contracts/",
    "v3-core/=lib/v3-core/",
    "v3-periphery/=lib/v3-periphery/contracts/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "ipfs",
    "appendCBOR": true
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "cancun",
  "viaIR": true
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"_positionManager","type":"address"},{"internalType":"address","name":"_swapRouter","type":"address"},{"internalType":"address","name":"_treasuryAddress","type":"address"},{"internalType":"address","name":"_receiver","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"DeadlineExpired","type":"error"},{"inputs":[],"name":"EnforcedPause","type":"error"},{"inputs":[],"name":"ExpectedPause","type":"error"},{"inputs":[],"name":"InsufficientFees","type":"error"},{"inputs":[],"name":"InsufficientLiquidity","type":"error"},{"inputs":[],"name":"InsufficientNormalLiquidity","type":"error"},{"inputs":[],"name":"InvalidLockDuration","type":"error"},{"inputs":[],"name":"InvalidPath","type":"error"},{"inputs":[],"name":"InvalidReceiver","type":"error"},{"inputs":[],"name":"InvalidRewardDuration","type":"error"},{"inputs":[],"name":"InvalidRouter","type":"error"},{"inputs":[],"name":"LiquidityStillLocked","type":"error"},{"inputs":[],"name":"LockExpired","type":"error"},{"inputs":[],"name":"NoRewardsAvailable","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[],"name":"RewardPeriodExpired","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[],"name":"SlippageExceeded","type":"error"},{"inputs":[],"name":"SwapFailed","type":"error"},{"inputs":[],"name":"ZeroAmount","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"DACKIEEmergencyWithdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"DACKIERewardDistributed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"periodNumber","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"startTime","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"endTime","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dackiePerSecond","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalAmount","type":"uint256"}],"name":"DACKIERewardPeriodStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint128","name":"liquidity","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"amountWETH","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountDACKIE","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint64","name":"newLockedUntil","type":"uint64"}],"name":"ExpiredLockExtended","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint128","name":"liquidityConverted","type":"uint128"}],"name":"ExpiredLockedConvertedToNormal","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountWETH","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountDACKIE","type":"uint256"}],"name":"FeesCollected","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountWETHIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountDACKIEIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountCbBTCUser","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountCbBTCTreasury","type":"uint256"}],"name":"FeesConverted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint128","name":"liquidityConverted","type":"uint128"},{"indexed":false,"internalType":"uint64","name":"lockedUntil","type":"uint64"}],"name":"LiquidityConvertedToLocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint128","name":"liquidityAdded","type":"uint128"},{"indexed":false,"internalType":"uint64","name":"lockedUntil","type":"uint64"}],"name":"LiquidityLocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint64","name":"oldLockedUntil","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"newLockedUntil","type":"uint64"}],"name":"LockDurationExtended","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint128","name":"liquidityAdded","type":"uint128"},{"indexed":false,"internalType":"uint64","name":"lockedUntil","type":"uint64"}],"name":"LockedLiquidityIncreased","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint128","name":"liquidityAdded","type":"uint128"},{"indexed":false,"internalType":"uint64","name":"newLockedUntil","type":"uint64"}],"name":"LockedLiquidityIncreasedAndExtended","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint128","name":"liquidityUnlocked","type":"uint128"}],"name":"LockedLiquidityUnlocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"receiver","type":"address"}],"name":"ReceiverUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes","name":"pathWETH","type":"bytes"},{"indexed":false,"internalType":"bytes","name":"pathDACKIE","type":"bytes"}],"name":"SwapPathsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"router","type":"address"}],"name":"SwapRouterUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"treasury","type":"address"},{"indexed":false,"internalType":"uint16","name":"feeBps","type":"uint16"}],"name":"TreasuryUpdated","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":"user","type":"address"},{"indexed":false,"internalType":"uint128","name":"liquidity","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"amountWETH","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountDACKIE","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[],"name":"DACKIE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEE_TIER","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_LOCK_DURATION","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_LOCK_MULTIPLIER","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_REWARD_DURATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_TREASURY_FEE_BPS","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_REWARD_DURATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PRECISION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REWARD_PRECISION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"accDACKIERewardPerLiquidity","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"accFee0PerLiquidity","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"accFee1PerLiquidity","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cbBTC","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"minWETH","type":"uint256"},{"internalType":"uint256","name":"minDACKIE","type":"uint256"},{"internalType":"uint256","name":"minCbBTC","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"collectAndConvertFees","outputs":[{"internalType":"uint256","name":"cbBTCSentToUser","type":"uint256"},{"internalType":"uint256","name":"cbBTCSentToTreasury","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"convertExpiredLockedToNormal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"liquidityToConvert","type":"uint128"},{"internalType":"uint32","name":"lockDuration","type":"uint32"}],"name":"convertToLocked","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountWETHDesired","type":"uint256"},{"internalType":"uint256","name":"amountDACKIEDesired","type":"uint256"},{"internalType":"uint256","name":"amountWETHMin","type":"uint256"},{"internalType":"uint256","name":"amountDACKIEMin","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountDACKIEDesired","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"uint256","name":"amountDACKIEMin","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"depositETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountWETHDesired","type":"uint256"},{"internalType":"uint256","name":"amountDACKIEDesired","type":"uint256"},{"internalType":"uint256","name":"amountWETHMin","type":"uint256"},{"internalType":"uint256","name":"amountDACKIEMin","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint32","name":"lockDuration","type":"uint32"}],"name":"depositLocked","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountDACKIEDesired","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"uint256","name":"amountDACKIEMin","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint32","name":"lockDuration","type":"uint32"}],"name":"depositLockedETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint32","name":"lockDuration","type":"uint32"}],"name":"extendLockDuration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"contract IUniswapV3Factory","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLatestRewardPeriod","outputs":[{"internalType":"uint256","name":"periodNumber","type":"uint256"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"uint256","name":"dackiePerSecond","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountWETHDesired","type":"uint256"},{"internalType":"uint256","name":"amountDACKIEDesired","type":"uint256"},{"internalType":"uint256","name":"amountWETHMin","type":"uint256"},{"internalType":"uint256","name":"amountDACKIEMin","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"increaseLockedLiquidity","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountWETHDesired","type":"uint256"},{"internalType":"uint256","name":"amountDACKIEDesired","type":"uint256"},{"internalType":"uint256","name":"amountWETHMin","type":"uint256"},{"internalType":"uint256","name":"amountDACKIEMin","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint32","name":"lockDuration","type":"uint32"}],"name":"increaseLockedLiquidityAndExtend","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isWETHToken0","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestPeriodDACKIEPerSecond","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestPeriodEndTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestPeriodNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestPeriodStartTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pathDACKIEToCbBTC","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pathWETHToCbBTC","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"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":[{"internalType":"address","name":"user","type":"address"}],"name":"pendingDACKIEReward","outputs":[{"internalType":"uint256","name":"pending","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"pendingFees","outputs":[{"internalType":"uint256","name":"pendingWETH","type":"uint256"},{"internalType":"uint256","name":"pendingDACKIE","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"position","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"positionManager","outputs":[{"internalType":"contract INonfungiblePositionManager","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"receiver","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"recipient","type":"address"}],"name":"rescueTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_receiver","type":"address"}],"name":"setReceiver","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"pathWETHToCbBTC_","type":"bytes"},{"internalType":"bytes","name":"pathDACKIEToCbBTC_","type":"bytes"}],"name":"setSwapPaths","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"swapRouter_","type":"address"}],"name":"setSwapRouter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_treasury","type":"address"}],"name":"setTreasuryAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"_bps","type":"uint16"}],"name":"setTreasuryFeeBps","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"swapRouter","outputs":[{"internalType":"contract ISwapRouter","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tickLower","outputs":[{"internalType":"int24","name":"","type":"int24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tickSpacing","outputs":[{"internalType":"int24","name":"","type":"int24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tickUpper","outputs":[{"internalType":"int24","name":"","type":"int24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalLiquidity","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalRewardWeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"treasuryAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"treasuryFeeBps","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unlockExpired","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"updateGlobalFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"}],"name":"upkeep","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"userInfo","outputs":[{"internalType":"uint128","name":"normalLiquidity","type":"uint128"},{"internalType":"uint128","name":"lockedLiquidity","type":"uint128"},{"internalType":"uint64","name":"lockedUntil","type":"uint64"},{"internalType":"uint256","name":"pendingWETH","type":"uint256"},{"internalType":"uint256","name":"pendingDACKIE","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"liquidityToRemove","type":"uint256"},{"internalType":"uint256","name":"amountWETHMin","type":"uint256"},{"internalType":"uint256","name":"amountDACKIEMin","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"liquidityToRemove","type":"uint256"},{"internalType":"uint256","name":"amountWETHMin","type":"uint256"},{"internalType":"uint256","name":"amountDACKIEMin","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"withdrawEmergency","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]

61014080604052346108c957608081615b1780380380916100208285610938565b8339810103126108c9576100338161095b565b61003f6020830161095b565b9161005860606100516040840161095b565b920161095b565b903315610925575f8054336001600160a01b0319821681178355604051959290916001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a3600180556001600160a01b03169283158015610914575b61066e57608084905260028054610100600160a81b031916600887901b610100600160a81b031617905563c45a015560e01b81526001600160a01b039490941693602081600481875afa9081156105fd575f916108d5575b506001600160a01b031660a08190526040516322afcccb60e01b8152612710600482015290602090829060249082905afa9081156105fd575f91610895575b508060c05260020b1561088b575b60c05160020b80156108775761018590620d89e7190560c05160020b9061096f565b60e05260c05160020b8015610877576101a990620d89e80560c05160020b9061096f565b610100526001610120526040517f42000000000000000000000000000000000000060000000000000000000000006020820152607d60ea1b60348201527fcbb7c0000ab88b473b1f5afd9ef808440eed33bf0000000000000000000000006037820152602b815261021b604b82610938565b8051906001600160401b03821161077a5760085490600182811c9216801561086d575b602083101461075c5781601f8493116107ff575b50602090601f8311600114610799575f9261078e575b50508160011b915f199060031b1c1916176008555b6040517f73326b4d0225c429bed050c11c4422d91470aaf4000000000000000000000000602082015261027160ec1b60348201527f42000000000000000000000000000000000000060000000000000000000000006037820152607d60ea1b604b8201527fcbb7c0000ab88b473b1f5afd9ef808440eed33bf000000000000000000000000604e82015260428152610316606282610938565b8051906001600160401b03821161077a5760095490600182811c92168015610770575b602083101461075c5781601f8493116106ee575b50602090601f8311600114610688575f9261067d575b50508160011b915f199060031b1c1916176009555b6001600160a01b0316801561066e57600a80546001600160b01b031916909117607d60a41b1790556001600160a01b0316801561065f57601180546001600160a01b03191691909117905560405163095ea7b360e01b8152600481018290525f1960248201526020816044815f7342000000000000000000000000000000000000065af180156105fd57610642575b5060405163095ea7b360e01b815260048101919091525f1960248201526020816044815f7373326b4d0225c429bed050c11c4422d91470aaf45af180156105fd57610625575b5060405163095ea7b360e01b8152600481018290525f1960248201526020816044815f7342000000000000000000000000000000000000065af180156105fd57610608575b5060405163095ea7b360e01b815260048101919091525f1960248201526020816044815f7373326b4d0225c429bed050c11c4422d91470aaf45af180156105fd576105ce575b60405161516490816109b38239608051818181610f350152818161115b015281816116fb01528181611d8c0152818161208f01528181613f2d015281816142650152818161451401528181614777015261484c015260a0518161068f015260c0518161062a015260e051818181611a0a01528181613e9d0152614484015261010051818181611ae501528181613ec601526144ad01526101205181818161106d015281816111190152818161167d01528181611d4c015281816134e5015281816137f601528181613e1101528181614092015281816141ed015281816143f801528181614632015281816146ff0152614d550152f35b6105ef9060203d6020116105f6575b6105e78183610938565b81019061099a565b505f6104d8565b503d6105dd565b6040513d5f823e3d90fd5b6106209060203d6020116105f6576105e78183610938565b610492565b61063d9060203d6020116105f6576105e78183610938565b61044d565b61065a9060203d6020116105f6576105e78183610938565b610407565b631e4ec46b60e01b5f5260045ffd5b63466d7fef60e01b5f5260045ffd5b015190505f80610363565b60095f9081528281209350601f198516905b8181106106d657509084600195949392106106be575b505050811b01600955610378565b01515f1960f88460031b161c191690555f80806106b0565b9293602060018192878601518155019501930161069a565b60095f529091507f6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af601f840160051c81019160208510610752575b90601f859493920160051c01905b818110610744575061034d565b5f8155849350600101610737565b9091508190610729565b634e487b7160e01b5f52602260045260245ffd5b91607f1691610339565b634e487b7160e01b5f52604160045260245ffd5b015190505f80610268565b60085f9081528281209350601f198516905b8181106107e757509084600195949392106107cf575b505050811b0160085561027d565b01515f1960f88460031b161c191690555f80806107c1565b929360206001819287860151815501950193016107ab565b60085f529091507ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee3601f840160051c81019160208510610863575b90601f859493920160051c01905b8181106108555750610252565b5f8155849350600101610848565b909150819061083a565b91607f169161023e565b634e487b7160e01b5f52601260045260245ffd5b60c860c052610163565b90506020813d6020116108cd575b816108b060209383610938565b810103126108c957518060020b81036108c9575f610155565b5f80fd5b3d91506108a3565b90506020813d60201161090c575b816108f060209383610938565b810103126108c957602061090560249261095b565b9150610116565b3d91506108e3565b506001600160a01b038516156100be565b631e4fbdf760e01b5f525f60045260245ffd5b601f909101601f19168101906001600160401b0382119082101761077a57604052565b51906001600160a01b03821682036108c957565b9060020b9060020b02908160020b91820361098657565b634e487b7160e01b5f52601160045260245ffd5b908160209103126108c9575180151581036108c9579056fe6080604052600436101561001a575b3615610018575f80fd5b005b5f5f3560e01c806305fd570614612e6e57806309218e9114612e415780630cf058b414612e16578063150b7a0214612dc057806315770f9214612d9a5780631608bbbd14612d7f57806317d70f7c14612d6257806318dbf54514612cc35780631959a00214612c5257806325d2a3f314612c235780632d7d8eed14612ace5780632ed66478146129d057806330ec24cc146129b257806336db87351461297857806338bc362c1461260e5780633b558ec8146125df5780633d6aa5e114610a835780633f4520f5146125c15780633f4ba83a146125595780633fb26f221461200c5780634127365714611de057806347d32ab014611dc25780634c22b3ed14611ceb5780634c69a6c914611cce5780634f1bfc9e14611caf5780635431c94e14611b0b57806355b812a814611acd57806355cafdea14611a3057806359c4f905146119f25780635ad182d3146119c35780635be7dc55146115ea5780635c662ddc146115d05780635c975abb146115ad5780635cc27d6f1461158f5780636605bfda1461150157806366975ef9146114e4578063674fb1b4146110925780636ba9c71b146110555780636d4cec7814611037578063715018a614610fdd578063718da7ee14610f64578063791b98bc14610f1f57806380a60f0c14610eeb57806383bc112414610ecd5780638456cb5914610e725780638c3853b014610e565780638da5cb5b14610e2f578063a3af179814610ac5578063a772fd2d14610a88578063aaf5eb6814610a83578063ad5c464814610a60578063b0b9151714610a3d578063b0fe2ae2146107cc578063b8b37980146107ae578063bf02b5ea146106eb578063c31c9c07146106be578063c45a015514610679578063c5f956af14610650578063d0c93a7c14610612578063d64b84b5146105f4578063df227bb2146105d6578063e80cfa5e146105b1578063f072fe20146103ca578063f2fde38b14610344578063f7260d3e1461031b5763fcf0b9d8146102fb575061000e565b346103185780600319360112610318576020600c54604051908152f35b80fd5b50346103185780600319360112610318576011546040516001600160a01b039091168152602090f35b50346103185760203660031901126103185761035e612fe7565b610366613b6a565b6001600160a01b031680156103b65781546001600160a01b03198116821783556001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08380a380f35b631e4fbdf760e01b82526004829052602482fd5b50346103185780600319360112610318576103e3613b90565b33815260056020526040812090815460801c156105a257600182016001600160401b038154164210610593576104176147ec565b61041f613a59565b6104283361345c565b811580159061058a575b61056a575b5050825460801c906005840190815480155f146105645750670de0b6b3a7640000945b670de0b6b3a764000086116104dc575b90816001600160801b0361048b8682670de0b6b3a76400009796541661363e565b166001600160801b03169055805467ffffffffffffffff191690555560405190815233907f3b1b8505ee84a56aa36376056ce2813b1f931924953865e6baf68bc218b961a290602090a26001805580f35b670de0b6b3a763ffff19860195861161055057670de0b6b3a76400006105058596978296613313565b0480610516575b508594935061046a565b6010546105419161053b9181116105475761053381601054613306565b60105561367e565b336149d1565b5f61050c565b8860105561367e565b634e487b7160e01b85526011600452602485fd5b9461045a565b6102584201918242116105505790610583929133614cb5565b5f80610437565b50801515610432565b635ee7b8d760e11b8252600482fd5b631f2a200560e01b8152600490fd5b5034610318578060031936011261031857602061ffff600a5460a01c16604051908152f35b50346103185780600319360112610318576020600f54604051908152f35b50346103185780600319360112610318576020601054604051908152f35b503461031857806003193601126103185760206040517f000000000000000000000000000000000000000000000000000000000000000060020b8152f35b5034610318578060031936011261031857600a546040516001600160a01b039091168152602090f35b50346103185780600319360112610318576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b503461031857806003193601126103185760025460405160089190911c6001600160a01b03168152602090f35b5034610318576106fa366132d7565b91610706949394613b90565b61070e613bb0565b82421161079f5783158015610797575b61078857839261074c9261074c9261073a610752973033613a0a565b6107458830336139ae565b87866143ce565b92613306565b9080610778575b5080610768575b506001805580f35b61077290336142ff565b5f610760565b6107829033614342565b5f610759565b631f2a200560e01b8652600486fd5b50841561071e565b631ab7da6b60e01b8652600486fd5b50346103185780600319360112610318576020600754604051908152f35b5034610318576040366003190112610318576004356001600160801b038116808203610a395760243563ffffffff81168103610a355761080a613b90565b610812613bb0565b8115610a265733845260056020526001600160801b036040852054168211610a17577fbf0227234580f604a05ef4fbcecdaaaebc4596dbe3bf5fe3e3f2d5b27cade13e9161088b6109ad926108656147ec565b61086d613a59565b33875260056020526001600160401b03600160408920015416613c0c565b903386526005602052604086206001600160801b036108ad87828454166136f9565b166001600160801b031982541617905533865260056020526108fa604087206108da87825460801c61363e565b81546001600160801b031660809190911b6001600160801b031916179055565b3386526005602052600160408720016001600160401b0383166001600160401b0319825416179055338652600560205260056040872061095163ffffffff61094b6001600160401b0342168761365e565b16614960565b92670de0b6b3a76400006109658583613313565b048181106109d6579061097791613306565b806109b7575b505b01556040519182913395836001600160801b0390911681526001600160401b03909116602082015260400190565b0390a26001805580f35b806109c76109d0926010546132f9565b601055336149d1565b5f61097d565b6109df91613306565b806109eb575b5061097f565b601054610a089161053b918111610a0e5761053381601054613306565b5f6109e5565b8a60105561367e565b635bb93cd760e01b8452600484fd5b631f2a200560e01b8452600484fd5b8380fd5b8280fd5b50346103185780600319360112610318576020604051674563918244f400008152f35b50346103185780600319360112610318576040516006602160991b018152602090f35b613282565b5034610318576040610ab5610a9c36613264565b92610aa8929192613b90565b610ab0613bb0565b613719565b6001805582519182526020820152f35b50346103185760203660031901126103185760043563ffffffff811690818103610a3957610af1613b90565b610af9613bb0565b338352600560205260408320805460801c15610a2657610b176147ec565b610b1f613a59565b60018101916001600160401b0383541690814210155f14610d4457505082158015610d37575b610d2857610b5d6001600160401b0342169384613bec565b926001600160401b03610b6f82613bcb565b166001600160401b03851611610d17575b6005820190815480155f14610d125750670de0b6b3a76400005b670de0b6b3a7640000811180610d05575b610c7e575b5063ffffffff61094b610bc3928761365e565b915460801c80610c26575b50556001600160401b0382166001600160401b03198254161790556001600160401b03604051911681527fba658ab540503bde77f18ef3e57928a936d504ef5c57bdd3f56f69b96a78dcfc60203392a25b6001805580f35b670de0b6b3a763ffff19830190838211610c6a57670de0b6b3a764000091610c4d91613313565b048015610bce57806109c7610c64926010546132f9565b5f610bce565b634e487b7160e01b87526011600452602487fd5b835460801c670de0b6b3a763ffff198201918211610cf157610bc392670de0b6b3a7640000610cb563ffffffff9461094b94613313565b0480610cc5575b50925050610bb0565b601054610ce29161053b918111610ce85761053381601054613306565b5f610cbc565b8b60105561367e565b634e487b7160e01b88526011600452602488fd5b50835460801c1515610bab565b610b9a565b9250610d2283613bcb565b92610b80565b630f962f3d60e31b8452600484fd5b506303c267008311610b45565b819450610d5a906001600160401b039492613c0c565b9160058101805480155f14610e2a5750670de0b6b3a76400005b610d8863ffffffff61094b8842168861365e565b925460801c9081151580610e21575b610de1575b50505582821683198254161790556040519283521660208201527f6af432668cf14bfd72598906f14d15dd09552b679e589c26b753ff547bc5a60860403392a2610c1f565b670de0b6b3a764000091610df8610dfe9286613306565b90613313565b0480610e0b575b80610d9c565b806109c7610e1b926010546132f9565b5f610e05565b50808411610d97565b610d74565b5034610318578060031936011261031857546040516001600160a01b039091168152602090f35b5034610318578060031936011261031857610e6f6147ec565b80f35b5034610318578060031936011261031857610e8b613b6a565b610e93613bb0565b600160ff1960025416176002557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586020604051338152a180f35b50346103185780600319360112610318576020600654604051908152f35b5034610318578060031936011261031857610f1b610f076131be565b604051918291602083526020830190613240565b0390f35b50346103185780600319360112610318576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b503461031857602036600319011261031857610f7e612fe7565b610f86613b6a565b6001600160a01b03168015610fce57601180546001600160a01b031916821790557f75fd3aa5d9b6e2a8a9d8894008c9263200713f4b1fa9113665e09ceac00277468280a280f35b631e4ec46b60e01b8252600482fd5b5034610318578060031936011261031857610ff6613b6a565b80546001600160a01b03198116825581906001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b50346103185780600319360112610318576020600d54604051908152f35b503461031857806003193601126103185760206040517f000000000000000000000000000000000000000000000000000000000000000015158152f35b5034610318576110a136613264565b6110ad93929193613b90565b6110b5613bb0565b8042116114d55782156114c6576110cb33614a64565b33855260056020526001600160801b0360408620541683116114b75784906110f16147ec565b6110f9613a59565b806111033361345c565b819291158015906114ae575b61149c575b5050507f000000000000000000000000000000000000000000000000000000000000000092835f1461148f579060406111f592965b60035482516001600160801b038916997f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169591939192611191856130a7565b8452602084018b81528585019190915260608401918252608084019283528451630624e65f60e11b81528451600482015290516001600160801b031660248201526040909301516044840152516064830152516084820152928390819060a4820190565b038186855af19081156114845760409261125b928591869161145c575b506001600160801b0390816003549387519461122d866130c2565b85523060208601521686840152166060820152835194858094819363fc6f786560e01b8352600483016136bc565b03925af1918215611451577f5874005e1be6e01dcd0a13611d0d006a60f97986eaf93aa45b4e3c0e62f76a909386928794611413575b5061053b61139d916004546001600160801b036112b08a8284166136f9565b16906001600160801b031916176004553389526005602052604089206001600160801b036112e18a828454166136f9565b166001600160801b0319825416179055670de0b6b3a764000061130660065483613313565b04670de0b6b3a764000061131c60075484613313565b0490338b52600560205280600260408d200154115f146113fc57338b526005602052611350600260408d2001918254613306565b90555b338a52600560205280600360408c200154115f146113e557338a526005602052611385600360408c2001918254613306565b90555b60105481116105475761053381601054613306565b156113df575b6113ad8133614bf8565b6113b782336142ff565b604080516001600160801b0390951685526020850191909152830152339180606081016109ad565b906113a3565b503389526005602052886003604082200155611388565b50338a526005602052896002604082200155611353565b61139d91945061053b935061143f9060403d60401161144a575b61143781836130f9565b8101906136a6565b939093949150611291565b503d61142d565b6040513d87823e3d90fd5b6001600160801b03925061147d9150853d871161144a5761143781836130f9565b9091611212565b6040513d85823e3d90fd5b6111f59190604090611149565b6114a69233614cb5565b5f8181611114565b5080151561110f565b635bb93cd760e01b8552600485fd5b631f2a200560e01b8552600485fd5b631ab7da6b60e01b8552600485fd5b503461031857806003193601126103185760206040516113888152f35b50346103185760203660031901126103185761151b612fe7565b611523613b6a565b6001600160a01b03168015611580577f1598bf1e0a31a6c640fb95264f355b11d3eb50ce1e89eaebe8823a0872876ff96020826bffffffffffffffffffffffff60a01b600a54161780600a5561ffff6040519160a01c168152a280f35b63466d7fef60e01b8252600482fd5b50346103185780600319360112610318576020604051620151808152f35b5034610318578060031936011261031857602060ff600254166040519015158152f35b5034610318578060031936011261031857610e6f33614a64565b5034610318576115f9366132d7565b9390611606939293613b90565b61160e613bb0565b84421161079f57811580156119bb575b6107885733865260056020526040862094855460801c156119ac576001600160401b0360018701541642101561199d576116f69160609161165d6147ec565b611665613a59565b611670853033613a0a565b61167b8630336139ae565b7f000000000000000000000000000000000000000000000000000000000000000096871561199457859287915b891561198e5790915b60035494604051956116c287613078565b86526020860152604085015284840152608083015260a08201526040518093819263219f5d1760e01b835260048301613600565b0381897f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af19384156119835786958780938197611914575b50925f5160206150cf5f395f51905f529592670de0b6b3a76400006118128a6005611863999661074c9c996004546001600160801b0361177a8682841661363e565b16906001600160801b03191617600455338152826020526117a6604082206108da86825460801c61363e565b856117be6001600160801b0360065496169586613313565b04338252836020526117d8600260408420019182546132f9565b905561180160036040886117ee60075489613313565b04933381528660205220019182546132f9565b9055015480610df857508290613313565b04806118fe575b50156118ef579061182d9196878592613306565b90806118df575b50806118cf575b50604080516001600160801b0387168152602081019590955284015233929081906060820190565b0390a233825260056020527f1632c94c5e5cdb04939cdca9be282eb8ebf86e6d8dacdbc60ce14b7fdf9f49696109ad6001600160401b036001604086200154166040519182913395836001600160801b0390911681526001600160401b03909116602082015260400190565b6118d990336142ff565b5f61183b565b6118e99033614342565b5f611834565b929061182d9196878592613306565b806109c761190e926010546132f9565b5f611819565b6118639592985061074c9750600591945092670de0b6b3a76400006118126119625f5160206150cf5f395f51905f52999660603d60601161197c575b61195a81836130f9565b8101906135de565b9590919c8d92969c50969950505050509295939093611738565b503d611950565b6040513d88823e3d90fd5b916116b1565b869286916116a8565b6307b7d7dd60e51b8752600487fd5b631f2a200560e01b8752600487fd5b50821561161e565b5034610318578060031936011261031857602060405173cbb7c0000ab88b473b1f5afd9ef808440eed33bf8152f35b503461031857806003193601126103185760206040517f000000000000000000000000000000000000000000000000000000000000000060020b8152f35b503461031857611a3f366132a4565b611a4d959495929192613b90565b611a55613bb0565b824211611abe5784158015611ab6575b6119ac579261074c9261074c92611a986107529796338b5260056020526001600160401b03600160408d20015416613c0c565b92611aa4873033613a0a565b611aaf8930336139ae565b8887613db8565b508515611a65565b631ab7da6b60e01b8752600487fd5b503461031857806003193601126103185760206040517f000000000000000000000000000000000000000000000000000000000000000060020b8152f35b503461031857604036600319011261031857611b25612fe7565b611b2d612ffd565b90611b36613b6a565b6001600160801b0360045416611c05575b6040516370a0823160e01b8152306004820152906001600160a01b0316602082602481845afa918215611bfa578492611bbe575b5060405163a9059cbb60e01b60208201526001600160a01b03939093166024840152604480840192909252908252610e6f9190611bb96064836130f9565b615076565b9291506020833d602011611bf2575b81611bda602093836130f9565b81010312611bee5791519091611bb9611b7b565b5f80fd5b3d9150611bcd565b6040513d86823e3d90fd5b6001600160a01b0381166006602160991b018114159081611c90575b81611c71575b50611b475760405162461bcd60e51b815260206004820152601960248201527f43616e6e6f742072657363756520636f726520746f6b656e73000000000000006044820152606490fd5b73cbb7c0000ab88b473b1f5afd9ef808440eed33bf915014155f611c27565b7373326b4d0225c429bed050c11c4422d91470aaf48114159150611c21565b503461031857806003193601126103185760206040516303c267008152f35b503461031857806003193601126103185760206040516127108152f35b503461031857611cfa36613264565b611d0693929193613b90565b8042116114d55782156114c657611d1c33614a64565b33855260056020526001600160801b0360408620541683116114b7578490611d426147ec565b611d4a613a59565b7f000000000000000000000000000000000000000000000000000000000000000092831561148f579060406111f5929660035482516001600160801b038916997f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169591939192611191856130a7565b50346103185780600319360112610318576020600e54604051908152f35b503461031857602036600319011261031857611dfa612fe7565b611e02613b6a565b6001600160a01b038116908115611ffd5760025460405163095ea7b360e01b815260089190911c6001600160a01b0316600482015260248101849052602081604481876006602160991b015af18015611bfa57611fe0575b5060025460405163095ea7b360e01b815260089190911c6001600160a01b0316600482015260248101849052602081604481877373326b4d0225c429bed050c11c4422d91470aaf45af18015611bfa57611fc3575b5060028054610100600160a81b03191660089290921b610100600160a81b031691909117905560405163095ea7b360e01b8152600481018290525f196024820152602081604481866006602160991b015af1801561148457611fa6575b5060405163095ea7b360e01b8152600481018290525f196024820152602081604481867373326b4d0225c429bed050c11c4422d91470aaf45af1801561148457611f79575b507f36db479a3b4d3672bd6f5fca4484283f60b5ac70647b1ceec13ecbb1d030a2df8280a280f35b611f9a9060203d602011611f9f575b611f9281836130f9565b81019061368e565b611f51565b503d611f88565b611fbe9060203d602011611f9f57611f9281836130f9565b611f0c565b611fdb9060203d602011611f9f57611f9281836130f9565b611eaf565b611ff89060203d602011611f9f57611f9281836130f9565b611e5a565b63466d7fef60e01b8352600483fd5b50346103185761201b366132a4565b94909391612027613b90565b61202f613bb0565b844211611abe5781158015612551575b6119ac573387526005602052604087205460801c156119ac57606061208a9133895260056020526120816001600160401b03600160408c200154169889613c0c565b9661165d6147ec565b03818a7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af19384156125465787968892899661251d575b50338952600560205260408920908789835460801c8c6121de63ffffffff61094b6005890196875480155f146125175750670de0b6b3a7640000945b6004546001600160801b0361211e8a82841661363e565b16906001600160801b0319161760045561215f61213f898d5460801c61363e565b8c546001600160801b031660809190911b6001600160801b031916178c55565b670de0b6b3a764000061217f6001600160801b036006549a16998a613313565b04338252600560205261219a600260408420019182546132f9565b90556121cc60036040670de0b6b3a76400006121b86007548d613313565b0493338152600560205220019182546132f9565b90556001600160401b0342169061365e565b94421061247357670de0b6b3a764000081118061246a575b61240e575b50801515806123fd575b612343575b50946122cf9460016109ad9a958561074c9a95670de0b6b3a76400007f71a7874ed64f51a56ccfba5d81d41437143cb111f3b34371dd09af3c498d1b349f9d98612262905f5160206150cf5f395f51905f529d613313565b048061232d575b505501805467ffffffffffffffff19166001600160401b038b161790551561231e57906122999196878592613306565b908061230e575b50806122fe575b50604080516001600160801b038a168152602081019590955284015233929081906060820190565b0390a26040519182913395836001600160801b0390911681526001600160401b03909116602082015260400190565b61230890336142ff565b5f6122a7565b6123189033614342565b5f6122a0565b92906122999196878592613306565b806109c761233d926010546132f9565b5f612269565b670de0b6b3a763ffff198401908482116123e9576109ad9a958561074c9a95670de0b6b3a76400007f71a7874ed64f51a56ccfba5d81d41437143cb111f3b34371dd09af3c498d1b349f9d98612262905f5160206150cf5f395f51905f529d98836123b46122cf9f9a60019a613313565b04806123d3575b50989d50505050959a50959a9c50959a50509461220a565b806109c76123e3926010546132f9565b5f6123bb565b634e487b7160e01b8e52601160045260248efd5b50670de0b6b3a76400008411612205565b670de0b6b3a763ffff1981019081116123e957612434670de0b6b3a76400009183613313565b048d81612442575b506121fb565b8161053b916124639360105410155f14610533575061053381601054613306565b5f8d61243c565b508115156121f6565b6109ad9a958561074c9a95670de0b6b3a76400007f71a7874ed64f51a56ccfba5d81d41437143cb111f3b34371dd09af3c498d1b349f9d98612262905f5160206150cf5f395f51905f529d98886001986122cf9f9a15158061250e575b6124dc575b5050613313565b8591610df86124eb9286613306565b04806124f8575b806124d5565b806109c7612508926010546132f9565b5f6124f2565b508084116124d0565b94612107565b919750945061253b915060603d60601161197c5761195a81836130f9565b91969091945f6120cb565b6040513d89823e3d90fd5b50821561203f565b5034610318578060031936011261031857612572613b6a565b60025460ff8116156125b25760ff19166002557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa6020604051338152a180f35b638dfc202b60e01b8252600482fd5b5034610318578060031936011261031857602060405162278d008152f35b503461031857806003193601126103185760206040517373326b4d0225c429bed050c11c4422d91470aaf48152f35b5034610318576040366003190112610318576004356001600160401b0381116129745761263f903690600401613013565b91906024356001600160401b038111610a3957612660903690600401613013565b61266b949194613b6a565b8115801561296c575b61295d576001600160401b03821161286857612691600854613040565b601f81116128f5575b508184601f821160011461288757859161287c575b508260011b905f198460031b1c1916176008555b6001600160401b038111612868576126dc600954613040565b601f8111612800575b5083601f821160011461276c57948161275b9286977f3195da4d785452d6d34a0562a0dc2f28565e777bf4faf6619c13c422f88ee68b9791612761575b508160011b905f198360031b1c1916176009555b61274d6040519586956040875260408701916135aa565b9184830360208601526135aa565b0390a180f35b90508201355f612722565b600985525f51602061510f5f395f51905f5290601f198316865b8181106127e8575091839161275b947f3195da4d785452d6d34a0562a0dc2f28565e777bf4faf6619c13c422f88ee68b989994106127cf575b5050600181811b01600955612736565b8301355f19600384901b60f8161c191690555f806127bf565b9192602060018192868c013581550194019201612786565b60098552601f820160051c5f51602061510f5f395f51905f52019060208310612853575b601f0160051c5f51602061510f5f395f51905f5201905b81811061284857506126e5565b85815560010161283b565b5f51602061510f5f395f51905f529150612824565b634e487b7160e01b84526041600452602484fd5b90508301355f6126af565b600886525f5160206150ef5f395f51905f52915083601f198116875b8181106128da5750106128c1575b5050600182811b016008556126c3565b8401355f19600385901b60f8161c191690555f806128b1565b878401358555600190940193602093840193879350016128a3565b60088552601f830160051c5f5160206150ef5f395f51905f52019060208410612948575b601f0160051c5f5160206150ef5f395f51905f5201905b81811061293d575061269a565b858155600101612930565b5f5160206150ef5f395f51905f529150612919565b6320db826760e01b8452600484fd5b508015612674565b5080fd5b5034610318578060031936011261031857600b54600c54600d54600e54604080519485526020850193909352918301526060820152608090f35b50346103185780600319360112610318576020600b54604051908152f35b506129da36613264565b916129e3613b90565b6129eb613bb0565b8242116114d55734158015612ac6575b6114c657612a2d92859492612a2592612a1334613ca8565b612a1e8530336139ae565b84346143ce565b929034613306565b80612a4b575b50612a3e9250613306565b8061076857506001805580f35b6006602160991b013b15612ac157604051632e1a7d4d60e01b8152600481018290528481602481836006602160991b015af1801561145157612aac575b50838080612a3e96612aa594335af1612a9f61352c565b5061356a565b8392612a33565b612ab78580926130f9565b612ac1575f612a88565b505050fd5b5083156129fb565b5060a0366003190112611bee576004359060643560843563ffffffff81168103611bee57612afa613b90565b612b02613bb0565b814211612c145734158015612c0c575b612bfd57612b6991612b3f612b6192335f5260056020526001600160401b03600160405f20015416613c0c565b90612b4934613ca8565b612b548630336139ae565b6044356024358734613db8565b919034613306565b9283612b7b575b612a3e929350613306565b6006602160991b013b15611bee57604051632e1a7d4d60e01b8152600481018590525f81602481836006602160991b015af18015612bf257612bd8575b50612bd083808080612a3e9798335af1612a9f61352c565b839250612b70565b612a3e93505f612be7916130f9565b612bd05f9350612bb8565b6040513d5f823e3d90fd5b631f2a200560e01b5f5260045ffd5b508315612b12565b631ab7da6b60e01b5f5260045ffd5b34611bee576020366003190112611bee576040612c46612c41612fe7565b61345c565b82519182526020820152f35b34611bee576020366003190112611bee5760a0612c6d612fe7565b600180831b0381165f526005602052612c8960405f209161345c565b906001600160401b036001845494015416604051936001600160801b038116855260801c6020850152604084015260608301526080820152f35b34611bee576020366003190112611bee5760043561ffff8116808203611bee5761138890612cef613b6a565b11612d5357600a805461ffff60a01b19811660a093841b61ffff60a01b1617918290556040519190921c61ffff1681526001600160a01b03909116907f1598bf1e0a31a6c640fb95264f355b11d3eb50ce1e89eaebe8823a0872876ff990602090a2005b63466d7fef60e01b5f5260045ffd5b34611bee575f366003190112611bee576020600354604051908152f35b34611bee575f366003190112611bee57610f1b610f0761311a565b34611bee575f366003190112611bee5760206001600160801b0360045416604051908152f35b34611bee576080366003190112611bee57612dd9612fe7565b50612de2612ffd565b506064356001600160401b038111611bee57612e02903690600401613013565b5050604051630a85bd0160e11b8152602090f35b34611bee576020366003190112611bee576020612e39612e34612fe7565b613344565b604051908152f35b34611bee575f366003190112611bee5760406003546001600160801b036004541682519182526020820152f35b34611bee576040366003190112611bee5760115460043590602435906001600160a01b03163303612fd8578115612bfd576201518081108015612fcc575b612fbd57612ebb8230336139ae565b612ec3613a59565b612ecd81426132f9565b9082600d54428111612f88575b5050670de0b6b3a7640000830290838204670de0b6b3a76400001484151715612f7457612f0691613326565b600b54915f198314612f7457612f6f60017f966e984f09b14de9e3af2a3e121a423498feeec6949fc36854f284ea269214d494019485600b5542600c5582600d5583600e5560405193849342859094939260609260808301968352602083015260408201520152565b0390a2005b634e487b7160e01b5f52601160045260245ffd5b612fb5929450612fae612fa5670de0b6b3a7640000924290613306565b600e5490613313565b04906132f9565b918380612eda565b63e7726b7960e01b5f5260045ffd5b5062278d008111612eac565b631e4ec46b60e01b5f5260045ffd5b600435906001600160a01b0382168203611bee57565b602435906001600160a01b0382168203611bee57565b9181601f84011215611bee578235916001600160401b038311611bee5760208381860195010111611bee57565b90600182811c9216801561306e575b602083101461305a57565b634e487b7160e01b5f52602260045260245ffd5b91607f169161304f565b60c081019081106001600160401b0382111761309357604052565b634e487b7160e01b5f52604160045260245ffd5b60a081019081106001600160401b0382111761309357604052565b608081019081106001600160401b0382111761309357604052565b61016081019081106001600160401b0382111761309357604052565b90601f801991011681019081106001600160401b0382111761309357604052565b604051905f826008549161312d83613040565b808352926001811690811561319f5750600114613153575b613151925003836130f9565b565b5060085f90815290915f5160206150ef5f395f51905f525b81831061318357505090602061315192820101613145565b602091935080600191548385890101520191019091849261316b565b6020925061315194915060ff191682840152151560051b820101613145565b604051905f82600954916131d183613040565b808352926001811690811561319f57506001146131f457613151925003836130f9565b5060095f90815290915f51602061510f5f395f51905f525b81831061322457505090602061315192820101613145565b602091935080600191548385890101520191019091849261320c565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6080906003190112611bee5760043590602435906044359060643590565b34611bee575f366003190112611bee576020604051670de0b6b3a76400008152f35b60c0906003190112611bee57600435906024359060443590606435906084359060a43563ffffffff81168103611bee5790565b60a0906003190112611bee576004359060243590604435906064359060843590565b91908201809211612f7457565b91908203918211612f7457565b81810292918115918404141715612f7457565b8115613330570490565b634e487b7160e01b5f52601260045260245ffd5b6001600160a01b0381165f9081526005602052604090209061336590613b01565b80156133b557600f5460105480151580613451575b80613446575b6133bb575b5061339b670de0b6b3a764000091600493613313565b04910154808210155f146133b5576133b291613306565b90565b50505f90565b91600d548042115f14613430576133e2612fa5670de0b6b3a7640000925b600c5490613306565b0490670de0b6b3a7640000820291808304670de0b6b3a76400001490151715612f7457613427670de0b6b3a76400009361342160049661339b95613326565b906132f9565b92509250613385565b50670de0b6b3a76400006133e2612fa5426133d9565b50600c541515613380565b50600c54421161337a565b60018060a01b03165f52600560205260405f2061348781546001600160801b038160801c91166132f9565b801561352457670de0b6b3a76400006134b1816134a660065485613313565b049260075490613313565b600284015491900491818110613519576003916134cd91613306565b925b0154808210613510576134e191613306565b905b7f00000000000000000000000000000000000000000000000000000000000000001561350c5791565b9091565b50505f906134e3565b505060035f926134cf565b50505f905f90565b3d15613565573d906001600160401b038211613093576040519161355a601f8201601f1916602001846130f9565b82523d5f602084013e565b606090565b1561357157565b60405162461bcd60e51b8152602060048201526011602482015270115512081c99599d5b990819985a5b1959607a1b6044820152606490fd5b908060209392818452848401375f828201840152601f01601f1916010190565b51906001600160801b0382168203611bee57565b90816060910312611bee576135f2816135ca565b916040602083015192015190565b91909160a08060c083019480518452602081015160208501526040810151604085015260608101516060850152608081015160808501520151910152565b906001600160801b03809116911601906001600160801b038211612f7457565b906001600160401b03809116911603906001600160401b038211612f7457565b600160ff1b8114612f74575f0390565b90816020910312611bee57518015158103611bee5790565b9190826040910312611bee576020825192015190565b91909160606001600160801b038160808401958051855260018060a01b036020820151166020860152826040820151166040860152015116910152565b906001600160801b03809116911603906001600160801b038211612f7457565b909193924211612c145761372c33614a64565b6137346147ec565b61373c613a59565b61374533614ee5565b5061374f3361345c565b92909180151590816139a4575b5061398b57801515908161399a575b5061398b57801580613983575b61397957335f90815260056020526040902054670de0b6b3a7640000906137e3906137b190608081901c906001600160801b031661363e565b826137c96001600160801b0360065493169283613313565b04335f526005602052600260405f20015560075490613313565b04335f526005602052600360405f2001557f00000000000000000000000000000000000000000000000000000000000000005f14613973575b5f938161395a575b8261393e575b841061392f57600a547f8ac7ff5b5964f58937b8d419145dca5eb8da6c5ad6cb45cd91f0cc76b6413e7b906001600160a01b038116908115158061391f575b156139135761271061388561ffff61388d9360a01c1689613313565b048097613306565b9384918780613903575b5050816138f4575b6040518481528160208201527f2e4fb6077d4acf86e12bb7411fb82b2b3eaa6a49787f4b1e17b423e7ea84116960403392a2604080519485526020850191909152830152606082018590523391608090a29190565b6138fe823361437b565b61389f565b61390c9161437b565b5f87613897565b5061388d5f8097613306565b5061ffff8160a01c161515613869565b638199f5f360e01b5f5260045ffd5b936139549061342161394e6131be565b85614f9d565b9361382a565b935061396d61396761311a565b82614f9d565b93613824565b9061381c565b505090505f905f90565b508115613778565b638d53e55360e01b5f5260045ffd5b905082105f61376b565b905082105f61375c565b6040516323b872dd60e01b60208201526001600160a01b03918216602482015291166044820152606480820192909252908152613151906139f06084826130f9565b7373326b4d0225c429bed050c11c4422d91470aaf4615076565b6040516323b872dd60e01b60208201526001600160a01b0391821660248201529116604482015260648082019290925290815261315190613a4c6084826130f9565b6006602160991b01615076565b600c548015613afe5780421115613afe57600d548042115f14613af357613a8290915b82613306565b80613a8b575050565b6010549081613a9c575b5050600c55565b613ab2670de0b6b3a764000091600e5490613313565b04670de0b6b3a7640000810290808204670de0b6b3a76400001490151715612f7457613ae991613ae191613326565b600f546132f9565b600f555f80613a95565b50613a824291613a7c565b50565b60018060a01b03165f5260056020526133b260405f20600581015480155f14613b4f57506001600160801b03670de0b6b3a7640000613b4781935b54938460801c613313565b0491166132f9565b670de0b6b3a7640000613b476001600160801b039293613b3c565b5f546001600160a01b03163303613b7d57565b63118cdaa760e01b5f523360045260245ffd5b600260015414613ba1576002600155565b633ee5aeb560e01b5f5260045ffd5b60ff60025416613bbc57565b63d93c066560e01b5f5260045ffd5b6001600160401b036303c26700911601906001600160401b038211612f7457565b906001600160401b03809116911601906001600160401b038211612f7457565b9063ffffffff1680158015613c9b575b613c8c57613c346001600160401b0342169182613bec565b906001600160401b03613c4682613bcb565b166001600160401b03831611613c7c575b506001600160401b0381166001600160401b038316115f14613c77575090565b905090565b613c869150613bcb565b5f613c57565b630f962f3d60e31b5f5260045ffd5b506303c267008111613c1c565b80613cb05750565b6006602160991b013b15611bee57604051630d0e30db60e41b8152905f90829060049082906006602160991b015af18015612bf257613cec5750565b5f613151916130f9565b9190826080910312611bee57815191613d11602082016135ca565b916060604083015192015190565b9190916101408061016083019460018060a01b03815116845260018060a01b03602082015116602085015262ffffff6040820151166040850152606081015160020b6060850152608081015160020b608085015260a081015160a085015260c081015160c085015260e081015160e085015261010081015161010085015260018060a01b03610120820151166101208501520151910152565b929593909495613dc66147ec565b613dce613a59565b335f52600560205260405f2093845460801c926005860192835480155f146142f95750670de0b6b3a7640000955b600354806141e15750986080939291613f289a7f000000000000000000000000000000000000000000000000000000000000000094855f146141db5792905b85156141d55791925b85156141ba576006602160991b01955b156141ad577373326b4d0225c429bed050c11c4422d91470aaf45b60405196613e7c886130dd565b60018060a01b0316875260018060a01b0316602087015261271060408701527f000000000000000000000000000000000000000000000000000000000000000060020b60608701527f000000000000000000000000000000000000000000000000000000000000000060020b8787015260a086015260c085015260e08401526101008301523061012083015261014082015260405180988192634418b22b60e11b835260048301613d1f565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af1928315612bf2575f945f945f985f91614160575b509161408791670de0b6b3a7640000939996976003555b6004546001600160801b03613f998a82841661363e565b16906001600160801b031916176004556001815460801c91613fe86001600160801b03613fc9818d1680966132f9565b83546001600160801b0316911660801b6001600160801b031916178255565b016001600160401b038c166001600160401b03198254161790558361400f60065483613313565b04335f52600560205261402a600260405f20019182546132f9565b90558361403960075483613313565b04335f526005602052614054600360405f20019182546132f9565b905561407163ffffffff61094b426001600160401b03168e61365e565b9586928115158061250e576124dc575050613313565b048061414a575b50557f00000000000000000000000000000000000000000000000000000000000000001561411e576141197f5bdc6dd5c648616a576565c45b71417f2f46374c1f89ea0b0699e3f592f643619194955b604080516001600160801b03861681526020810188905290810188905295969533905f5160206150cf5f395f51905f529080606081016122cf565b0390a2565b937f5bdc6dd5c648616a576565c45b71417f2f46374c1f89ea0b0699e3f592f6436190614119906140de565b806109c761415a926010546132f9565b5f61408e565b90506140879850670de0b6b3a764000092965061419691955060803d6080116141a6575b61418e81836130f9565b810190613cf6565b9099929791969193509091613f6b565b503d614184565b6006602160991b01613e6f565b7373326b4d0225c429bed050c11c4422d91470aaf495613e54565b92613e44565b90613e3b565b92916060946142609b927f000000000000000000000000000000000000000000000000000000000000000091825f146142f35793915b156142ed5791925b6040519561422c87613078565b86526020860152604085015284840152608083015260a08201526040518098819263219f5d1760e01b835260048301613600565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af1928315612bf2575f935f975f916142b8575b50670de0b6b3a76400009161408791989596613f82565b6140879850670de0b6b3a76400009295506142e2915060603d60601161197c5761195a81836130f9565b9098509094916142a1565b9261421f565b91614217565b95613dfc565b60405163a9059cbb60e01b60208201526001600160a01b0390911660248201526044810191909152613151906139f081606481015b03601f1981018352826130f9565b60405163a9059cbb60e01b60208201526001600160a01b039091166024820152604481019190915261315190613a4c8160648101614334565b60405163a9059cbb60e01b60208201526001600160a01b0390911660248201526044810191909152613151906143b48160648101614334565b73cbb7c0000ab88b473b1f5afd9ef808440eed33bf615076565b92909493916143db6147ec565b6143e3613a59565b600354806146f3575094608093929161450f967f000000000000000000000000000000000000000000000000000000000000000094855f146146ed5792905b85156146e75791925b85156146cc576006602160991b01955b156146bf577373326b4d0225c429bed050c11c4422d91470aaf45b60405196614463886130dd565b60018060a01b0316875260018060a01b0316602087015261271060408701527f000000000000000000000000000000000000000000000000000000000000000060020b60608701527f000000000000000000000000000000000000000000000000000000000000000060020b8787015260a086015260c085015260e08401526101008301523061012083015261014082015260405180948192634418b22b60e11b835260048301613d1f565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af1908115612bf2575f915f905f945f91614697575b509390926003555b6004546001600160801b036145718582841661363e565b16906001600160801b03191617600455335f52600560205260405f206001600160801b036145a2858284541661363e565b166001600160801b0319825416179055614630600654670de0b6b3a76400006145d56001600160801b0387169283613313565b04335f5260056020526145f0600260405f20019182546132f9565b9055670de0b6b3a764000061460760075483613313565b04335f526005602052614622600360405f20019182546132f9565b90556109c7816010546132f9565b7f0000000000000000000000000000000000000000000000000000000000000000156146915792915b604080516001600160801b03909216825260208201859052810183905233905f5160206150cf5f395f51905f52908060608101614119565b91614659565b92945050506146b5915060803d6080116141a65761418e81836130f9565b939190935f614552565b6006602160991b01614456565b7373326b4d0225c429bed050c11c4422d91470aaf49561443b565b9261442b565b90614422565b929160609461477297927f000000000000000000000000000000000000000000000000000000000000000091825f146147e65793915b156147e05791925b6040519561473e87613078565b86526020860152604085015284840152608083015260a08201526040518094819263219f5d1760e01b835260048301613600565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af1908115612bf2575f915f935f916147ba575b509261455a565b919350506147d7915060603d60601161197c5761195a81836130f9565b9290925f6147b3565b92614731565b91614729565b6003548015801561494d575b613afe57604080516148479261480d826130c2565b81523060208201526001600160801b03828201526001600160801b03606082015281518093819263fc6f786560e01b8352600483016136bc565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af1908115612bf2575f905f9261492b575b50801580156148e2575b50508015801561489e575050565b670de0b6b3a76400008202918204670de0b6b3a7640000141715612f74576148d56148dd916001600160801b036004541690613326565b6007546132f9565b600755565b670de0b6b3a76400008202918204670de0b6b3a7640000141715612f7457614919614921916001600160801b036004541690613326565b6006546132f9565b6006555f80614890565b9050614946915060403d60401161144a5761143781836130f9565b905f614886565b506001600160801b0360045416156147f8565b63ffffffff1680156149c4576303c267008110156149b75780673782dace9d9000000290673782dace9d900000820403612f74576303c267009004670de0b6b3a76400000180670de0b6b3a764000011612f745790565b50674563918244f4000090565b50670de0b6b3a764000090565b6001600160a01b03165f9081526005602052604081209190811315614a1a576004670de0b6b3a7640000614a0b614a1693600f5490613313565b0492019182546132f9565b9055565b5f8112614a25575050565b670de0b6b3a7640000614a45614a3c60049361367e565b600f5490613313565b04910190808254115f14614a5e57614a16908254613306565b505f9055565b6001600160a01b0381165f818152600560205260409020805491929160801c151580614be1575b614a9457505050565b614a9c613a59565b6005810191825480155f14614bdc5750670de0b6b3a76400005b670de0b6b3a76400008111614b39575b50506001600160801b03670de0b6b3a76400007f3a25fa2e4f44fc8ec3d88389b078a4e5532f3e7194f011d2b73a4e876a66f4369360018484614b13602097548060801c9788911661363e565b166001600160801b0316815501805467ffffffffffffffff1916905555604051908152a2565b825460801c93670de0b6b3a763ffff198201918211612f745760016020946001600160801b0394670de0b6b3a7640000614b9581967f3a25fa2e4f44fc8ec3d88389b078a4e5532f3e7194f011d2b73a4e876a66f4369a613313565b049081614ba9575b50509450509350614ac6565b601054614bcc92614bc6918111614bd35761053381601054613306565b906149d1565b5f80614b9d565b5f60105561367e565b614ab6565b506001600160401b03600182015416421015614a8b565b5f9082614c0457505050565b6006602160991b013b15611bee57604051632e1a7d4d60e01b8152600481018490525f81602481836006602160991b015af18015612bf257614ca0575b5090918291829182916001600160a01b03165af1614c5d61352c565b5015614c6557565b60405162461bcd60e51b8152602060048201526013602482015272115512081d1c985b9cd9995c8819985a5b1959606a1b6044820152606490fd5b614cad9192505f906130f9565b5f905f614c41565b91909250821580614edd575b614ed857614cce82614ee5565b506001600160a01b0382165f81815260056020526040902054909390670de0b6b3a764000090614d4290614d1090608081901c906001600160801b031661363e565b82614d286001600160801b0360065493169283613313565b04875f526005602052600260405f20015560075490613313565b04845f526005602052600360405f2001557f00000000000000000000000000000000000000000000000000000000000000005f14614ed2575b5f9281614ebf575b82614ea9575b83614d96575b5050505050565b600a547f8ac7ff5b5964f58937b8d419145dca5eb8da6c5ad6cb45cd91f0cc76b6413e7b94614e609290916001600160a01b038116919082151580614e99575b15614e8d57612710614df261ffff614dfa9360a01c1686613313565b048094613306565b918380614e7d575b50508180614e6d575b5050867f2e4fb6077d4acf86e12bb7411fb82b2b3eaa6a49787f4b1e17b423e7ea84116960408051878152886020820152a2604051948594859094939260609260808301968352602083015260408201520152565b0390a25f80808080614d8f565b614e769161437b565b5f81614e0b565b614e869161437b565b5f83614e02565b50614dfa5f8094613306565b5061ffff8160a01c161515614dd6565b92614eb99061342161394e6131be565b92614d89565b9250614ecc61396761311a565b92614d83565b90614d7b565b505050565b508015614cc1565b6001600160a01b0381165f8181526005602052604090209190614f0782613b01565b8015614f9457670de0b6b3a7640000614f25600492600f5490613313565b049301918254808510155f14614f8c57614f3f9085613306565b905b81614f4f5750505055600190565b81614f8091602094967f1ea97d707845967623ce740ccc94ddadf7dea761b865a44d0ff09da7404d066e96556142ff565b604051908152a2600190565b505f90614f41565b50505050600190565b600254604051926020929160081c6001600160a01b031690614fbe856130c2565b84526150065f848601953087526040810194855260608101968288526040519788968795869463b858183f60e01b86528a6004870152516080602487015260a4860190613240565b92516001600160a01b0316604485015251606484015251608483015203925af15f9181615042575b506133b25763081ceff360e41b5f5260045ffd5b9091506020813d60201161506e575b8161505e602093836130f9565b81010312611bee5751905f61502e565b3d9150615051565b905f602091828151910182855af115612bf2575f513d6150c557506001600160a01b0381163b155b6150a55750565b635274afe760e01b5f9081526001600160a01b0391909116600452602490fd5b6001141561509e56fe2c057cf23622b35e0ed9529292a5f7e4cc561dc18ad64550b35e47ff7f287dcbf3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee36e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7afa26469706673582212204d266e960103008a4a99e35fade2f03575378edf2c747c47539702a08a5eab1c64736f6c634300081c0033000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b770000000000000000000000006f887c0bee01fafaca39e46ca14cc1d48e28090f00000000000000000000000035160d6a914c553e627fe81ea587791f5cbd4f03000000000000000000000000e1581c10ee235f0debb655ea365100bcbd84bad2

Deployed Bytecode

0x6080604052600436101561001a575b3615610018575f80fd5b005b5f5f3560e01c806305fd570614612e6e57806309218e9114612e415780630cf058b414612e16578063150b7a0214612dc057806315770f9214612d9a5780631608bbbd14612d7f57806317d70f7c14612d6257806318dbf54514612cc35780631959a00214612c5257806325d2a3f314612c235780632d7d8eed14612ace5780632ed66478146129d057806330ec24cc146129b257806336db87351461297857806338bc362c1461260e5780633b558ec8146125df5780633d6aa5e114610a835780633f4520f5146125c15780633f4ba83a146125595780633fb26f221461200c5780634127365714611de057806347d32ab014611dc25780634c22b3ed14611ceb5780634c69a6c914611cce5780634f1bfc9e14611caf5780635431c94e14611b0b57806355b812a814611acd57806355cafdea14611a3057806359c4f905146119f25780635ad182d3146119c35780635be7dc55146115ea5780635c662ddc146115d05780635c975abb146115ad5780635cc27d6f1461158f5780636605bfda1461150157806366975ef9146114e4578063674fb1b4146110925780636ba9c71b146110555780636d4cec7814611037578063715018a614610fdd578063718da7ee14610f64578063791b98bc14610f1f57806380a60f0c14610eeb57806383bc112414610ecd5780638456cb5914610e725780638c3853b014610e565780638da5cb5b14610e2f578063a3af179814610ac5578063a772fd2d14610a88578063aaf5eb6814610a83578063ad5c464814610a60578063b0b9151714610a3d578063b0fe2ae2146107cc578063b8b37980146107ae578063bf02b5ea146106eb578063c31c9c07146106be578063c45a015514610679578063c5f956af14610650578063d0c93a7c14610612578063d64b84b5146105f4578063df227bb2146105d6578063e80cfa5e146105b1578063f072fe20146103ca578063f2fde38b14610344578063f7260d3e1461031b5763fcf0b9d8146102fb575061000e565b346103185780600319360112610318576020600c54604051908152f35b80fd5b50346103185780600319360112610318576011546040516001600160a01b039091168152602090f35b50346103185760203660031901126103185761035e612fe7565b610366613b6a565b6001600160a01b031680156103b65781546001600160a01b03198116821783556001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08380a380f35b631e4fbdf760e01b82526004829052602482fd5b50346103185780600319360112610318576103e3613b90565b33815260056020526040812090815460801c156105a257600182016001600160401b038154164210610593576104176147ec565b61041f613a59565b6104283361345c565b811580159061058a575b61056a575b5050825460801c906005840190815480155f146105645750670de0b6b3a7640000945b670de0b6b3a764000086116104dc575b90816001600160801b0361048b8682670de0b6b3a76400009796541661363e565b166001600160801b03169055805467ffffffffffffffff191690555560405190815233907f3b1b8505ee84a56aa36376056ce2813b1f931924953865e6baf68bc218b961a290602090a26001805580f35b670de0b6b3a763ffff19860195861161055057670de0b6b3a76400006105058596978296613313565b0480610516575b508594935061046a565b6010546105419161053b9181116105475761053381601054613306565b60105561367e565b336149d1565b5f61050c565b8860105561367e565b634e487b7160e01b85526011600452602485fd5b9461045a565b6102584201918242116105505790610583929133614cb5565b5f80610437565b50801515610432565b635ee7b8d760e11b8252600482fd5b631f2a200560e01b8152600490fd5b5034610318578060031936011261031857602061ffff600a5460a01c16604051908152f35b50346103185780600319360112610318576020600f54604051908152f35b50346103185780600319360112610318576020601054604051908152f35b503461031857806003193601126103185760206040517f00000000000000000000000000000000000000000000000000000000000000c860020b8152f35b5034610318578060031936011261031857600a546040516001600160a01b039091168152602090f35b50346103185780600319360112610318576040517f0000000000000000000000003d237ac6d2f425d2e890cc99198818cc1fa488706001600160a01b03168152602090f35b503461031857806003193601126103185760025460405160089190911c6001600160a01b03168152602090f35b5034610318576106fa366132d7565b91610706949394613b90565b61070e613bb0565b82421161079f5783158015610797575b61078857839261074c9261074c9261073a610752973033613a0a565b6107458830336139ae565b87866143ce565b92613306565b9080610778575b5080610768575b506001805580f35b61077290336142ff565b5f610760565b6107829033614342565b5f610759565b631f2a200560e01b8652600486fd5b50841561071e565b631ab7da6b60e01b8652600486fd5b50346103185780600319360112610318576020600754604051908152f35b5034610318576040366003190112610318576004356001600160801b038116808203610a395760243563ffffffff81168103610a355761080a613b90565b610812613bb0565b8115610a265733845260056020526001600160801b036040852054168211610a17577fbf0227234580f604a05ef4fbcecdaaaebc4596dbe3bf5fe3e3f2d5b27cade13e9161088b6109ad926108656147ec565b61086d613a59565b33875260056020526001600160401b03600160408920015416613c0c565b903386526005602052604086206001600160801b036108ad87828454166136f9565b166001600160801b031982541617905533865260056020526108fa604087206108da87825460801c61363e565b81546001600160801b031660809190911b6001600160801b031916179055565b3386526005602052600160408720016001600160401b0383166001600160401b0319825416179055338652600560205260056040872061095163ffffffff61094b6001600160401b0342168761365e565b16614960565b92670de0b6b3a76400006109658583613313565b048181106109d6579061097791613306565b806109b7575b505b01556040519182913395836001600160801b0390911681526001600160401b03909116602082015260400190565b0390a26001805580f35b806109c76109d0926010546132f9565b601055336149d1565b5f61097d565b6109df91613306565b806109eb575b5061097f565b601054610a089161053b918111610a0e5761053381601054613306565b5f6109e5565b8a60105561367e565b635bb93cd760e01b8452600484fd5b631f2a200560e01b8452600484fd5b8380fd5b8280fd5b50346103185780600319360112610318576020604051674563918244f400008152f35b50346103185780600319360112610318576040516006602160991b018152602090f35b613282565b5034610318576040610ab5610a9c36613264565b92610aa8929192613b90565b610ab0613bb0565b613719565b6001805582519182526020820152f35b50346103185760203660031901126103185760043563ffffffff811690818103610a3957610af1613b90565b610af9613bb0565b338352600560205260408320805460801c15610a2657610b176147ec565b610b1f613a59565b60018101916001600160401b0383541690814210155f14610d4457505082158015610d37575b610d2857610b5d6001600160401b0342169384613bec565b926001600160401b03610b6f82613bcb565b166001600160401b03851611610d17575b6005820190815480155f14610d125750670de0b6b3a76400005b670de0b6b3a7640000811180610d05575b610c7e575b5063ffffffff61094b610bc3928761365e565b915460801c80610c26575b50556001600160401b0382166001600160401b03198254161790556001600160401b03604051911681527fba658ab540503bde77f18ef3e57928a936d504ef5c57bdd3f56f69b96a78dcfc60203392a25b6001805580f35b670de0b6b3a763ffff19830190838211610c6a57670de0b6b3a764000091610c4d91613313565b048015610bce57806109c7610c64926010546132f9565b5f610bce565b634e487b7160e01b87526011600452602487fd5b835460801c670de0b6b3a763ffff198201918211610cf157610bc392670de0b6b3a7640000610cb563ffffffff9461094b94613313565b0480610cc5575b50925050610bb0565b601054610ce29161053b918111610ce85761053381601054613306565b5f610cbc565b8b60105561367e565b634e487b7160e01b88526011600452602488fd5b50835460801c1515610bab565b610b9a565b9250610d2283613bcb565b92610b80565b630f962f3d60e31b8452600484fd5b506303c267008311610b45565b819450610d5a906001600160401b039492613c0c565b9160058101805480155f14610e2a5750670de0b6b3a76400005b610d8863ffffffff61094b8842168861365e565b925460801c9081151580610e21575b610de1575b50505582821683198254161790556040519283521660208201527f6af432668cf14bfd72598906f14d15dd09552b679e589c26b753ff547bc5a60860403392a2610c1f565b670de0b6b3a764000091610df8610dfe9286613306565b90613313565b0480610e0b575b80610d9c565b806109c7610e1b926010546132f9565b5f610e05565b50808411610d97565b610d74565b5034610318578060031936011261031857546040516001600160a01b039091168152602090f35b5034610318578060031936011261031857610e6f6147ec565b80f35b5034610318578060031936011261031857610e8b613b6a565b610e93613bb0565b600160ff1960025416176002557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586020604051338152a180f35b50346103185780600319360112610318576020600654604051908152f35b5034610318578060031936011261031857610f1b610f076131be565b604051918291602083526020830190613240565b0390f35b50346103185780600319360112610318576040517f000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b776001600160a01b03168152602090f35b503461031857602036600319011261031857610f7e612fe7565b610f86613b6a565b6001600160a01b03168015610fce57601180546001600160a01b031916821790557f75fd3aa5d9b6e2a8a9d8894008c9263200713f4b1fa9113665e09ceac00277468280a280f35b631e4ec46b60e01b8252600482fd5b5034610318578060031936011261031857610ff6613b6a565b80546001600160a01b03198116825581906001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b50346103185780600319360112610318576020600d54604051908152f35b503461031857806003193601126103185760206040517f000000000000000000000000000000000000000000000000000000000000000115158152f35b5034610318576110a136613264565b6110ad93929193613b90565b6110b5613bb0565b8042116114d55782156114c6576110cb33614a64565b33855260056020526001600160801b0360408620541683116114b75784906110f16147ec565b6110f9613a59565b806111033361345c565b819291158015906114ae575b61149c575b5050507f000000000000000000000000000000000000000000000000000000000000000192835f1461148f579060406111f592965b60035482516001600160801b038916997f000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b776001600160a01b03169591939192611191856130a7565b8452602084018b81528585019190915260608401918252608084019283528451630624e65f60e11b81528451600482015290516001600160801b031660248201526040909301516044840152516064830152516084820152928390819060a4820190565b038186855af19081156114845760409261125b928591869161145c575b506001600160801b0390816003549387519461122d866130c2565b85523060208601521686840152166060820152835194858094819363fc6f786560e01b8352600483016136bc565b03925af1918215611451577f5874005e1be6e01dcd0a13611d0d006a60f97986eaf93aa45b4e3c0e62f76a909386928794611413575b5061053b61139d916004546001600160801b036112b08a8284166136f9565b16906001600160801b031916176004553389526005602052604089206001600160801b036112e18a828454166136f9565b166001600160801b0319825416179055670de0b6b3a764000061130660065483613313565b04670de0b6b3a764000061131c60075484613313565b0490338b52600560205280600260408d200154115f146113fc57338b526005602052611350600260408d2001918254613306565b90555b338a52600560205280600360408c200154115f146113e557338a526005602052611385600360408c2001918254613306565b90555b60105481116105475761053381601054613306565b156113df575b6113ad8133614bf8565b6113b782336142ff565b604080516001600160801b0390951685526020850191909152830152339180606081016109ad565b906113a3565b503389526005602052886003604082200155611388565b50338a526005602052896002604082200155611353565b61139d91945061053b935061143f9060403d60401161144a575b61143781836130f9565b8101906136a6565b939093949150611291565b503d61142d565b6040513d87823e3d90fd5b6001600160801b03925061147d9150853d871161144a5761143781836130f9565b9091611212565b6040513d85823e3d90fd5b6111f59190604090611149565b6114a69233614cb5565b5f8181611114565b5080151561110f565b635bb93cd760e01b8552600485fd5b631f2a200560e01b8552600485fd5b631ab7da6b60e01b8552600485fd5b503461031857806003193601126103185760206040516113888152f35b50346103185760203660031901126103185761151b612fe7565b611523613b6a565b6001600160a01b03168015611580577f1598bf1e0a31a6c640fb95264f355b11d3eb50ce1e89eaebe8823a0872876ff96020826bffffffffffffffffffffffff60a01b600a54161780600a5561ffff6040519160a01c168152a280f35b63466d7fef60e01b8252600482fd5b50346103185780600319360112610318576020604051620151808152f35b5034610318578060031936011261031857602060ff600254166040519015158152f35b5034610318578060031936011261031857610e6f33614a64565b5034610318576115f9366132d7565b9390611606939293613b90565b61160e613bb0565b84421161079f57811580156119bb575b6107885733865260056020526040862094855460801c156119ac576001600160401b0360018701541642101561199d576116f69160609161165d6147ec565b611665613a59565b611670853033613a0a565b61167b8630336139ae565b7f000000000000000000000000000000000000000000000000000000000000000196871561199457859287915b891561198e5790915b60035494604051956116c287613078565b86526020860152604085015284840152608083015260a08201526040518093819263219f5d1760e01b835260048301613600565b0381897f000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b776001600160a01b03165af19384156119835786958780938197611914575b50925f5160206150cf5f395f51905f529592670de0b6b3a76400006118128a6005611863999661074c9c996004546001600160801b0361177a8682841661363e565b16906001600160801b03191617600455338152826020526117a6604082206108da86825460801c61363e565b856117be6001600160801b0360065496169586613313565b04338252836020526117d8600260408420019182546132f9565b905561180160036040886117ee60075489613313565b04933381528660205220019182546132f9565b9055015480610df857508290613313565b04806118fe575b50156118ef579061182d9196878592613306565b90806118df575b50806118cf575b50604080516001600160801b0387168152602081019590955284015233929081906060820190565b0390a233825260056020527f1632c94c5e5cdb04939cdca9be282eb8ebf86e6d8dacdbc60ce14b7fdf9f49696109ad6001600160401b036001604086200154166040519182913395836001600160801b0390911681526001600160401b03909116602082015260400190565b6118d990336142ff565b5f61183b565b6118e99033614342565b5f611834565b929061182d9196878592613306565b806109c761190e926010546132f9565b5f611819565b6118639592985061074c9750600591945092670de0b6b3a76400006118126119625f5160206150cf5f395f51905f52999660603d60601161197c575b61195a81836130f9565b8101906135de565b9590919c8d92969c50969950505050509295939093611738565b503d611950565b6040513d88823e3d90fd5b916116b1565b869286916116a8565b6307b7d7dd60e51b8752600487fd5b631f2a200560e01b8752600487fd5b50821561161e565b5034610318578060031936011261031857602060405173cbb7c0000ab88b473b1f5afd9ef808440eed33bf8152f35b503461031857806003193601126103185760206040517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2766060020b8152f35b503461031857611a3f366132a4565b611a4d959495929192613b90565b611a55613bb0565b824211611abe5784158015611ab6575b6119ac579261074c9261074c92611a986107529796338b5260056020526001600160401b03600160408d20015416613c0c565b92611aa4873033613a0a565b611aaf8930336139ae565b8887613db8565b508515611a65565b631ab7da6b60e01b8752600487fd5b503461031857806003193601126103185760206040517f00000000000000000000000000000000000000000000000000000000000d89a060020b8152f35b503461031857604036600319011261031857611b25612fe7565b611b2d612ffd565b90611b36613b6a565b6001600160801b0360045416611c05575b6040516370a0823160e01b8152306004820152906001600160a01b0316602082602481845afa918215611bfa578492611bbe575b5060405163a9059cbb60e01b60208201526001600160a01b03939093166024840152604480840192909252908252610e6f9190611bb96064836130f9565b615076565b9291506020833d602011611bf2575b81611bda602093836130f9565b81010312611bee5791519091611bb9611b7b565b5f80fd5b3d9150611bcd565b6040513d86823e3d90fd5b6001600160a01b0381166006602160991b018114159081611c90575b81611c71575b50611b475760405162461bcd60e51b815260206004820152601960248201527f43616e6e6f742072657363756520636f726520746f6b656e73000000000000006044820152606490fd5b73cbb7c0000ab88b473b1f5afd9ef808440eed33bf915014155f611c27565b7373326b4d0225c429bed050c11c4422d91470aaf48114159150611c21565b503461031857806003193601126103185760206040516303c267008152f35b503461031857806003193601126103185760206040516127108152f35b503461031857611cfa36613264565b611d0693929193613b90565b8042116114d55782156114c657611d1c33614a64565b33855260056020526001600160801b0360408620541683116114b7578490611d426147ec565b611d4a613a59565b7f000000000000000000000000000000000000000000000000000000000000000192831561148f579060406111f5929660035482516001600160801b038916997f000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b776001600160a01b03169591939192611191856130a7565b50346103185780600319360112610318576020600e54604051908152f35b503461031857602036600319011261031857611dfa612fe7565b611e02613b6a565b6001600160a01b038116908115611ffd5760025460405163095ea7b360e01b815260089190911c6001600160a01b0316600482015260248101849052602081604481876006602160991b015af18015611bfa57611fe0575b5060025460405163095ea7b360e01b815260089190911c6001600160a01b0316600482015260248101849052602081604481877373326b4d0225c429bed050c11c4422d91470aaf45af18015611bfa57611fc3575b5060028054610100600160a81b03191660089290921b610100600160a81b031691909117905560405163095ea7b360e01b8152600481018290525f196024820152602081604481866006602160991b015af1801561148457611fa6575b5060405163095ea7b360e01b8152600481018290525f196024820152602081604481867373326b4d0225c429bed050c11c4422d91470aaf45af1801561148457611f79575b507f36db479a3b4d3672bd6f5fca4484283f60b5ac70647b1ceec13ecbb1d030a2df8280a280f35b611f9a9060203d602011611f9f575b611f9281836130f9565b81019061368e565b611f51565b503d611f88565b611fbe9060203d602011611f9f57611f9281836130f9565b611f0c565b611fdb9060203d602011611f9f57611f9281836130f9565b611eaf565b611ff89060203d602011611f9f57611f9281836130f9565b611e5a565b63466d7fef60e01b8352600483fd5b50346103185761201b366132a4565b94909391612027613b90565b61202f613bb0565b844211611abe5781158015612551575b6119ac573387526005602052604087205460801c156119ac57606061208a9133895260056020526120816001600160401b03600160408c200154169889613c0c565b9661165d6147ec565b03818a7f000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b776001600160a01b03165af19384156125465787968892899661251d575b50338952600560205260408920908789835460801c8c6121de63ffffffff61094b6005890196875480155f146125175750670de0b6b3a7640000945b6004546001600160801b0361211e8a82841661363e565b16906001600160801b0319161760045561215f61213f898d5460801c61363e565b8c546001600160801b031660809190911b6001600160801b031916178c55565b670de0b6b3a764000061217f6001600160801b036006549a16998a613313565b04338252600560205261219a600260408420019182546132f9565b90556121cc60036040670de0b6b3a76400006121b86007548d613313565b0493338152600560205220019182546132f9565b90556001600160401b0342169061365e565b94421061247357670de0b6b3a764000081118061246a575b61240e575b50801515806123fd575b612343575b50946122cf9460016109ad9a958561074c9a95670de0b6b3a76400007f71a7874ed64f51a56ccfba5d81d41437143cb111f3b34371dd09af3c498d1b349f9d98612262905f5160206150cf5f395f51905f529d613313565b048061232d575b505501805467ffffffffffffffff19166001600160401b038b161790551561231e57906122999196878592613306565b908061230e575b50806122fe575b50604080516001600160801b038a168152602081019590955284015233929081906060820190565b0390a26040519182913395836001600160801b0390911681526001600160401b03909116602082015260400190565b61230890336142ff565b5f6122a7565b6123189033614342565b5f6122a0565b92906122999196878592613306565b806109c761233d926010546132f9565b5f612269565b670de0b6b3a763ffff198401908482116123e9576109ad9a958561074c9a95670de0b6b3a76400007f71a7874ed64f51a56ccfba5d81d41437143cb111f3b34371dd09af3c498d1b349f9d98612262905f5160206150cf5f395f51905f529d98836123b46122cf9f9a60019a613313565b04806123d3575b50989d50505050959a50959a9c50959a50509461220a565b806109c76123e3926010546132f9565b5f6123bb565b634e487b7160e01b8e52601160045260248efd5b50670de0b6b3a76400008411612205565b670de0b6b3a763ffff1981019081116123e957612434670de0b6b3a76400009183613313565b048d81612442575b506121fb565b8161053b916124639360105410155f14610533575061053381601054613306565b5f8d61243c565b508115156121f6565b6109ad9a958561074c9a95670de0b6b3a76400007f71a7874ed64f51a56ccfba5d81d41437143cb111f3b34371dd09af3c498d1b349f9d98612262905f5160206150cf5f395f51905f529d98886001986122cf9f9a15158061250e575b6124dc575b5050613313565b8591610df86124eb9286613306565b04806124f8575b806124d5565b806109c7612508926010546132f9565b5f6124f2565b508084116124d0565b94612107565b919750945061253b915060603d60601161197c5761195a81836130f9565b91969091945f6120cb565b6040513d89823e3d90fd5b50821561203f565b5034610318578060031936011261031857612572613b6a565b60025460ff8116156125b25760ff19166002557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa6020604051338152a180f35b638dfc202b60e01b8252600482fd5b5034610318578060031936011261031857602060405162278d008152f35b503461031857806003193601126103185760206040517373326b4d0225c429bed050c11c4422d91470aaf48152f35b5034610318576040366003190112610318576004356001600160401b0381116129745761263f903690600401613013565b91906024356001600160401b038111610a3957612660903690600401613013565b61266b949194613b6a565b8115801561296c575b61295d576001600160401b03821161286857612691600854613040565b601f81116128f5575b508184601f821160011461288757859161287c575b508260011b905f198460031b1c1916176008555b6001600160401b038111612868576126dc600954613040565b601f8111612800575b5083601f821160011461276c57948161275b9286977f3195da4d785452d6d34a0562a0dc2f28565e777bf4faf6619c13c422f88ee68b9791612761575b508160011b905f198360031b1c1916176009555b61274d6040519586956040875260408701916135aa565b9184830360208601526135aa565b0390a180f35b90508201355f612722565b600985525f51602061510f5f395f51905f5290601f198316865b8181106127e8575091839161275b947f3195da4d785452d6d34a0562a0dc2f28565e777bf4faf6619c13c422f88ee68b989994106127cf575b5050600181811b01600955612736565b8301355f19600384901b60f8161c191690555f806127bf565b9192602060018192868c013581550194019201612786565b60098552601f820160051c5f51602061510f5f395f51905f52019060208310612853575b601f0160051c5f51602061510f5f395f51905f5201905b81811061284857506126e5565b85815560010161283b565b5f51602061510f5f395f51905f529150612824565b634e487b7160e01b84526041600452602484fd5b90508301355f6126af565b600886525f5160206150ef5f395f51905f52915083601f198116875b8181106128da5750106128c1575b5050600182811b016008556126c3565b8401355f19600385901b60f8161c191690555f806128b1565b878401358555600190940193602093840193879350016128a3565b60088552601f830160051c5f5160206150ef5f395f51905f52019060208410612948575b601f0160051c5f5160206150ef5f395f51905f5201905b81811061293d575061269a565b858155600101612930565b5f5160206150ef5f395f51905f529150612919565b6320db826760e01b8452600484fd5b508015612674565b5080fd5b5034610318578060031936011261031857600b54600c54600d54600e54604080519485526020850193909352918301526060820152608090f35b50346103185780600319360112610318576020600b54604051908152f35b506129da36613264565b916129e3613b90565b6129eb613bb0565b8242116114d55734158015612ac6575b6114c657612a2d92859492612a2592612a1334613ca8565b612a1e8530336139ae565b84346143ce565b929034613306565b80612a4b575b50612a3e9250613306565b8061076857506001805580f35b6006602160991b013b15612ac157604051632e1a7d4d60e01b8152600481018290528481602481836006602160991b015af1801561145157612aac575b50838080612a3e96612aa594335af1612a9f61352c565b5061356a565b8392612a33565b612ab78580926130f9565b612ac1575f612a88565b505050fd5b5083156129fb565b5060a0366003190112611bee576004359060643560843563ffffffff81168103611bee57612afa613b90565b612b02613bb0565b814211612c145734158015612c0c575b612bfd57612b6991612b3f612b6192335f5260056020526001600160401b03600160405f20015416613c0c565b90612b4934613ca8565b612b548630336139ae565b6044356024358734613db8565b919034613306565b9283612b7b575b612a3e929350613306565b6006602160991b013b15611bee57604051632e1a7d4d60e01b8152600481018590525f81602481836006602160991b015af18015612bf257612bd8575b50612bd083808080612a3e9798335af1612a9f61352c565b839250612b70565b612a3e93505f612be7916130f9565b612bd05f9350612bb8565b6040513d5f823e3d90fd5b631f2a200560e01b5f5260045ffd5b508315612b12565b631ab7da6b60e01b5f5260045ffd5b34611bee576020366003190112611bee576040612c46612c41612fe7565b61345c565b82519182526020820152f35b34611bee576020366003190112611bee5760a0612c6d612fe7565b600180831b0381165f526005602052612c8960405f209161345c565b906001600160401b036001845494015416604051936001600160801b038116855260801c6020850152604084015260608301526080820152f35b34611bee576020366003190112611bee5760043561ffff8116808203611bee5761138890612cef613b6a565b11612d5357600a805461ffff60a01b19811660a093841b61ffff60a01b1617918290556040519190921c61ffff1681526001600160a01b03909116907f1598bf1e0a31a6c640fb95264f355b11d3eb50ce1e89eaebe8823a0872876ff990602090a2005b63466d7fef60e01b5f5260045ffd5b34611bee575f366003190112611bee576020600354604051908152f35b34611bee575f366003190112611bee57610f1b610f0761311a565b34611bee575f366003190112611bee5760206001600160801b0360045416604051908152f35b34611bee576080366003190112611bee57612dd9612fe7565b50612de2612ffd565b506064356001600160401b038111611bee57612e02903690600401613013565b5050604051630a85bd0160e11b8152602090f35b34611bee576020366003190112611bee576020612e39612e34612fe7565b613344565b604051908152f35b34611bee575f366003190112611bee5760406003546001600160801b036004541682519182526020820152f35b34611bee576040366003190112611bee5760115460043590602435906001600160a01b03163303612fd8578115612bfd576201518081108015612fcc575b612fbd57612ebb8230336139ae565b612ec3613a59565b612ecd81426132f9565b9082600d54428111612f88575b5050670de0b6b3a7640000830290838204670de0b6b3a76400001484151715612f7457612f0691613326565b600b54915f198314612f7457612f6f60017f966e984f09b14de9e3af2a3e121a423498feeec6949fc36854f284ea269214d494019485600b5542600c5582600d5583600e5560405193849342859094939260609260808301968352602083015260408201520152565b0390a2005b634e487b7160e01b5f52601160045260245ffd5b612fb5929450612fae612fa5670de0b6b3a7640000924290613306565b600e5490613313565b04906132f9565b918380612eda565b63e7726b7960e01b5f5260045ffd5b5062278d008111612eac565b631e4ec46b60e01b5f5260045ffd5b600435906001600160a01b0382168203611bee57565b602435906001600160a01b0382168203611bee57565b9181601f84011215611bee578235916001600160401b038311611bee5760208381860195010111611bee57565b90600182811c9216801561306e575b602083101461305a57565b634e487b7160e01b5f52602260045260245ffd5b91607f169161304f565b60c081019081106001600160401b0382111761309357604052565b634e487b7160e01b5f52604160045260245ffd5b60a081019081106001600160401b0382111761309357604052565b608081019081106001600160401b0382111761309357604052565b61016081019081106001600160401b0382111761309357604052565b90601f801991011681019081106001600160401b0382111761309357604052565b604051905f826008549161312d83613040565b808352926001811690811561319f5750600114613153575b613151925003836130f9565b565b5060085f90815290915f5160206150ef5f395f51905f525b81831061318357505090602061315192820101613145565b602091935080600191548385890101520191019091849261316b565b6020925061315194915060ff191682840152151560051b820101613145565b604051905f82600954916131d183613040565b808352926001811690811561319f57506001146131f457613151925003836130f9565b5060095f90815290915f51602061510f5f395f51905f525b81831061322457505090602061315192820101613145565b602091935080600191548385890101520191019091849261320c565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b6080906003190112611bee5760043590602435906044359060643590565b34611bee575f366003190112611bee576020604051670de0b6b3a76400008152f35b60c0906003190112611bee57600435906024359060443590606435906084359060a43563ffffffff81168103611bee5790565b60a0906003190112611bee576004359060243590604435906064359060843590565b91908201809211612f7457565b91908203918211612f7457565b81810292918115918404141715612f7457565b8115613330570490565b634e487b7160e01b5f52601260045260245ffd5b6001600160a01b0381165f9081526005602052604090209061336590613b01565b80156133b557600f5460105480151580613451575b80613446575b6133bb575b5061339b670de0b6b3a764000091600493613313565b04910154808210155f146133b5576133b291613306565b90565b50505f90565b91600d548042115f14613430576133e2612fa5670de0b6b3a7640000925b600c5490613306565b0490670de0b6b3a7640000820291808304670de0b6b3a76400001490151715612f7457613427670de0b6b3a76400009361342160049661339b95613326565b906132f9565b92509250613385565b50670de0b6b3a76400006133e2612fa5426133d9565b50600c541515613380565b50600c54421161337a565b60018060a01b03165f52600560205260405f2061348781546001600160801b038160801c91166132f9565b801561352457670de0b6b3a76400006134b1816134a660065485613313565b049260075490613313565b600284015491900491818110613519576003916134cd91613306565b925b0154808210613510576134e191613306565b905b7f00000000000000000000000000000000000000000000000000000000000000011561350c5791565b9091565b50505f906134e3565b505060035f926134cf565b50505f905f90565b3d15613565573d906001600160401b038211613093576040519161355a601f8201601f1916602001846130f9565b82523d5f602084013e565b606090565b1561357157565b60405162461bcd60e51b8152602060048201526011602482015270115512081c99599d5b990819985a5b1959607a1b6044820152606490fd5b908060209392818452848401375f828201840152601f01601f1916010190565b51906001600160801b0382168203611bee57565b90816060910312611bee576135f2816135ca565b916040602083015192015190565b91909160a08060c083019480518452602081015160208501526040810151604085015260608101516060850152608081015160808501520151910152565b906001600160801b03809116911601906001600160801b038211612f7457565b906001600160401b03809116911603906001600160401b038211612f7457565b600160ff1b8114612f74575f0390565b90816020910312611bee57518015158103611bee5790565b9190826040910312611bee576020825192015190565b91909160606001600160801b038160808401958051855260018060a01b036020820151166020860152826040820151166040860152015116910152565b906001600160801b03809116911603906001600160801b038211612f7457565b909193924211612c145761372c33614a64565b6137346147ec565b61373c613a59565b61374533614ee5565b5061374f3361345c565b92909180151590816139a4575b5061398b57801515908161399a575b5061398b57801580613983575b61397957335f90815260056020526040902054670de0b6b3a7640000906137e3906137b190608081901c906001600160801b031661363e565b826137c96001600160801b0360065493169283613313565b04335f526005602052600260405f20015560075490613313565b04335f526005602052600360405f2001557f00000000000000000000000000000000000000000000000000000000000000015f14613973575b5f938161395a575b8261393e575b841061392f57600a547f8ac7ff5b5964f58937b8d419145dca5eb8da6c5ad6cb45cd91f0cc76b6413e7b906001600160a01b038116908115158061391f575b156139135761271061388561ffff61388d9360a01c1689613313565b048097613306565b9384918780613903575b5050816138f4575b6040518481528160208201527f2e4fb6077d4acf86e12bb7411fb82b2b3eaa6a49787f4b1e17b423e7ea84116960403392a2604080519485526020850191909152830152606082018590523391608090a29190565b6138fe823361437b565b61389f565b61390c9161437b565b5f87613897565b5061388d5f8097613306565b5061ffff8160a01c161515613869565b638199f5f360e01b5f5260045ffd5b936139549061342161394e6131be565b85614f9d565b9361382a565b935061396d61396761311a565b82614f9d565b93613824565b9061381c565b505090505f905f90565b508115613778565b638d53e55360e01b5f5260045ffd5b905082105f61376b565b905082105f61375c565b6040516323b872dd60e01b60208201526001600160a01b03918216602482015291166044820152606480820192909252908152613151906139f06084826130f9565b7373326b4d0225c429bed050c11c4422d91470aaf4615076565b6040516323b872dd60e01b60208201526001600160a01b0391821660248201529116604482015260648082019290925290815261315190613a4c6084826130f9565b6006602160991b01615076565b600c548015613afe5780421115613afe57600d548042115f14613af357613a8290915b82613306565b80613a8b575050565b6010549081613a9c575b5050600c55565b613ab2670de0b6b3a764000091600e5490613313565b04670de0b6b3a7640000810290808204670de0b6b3a76400001490151715612f7457613ae991613ae191613326565b600f546132f9565b600f555f80613a95565b50613a824291613a7c565b50565b60018060a01b03165f5260056020526133b260405f20600581015480155f14613b4f57506001600160801b03670de0b6b3a7640000613b4781935b54938460801c613313565b0491166132f9565b670de0b6b3a7640000613b476001600160801b039293613b3c565b5f546001600160a01b03163303613b7d57565b63118cdaa760e01b5f523360045260245ffd5b600260015414613ba1576002600155565b633ee5aeb560e01b5f5260045ffd5b60ff60025416613bbc57565b63d93c066560e01b5f5260045ffd5b6001600160401b036303c26700911601906001600160401b038211612f7457565b906001600160401b03809116911601906001600160401b038211612f7457565b9063ffffffff1680158015613c9b575b613c8c57613c346001600160401b0342169182613bec565b906001600160401b03613c4682613bcb565b166001600160401b03831611613c7c575b506001600160401b0381166001600160401b038316115f14613c77575090565b905090565b613c869150613bcb565b5f613c57565b630f962f3d60e31b5f5260045ffd5b506303c267008111613c1c565b80613cb05750565b6006602160991b013b15611bee57604051630d0e30db60e41b8152905f90829060049082906006602160991b015af18015612bf257613cec5750565b5f613151916130f9565b9190826080910312611bee57815191613d11602082016135ca565b916060604083015192015190565b9190916101408061016083019460018060a01b03815116845260018060a01b03602082015116602085015262ffffff6040820151166040850152606081015160020b6060850152608081015160020b608085015260a081015160a085015260c081015160c085015260e081015160e085015261010081015161010085015260018060a01b03610120820151166101208501520151910152565b929593909495613dc66147ec565b613dce613a59565b335f52600560205260405f2093845460801c926005860192835480155f146142f95750670de0b6b3a7640000955b600354806141e15750986080939291613f289a7f000000000000000000000000000000000000000000000000000000000000000194855f146141db5792905b85156141d55791925b85156141ba576006602160991b01955b156141ad577373326b4d0225c429bed050c11c4422d91470aaf45b60405196613e7c886130dd565b60018060a01b0316875260018060a01b0316602087015261271060408701527ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2766060020b60608701527f00000000000000000000000000000000000000000000000000000000000d89a060020b8787015260a086015260c085015260e08401526101008301523061012083015261014082015260405180988192634418b22b60e11b835260048301613d1f565b03815f7f000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b776001600160a01b03165af1928315612bf2575f945f945f985f91614160575b509161408791670de0b6b3a7640000939996976003555b6004546001600160801b03613f998a82841661363e565b16906001600160801b031916176004556001815460801c91613fe86001600160801b03613fc9818d1680966132f9565b83546001600160801b0316911660801b6001600160801b031916178255565b016001600160401b038c166001600160401b03198254161790558361400f60065483613313565b04335f52600560205261402a600260405f20019182546132f9565b90558361403960075483613313565b04335f526005602052614054600360405f20019182546132f9565b905561407163ffffffff61094b426001600160401b03168e61365e565b9586928115158061250e576124dc575050613313565b048061414a575b50557f00000000000000000000000000000000000000000000000000000000000000011561411e576141197f5bdc6dd5c648616a576565c45b71417f2f46374c1f89ea0b0699e3f592f643619194955b604080516001600160801b03861681526020810188905290810188905295969533905f5160206150cf5f395f51905f529080606081016122cf565b0390a2565b937f5bdc6dd5c648616a576565c45b71417f2f46374c1f89ea0b0699e3f592f6436190614119906140de565b806109c761415a926010546132f9565b5f61408e565b90506140879850670de0b6b3a764000092965061419691955060803d6080116141a6575b61418e81836130f9565b810190613cf6565b9099929791969193509091613f6b565b503d614184565b6006602160991b01613e6f565b7373326b4d0225c429bed050c11c4422d91470aaf495613e54565b92613e44565b90613e3b565b92916060946142609b927f000000000000000000000000000000000000000000000000000000000000000191825f146142f35793915b156142ed5791925b6040519561422c87613078565b86526020860152604085015284840152608083015260a08201526040518098819263219f5d1760e01b835260048301613600565b03815f7f000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b776001600160a01b03165af1928315612bf2575f935f975f916142b8575b50670de0b6b3a76400009161408791989596613f82565b6140879850670de0b6b3a76400009295506142e2915060603d60601161197c5761195a81836130f9565b9098509094916142a1565b9261421f565b91614217565b95613dfc565b60405163a9059cbb60e01b60208201526001600160a01b0390911660248201526044810191909152613151906139f081606481015b03601f1981018352826130f9565b60405163a9059cbb60e01b60208201526001600160a01b039091166024820152604481019190915261315190613a4c8160648101614334565b60405163a9059cbb60e01b60208201526001600160a01b0390911660248201526044810191909152613151906143b48160648101614334565b73cbb7c0000ab88b473b1f5afd9ef808440eed33bf615076565b92909493916143db6147ec565b6143e3613a59565b600354806146f3575094608093929161450f967f000000000000000000000000000000000000000000000000000000000000000194855f146146ed5792905b85156146e75791925b85156146cc576006602160991b01955b156146bf577373326b4d0225c429bed050c11c4422d91470aaf45b60405196614463886130dd565b60018060a01b0316875260018060a01b0316602087015261271060408701527ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2766060020b60608701527f00000000000000000000000000000000000000000000000000000000000d89a060020b8787015260a086015260c085015260e08401526101008301523061012083015261014082015260405180948192634418b22b60e11b835260048301613d1f565b03815f7f000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b776001600160a01b03165af1908115612bf2575f915f905f945f91614697575b509390926003555b6004546001600160801b036145718582841661363e565b16906001600160801b03191617600455335f52600560205260405f206001600160801b036145a2858284541661363e565b166001600160801b0319825416179055614630600654670de0b6b3a76400006145d56001600160801b0387169283613313565b04335f5260056020526145f0600260405f20019182546132f9565b9055670de0b6b3a764000061460760075483613313565b04335f526005602052614622600360405f20019182546132f9565b90556109c7816010546132f9565b7f0000000000000000000000000000000000000000000000000000000000000001156146915792915b604080516001600160801b03909216825260208201859052810183905233905f5160206150cf5f395f51905f52908060608101614119565b91614659565b92945050506146b5915060803d6080116141a65761418e81836130f9565b939190935f614552565b6006602160991b01614456565b7373326b4d0225c429bed050c11c4422d91470aaf49561443b565b9261442b565b90614422565b929160609461477297927f000000000000000000000000000000000000000000000000000000000000000191825f146147e65793915b156147e05791925b6040519561473e87613078565b86526020860152604085015284840152608083015260a08201526040518094819263219f5d1760e01b835260048301613600565b03815f7f000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b776001600160a01b03165af1908115612bf2575f915f935f916147ba575b509261455a565b919350506147d7915060603d60601161197c5761195a81836130f9565b9290925f6147b3565b92614731565b91614729565b6003548015801561494d575b613afe57604080516148479261480d826130c2565b81523060208201526001600160801b03828201526001600160801b03606082015281518093819263fc6f786560e01b8352600483016136bc565b03815f7f000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b776001600160a01b03165af1908115612bf2575f905f9261492b575b50801580156148e2575b50508015801561489e575050565b670de0b6b3a76400008202918204670de0b6b3a7640000141715612f74576148d56148dd916001600160801b036004541690613326565b6007546132f9565b600755565b670de0b6b3a76400008202918204670de0b6b3a7640000141715612f7457614919614921916001600160801b036004541690613326565b6006546132f9565b6006555f80614890565b9050614946915060403d60401161144a5761143781836130f9565b905f614886565b506001600160801b0360045416156147f8565b63ffffffff1680156149c4576303c267008110156149b75780673782dace9d9000000290673782dace9d900000820403612f74576303c267009004670de0b6b3a76400000180670de0b6b3a764000011612f745790565b50674563918244f4000090565b50670de0b6b3a764000090565b6001600160a01b03165f9081526005602052604081209190811315614a1a576004670de0b6b3a7640000614a0b614a1693600f5490613313565b0492019182546132f9565b9055565b5f8112614a25575050565b670de0b6b3a7640000614a45614a3c60049361367e565b600f5490613313565b04910190808254115f14614a5e57614a16908254613306565b505f9055565b6001600160a01b0381165f818152600560205260409020805491929160801c151580614be1575b614a9457505050565b614a9c613a59565b6005810191825480155f14614bdc5750670de0b6b3a76400005b670de0b6b3a76400008111614b39575b50506001600160801b03670de0b6b3a76400007f3a25fa2e4f44fc8ec3d88389b078a4e5532f3e7194f011d2b73a4e876a66f4369360018484614b13602097548060801c9788911661363e565b166001600160801b0316815501805467ffffffffffffffff1916905555604051908152a2565b825460801c93670de0b6b3a763ffff198201918211612f745760016020946001600160801b0394670de0b6b3a7640000614b9581967f3a25fa2e4f44fc8ec3d88389b078a4e5532f3e7194f011d2b73a4e876a66f4369a613313565b049081614ba9575b50509450509350614ac6565b601054614bcc92614bc6918111614bd35761053381601054613306565b906149d1565b5f80614b9d565b5f60105561367e565b614ab6565b506001600160401b03600182015416421015614a8b565b5f9082614c0457505050565b6006602160991b013b15611bee57604051632e1a7d4d60e01b8152600481018490525f81602481836006602160991b015af18015612bf257614ca0575b5090918291829182916001600160a01b03165af1614c5d61352c565b5015614c6557565b60405162461bcd60e51b8152602060048201526013602482015272115512081d1c985b9cd9995c8819985a5b1959606a1b6044820152606490fd5b614cad9192505f906130f9565b5f905f614c41565b91909250821580614edd575b614ed857614cce82614ee5565b506001600160a01b0382165f81815260056020526040902054909390670de0b6b3a764000090614d4290614d1090608081901c906001600160801b031661363e565b82614d286001600160801b0360065493169283613313565b04875f526005602052600260405f20015560075490613313565b04845f526005602052600360405f2001557f00000000000000000000000000000000000000000000000000000000000000015f14614ed2575b5f9281614ebf575b82614ea9575b83614d96575b5050505050565b600a547f8ac7ff5b5964f58937b8d419145dca5eb8da6c5ad6cb45cd91f0cc76b6413e7b94614e609290916001600160a01b038116919082151580614e99575b15614e8d57612710614df261ffff614dfa9360a01c1686613313565b048094613306565b918380614e7d575b50508180614e6d575b5050867f2e4fb6077d4acf86e12bb7411fb82b2b3eaa6a49787f4b1e17b423e7ea84116960408051878152886020820152a2604051948594859094939260609260808301968352602083015260408201520152565b0390a25f80808080614d8f565b614e769161437b565b5f81614e0b565b614e869161437b565b5f83614e02565b50614dfa5f8094613306565b5061ffff8160a01c161515614dd6565b92614eb99061342161394e6131be565b92614d89565b9250614ecc61396761311a565b92614d83565b90614d7b565b505050565b508015614cc1565b6001600160a01b0381165f8181526005602052604090209190614f0782613b01565b8015614f9457670de0b6b3a7640000614f25600492600f5490613313565b049301918254808510155f14614f8c57614f3f9085613306565b905b81614f4f5750505055600190565b81614f8091602094967f1ea97d707845967623ce740ccc94ddadf7dea761b865a44d0ff09da7404d066e96556142ff565b604051908152a2600190565b505f90614f41565b50505050600190565b600254604051926020929160081c6001600160a01b031690614fbe856130c2565b84526150065f848601953087526040810194855260608101968288526040519788968795869463b858183f60e01b86528a6004870152516080602487015260a4860190613240565b92516001600160a01b0316604485015251606484015251608483015203925af15f9181615042575b506133b25763081ceff360e41b5f5260045ffd5b9091506020813d60201161506e575b8161505e602093836130f9565b81010312611bee5751905f61502e565b3d9150615051565b905f602091828151910182855af115612bf2575f513d6150c557506001600160a01b0381163b155b6150a55750565b635274afe760e01b5f9081526001600160a01b0391909116600452602490fd5b6001141561509e56fe2c057cf23622b35e0ed9529292a5f7e4cc561dc18ad64550b35e47ff7f287dcbf3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee36e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7afa26469706673582212204d266e960103008a4a99e35fade2f03575378edf2c747c47539702a08a5eab1c64736f6c634300081c0033

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

000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b770000000000000000000000006f887c0bee01fafaca39e46ca14cc1d48e28090f00000000000000000000000035160d6a914c553e627fe81ea587791f5cbd4f03000000000000000000000000e1581c10ee235f0debb655ea365100bcbd84bad2

-----Decoded View---------------
Arg [0] : _positionManager (address): 0xCfB05AB06D338FD85BBF4486e69809D96A906b77
Arg [1] : _swapRouter (address): 0x6F887c0Bee01FAfacA39E46cA14cc1D48e28090F
Arg [2] : _treasuryAddress (address): 0x35160D6A914C553E627Fe81EA587791f5CBD4f03
Arg [3] : _receiver (address): 0xe1581C10EE235F0DEbb655EA365100bCBD84BAD2

-----Encoded View---------------
4 Constructor Arguments found :
Arg [0] : 000000000000000000000000cfb05ab06d338fd85bbf4486e69809d96a906b77
Arg [1] : 0000000000000000000000006f887c0bee01fafaca39e46ca14cc1d48e28090f
Arg [2] : 00000000000000000000000035160d6a914c553e627fe81ea587791f5cbd4f03
Arg [3] : 000000000000000000000000e1581c10ee235f0debb655ea365100bcbd84bad2


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.