> ## Documentation Index
> Fetch the complete documentation index at: https://build.flashnet.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Managing Positions

> List, monitor, rebalance, and close V3 liquidity positions

V3 positions are non-fungible. Each position is uniquely identified by the pool, your public key, and the tick range. You can have multiple positions in the same pool at different ranges.

## List Your Positions

```typescript theme={null}
async function listPositions() {
  const response = await client.listConcentratedPositions();

  console.log(`Total positions: ${response.totalCount}`);

  for (const position of response.positions) {
    console.log(`Pool: ${position.poolId}`);
    console.log(`  Range: ${position.tickLower} to ${position.tickUpper}`);
    console.log(`  Liquidity: ${position.liquidity}`);
    console.log(`  In range: ${position.inRange}`);
    console.log(`  Uncollected fees: ${position.uncollectedFeesA} A, ${position.uncollectedFeesB} B`);
  }

  return response.positions;
}
```

### Filter by Pool

```typescript theme={null}
const response = await client.listConcentratedPositions({
  poolId: "specific-pool-id",
});
```

### Pagination

```typescript theme={null}
const response = await client.listConcentratedPositions({
  page: 2,
  pageSize: 50,  // max 100
});

console.log(`Page ${response.page} of ${Math.ceil(response.totalCount / response.pageSize)}`);
```

## Position Data

Each position includes:

| Field              | Description                                |
| ------------------ | ------------------------------------------ |
| `poolId`           | Pool identifier (LP identity public key)   |
| `owner`            | Your public key                            |
| `tickLower`        | Lower tick of your range                   |
| `tickUpper`        | Upper tick of your range                   |
| `liquidity`        | V3 liquidity units in your position        |
| `uncollectedFeesA` | Earned fees for asset A not yet claimed    |
| `uncollectedFeesB` | Earned fees for asset B not yet claimed    |
| `assetAAddress`    | Token A identifier                         |
| `assetBAddress`    | Token B identifier                         |
| `inRange`          | Whether current price is within your range |
| `createdAt`        | Position creation timestamp                |

## Understanding Liquidity Units

The `liquidity` field is a V3 math value, not a token amount. To understand what assets you have:

```typescript theme={null}
import { V3TickMath } from '@flashnet/sdk';

function estimatePositionValue(
  liquidity: string,
  tickLower: number,
  tickUpper: number,
  currentTick: number
) {
  // If out of range below, position is 100% asset A
  if (currentTick < tickLower) {
    console.log('Position is 100% asset A (below range)');
  }
  // If out of range above, position is 100% asset B
  else if (currentTick >= tickUpper) {
    console.log('Position is 100% asset B (above range)');
  }
  // If in range, mixed
  else {
    console.log('Position is mixed (in range)');
  }

  // For precise amounts, use getPoolLiquidity which returns range data
}
```

## Monitoring Position Health

### Check Range Status

```typescript theme={null}
async function checkPositionHealth(poolId: string, tickLower: number, tickUpper: number) {
  const positions = await client.listConcentratedPositions({ poolId });

  const position = positions.positions.find(
    p => p.tickLower === tickLower && p.tickUpper === tickUpper
  );

  if (!position) {
    throw new Error('Position not found');
  }

  if (position.inRange) {
    console.log('Position is earning fees');
  } else {
    console.log('Position is out of range - consider rebalancing');
  }

  // Check uncollected fees
  const hasSignificantFees =
    BigInt(position.uncollectedFeesA) > 0n ||
    BigInt(position.uncollectedFeesB) > 0n;

  if (hasSignificantFees) {
    console.log('Consider collecting fees:', {
      feeA: position.uncollectedFeesA,
      feeB: position.uncollectedFeesB,
    });
  }

  return position;
}
```

### Calculate Range as Prices

```typescript theme={null}
import { V3TickMath } from '@flashnet/sdk';

function getPositionPriceRange(
  tickLower: number,
  tickUpper: number,
  baseDecimals: number,
  quoteDecimals: number
) {
  const priceLower = V3TickMath.tickToHumanPrice({
    tick: tickLower,
    baseDecimals,
    quoteDecimals,
  });

  const priceUpper = V3TickMath.tickToHumanPrice({
    tick: tickUpper,
    baseDecimals,
    quoteDecimals,
  });

  console.log(`Price range: $${priceLower} - $${priceUpper}`);
  return { priceLower, priceUpper };
}
```

## Position Lifecycle

### Create New Position

```typescript theme={null}
async function createPosition(
  poolId: string,
  priceLower: string,
  priceUpper: string,
  amountA: string,
  amountB: string
) {
  // Convert prices to ticks
  const range = V3TickMath.tickRangeFromPrices({
    priceLower,
    priceUpper,
    baseDecimals: 8,
    quoteDecimals: 6,
    tickSpacing: 60,
  });

  const response = await client.increaseLiquidity({
    poolId,
    tickLower: range.tickLower,
    tickUpper: range.tickUpper,
    amountADesired: amountA,
    amountBDesired: amountB,
    amountAMin: "0",
    amountBMin: "0",
  });

  console.log('Position created');
  console.log('Liquidity:', response.liquidityAdded);
  console.log('Used:', response.amountAUsed, 'A,', response.amountBUsed, 'B');

  return response;
}
```

### Add to Existing Position

Adding liquidity to the same tick range increases your existing position:

```typescript theme={null}
async function addToPosition(
  poolId: string,
  tickLower: number,
  tickUpper: number,
  additionalA: string,
  additionalB: string
) {
  const response = await client.increaseLiquidity({
    poolId,
    tickLower,
    tickUpper,
    amountADesired: additionalA,
    amountBDesired: additionalB,
    amountAMin: "0",
    amountBMin: "0",
  });

  console.log('Added to position');
  console.log('New total liquidity increase:', response.liquidityAdded);

  return response;
}
```

