Developers
Reading market & account data
SDK reader functions for registry state, markets, series, accounts, orderbook levels, and stress snapshots.
All read functions are async and take a SuiClient as their first argument. They query on-chain state via Sui's gRPC / GraphQL API.
import { SuiClient, getFullnodeUrl } from '@mysten/sui/client';
const client = new SuiClient({ url: getFullnodeUrl('testnet') });Registry
import { getRegistry, getAccountRegistryId, REGISTRY_ID_TESTNET } from '@phasis-lab/sdk';
// Fetch the OptionsRegistry
const registry = await getRegistry(client, REGISTRY_ID_TESTNET);
console.log('Paused:', registry.paused);
console.log('Version:', registry.currentVersion);
console.log('Markets:', registry.markets); // { BTC: '0x...', ... }
// Resolve the shared AccountRegistry ID (stored as a dynamic field on the registry)
const accountRegistryId = await getAccountRegistryId(client, REGISTRY_ID_TESTNET);Markets and series
import { listMarkets, getMarket, REGISTRY_ID_TESTNET } from '@phasis-lab/sdk';
// List all markets registered in the protocol
const markets = await listMarkets(client, REGISTRY_ID_TESTNET);
markets.forEach(m => {
const expiryDate = new Date(Number(m.expiryTs));
console.log(`${m.asset} market expires ${expiryDate.toISOString()}`);
console.log(`State: ${m.state}`); // 'Listed' | 'Halted' | 'Settling' | 'Closed'
});
// Fetch a specific market and its series
const BTC_MARKET_ID = '0xbac3cacd5169a741ab179f34e94011a3653835402732ebbfaad8bc18a5d8d264';
const market = await getMarket(client, BTC_MARKET_ID);
console.log('BTC market state:', market.state);
console.log('Expiry:', new Date(Number(market.expiryTs)).toISOString());
console.log('Series count:', market.series.length);
// Iterate series rows
market.series.forEach(s => {
const strikeUsd = Number(s.strike) / 1e9;
const fairPrice = Number(s.fairPrice) / 1e9;
const intrinsic = Number(s.intrinsicValue) / 1e9;
const openInterest = Number(s.oi) / 1e6;
const type = s.isCall ? 'CALL' : 'PUT';
console.log(
`${type} $${strikeUsd}: fair=${fairPrice.toFixed(4)}, ` +
`intrinsic=${intrinsic.toFixed(4)}, OI=${openInterest} contracts`
);
});Each entry in market.series has the shape:
| Field | Raw type | Human conversion |
|---|---|---|
strike | bigint (UD30x9) | / 1e9 → USD |
isCall | boolean | — |
fairPrice | bigint (UD30x9) | / 1e9 → USD per contract |
intrinsicValue | bigint (UD30x9) | / 1e9 → USD per contract |
oi | bigint | / 1e6 → number of contracts |
seriesId | string | Object ID of the series |
expiryTs | bigint | Milliseconds UTC timestamp |
Accounts
import {
findAccountId,
getAccount,
getAccountOrders,
accountNavCash,
accountFreeQuote,
REGISTRY_ID_TESTNET,
} from '@phasis-lab/sdk';
// Look up the account ID for a wallet address
const accountId = await findAccountId(client, REGISTRY_ID_TESTNET, walletAddress);
if (!accountId) {
console.log('No account found — call openAccountV2 first');
}
// Fetch account state
const account = await getAccount(client, accountId);
console.log('Owner:', account.owner);
console.log('Balance:', Number(account.balanceQuote) / 1e6, 'USDC');
console.log('Locked margin:', Number(account.lockedMargin) / 1e6, 'USDC');
console.log('Quote locked (bids):', Number(account.quoteLocked) / 1e6, 'USDC');
console.log('Free balance:', Number(accountFreeQuote(account)) / 1e6, 'USDC');
console.log('Cash NAV:', Number(accountNavCash(account)) / 1e6, 'USDC');
// Per-asset margin locks
for (const [assetCode, lock] of Object.entries(account.lockByAsset)) {
const assetName = ['SUI', 'BTC', 'ETH'][Number(assetCode)] ?? assetCode;
console.log(` ${assetName} margin lock: ${Number(lock) / 1e6} USDC`);
}
// Positions
for (const [seriesId, pos] of Object.entries(account.positions)) {
const assetName = ['SUI', 'BTC', 'ETH'][pos.asset] ?? String(pos.asset);
const type = pos.isCall ? 'CALL' : 'PUT';
const strikeUsd = Number(pos.strike) / 1e9;
const qty = Number(pos.netQtyMag) / 1e6;
const direction = pos.netIsShort ? 'SHORT' : 'LONG';
console.log(` ${assetName} ${type} $${strikeUsd} ${direction} ${qty} contracts`);
}Resting orders
// Fetch resting limit orders for an account
const orders = await getAccountOrders(client, accountId);
orders.forEach(o => {
const side = o.side === 0 ? 'BID' : 'ASK';
const price = Number(o.restingPrice) / 1e9;
const qty = Number(o.remainingQty) / 1e6;
console.log(` Order ${o.orderId}: ${side} ${qty} contracts @ $${price}`);
});Each order object has:
| Field | Description |
|---|---|
orderId | bigint — u128 used in cancelOrder |
side | 0 (BID) or 1 (ASK) |
restingPrice | bigint UD30x9 — divide by 1e9 for USD |
remainingQty | bigint — divide by 1e6 for contracts |
strike | bigint UD30x9 |
isCall | boolean |
Orderbook
import { getLevel2, getMidPrice } from '@phasis-lab/sdk';
const BTC_MARKET_ID = '0xbac3cacd5169a741ab179f34e94011a3653835402732ebbfaad8bc18a5d8d264';
const strike = 70_000_000_000_000n; // $70k in UD30x9
// Fetch up to 20 bid levels
const bids = await getLevel2(client, {
marketId: BTC_MARKET_ID,
strike,
isCall: true,
isBid: true,
ticks: 20,
});
console.log('Bids:', bids.bids); // [{ price: number, qty: number }, ...]
// price and qty are already human-scaled (÷1e9 and ÷1e6)
// Fetch ask levels
const asks = await getLevel2(client, {
marketId: BTC_MARKET_ID,
strike,
isCall: true,
isBid: false,
ticks: 20,
});
// Mid-price
try {
const mid = await getMidPrice(client, {
marketId: BTC_MARKET_ID,
strike,
isCall: true,
});
console.log('Mid price:', mid, 'USD');
} catch (e) {
if ((e as Error).message.includes('EEmptyOrderbook')) {
console.log('No mid-price — book is empty');
} else {
throw e;
}
}Stress snapshots
Stress snapshots store the per-asset margin model inputs: spot price, implied volatility, and the pre-computed N-point Black-Scholes stress grid for each listed series. See Margin model for the model description.
import { getStressSnapshot } from '@phasis-lab/sdk';
const BTC_SNAPSHOT_ID = '0x513b72314c521cf4d20c31ace3f2e2a13e8813aa0c5a59700c02f5a39fc1f1d4';
const snapshot = await getStressSnapshot(client, BTC_SNAPSHOT_ID);
console.log('Asset code:', snapshot.asset); // 1 = BTC
console.log('Spot price:', snapshot.spotPrice / 1e9, 'USD');
console.log('IV:', snapshot.iv / 1e9); // annualised, e.g. 0.65 = 65%
console.log('Published at:', new Date(Number(snapshot.updatedAt)));
console.log('Series rows:', snapshot.rows.length);
snapshot.rows.forEach(row => {
const strike = Number(row.strike) / 1e9;
const type = row.isCall ? 'CALL' : 'PUT';
const fair = Number(row.fairPrice) / 1e9;
const intrinsic = Number(row.intrinsicValue) / 1e9;
const gridPoints = row.stressGrid.length;
console.log(
` ${type} $${strike}: fair=${fair.toFixed(4)}, ` +
`intrinsic=${intrinsic.toFixed(4)}, stress grid [${gridPoints} points]`
);
// row.stressGrid: bigint[] — signed FP9 deltas used for portfolio margin
});Stress snapshots are updated periodically by the Phasis keeper service. The protocol enforces a freshness window — trades are rejected if the snapshot is stale.