> ## Documentation Index
> Fetch the complete documentation index at: https://continuum-ec12e897.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Reading on-chain state

> Patterns for fetching market data, NAV, pool depth, position state, and risk - efficiently.

Continuum's state is fully on-chain. This page covers the common reads you'll need to build a UI, a bot, or a monitoring dashboard.

## What lives where

| Data                                        | Account                                    | Program                   |
| ------------------------------------------- | ------------------------------------------ | ------------------------- |
| Symbol, mints, fees, NAV source, risk state | `Market` PDA                               | `mint-redeem`             |
| Total L+S supply, vault balance             | `Market` + `CollateralVault` token account | `mint-redeem` + SPL Token |
| Live observations, TWAP, confidence         | `OracleConfig` PDA                         | `oracle`                  |
| OI cap, current OI, inventory state         | `Clp` PDA                                  | `clp`                     |
| Capital allocation, profit accumulation     | `GlobalClp` + `Clp`                        | `clp`                     |
| Book base price, bins, depth                | `Pool` PDA (per side)                      | `clp`                     |
| Pending committed orders                    | `Order` PDAs                               | `clp`                     |
| User token balance                          | User's ATA                                 | SPL Token                 |

## Fetch a single market end-to-end

```typescript theme={null}
import { Program, AnchorProvider, BN } from "@coral-xyz/anchor";
import { PublicKey } from "@solana/web3.js";
import mintRedeemIdl from "./mint_redeem.json";
import clpIdl from "./clp.json";
import oracleIdl from "./oracle.json";

const mintRedeem = new Program(mintRedeemIdl, provider);
const clp        = new Program(clpIdl, provider);
const oracle     = new Program(oracleIdl, provider);

const symbol = "QQQ";
const [marketPDA] = PublicKey.findProgramAddressSync(
  [Buffer.from("market"), Buffer.from(symbol)],
  mintRedeem.programId,
);
const [clpPDA] = PublicKey.findProgramAddressSync(
  [Buffer.from("clp"), marketPDA.toBuffer()],
  clp.programId,
);

const [market, clpAccount, vaultBalance] = await Promise.all([
  mintRedeem.account.market.fetch(marketPDA),
  clp.account.clp.fetch(clpPDA),
  provider.connection.getTokenAccountBalance(market.collateralVault),
]);

const oracleConfig = await oracle.account.oracleConfig.fetch(market.oracleAddress);

// Derive NAV
const lNav = market.userTwapPrice.gtn(0) ? market.userTwapPrice : market.initialLPrice;
const sNav = market.initialLPrice.mul(market.initialSPrice).div(lNav);

console.log({
  symbol: market.assetSymbol,
  riskState: Object.keys(market.riskState)[0],
  lNav: lNav.toNumber() / 1e6,
  sNav: sNav.toNumber() / 1e6,
  oiCap: clpAccount.oiCap.toString(),
  currentOi: clpAccount.currentOi.toString(),
  vaultBalance: vaultBalance.value.uiAmountString,
  oracleStaleSec: Date.now() / 1000 - oracleConfig.lastUpdateTime.toNumber(),
});
```

## Batch-fetch all live markets

For UIs showing every market, use one `getMultipleAccountsInfo` call:

```typescript theme={null}
import { unpackAccount } from "@solana/spl-token";

// Build the list of market PDAs from your registry / market-addresses.json
const symbols = ["QQQ", "SPY", "XAU", "VXX"];
const marketPDAs = symbols.map(s =>
  PublicKey.findProgramAddressSync(
    [Buffer.from("market"), Buffer.from(s)],
    mintRedeem.programId,
  )[0],
);

// Single RPC call
const accounts = await provider.connection.getMultipleAccountsInfo(marketPDAs);
const markets = accounts.map(acc =>
  acc ? mintRedeem.coder.accounts.decode("market", acc.data) : null,
);

console.log(markets.map(m => ({
  symbol: m?.assetSymbol,
  oi: m?.totalLSupply.toString(),
  riskState: Object.keys(m?.riskState ?? {})[0],
})));
```

For batched balances of corresponding `collateral_vault`s, similar approach with `getMultipleAccounts` decoded as token accounts.

## NAV from on-chain TWAP

NAV is **derived**, not stored:

```typescript theme={null}
function getNav(market): { lNav: BN, sNav: BN } {
  const lNav = market.userTwapPrice.gtn(0)
    ? market.userTwapPrice
    : market.initialLPrice;
  const sNav = market.initialLPrice.mul(market.initialSPrice).div(lNav);
  return { lNav, sNav };
}
```

If you need a price-as-float (for display):

```typescript theme={null}
const lNavUI = lNav.toNumber() / 10 ** 6; // 6 decimals for cUSDC
```

## Book price and depth (native pools)

The whole book is one account - a single fetch returns price and depth:

