Skip to main content
PulseScanner.io

Address

0x2e087da8b33746554e53c8d630098e591ea2a8a2
Current Holdings
$0.00
TXs sent
not counted
First Active
2026-08-09
block 27,244,121
Last Active
36 days ago
block 27,244,437
Funded By
0xc744…b930

Net worth historyi

2 snapshots · to block 27,480,474coverage change 26 Augcoverage change 27 Augcoverage change 27 Augcoverage change 27 Augcoverage change 27 Aug
exact matchLiquidityManagerv2solc 0.8.20+commit.a1b79de6runtime exact · creation exact
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

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

interface IPulseXPool {
    function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
}

interface IPulseXRouter {
    function swapExactETHForTokens(uint256 amountOutMin, address[] calldata path, address to, uint256 deadline) external payable returns (uint256[] memory amounts);
    function swapExactTokensForETH(uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to, uint256 deadline) external returns (uint256[] memory amounts);
    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint256 amountIn,          // Exact amount of input tokens to swap
        uint256 amountOutMin,      // Minimum amount of ETH to receive
        address[] calldata path,   // Swap path (e.g., [token, WPLS])
        address to,                // Address to receive the ETH
        uint256 deadline           // Transaction deadline
    ) external;
    function getAmountsOut(uint256 amountIn, address[] calldata path) external view returns (uint256[] memory amounts);
}

interface IWPLS {
    function deposit() external payable;
    function withdraw(uint256 wad) external;
}

interface INonfungiblePositionManager {
    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 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 decreaseLiquidity(DecreaseLiquidityParams calldata params) external payable returns (uint256 amount0, uint256 amount1);
    function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1);
    function unwrapWETH9(uint256 amountMinimum, address recipient) external payable;
    function sweepToken(address token, uint256 amountMinimum, address recipient) external payable;
    function multicall(bytes[] calldata data) external payable returns (bytes[] memory results);
    function safeTransferFrom(address from, address to, uint256 tokenId) external;
    function refundETH() external payable;
    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
    );
}

