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]) / PRECISION

where 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 position

PositionInfo 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 };
}