> For the complete documentation index, see [llms.txt](https://docs.ferra.ag/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ferra.ag/integration/damm/typescript-sdk/position.md).

# Position

### Get Positions

Retrieve liquidity positions from Ferra DAMM pools.

#### Quick Start

```typescript
// Get all user positions
const positions = await sdk.Position.getPositionList('0x...')

// Get single position
const position = await sdk.Position.getPositionById('0x...')
```

#### Get User Positions

```typescript
const userAddress = '0x...'

// All positions
const allPositions = await sdk.Position.getPositionList(userAddress)

// Filter by pools
const poolIds = ['0xpool1...', '0xpool2...']
const poolPositions = await sdk.Position.getPositionList(userAddress, poolIds)

// Without display data (faster)
const positions = await sdk.Position.getPositionList(userAddress, [], false)
```

#### Get Single Position

```typescript
const positionId = '0x...'

// With rewards
const position = await sdk.Position.getPositionById(positionId)

// Without rewards (faster)
const position = await sdk.Position.getPositionById(positionId, false)
```

#### Get Simple Position

Lightweight fetch without reward calculations — faster when you only need basic position data:

```typescript
const position = await sdk.Position.getSimplePosition(positionId)

// Without NFT display metadata (even faster)
const position = await sdk.Position.getSimplePosition(positionId, false)
```

#### Position Data

```typescript
type Position = {
  // Identity
  pos_object_id: string
  owner: string
  pool: string
  type: string
  index: number

  // Price range
  tick_lower_index: number
  tick_upper_index: number
  liquidity: string

  // Tokens
  coin_type_a: string
  coin_type_b: string

  // Status
  position_status: string
  lock_until: string

  // Fees (if calculated)
  fee_growth_inside_a?: string
  fee_growth_inside_b?: string
  fee_owed_a?: string
  fee_owed_b?: string

  // Rewards (if calculated)
  reward_amount_owed_0?: string
  reward_amount_owed_1?: string
  reward_amount_owed_2?: string
  reward_growth_inside_0?: string
  reward_growth_inside_1?: string
  reward_growth_inside_2?: string

  // NFT display
  name?: string
  description?: string
  link?: string
  image_url?: string
}
```

#### Get Token Amounts

```typescript
const lowerSqrtPrice = TickMath.tickIndexToSqrtPriceX64(
  position.tick_lower_index
);
const upperSqrtPrice = TickMath.tickIndexToSqrtPriceX64(
  position.tick_upper_index
);

const liquidity = new BN(position.liquidity);
const slippage = new Percentage(new BN(0), new BN(1000));
const curSqrtPrice = new BN(pool.currentSqrtPrice);
const coinAmounts = DammPoolUtil.getCoinAmountFromLiquidity(
  liquidity,
  curSqrtPrice,
  lowerSqrtPrice,
  upperSqrtPrice,
  true
);

const { tokenMaxA, tokenMaxB } = adjustForCoinSlippage(
  coinAmounts,
  slippage,
  false
);

const amountA = tokenMaxA.toNumber();
const amountB = tokenMaxB.toNumber();

console.log(amountA, amountB)
```

#### Filter Positions

**Active Positions**

```typescript
const activePositions = positions.filter(
  pos => BigInt(pos.liquidity) > 0n
)
```

**In-Range Positions**

```typescript
const pool = await sdk.Pool.getPool(poolId)
const currentTick = pool.currentTickIndex

const inRange = positions.filter(pos =>
  pos.tick_lower_index <= currentTick &&
  currentTick < pos.tick_upper_index
)
```

**By Token Pair**

```typescript
const suiUsdc = positions.filter(pos =>
  pos.coin_type_a.includes('sui') &&
  pos.coin_type_b.includes('usdc')
)
```

#### Transaction History

```typescript
const history = await sdk.Position.getPositionTransactionList({
  posId: positionId,
  paginationArgs: { limit: 10 }
})

history.data.forEach(tx => {
  console.log(tx.type, tx.timestamp)
})
```

#### Complete Example

```typescript
// 1. Get all user positions
const userAddress = '0x...'
const positions = await sdk.Position.getPositionList(userAddress)

// 2. Check each position
for (const pos of positions) {
  // Get pool info
  const pool = await sdk.Pool.getPool(pos.pool)

  // Check if in range
  const inRange = pos.tick_lower_index <= pool.currentTickIndex &&
                  pool.currentTickIndex < pos.tick_upper_index

  // Calculate fees using Rewarder module
  const feeResult = await sdk.Rewarder.fetchPosFeeAmount([{
    poolAddress: pool.poolAddress,
    positionId: pos.pos_object_id,
    coinTypeA: pool.coinTypeA,
    coinTypeB: pool.coinTypeB
  }])

  console.log({
    position: pos.pos_object_id,
    liquidity: pos.liquidity,
    inRange,
    fees: feeResult.length > 0 ? {
      tokenA: feeResult[0].feeOwedA.toString(),
      tokenB: feeResult[0].feeOwedB.toString()
    } : null
  })
}
```

#### Error Handling

```typescript
try {
  const position = await sdk.Position.getPositionById(posId)
} catch (error) {
  console.log('Position not found')
}
```

***

### Open Position

Create a new liquidity position NFT in a Ferra DAMM pool.

#### Quick Start

```typescript
import { dammMainnet, initFerraSDK } from '@ferra-labs/damm'

// Initialize SDK
const sdk = initFerraSDK({
  network: 'beta',
  fullNodeUrl: 'https://...',
  wallet: '0x...'
})

const tx = sdk.Position.openPositionTransactionPayload({
  pool_id: '0x...',
  coinTypeA: '0x2::sui::SUI',
  coinTypeB: '0x...::usdc::USDC',
  tick_lower: '-1000',
  tick_upper: '1000'
})

// Execute
const result = await sdk.fullClient.signAndExecuteTransaction({
  transaction: tx,
  signer: keypair
})
```

#### Parameters

| Parameter   | Type   | Description         |
| ----------- | ------ | ------------------- |
| pool\_id    | string | Pool object ID      |
| coinTypeA   | string | Type of coin A      |
| coinTypeB   | string | Type of coin B      |
| tick\_lower | string | Lower tick boundary |
| tick\_upper | string | Upper tick boundary |

#### Price Range

**Set Custom Range**

```typescript
import { TickMath } from '@ferra-labs/damm'

// Price range: 0.9 - 1.1 USDC per SUI
const lowerPrice = 0.9
const upperPrice = 1.1

const tickLower = TickMath.priceToTickIndex(lowerPrice, 9, 6)
const tickUpper = TickMath.priceToTickIndex(upperPrice, 9, 6)
```

**Range Around Current Price**

```typescript
const pool = await sdk.Pool.getPool(poolId)
const currentTick = pool.currentTickIndex
const tickSpacing = Number(pool.tickSpacing)

// ±10 ticks from current price
const tickLower = currentTick - 10 * tickSpacing
const tickUpper = currentTick + 10 * tickSpacing
```

#### Complete Example

```typescript
// 1. Get pool info
const pool = await sdk.Pool.getPool(poolId)

// 2. Set price range
const tickLower = '-1000'
const tickUpper = '1000'

// 3. Open position
const tx = sdk.Position.openPositionTransactionPayload({
  pool_id: poolId,
  coinTypeA: pool.coinTypeA,
  coinTypeB: pool.coinTypeB,
  tick_lower: tickLower,
  tick_upper: tickUpper
})

// 4. Execute
const result = await sdk.fullClient.signAndExecuteTransaction({
  transaction: tx,
  signer: keypair
})

console.log('Position NFT created:', result.digest)
```

#### Important Notes

* Opening a position creates an empty NFT without liquidity
* Add liquidity separately after position creation
* Position NFT is transferable and tradeable
* Each position has unique tick range that cannot be changed

***

### Add Liquidity

Add tokens to a liquidity position in Ferra DAMM pools.

#### Methods

**1. Add Liquidity by Amount**

Specify exact liquidity amount to add:

```typescript
import { dammMainnet, initFerraSDK } from '@ferra-labs/damm'

// Initialize SDK
const sdk = initFerraSDK({
  network: 'beta',
  fullNodeUrl: 'https://...',
  wallet: '0x...'
})

const poolId = "0x..."
const pool = await sdk.Pool.getPool(poolId)
const curSqrtPrice = new BN(pool.currentSqrtPrice)
const tick_lower_index = -220
const tick_upper_index = 200
const slippageTolerance = new Percentage(new BN(5), new BN(100))

const totalAmount = '10'
const tokenPriceA = '0.2'
const tokenPriceB = '1'

const coinAmounts = DammPoolUtil.estCoinAmountsFromTotalAmount(
  tick_lower_index,
  tick_upper_index,
  curSqrtPrice,
  totalAmount,
  tokenPriceA,
  tokenPriceB
)

const amountA = toDecimalsAmount(coinAmounts.amountA.toFixed(6, Decimal.ROUND_UP).toString(), 6)
const amountB = toDecimalsAmount(coinAmounts.amountB.toFixed(6, Decimal.ROUND_UP).toString(), 6)

const tokenAmounts = {
  coinA: new BN(amountA),
  coinB: new BN(amountB),
}

const liquidity = DammPoolUtil.estimateLiquidityFromcoinAmounts(
  curSqrtPrice,
  tick_lower_index,
  tick_upper_index,
  tokenAmounts
)

console.log('liquidity: ', liquidity.toString())

const { tokenMaxA, tokenMaxB } = adjustForCoinSlippage(tokenAmounts, slippageTolerance, true)

const addLiquidityPayloadParams: AddLiquidityParams = {
  coinTypeA: pool.coinTypeA,
  coinTypeB: pool.coinTypeB,
  pool_id: pool.poolAddress,
  tick_lower: tick_lower_index.toString(),
  tick_upper: tick_upper_index.toString(),
  delta_liquidity: liquidity.toString(),
  max_amount_a: tokenMaxA.toString(),
  max_amount_b: tokenMaxB.toString(),
  pos_id: '',
  rewarder_coin_types: [],
  collect_fee: false,
}

const payload = await sdk.Position.createAddLiquidityPayload(addLiquidityPayloadParams)

const transferTxn = await sdk.fullClient.sendTransaction(sendKeypair, payload)
console.log('createAddLiquidityPayload: ', transferTxn)
```

**2. Add Liquidity Fixed Token**

Fix one token amount, calculate the other:

```typescript
import { dammMainnet, initFerraSDK } from '@ferra-labs/damm'

// Initialize SDK
const sdk = initFerraSDK({
  network: 'beta',
  fullNodeUrl: 'https://...',
  wallet: '0x...'
})

const poolId = '0x...'
const pool = await sdk.Pool.getPool(poolId)
const lowerTick = TickMath.getPrevInitializableTickIndex(
  new BN(pool.currentTickIndex).toNumber(),
  new BN(pool.tickSpacing).toNumber()
)
const upperTick = TickMath.getNextInitializableTickIndex(
  new BN(pool.currentTickIndex).toNumber(),
  new BN(pool.tickSpacing).toNumber()
)
const coinAmount = new BN(100)
const fix_amount_a = true
const slippage = 0.01
const curSqrtPrice = new BN(pool.currentSqrtPrice)

const liquidityInput = DammPoolUtil.estLiquidityAndcoinAmountFromOneAmounts(
  lowerTick,
  upperTick,
  coinAmount,
  fix_amount_a,
  true,
  slippage,
  curSqrtPrice
)

const amount_a = fix_amount_a ? coinAmount.toNumber() : liquidityInput.tokenMaxA.toNumber()
const amount_b = fix_amount_a ? liquidityInput.tokenMaxB.toNumber() : coinAmount.toNumber()

const addLiquidityPayloadParams: AddLiquidityFixTokenParams = {
  coinTypeA: pool.coinTypeA,
  coinTypeB: pool.coinTypeB,
  pool_id: pool.poolAddress,
  tick_lower: lowerTick.toString(),
  tick_upper: upperTick.toString(),
  fix_amount_a,
  amount_a,
  amount_b,
  slippage,
  is_open: true,
  rewarder_coin_types: [],
  collect_fee: false,
  pos_id: '',
}

const tx = await sdk.Position.createAddLiquidityFixTokenPayload(addLiquidityPayloadParams, {
  slippage: slippage,
  curSqrtPrice: curSqrtPrice,
})

const result = await sdk.fullClient.signAndExecuteTransaction({
  transaction: tx,
  signer: keypair
})
```

#### Important Notes

* SDK automatically collects fees before adding liquidity
* Excess tokens are returned to sender
* Position must exist before adding liquidity
* Cannot change position's tick range

***

### Remove Liquidity

Withdraw tokens from a liquidity position in Ferra DAMM pools.

#### Quick Start

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

#### Parameters

| Parameter             | Type      | Description                         |
| --------------------- | --------- | ----------------------------------- |
| pool\_id              | string    | Pool object ID                      |
| pos\_id               | string    | Position object ID                  |
| coinTypeA             | string    | Type of coin A                      |
| coinTypeB             | string    | Type of coin B                      |
| delta\_liquidity      | string    | Amount of liquidity to remove       |
| min\_amount\_a        | string    | Minimum amount of coin A to receive |
| min\_amount\_b        | string    | Minimum amount of coin B to receive |
| collect\_fee          | boolean   | Whether to collect fees             |
| rewarder\_coin\_types | string\[] | Rewarder coin types to collect      |

#### Calculate Token Amounts

```typescript
async function estimateRemovePosition(
  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 = DammPoolUtil.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, decimalsA, decimalsB) {
  const curSqrtPrice = new BN(pool.currentSqrtPrice);

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

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

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

  const coinAValue = fromDecimalsAmount(
    amounts.coinA.toNumber(),
    decimalsA
  );

  const coinBValue = fromDecimalsAmount(
    amounts.coinB.toNumber(),
    decimalsB
  );

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

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

#### Complete Example

```typescript
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(
  pool,
  position,
  amountA,
  amountB
);

const rewards = await sdk.Rewarder.fetchPositionRewarders(
  pool,
  position.pos_object_id
);

const rewardCoinTypes = rewards
  .filter((item) => Number(item.amount_owed) > 0)
  .map((item) => item.coin_address);

const removeLiquidityPayloadParams: RemoveLiquidityParams = {
  coinTypeA: pool.coinTypeA,
  coinTypeB: pool.coinTypeB,
  pool_id: pool.poolAddress,
  min_amount_a: tokenMaxA.toString(),
  min_amount_b: tokenMaxB.toString(),
  collect_fee: true,
  rewarder_coin_types: rewardCoinTypes,
  pos_id: position.pos_object_id,
  delta_liquidity: new BN(position.liquidity).lte(new BN(liquidity))
    ? position.liquidity.toString()
    : liquidity.toString(),
};

const tx = await sdk.Position.removeLiquidityTransactionPayload(removeLiquidityPayloadParams);

const result = await sdk.fullClient.signAndExecuteTransaction({
  transaction: tx,
  signer: keypair
})
```

#### Close Position

Remove all liquidity and close a position permanently.

**Quick Start**

```typescript
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    | Pool object ID                      |
| pos\_id               | string    | Position object ID                  |
| coinTypeA             | string    | Type of coin A                      |
| coinTypeB             | string    | Type of coin B                      |
| min\_amount\_a        | string    | Minimum amount of coin A to receive |
| min\_amount\_b        | string    | Minimum amount of coin B to receive |
| collect\_fee          | boolean   | Whether to collect fees             |
| rewarder\_coin\_types | string\[] | Rewarder coin types to collect      |

**Close with Slippage Protection**

```typescript
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 = DammPoolUtil.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,
  };
}
```

**Complete Example**

```typescript
const position = await sdk.Position.getPositionById(positionId)
const pool = await sdk.Pool.getPool(position.pool)
const amountA = 100000000
const amountB = 2000000000

const { tokenMaxA, tokenMaxB, liquidity } = await estimateClosePosition(
  pool,
  position,
  amountA,
  amountB
);

const rewards = await sdk.Rewarder.fetchPositionRewarders(
  pool,
  position.pos_object_id
);

const rewardCoinTypes = rewards
  .filter((item) => Number(item.amount_owed) > 0)
  .map((item) => item.coin_address);

const closeLiquidityPayloadParams: ClosePositionParams = {
  coinTypeA: pool.coinTypeA,
  coinTypeB: pool.coinTypeB,
  pool_id: pool.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**

| Feature            | Close Position | Remove Liquidity |
| ------------------ | -------------- | ---------------- |
| Burns Position NFT | Yes            | No               |
| Partial Removal    | No             | Yes              |
| Recoverable        | No             | Yes              |

***

### Lock Position

Lock a position to prevent closing or removing liquidity until a specified time. Useful for governance, vesting, or proving commitment to liquidity.

#### Lock a Position

```typescript
const pool = await sdk.Pool.getPool(poolId)

// Lock for 30 days
const thirtyDaysFromNow = Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60

const tx = await sdk.Position.lockPosition(
  pool,
  positionId,
  thirtyDaysFromNow
)

const result = await sdk.fullClient.signAndExecuteTransaction({
  transaction: tx,
  signer: keypair
})

console.log('Position locked:', result.digest)
```

#### Check Lock Status

```typescript
// By position ID
const [lockTimestamp, currentTimestamp, isLocked] =
  await sdk.Position.getLockPositionStatusById(positionId)

console.log({
  lockedUntil: new Date(lockTimestamp * 1000).toISOString(),
  currentTime: new Date(currentTimestamp * 1000).toISOString(),
  isLocked
})

// From position object (no extra RPC call)
const position = await sdk.Position.getPositionById(positionId)
const [lock, now, locked] = await sdk.Position.getLockPositionStatus(position)

if (locked) {
  const remainingMs = (lock - now) * 1000
  console.log(`Unlocks in ${Math.ceil(remainingMs / 86400000)} days`)
}
```

#### Important Notes

* Locked positions cannot be closed or have liquidity removed
* You can still collect fees and rewards from locked positions
* Lock time can only be extended, not shortened
* The lock timestamp is in Unix seconds

***

#### Error Handling

```typescript
try {
  const tx = await sdk.Position.removeLiquidityTransactionPayload(params)
} catch (error) {
  if (error.message.includes('Slippage exceeded')) {
    console.log('Price moved too much, increase slippage')
  }
  if (error.message.includes('Insufficient liquidity')) {
    console.log('Not enough liquidity in position')
  }
}
```

#### Important Notes

* SDK automatically collects fees before removing liquidity
* Tokens are sent directly to sender address
* Cannot remove more liquidity than position contains
* Position NFT remains after removing all liquidity
* Closing automatically collects all fees and rewards
* Position NFT is burned on close and cannot be recovered
