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
Data Account Program Symbol, mints, fees, NAV source, risk state Market PDAmint-redeemTotal L+S supply, vault balance Market + CollateralVault token accountmint-redeem + SPL TokenLive observations, TWAP, confidence OracleConfig PDAoracleOI cap, current OI, inventory state Clp PDAclpCapital allocation, profit accumulation GlobalClp + ClpclpPool active bin, depth, fees LbPair + bin arraysMeteora DLMM (external) User token balance User’s ATA SPL 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 from on-chain TWAP
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.