Position Management

Get user positions

const positions: ClmmPosition[] = positionManager.getUserPositions('0xaddress');

Get position reward

const positions: ClmmPosition[] = positionManager.getUserPositions('0xaddress');
const positionWithReward: ClmmPosition[] = positionManager.getPositionReward(positions);

Open/Increase a Position

import { ClmmPosition, CoinAmount, Percent, BN } from '@flowx-finance/sdk';
import { TransactionResult } from '@mysten/sui/transactions';


// Method 1: Create position with specific liquidity amount
const tickLower = -3000; // Lower price tick (must be divisible by tick spacing: 60 for MEDIUM fee)
const tickUpper = 3000; // Upper price tick (must be divisible by tick spacing: 60 for MEDIUM fee)
const liquidity = new BN(1000);

const position = new ClmmPosition({
  objectId: '0x...', // Optional: Your position object id if you want increase exist position
  owner: '0x...', // Your address
  pool: pool,
  tickLower,
  tickUpper,
  liquidity,
  coinsOwedX: 0,
  coinsOwedY: 0,
  feeGrowthInsideXLast: 0,
  feeGrowthInsideYLast: 0,
  rewardInfos: [],
});

// Method 2: Create position with specific token amounts
const amountX = new BN('1000000000'); // 1 SUI (9 decimals)
const amountY = new BN('1000000'); // 1 USDC (6 decimals)

const positionFromAmounts = ClmmPosition.fromAmounts({
  objectId: '0x...', // Optional: Your position object id if you want increase exist position
  owner: '0x...', // Your address
  pool: pool,
  tickLower: -3000,
  tickUpper: 3000,
  amountX: amountX,
  amountY: amountY,
  useFullPrecision: true, // Use full precision for liquidity calculation
});

// Method 3: Create position with only amountX (single-sided liquidity)
const positionOnlyX = ClmmPosition.fromAmountX({
  objectId: '0x...', // Optional: Your position object id if you want increase exist position
  owner: '0x...', // Your address
  pool: pool,
  tickLower: -3000,
  tickUpper: 3000,
  amountX: amountX, // Only provide amountX
  // amountY not provided - will be calculated based on current price
  useFullPrecision: true,
});

// Method 4: Create position with only amountY (single-sided liquidity)
const positionOnlyY = ClmmPosition.fromAmountY({
  objectId: '0x...', // Optional: Your position object id if you want increase exist position
  owner: '0x...', // Your address
  pool: pool,
  tickLower: -3000,
  tickUpper: 3000,
  amountY: amountY, // Only provide amountY
  // amountX not provided - will be calculated based on current price
  useFullPrecision: true,
});

// Create position with liquidity

const options = {
  slippageTolerance: new Percent(1, 100), // 1% slippage
  deadline: Date.now() + 3600 * 1000, // 1 hour from now
  createPosition: true,
};
const tx = new Transaction();
const createdPosition = positionManager.tx(tx).increaseLiquidity(position, options);
tx.transferObjects([createdPosition], recipient);

Decrease Liquidity


// Define MaxU64 constant for convenience
const MaxU64 = new BN('18446744073709551615');

// Example 1: Remove 50% of liquidity
const positionId = '0x...'; // Position object ID
const position = await positionManager.getPosition(positionId);
const liquidityToRemove = position.liquidity.div(new BN(2));

const positionWillBeDecreased = new ClmmPosition({
  owner: position.owner,
  pool: position.pool,
  tickLower: position.tickLower,
  tickUpper: position.tickUpper,
  liquidity: liquidityToRemove,
  coinsOwedX: 0,
  coinsOwedY: 0,
  feeGrowthInsideXLast: 0,
  feeGrowthInsideYLast: 0,
  rewardInfos: [],
});

const burnAmounts = {
  amountX: positionWillBeDecreased.amountX,
  amountY: positionWillBeDecreased.amountY,
};