```typescript theme={null}
const [poolPda] = PublicKey.findProgramAddressSync(
  [Buffer.from("pool"), marketPDA.toBuffer(), Buffer.from([0])], // 0 = long, 1 = short
  clp.programId,
);
const pool = await clp.account.pool.fetch(poolPda);

// Anchor camelCases base_price_1e6 as basePrice1E6 (capital E)
const basePrice = Number(pool.basePrice1E6) / 1e6;

// Depth: asks are synth per bin (above base), bids are cUSDC per bin (below)
const askSynth = pool.asks.slice(0, pool.numBins).reduce((s, b) => s + Number(b), 0);
const bidUsdc  = pool.bids.slice(0, pool.numBins).reduce((s, b) => s + Number(b), 0);
```

Compare `basePrice` to `lNavUI` for drift:

```typescript theme={null}
const driftBps = (basePrice / lNavUI - 1) * 10_000;
console.log(`Book drift: ${driftBps.toFixed(1)} bps`);
```

While the underlying market is open this stays inside the per-market reposition gate (20-40bps); during closed hours the book floats by design.

## User position value

```typescript theme={null}
const wallet = new PublicKey("...");

// User's L/S balances
const longAta  = getAssociatedTokenAddressSync(market.longMint, wallet);
const shortAta = getAssociatedTokenAddressSync(market.shortMint, wallet);

const [longBal, shortBal] = await Promise.all([
  provider.connection.getTokenAccountBalance(longAta),
  provider.connection.getTokenAccountBalance(shortAta),
]);

const nL = new BN(longBal.value.amount);
const nS = new BN(shortBal.value.amount);

// Position value at NAV (in cUSDC base units)
const valueAtNav = nL.mul(lNav).div(new BN(10).pow(new BN(6)))
                   .add(nS.mul(sNav).div(new BN(10).pow(new BN(6))));

console.log("Position NAV value:", valueAtNav.toNumber() / 1e6, "cUSDC");
```

For mark-to-market on the **book** (which may float from NAV during closed hours), substitute the book's base price for NAV.

## Risk and oracle freshness

```typescript theme={null}
const market = await mintRedeem.account.market.fetch(marketPDA);
const oracle = await oracleProgram.account.oracleConfig.fetch(market.oracleAddress);

const riskState = Object.keys(market.riskState)[0]; // "normal" | "proxyMode" | ...
const oracleStale = Date.now() / 1000 - oracle.lastUpdateTime.toNumber() > 30;
const confidenceBps = (oracle.lastConfidence.toNumber() / oracle.lastPrice.toNumber()) * 10_000;

if (riskState === "stress") console.warn("Mints disabled");
if (oracleStale) console.warn("Oracle stale; ProxyMode imminent");
if (confidenceBps > 100) console.warn(`Wide confidence (${confidenceBps.toFixed(0)} bps)`);
```

## Subscribing to live updates

For a reactive UI, use Solana's `onAccountChange` for the Market PDA:

```typescript theme={null}
const sub = provider.connection.onAccountChange(marketPDA, (accountInfo) => {
  const market = mintRedeem.coder.accounts.decode("market", accountInfo.data);
  console.log("New TWAP:", market.userTwapPrice.toString());
}, "confirmed");

// Cleanup
provider.connection.removeAccountChangeListener(sub);
```

The Market account is updated on every `update_risk_state`, every mint/redeem (supply changes), and via TWAP propagation. Frequency: a few updates per minute in normal conditions.

For book prices, subscribe to the per-side `Pool` accounts with `onAccountChange` - one account per book, so it's cheap.

## Common pitfalls

**Wrong oracle account.** Use `market.oracle_address`, never hard-code. The address is per-market; reusing one across markets returns wrong NAV.

**TWAP == 0 trap.** Right after a market init, TWAP is zero. Fall back to `initial_l_price`. The frontend's `useMarkets()` hook handles this - replicate the pattern.

**Stale market data after mint/redeem.** Anchor's account-fetch returns the *current* state, but if you're displaying stale numbers from before the user's tx, refetch after `confirmed`. The Anchor provider's `sendAndConfirm` returns when the cluster is at `confirmed` - re-fetch then.

**Pool not initialized.** `clp.account.pool.fetchNullable(poolPda)` returns null if the book hasn't been bootstrapped for that (market, side) yet. Use the registry / `market-addresses.json` as the source of truth for pools that should exist.

**Decimals.** Everything in account state is in **base units** (lamports for cUSDC, base units for L/S). Divide by `10^6` for display. Don't assume decimals from the symbol.

## See also

<CardGroup cols={2}>
  <Card title="Mint flow" icon="circle-plus" href="/flows/mint">
    What `mint_paired` does on-chain.
  </Card>

  <Card title="SDK setup" icon="terminal" href="/build/sdk-setup">
    Bootstrapping a project to make these reads.
  </Card>
</CardGroup>