### Collect Fees

```typescript theme={null}
async function collectPositionFees(poolId: string, tickLower: number, tickUpper: number) {
  const response = await client.collectFees({
    poolId,
    tickLower,
    tickUpper,
  });

  console.log('Fees collected');
  console.log('Asset A:', response.feesCollectedA);
  console.log('Asset B:', response.feesCollectedB);

  return response;
}
```

### Remove Partial Liquidity

```typescript theme={null}
async function removePartial(
  poolId: string,
  tickLower: number,
  tickUpper: number,
  liquidityToRemove: string
) {
  const response = await client.decreaseLiquidity({
    poolId,
    tickLower,
    tickUpper,
    liquidityToRemove,
    amountAMin: "0",
    amountBMin: "0",
  });

  console.log('Removed:', response.liquidityRemoved, 'liquidity');
  console.log('Received:', response.amountA, 'A,', response.amountB, 'B');
  console.log('Fees:', response.feesCollectedA, 'A,', response.feesCollectedB, 'B');

  return response;
}
```

### Close Position

Pass `"0"` as liquidity to remove everything:

```typescript theme={null}
async function closePosition(poolId: string, tickLower: number, tickUpper: number) {
  const response = await client.decreaseLiquidity({
    poolId,
    tickLower,
    tickUpper,
    liquidityToRemove: "0",  // Remove all
    amountAMin: "0",
    amountBMin: "0",
  });

  console.log('Position closed');
  console.log('Total received:', response.amountA, 'A,', response.amountB, 'B');
  console.log('Final fees:', response.feesCollectedA, 'A,', response.feesCollectedB, 'B');

  return response;
}
```

### Rebalance to New Range

When price moves out of your range, rebalance atomically:

<Warning>
  Rebalancing only supports moving to a **tighter range** (subset of the original). The new range must require less or equal capital. Rebalancing to a wider range that requires additional deposits is not supported.
</Warning>

```typescript theme={null}
async function rebalanceToTighterRange(poolId: string, currentPosition: {
  tickLower: number;
  tickUpper: number;
}) {
  // Get current pool state
  const liquidity = await client.getPoolLiquidity(poolId);
  const currentTick = liquidity.currentTick;
  const tickSpacing = liquidity.tickSpacing;

  // Create a TIGHTER range inside the current position
  // New range must be a subset of old range to avoid requiring additional deposits
  const shrinkAmount = tickSpacing * 2;
  const newLower = currentPosition.tickLower + shrinkAmount;
  const newUpper = currentPosition.tickUpper - shrinkAmount;

  const response = await client.rebalancePosition({
    poolId,
    oldTickLower: currentPosition.tickLower,
    oldTickUpper: currentPosition.tickUpper,
    newTickLower: newLower,
    newTickUpper: newUpper,
    liquidityToMove: "0",  // Move all
  });

  console.log('Rebalanced');
  console.log('New range:', newLower, 'to', newUpper);
  console.log('Old liquidity:', response.oldLiquidity);
  console.log('New liquidity:', response.newLiquidity);

  return response;
}
```

## Multiple Positions Strategy

You can deploy capital across multiple ranges:

```typescript theme={null}
async function deployMultipleRanges(poolId: string, totalAmountA: string, totalAmountB: string) {
  const ranges = [
    { priceLower: "80000", priceUpper: "85000", weight: 20 },
    { priceLower: "85000", priceUpper: "95000", weight: 60 },
    { priceLower: "95000", priceUpper: "100000", weight: 20 },
  ];

  const totalWeight = ranges.reduce((sum, r) => sum + r.weight, 0);

  for (const range of ranges) {
    const amountA = (BigInt(totalAmountA) * BigInt(range.weight) / BigInt(totalWeight)).toString();
    const amountB = (BigInt(totalAmountB) * BigInt(range.weight) / BigInt(totalWeight)).toString();

    const tickRange = V3TickMath.tickRangeFromPrices({
      priceLower: range.priceLower,
      priceUpper: range.priceUpper,
      baseDecimals: 8,
      quoteDecimals: 6,
      tickSpacing: 60,
    });

    await client.increaseLiquidity({
      poolId,
      tickLower: tickRange.tickLower,
      tickUpper: tickRange.tickUpper,
      amountADesired: amountA,
      amountBDesired: amountB,
      amountAMin: "0",
      amountBMin: "0",
    });

    console.log(`Created position at ${range.priceLower}-${range.priceUpper}`);
  }
}
```

## Slippage Protection

Use `amountAMin` and `amountBMin` to protect against price movement:

```typescript theme={null}
async function addLiquidityWithSlippage(
  poolId: string,
  tickLower: number,
  tickUpper: number,
  amountA: string,
  amountB: string,
  slippagePercent: number = 1  // 1% default
) {
  const slippageMultiplier = (100 - slippagePercent) / 100;

  const response = await client.increaseLiquidity({
    poolId,
    tickLower,
    tickUpper,
    amountADesired: amountA,
    amountBDesired: amountB,
    amountAMin: Math.floor(Number(amountA) * slippageMultiplier).toString(),
    amountBMin: Math.floor(Number(amountB) * slippageMultiplier).toString(),
  });

  return response;
}
```

If actual amounts fall below minimums due to price movement, the transaction fails and funds are returned.

## Next Steps

* [Free Balances](/products/flashnet-amm/concentrated-balances) - Reduce overhead for frequent rebalancing
* [Pool Creation](/products/flashnet-amm/concentrated-creation) - Launch new V3 pools
* [Position Locks](/products/flashnet-amm/lock-overview) - Commit a position to the pool until expiry
* [Error Handling](/products/flashnet-amm/errors) - Handle failures gracefully
