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, ...] ← ResetThis 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:
- Proportional reserves— Your share of each token's reserves
- All pending fees — Automatically claimed and added to output
Output per token i:
baseAmount = shares * reserves[i] / tick.totalShares
feeAmount = pendingFees[i]
total = baseAmount + feeAmountPosition 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.