Managing Positions

Once you've added liquidity, you'll need to monitor your position, claim fees, and eventually remove liquidity. This page covers all three.

Reading a Position

import { readPosition } from '@taurus-swap/sdk';

const position = await readPosition(
  algodClient,
  POOL_APP_ID,
  address,
  tickId
);

console.log('Position:', {
  shares: position.shares,
  pendingFees: position.pendingFees,
  feeCheckpoints: position.feeCheckpoints
});

Position Type

interface Position {
  shares: bigint;           // Your share of tick's total liquidity
  pendingFees: bigint[];    // Accrued fees per token (computed client-side)
  feeCheckpoints: bigint[]; // Fee growth snapshot per token (on-chain)
}

Computing Pending Fees

The SDK computes pending fees using the fee growth formula:

// From @taurus-swap/sdk/pool/fees.ts

export function computePendingFees(
  positionShares: bigint,
  feeGrowth: bigint[],
  feeCheckpoints: bigint[],
  tickTotalR: bigint
): bigint[] {
  return feeGrowth.map((growth, i) => {
    const deltaGrowth = growth - feeCheckpoints[i];
    return (positionShares * deltaGrowth) / PRECISION / tickTotalR;
  });
}

This is called automatically by readPosition, so you getpendingFees ready to display.

Claiming Fees

import { buildClaimFeesGroup } from '@taurus-swap/sdk';

async function claimFees(tickId: number) {
  const { txGroup } = await buildClaimFeesGroup(
    algodClient,
    POOL_APP_ID,
    account,
    tickId
  );

  const signedTxns = await wallet.signTransaction(
    txGroup.map((tx) => tx.txn)
  );

  const result = await algodClient
    .sendGroupTransaction(signedTxns)
    .do();

  await algosdk.waitForConfirmation(algodClient, result.txId, 4);

  console.log('Fees claimed!', result.txId);
}

Fee Checkpoint Mechanic

When you claim fees, your checkpoint is updated to the current fee growth:

Before claim:
  feeCheckpoints = [100, 200, 150, ...]
  feeGrowth = [150, 250, 180, ...]
  pending = feeGrowth - checkpoint = [50, 50, 30, ...]

After claim:
  feeCheckpoints = [150, 250, 180, ...]  ← Updated to current
  pending = [0, 0, 0, ...]  ← Reset

This ensures you don't claim the same fees twice.

Removing Liquidity

To withdraw your entire position:

import { buildRemoveLiquidityGroup } from '@taurus-swap/sdk';

async function removeLiquidity(tickId: number) {
  const position = await readPosition(
    algodClient,
    POOL_APP_ID,
    account.addr,
    tickId
  );

  const { txGroup } = await buildRemoveLiquidityGroup(
    algodClient,
    POOL_APP_ID,
    account,
    {
      tickId,
      sharesToRemove: position.shares  // Remove everything
    }
  );

  const signedTxns = await wallet.signTransaction(
    txGroup.map((tx) => tx.txn)
  );

  const result = await algodClient
    .sendGroupTransaction(signedTxns)
    .do();

  await algosdk.waitForConfirmation(algodClient, result.txId, 4);

  console.log('Liquidity removed!', result.txId);
}

Partial Removal

To remove only some of your liquidity:

const position = await readPosition(...);

// Remove 50% of shares
const sharesToRemove = position.shares / 2n;

const { txGroup } = await buildRemoveLiquidityGroup(
  algodClient,
  POOL_APP_ID,
  account,
  {
    tickId,
    sharesToRemove
  }
);

Your remaining shares stay in the position box. Fees continue to accrue.

What You Receive

When removing liquidity, you get:

  1. Proportional reserves— Your share of each token's reserves
  2. All pending fees — Automatically claimed and added to output
Output per token i:
  baseAmount = shares * reserves[i] / tick.totalShares
  feeAmount = pendingFees[i]
  total = baseAmount + feeAmount

Position NFT

Each LP position is uniquely identified by:

positionKey = \`pos:\${address}\${tickId}\`

This key is the box name. Only the address owner can modify or remove the position.

Monitoring Multiple Positions

import { readAllPositions } from '@taurus-swap/sdk';

// Get all positions for an address
const positions = await readAllPositions(
  algodClient,
  POOL_APP_ID,
  address
);

// positions is an array of { tickId, position }
for (const { tickId, position } of positions) {
  console.log(`Tick ${tickId}: ${position.shares} shares`);
  console.log(`  Pending fees:`, position.pendingFees);
}

React Hook Example

function useLiquidityPosition(tickId: number) {
  const { address } = useWallet();
  const [position, setPosition] = useState<Position | null>(null);

  useEffect(() => {
    if (!address || !tickId) return;

    const fetch = async () => {
      const pos = await readPosition(algodClient, POOL_APP_ID, address, tickId);
      setPosition(pos);
    };

    fetch();
    const interval = setInterval(fetch, 60_000);  // Refresh every minute
    return () => clearInterval(interval);
  }, [address, tickId]);

  return position;
}

// Usage in component:
function PositionCard({ tickId }: { tickId: number }) {
  const position = useLiquidityPosition(tickId);

  if (!position) return <div>Loading...</div>;

  return (
    <div>
      <div>Shares: {position.shares.toString()}</div>
      <div>Pending Fees:</div>
      {position.pendingFees.map((fee, i) => (
        <div key={i}>Token {i}: {formatAmount(fee)}</div>
      ))}
      <button onClick={() => claimFees(tickId)}>Claim Fees</button>
    </div>
  );
}
Next: See API Reference for complete type definitions.