Skip to main content

Documentation Index

Fetch the complete documentation index at: https://continuum-ec12e897.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

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

DataAccountProgram
Symbol, mints, fees, NAV source, risk stateMarket PDAmint-redeem
Total L+S supply, vault balanceMarket + CollateralVault token accountmint-redeem + SPL Token
Live observations, TWAP, confidenceOracleConfig PDAoracle
OI cap, current OI, inventory stateClp PDAclp
Capital allocation, profit accumulationGlobalClp + Clpclp
Pool active bin, depth, feesLbPair + bin arraysMeteora DLMM (external)
User token balanceUser’s ATASPL Token

Fetch a single market end-to-end

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:
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_vaults, similar approach with getMultipleAccounts decoded as token accounts. NAV is derived, not stored:
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):
const lNavUI = lNav.toNumber() / 10 ** 6; // 6 decimals for cUSDC

Pool price and depth (Meteora)

import DLMM from "@meteora-ag/dlmm";

const dlmm = await DLMM.create(connection, longPoolAddr);
const activeId = dlmm.lbPair.activeId;
const pricePerLamport = dlmm.fromPricePerLamport(
  dlmm.getPricePerLamport(activeId),
);

// Depth around active
const bins = await dlmm.getBinsBetweenLowerAndUpperBound(
  activeId - 50,
  activeId + 50,
);
const totalY = bins.reduce((s, b) => s + Number(b.amountY), 0); // cUSDC
const totalX = bins.reduce((s, b) => s + Number(b.amountX), 0); // L (or S)
You can compare pricePerLamport to lNavUI to compute spread:
const spreadBps = (pricePerLamport / lNavUI - 1) * 10_000;
console.log(`Spread: ${spreadBps.toFixed(1)} bps`);
For full keeper-style arb spread including pool fees, slippage, and worst-case quoting, see the reference keeper’s arb.rs.

User position value

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 pool (which may differ from NAV transiently), substitute pool_price for NAV.

Risk and oracle freshness

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:
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 pool prices, subscribe to the LbPair account directly via Meteora’s SDK or vanilla onAccountChange.

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 address mismatch. If you read clp.meteora_long_pool and find it equals the default pubkey (all zeros), pools are not configured for that market 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

Mint flow

What mint_paired does on-chain.

SDK setup

Bootstrapping a project to make these reads.