Address
0x2e087da8b33746554e53c8d630098e591ea2a8a2Current 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
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 {}
}