Reading Pool State
readPoolState is the foundation of every SDK operation. It performs multiple Algod calls — global state, the reserves box, the fee_growth box, every tick: box, and every token: box — and returns a single typed snapshot.
Basic Usage
import { readPoolState } from '@taurusswap/sdk';
import algosdk from 'algosdk';
const algod = new algosdk.Algodv2('', 'https://testnet-api.algonode.cloud');
const pool = await readPoolState(algod, 758284478);
console.log('Tokens:', pool.n);
console.log('Active ticks:', pool.ticks.length);
console.log('Fee:', Number(pool.feeBps) / 100, '%');Via TaurusClient (with 10s cache):
const pool = await client.getPoolState();
// Force a fresh fetch, bypassing the cache:
const fresh = await client.getPoolState(true);
// Manually evict the cache:
client.invalidateCache();PoolState Type
interface PoolState {
appId: number; // Algorand application ID
n: number; // Number of tokens (e.g. 5)
// Pool geometry (AMOUNT_SCALE units = raw_microunits / 1_000)
sqrtN: bigint; // floor(√n × 10⁹) — PRECISION-scaled
invSqrtN: bigint; // floor(1/√n × 10⁹) — PRECISION-scaled
sumX: bigint; // ∑ reserves[i] (AMOUNT_SCALE units)
sumXSq: bigint; // ∑ reserves[i]² (AMOUNT_SCALE² units)
virtualOffset: bigint; // added to each reserve in math space (AMOUNT_SCALE units)
// Consolidated tick aggregates (AMOUNT_SCALE units)
rInt: bigint; // sum of r across all INTERIOR ticks
sBound: bigint; // effective radius of outermost BOUNDARY tick
kBound: bigint; // hyperplane offset of outermost BOUNDARY tick
totalR: bigint; // sum of r across ALL ticks
// Fees
feeBps: bigint; // 30n = 0.30%
feeGrowth: bigint[]; // [n] PRECISION-scaled per-token fee accumulators (monotone)
// Reserves
actualReservesRaw: bigint[]; // [n] actual on-chain balances, raw microunits
reserves: bigint[]; // [n] math-space reserves = actualReservesRaw/1000 + virtualOffset
// Ticks
numTicks: number; // monotonic counter — next tick ID (never reused)
ticks: Tick[]; // all live tick objects
// Tokens
tokenAsaIds: number[]; // [n] Algorand Standard Asset IDs
tokenDecimals: number[]; // [n] decimals per token (USDC/USDT = 6)
}Field Guide
Reserves
actualReservesRaw[i] is the true on-chain balance of token i in raw microunits (no virtual offset). This is what you use for TVL and display:
const tvlUsd = pool.actualReservesRaw.reduce(
(sum, r) => sum + Number(r), 0
) / 1e6; // stablecoins have 6 decimals
pool.actualReservesRaw.forEach((r, i) => {
console.log(`Token ${pool.tokenAsaIds[i]}: ${Number(r) / 1e6} USD`);
});reserves[i] is the math-space value: actualReservesRaw[i] / 1000 + virtualOffset. Use these only when calling raw math functions. The public SDK API handles the conversion automatically.
Ticks
interface Tick {
id: number; // Monotonic ID assigned at creation (never reused)
r: bigint; // Sphere radius — AMOUNT_SCALE units
k: bigint; // Hyperplane offset — AMOUNT_SCALE units
state: TickState; // INTERIOR (0) or BOUNDARY (1)
totalShares: bigint; // Sum of all LP shares in this tick
}A tick is INTERIORwhen the pool's current price is inside its sphere (normal operation). It flips to BOUNDARYwhen a large swap pushes the price to the tick's edge, reducing effective liquidity until the price recovers.
const interior = pool.ticks.filter(t => t.state === 0 /* TickState.INTERIOR */);
const boundary = pool.ticks.filter(t => t.state === 1 /* TickState.BOUNDARY */);
console.log(`${interior.length} interior, ${boundary.length} boundary ticks`);Fee Growth
feeGrowth[i] is a PRECISION-scaled monotone accumulator. It represents the total fees earned per unit of r deposited into the pool over all time for token i. LPs compute their claimable fees as:
claimable_fee[i] = positionR × (feeGrowth[i] - checkpoint[i]) / PRECISIONwhere positionR = tick.r × shares / tick.totalShares and checkpoint[i] is the value of feeGrowth[i] at the time the LP last claimed. The SDK computes this automatically in readPosition() and client.getPosition().
sqrtN / invSqrtN
Precomputed constants floor(√n × 10⁹) and floor(1/√n × 10⁹). These are embedded in pool global state so every operation uses the same integer approximation as the contract. You should not need to use these directly.
Reading a Position
An LP's position is stored in a pos: box keyed by(ownerPublicKey, tickId). The SDK reads it and computes claimable fees:
import { readPosition } from '@taurusswap/sdk';
// Returns null if the address has no position in this tick
const position = await readPosition(
algod,
POOL_APP_ID,
'OWNER_ADDRESS',
tickId, // the tick ID
pool.n,
pool.feeGrowth,
tick, // the Tick object from pool.ticks
);
if (position) {
console.log('Shares:', position.shares);
console.log('Position r (AMOUNT_SCALE):', position.positionR);
console.log('Claimable fees (raw microunits):', position.claimableFees);
// Format fees
position.claimableFees.forEach((fee, i) => {
console.log(` Token ${i}: ${Number(fee) / 1e6} USD`);
});
}Via TaurusClient:
const position = await client.getPosition('OWNER_ADDRESS', tickId);
// null if no positionPositionInfo Type
interface PositionInfo {
tickId: number; // Which tick this position is in
shares: bigint; // LP's share count in this tick
positionR: bigint; // tick.r × shares / tick.totalShares (AMOUNT_SCALE units)
claimableFees: bigint[]; // [n] per-token fees in raw microunits — ready to display
}Polling Pattern
Pool state changes every swap and LP operation (~4 second Algorand block time). Refresh every 30s for display purposes; use the 10s cache in TaurusClientfor swap quoting so repeated UI calls don't hammer Algod.
// React hook example
import { useEffect, useState } from 'react';
import { type PoolState } from '@taurusswap/sdk';
import { taurusClient } from '@/lib/taurus';
export function usePoolState() {
const [state, setState] = useState<PoolState | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let active = true;
const fetch = async () => {
try {
const s = await taurusClient.getPoolState();
if (active) setState(s);
} finally {
if (active) setLoading(false);
}
};
fetch();
const id = setInterval(fetch, 30_000);
return () => { active = false; clearInterval(id); };
}, []);
return { state, loading };
}