Close Position

Remove all liquidity and close a position permanently.

Quick Start

const tx = await sdk.Position.closePositionTransactionPayload({
  pool_id: '0x...',
  pos_id: '0x...',
  coinTypeA: '0x2::sui::SUI',
  coinTypeB: '0x...::usdc::USDC',
  min_amount_a: '490000000',
  min_amount_b: '490000'
  collect_fee: true
  rewarder_coin_types: [],
})

Parameters

Parameter
Type
Description

pool_id

string

The object id about which pool you want to operation.

pos_id

string

Position object ID

coinTypeA

string

First token type

coinTypeB

string

Second token type

min_amount_a

string

Minimum token A to receive

min_amount_b

string

Minimum token B to receive

collect_fee

boolean

Indicates whether to collect fees during the removal.

rewarder_coin_types

string[]

Coin types associated with rewarder contracts.

Close with Slippage Protection

async function estimateClosePosition(
  pool,
  position,
  amountABigint,
  amountBBigint
) {
  const slippage = new Percentage(new BN(5), new BN(1000));
  const { curSqrtPrice, lowerSqrtPrice, upperSqrtPrice } = getCoinsRate(
    pool,
    position
  );

  const tokenAmounts = adjustForCoinSlippage(
    {
      coinA: new BN(amountABigint.toFixed(0)),
      coinB: new BN(amountBBigint.toFixed(0)),
    },
    slippage,
    false
  );
  const liquidity = ClmmPoolUtil.estimateLiquidityFromcoinAmounts(
    curSqrtPrice,
    Number(position.tick_lower_index),
    Number(position.tick_upper_index),
    {
      coinA: new BN(Math.ceil(amountABigint)),
      coinB: new BN(Math.ceil(amountBBigint)),
    }
  );
  return {
    tokenMaxA: tokenAmounts.tokenMaxA,
    tokenMaxB: tokenAmounts.tokenMaxB,
    lowerTick: lowerSqrtPrice,
    upperTick: upperSqrtPrice,
    curSqrtPrice,
    liquidity,
  };
}

function getCoinsRate(pool, position) {
  const curSqrtPrice = new BN(pool.current_sqrt_price);

  const lowerSqrtPrice = TickMath.tickIndexToSqrtPriceX64(
    position.tick_lower_index
  );

  const upperSqrtPrice = TickMath.tickIndexToSqrtPriceX64(
    position.tick_upper_index
  );

  const amounts = ClmmPoolUtil.getCoinAmountFromLiquidity(
    new BN(position.liquidity),
    curSqrtPrice,
    lowerSqrtPrice,
    upperSqrtPrice,
    false
  );

  const coinAValue = fromDecimalsAmount(
    amounts.coinA.toNumber(),
    pool.coinA?.decimals ?? 1
  );

  const coinBValue = fromDecimalsAmount(
    amounts.coinB.toNumber(),
    pool.coinB?.decimals ?? 1
  );

  const total = coinAValue + coinBValue;
  const percent = {
    ratioA: coinAValue / total,
    ratioB: coinBValue / total,
  };

  return {
    percent,
    curSqrtPrice,
    lowerSqrtPrice,
    upperSqrtPrice,
  };
}

Complete Example

const position = await sdk.Position.getPositionById(positionId)
const pool = await sdk.Pool.getPool(position.pool)
const amountA = 100000000
const amountB = 2000000000
const percent = 0.2 
const { tokenMaxA, tokenMaxB, liquidity } = await estimateRemovePosition(poolOnchain, position, amountA, amountB);
const rewards = await ferraSDK.Rewarder.fetchPositionRewarders(
        poolOnchain,
        position.pos_object_id
      );
const rewardCoinTypes = rewards
        .filter((item) => Number(item.amount_owed) > 0)
        .map((item) => item.coin_address);
const closeLiquidityPayloadParams: ClosePositionParams = {
        coinTypeA: poolOnchain.coinTypeA,
        coinTypeB: poolOnchain.coinTypeB,
        pool_id: poolOnchain.poolAddress,
        min_amount_a: tokenMaxA.toString(),
        min_amount_b: tokenMaxB.toString(),
        collect_fee: true,
        rewarder_coin_types: rewardCoinTypes,
        pos_id: position.pos_object_id,
      };
const tx = await sdk.Position.closePositionTransactionPayload(closeLiquidityPayloadParams);       
const result = await sdk.fullClient.signAndExecuteTransaction({
  transaction: tx,
  signer: keypair
})

Close vs Remove Liquidity

Action
Effect
Position NFT

Remove Liquidity

Withdraws tokens

Remains (empty)

Close Position

Withdraws all + burns NFT

Destroyed

Error Handling

try {
  const tx = await sdk.Position.closePositionTransactionPayload(params)
} catch (error) {
  if (error.message.includes('Slippage exceeded')) {
    console.log('Price moved, increase slippage')
  }
  if (error.message.includes('Position not found')) {
    console.log('Position does not exist')
  }
}

Important Notes

  • Closing automatically collects all fees and rewards

  • Position NFT is burned and cannot be recovered

  • All liquidity must be removed (SDK handles this)

  • Cannot close position with active orders

Last updated