contract LiquidityManagerv2 is Ownable, IERC721Receiver {
    using SafeERC20 for IERC20;

    address public collectWallet;
    address public managerWallet;

    address public token0;
    address public token1;
    uint24 public fee;
    uint256 public lpTokenId;
    uint24 public slippage;
    uint128 public liquidity;
    int24 public lowertick;
    int24 public uppertick;

    // Address of Wrapped PLS (WPLS)
    address public constant WPLS = 0xA1077a294dDE1B09bB078844df40758a5D0f9a27;

    // LP Pair address for PulseX
    address public lpPair;

    // PulseX Router address
    address public pulseXRouter02 = 0x98bf93ebf5c380C0e6Ae8e192A7e2AE08edAcc02;
    address public pulseXRouter01 = 0x165C3410fC91EF562C50559f7d2289fEbed552d9;

    //address public 9inch v3
    INonfungiblePositionManager public positionManager = INonfungiblePositionManager(0x18A532b36A9F6B10b3FEC5BF225C00A0Ec89B79E); 


    constructor(
        address _token0,
        address _token1,
        uint24 _fee,
        uint24 _slippage,
        address _lpPair,
        address _collectWallet,
        address _managerWallet
    ) Ownable(msg.sender) {
        token0 = _token0;
        token1 = _token1;
        fee = _fee;
        slippage = _slippage;
        lpPair = _lpPair; // Set the LP pair address
        collectWallet = _collectWallet;
        managerWallet = _managerWallet;
    }

    // Update token0 and token1 (only owner)
    function updateTokens(address _token0, address _token1, address _lpPair) external onlyOwner {
        token0 = _token0;
        token1 = _token1;
        lpPair = _lpPair;
    }

    // update fee
    function updateFee(uint24 _fee) external onlyOwner {
        fee = _fee;
    }

    // Update slippage
    function setSlippage(uint24 _slippage) external onlyOwner {
        slippage = _slippage;
    }

    // set manager wallet
    function setManager(address _managerWallet) external onlyOwner {
        managerWallet = _managerWallet;
    }

    function setLptokenId(uint256 _ID, uint128 _liquidity) external onlyOwner {
        lpTokenId = _ID;
        liquidity = _liquidity;
    }

    // Update callect wallet
    function setCollectWallet(address _collectWallet) external onlyOwner {
        collectWallet = _collectWallet;
    }

    // Get current upper and lower ticks
    function getTicks() external view returns (int24 tickLower, int24 tickUpper) {
        require(lpTokenId != 0, "No liquidity position found");
        return (lowertick, uppertick);
    }

    // get manager wallet
    function getManager() external view returns (address _managerWallet) {
        return (managerWallet);
    }

    // Get current upper and lower ticks
    function getlpID() external view returns (uint256 _lpTokenId) {
        return lpTokenId;
    }

    function mintLiquidity(
        int24 _tickLower,
        int24 _tickUpper
    ) external payable {
        require(msg.sender == managerWallet || msg.sender == owner(), "Not authorized");
        require(lpTokenId == 0, "Another liquidity position found");

        // Fetch the current balances of token0 and token1
        uint256 balance0 = token0 == WPLS ? address(this).balance : IERC20(token0).balanceOf(address(this));
        uint256 balance1 = IERC20(token1).balanceOf(address(this));

        // Ensure the contract has sufficient balances of token0 and token1
        require(balance0 > 0 && balance1 > 0, "Insufficient token balances");

        // Calculate the minimum amounts based on slippage tolerance
        uint256 amount0Min = (balance0 * (100 - slippage)) / 100; // e.g., 5% slippage tolerance
        uint256 amount1Min = (balance1 * (100 - slippage)) / 100;

        // Approve the position manager to spend the tokens
        if (token0 != WPLS) {
            IERC20(token0).approve(address(positionManager), balance0);
        }
        IERC20(token1).approve(address(positionManager), balance1);

        // Define the mint parameters
        INonfungiblePositionManager.MintParams memory params = INonfungiblePositionManager.MintParams({
            token0: token0,
            token1: token1,
            fee: fee,
            tickLower: _tickLower,
            tickUpper: _tickUpper,
            amount0Desired: balance0, // Use the entire balance of token0
            amount1Desired: balance1, // Use the entire balance of token1
            amount0Min: amount0Min,
            amount1Min: amount1Min,
            recipient: address(this), // Mint the NFT to this contract
            deadline: block.timestamp + 10 minutes
        });

        // Encode the calls for multicall
        bytes[] memory data = new bytes[](2);
        data[0] = abi.encodeWithSelector(
            positionManager.mint.selector,
            params
        );
        data[1] = abi.encodeWithSelector(
            positionManager.refundETH.selector
        );

        // Execute the multicall
        bytes[] memory results = positionManager.multicall{value: token0 == WPLS ? balance0 : 0}(data);

        // Decode the result of the mint call to get tokenId, liquidity, amount0, and amount1
        (uint256 tokenId, uint128 _liquidity, uint256 amount0, uint256 amount1) = abi.decode(
            results[0],
            (uint256, uint128, uint256, uint256)
        );

        // Update the state variables
        lpTokenId = tokenId;
        liquidity = _liquidity;
        lowertick = _tickLower;
        uppertick = _tickUpper;

    }   

    // Decrease liquidity, collect fees, unwrap WPLS, and sweep tokens in a single multicall
    function decreaseAndCollect() external  {
        require(msg.sender == managerWallet || msg.sender == owner(), "Not authorized");
        require(lpTokenId != 0, "No liquidity position found");

        // Encode the calls for multicall
        bytes[] memory data = new bytes[](4);

        // 2. Collect fees, send to collectWallet
        data[0] = abi.encodeWithSelector(
            positionManager.collect.selector,
            INonfungiblePositionManager.CollectParams({
                tokenId: lpTokenId,
                recipient: collectWallet,
                amount0Max: type(uint128).max, // Collect all available fees
                amount1Max: type(uint128).max
            })
        );

        // 1. Decrease liquidity (remove all liquidity)
        data[1] = abi.encodeWithSelector(
            positionManager.decreaseLiquidity.selector,
            INonfungiblePositionManager.DecreaseLiquidityParams({
                tokenId: lpTokenId,
                liquidity: liquidity, // Remove all liquidity
                amount0Min: 0, // No minimum amount enforced
                amount1Min: 0, // No minimum amount enforced
                deadline: block.timestamp + 10 minutes
            })
        );

        // 2. Collect the removed tokens
        data[2] = abi.encodeWithSelector(
            positionManager.collect.selector,
            INonfungiblePositionManager.CollectParams({
                tokenId: lpTokenId,
                recipient: address(this),
                amount0Max: type(uint128).max, // Collect all available fees
                amount1Max: type(uint128).max
            })
        );

        // 4. Sweep tokens (e.g., dust or uncollected fees)
        data[3] = abi.encodeWithSelector(
            positionManager.sweepToken.selector,
            token0 == WPLS ? token1 : token0, // Sweep the non-WPLS token
            0, // No minimum amount enforced
            address(this)
        );

        // Execute the multicall
        positionManager.multicall(data);

        // Set lpTokenId to zero after decreasing liquidity
        lpTokenId = 0;
        liquidity = 0;

    }

    

    function getBestRouter(address tokenIn, address tokenOut, uint256 amountIn)
        internal
        view
        returns (address bestRouter, uint256 bestAmountOut)
    {
        address[] memory path = new address[](2);
        path[0] = tokenIn;
        path[1] = tokenOut;

        uint256 amountOut01 = getAmountOutSafe(pulseXRouter01, amountIn, path);
        uint256 amountOut02 = getAmountOutSafe(pulseXRouter02, amountIn, path);

        if (amountOut01 >= amountOut02) {
            return (pulseXRouter01, amountOut01);
        } else {
            return (pulseXRouter02, amountOut02);
        }
    }

    function rebalance() external {
        require(msg.sender == managerWallet || msg.sender == owner(), "Not authorized");
        require(lpTokenId == 0, "Liquidity position must be closed before rebalancing");

        // Unwrap any WPLS to PLS
        uint256 wplsBalance = IERC20(WPLS).balanceOf(address(this));
        if (wplsBalance > 0) {
            IWPLS(WPLS).withdraw(wplsBalance);
        }

        // Get the current balances of token0 and token1
        uint256 balance0 = token0 == WPLS ? address(this).balance : IERC20(token0).balanceOf(address(this));
        uint256 balance1 = IERC20(token1).balanceOf(address(this));

        // Get the price of token1 in terms of token0
        uint256 price1In0 = getToken1PriceInToken0();

        // Calculate the USD value of token0 and token1
        uint256 value0 = balance0;
        uint256 value1 = (balance1 * price1In0) / 1e18;

        if (value0 > value1) {
            // Swap excess token0 for token1
            uint256 excess0 = (value0 - value1) / 2;

            if (token0 == WPLS) {
                // Swap PLS for token1
                (address bestRouter, uint256 expectedOut) = getBestRouter(WPLS, token1, excess0);
                require(expectedOut > 0, "No route available");
                uint256 amountOutMin = (expectedOut * (100 - slippage)) / 100;

                address[] memory path = new address[](2);
                path[0] = WPLS;
                path[1] = token1;

                IPulseXRouter(bestRouter).swapExactETHForTokens{value: excess0}(
                    amountOutMin, path, address(this), block.timestamp + 10 minutes
                );
            } else {
                // Swap token0 for PLS
                (address bestRouter, uint256 expectedOut) = getBestRouter(token0, WPLS, excess0);
                require(expectedOut > 0, "No route available");
                uint256 amountOutMin = (expectedOut * (100 - slippage)) / 100;

                IERC20(token0).approve(address(bestRouter), excess0); // FIX: approve the router actually used
                address[] memory path = new address[](2);
                path[0] = token0;
                path[1] = WPLS;

                IPulseXRouter(bestRouter).swapExactTokensForETHSupportingFeeOnTransferTokens(
                    excess0, amountOutMin, path, address(this), block.timestamp + 10 minutes
                );
            }
        } else if (value1 > value0) {
            // Swap excess token1 for token0
            uint256 excess1 = ((value1 - value0) / 2) * 1e18 / price1In0;

            if (token1 == WPLS) {
                // Swap PLS for token0
                (address bestRouter, uint256 expectedOut) = getBestRouter(WPLS, token0, excess1);
                require(expectedOut > 0, "No route available");
                uint256 amountOutMin = (expectedOut * (100 - slippage)) / 100;

                address[] memory path = new address[](2);
                path[0] = WPLS;
                path[1] = token0;

                IPulseXRouter(bestRouter).swapExactETHForTokens{value: excess1}(
                    amountOutMin, path, address(this), block.timestamp + 10 minutes
                );
            } else {
                // Swap token1 for PLS
                (address bestRouter, uint256 expectedOut) = getBestRouter(token1, WPLS, excess1);
                require(expectedOut > 0, "No route available");
                uint256 amountOutMin = (expectedOut * (100 - slippage)) / 100;

                IERC20(token1).approve(address(bestRouter), excess1);
                address[] memory path = new address[](2);
                path[0] = token1;
                path[1] = WPLS;

                IPulseXRouter(bestRouter).swapExactTokensForETHSupportingFeeOnTransferTokens(
                    excess1, amountOutMin, path, address(this), block.timestamp + 10 minutes
                );
            }
        }
    }

    // Safe wrapper for getAmountsOut to prevent reverts
    function getAmountOutSafe(address router, uint256 amountIn, address[] memory path) internal view returns (uint256) {
        IPulseXRouter Irouter = IPulseXRouter(router);
        try Irouter.getAmountsOut(amountIn, path) returns (uint256[] memory amounts) {
            return amounts[1];
        } catch {
            return 0;
        }
    }

    // Function to get the price of token1 in terms of token0
    function getToken1PriceInToken0() public view returns (uint256) {
        // Get the reserves of the pool
        (uint112 reserve0, uint112 reserve1, ) = IPulseXPool(lpPair).getReserves();

        // Calculate the price of token1 in terms of token0
        return (uint256(reserve0) * 1e18) / uint256(reserve1);
    }

    // Transfer the liquidity position NFT to another wallet
    function transferPositionNFT(address to) external onlyOwner {
        require(lpTokenId != 0, "No liquidity position found");
        // Transfer the NFT from this contract to the provided address
        positionManager.safeTransferFrom(address(this), to, lpTokenId);

        lpTokenId = 0;
    }

    // Withdraw ERC-20 tokens from the contract
    function withdrawTokens(address token, uint256 amount) external onlyOwner {
        IERC20(token).safeTransfer(msg.sender, amount);
    }

   function withdrawPLS(uint256 amount) external onlyOwner {
        require(address(this).balance >= amount, "Insufficient PLS balance");
        (bool success, ) = payable(msg.sender).call{value: amount}("");
        require(success, "PLS transfer failed");
    }

    // ERC-721 receiver function
    function onERC721Received(
        address, // operator (unused)
        address, // from (unused)
        uint256 ID, // tokenId (unused)
        bytes calldata // data (unused)
    ) external override returns (bytes4) {
        lpTokenId = ID;
        return this.onERC721Received.selector;
    }

    // Allow the contract to receive PLS (native token)
    receive() external payable {}
}