phasis.
Phasis Docs
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:

FieldRaw typeHuman conversion
strikebigint (UD30x9)/ 1e9 → USD
isCallboolean
fairPricebigint (UD30x9)/ 1e9 → USD per contract
intrinsicValuebigint (UD30x9)/ 1e9 → USD per contract
oibigint/ 1e6 → number of contracts
seriesIdstringObject ID of the series
expiryTsbigintMilliseconds 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:

FieldDescription
orderIdbigint — u128 used in cancelOrder
side0 (BID) or 1 (ASK)
restingPricebigint UD30x9 — divide by 1e9 for USD
remainingQtybigint — divide by 1e6 for contracts
strikebigint UD30x9
isCallboolean

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.

Connect a Wallet

No Sui wallet detected in this browser.

Install Sui Wallet

Phasis supports any wallet that implements the Sui Wallet Standard.