const decreaseOptions = {
  slippageTolerance: new Percent(1, 100),
  deadline: Date.now() + 3600 * 1000, // 1 hour from now
  collectOptions: {
    expectedCoinOwedX: CoinAmount.fromRawAmount(coinX, burnAmounts.amountX),
    expectedCoinOwedY: CoinAmount.fromRawAmount(coinY, burnAmounts.amountY),
  },
};

const tx = new Transaction();
positionManager.tx(tx).decreaseLiquidity(position, decreaseOptions);

// Example 2: Remove all liquidity and close position
const positionToClose = await positionManager.getPosition(positionId);

const closeOptions = {
  slippageTolerance: new Percent(1, 100),
  deadline: Date.now() + 3600 * 1000, // 1 hour from now
  collectOptions: {
    // Use MaxU64 to ensure we collect all available fees regardless of fee growth during processing
    expectedCoinOwedX: CoinAmount.fromRawAmount(coinX, MaxU64),
    expectedCoinOwedY: CoinAmount.fromRawAmount(coinY, MaxU64),
  },
};

const closeTx = new Transaction();
positionManager.tx(closeTx).decreaseLiquidity(positionToClose, closeOptions);

// Get all available rewards before closing
const rewards = await positionToClose.getRewards();

// Collect all available rewards
for (let i = 0; i < rewards.length; i++) {
  if (rewards[i].gt(new BN(0))) {
    const collectRewardOptions = {
      expectedRewardOwed: CoinAmount.fromRawAmount(
        positionToClose.pool.poolRewards[i].coin,
        MaxU64 // Use MaxU64 for rewards as well
      ),
      recipient: '0x...', // Optional recipient
    };

    positionManager.collectPoolReward(positionToClose, i, collectRewardOptions);
  }
}

// Close the position (burn the NFT)
positionManager.closePosition(positionToClose, closeTx);

Collecting Fees and Rewards


// Define MaxU64 constant for convenience
const MaxU64 = new BN('18446744073709551615');

// Example 1: Collect accumulated fees only
const positionId = '0x...'; // Position object ID
const position = await positionManager.getPosition(positionId);

// Get current fees
const fees = await position.getFees();

if (fees.amountX.gt(new BN(0)) || fees.amountY.gt(new BN(0))) {
  const collectFeeOptions = {
    expectedCoinOwedX: CoinAmount.fromRawAmount(coinX, MaxU64),
    expectedCoinOwedY: CoinAmount.fromRawAmount(coinY, MaxU64),
    recipient: '0x...', // Optional recipient
  };

  const tx = new Transaction();
  positionManager.tx(tx);

  // Collect returns the collected coin objects
  const [collectedX, collectedY] = positionManager.collect(position, collectFeeOptions) as TransactionResult;

  // Transfer to recipient if not specified in collectFeeOptions
  if (!collectFeeOptions.recipient) {
    tx.transferObjects([collectedX, collectedY], position.owner);
  }
}

// Example 2: Collect specific reward tokens
const rewards = await position.getRewards();

for (let i = 0; i < rewards.length; i++) {
  if (rewards[i].gt(new BN(0))) {
    const collectRewardOptions = {
      expectedRewardOwed: CoinAmount.fromRawAmount(position.pool.poolRewards[i].coin, MaxU64),
      recipient: '0x...', // Optional recipient
    };

    const tx = new Transaction();
    positionManager.tx(tx);
    positionManager.collectPoolReward(position, i, collectRewardOptions);
  }
}

Rebalance example

const tx = new Transaction();
const rebalancer = new Rebalancer({
  network: "mainnet",
});
const newPosition = await rebalancer.rebalance(
  clmmPosition,
  tickLower,
  tickUpper,
  {
    slippageTolerance: 1000,
    priceImpactPercentThreshold: -5000,
    minZapAmounts: {
      amountX: 1000,
      amountY: 1000,
    },
  },
)(tx);
tx.transferObjects([newPosition], position.owner);

Last updated

Was this helpful?