# 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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.ferra.ag/integration/damm/typescript-sdk/position.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
