Reading Data
How to fetch and parse market, position, and order book data from Seesaw.
Account Discovery
Deriving PDAs
All account addresses are deterministically derived:
import { PublicKey } from '@solana/web3.js';
import {
findConfigPda,
findMarketPda,
findOrderbookPda,
findVaultPda,
findPositionPda,
} from '@seesaw/sdk';
const programId = new PublicKey('SEESAW_PROGRAM_ID');
// Config (singleton)
const [configPda] = findConfigPda(programId);
// Market for specific epoch
const durationSeconds = 900; // default; configurable per market (60–604800)
const marketId = BigInt(Math.floor(Date.now() / 1000 / durationSeconds));
const [marketPda] = findMarketPda(marketId, programId);
// Related accounts
const [orderbookPda] = findOrderbookPda(marketPda, programId);
const [vaultPda] = findVaultPda(marketPda, programId);
// User position
const userWallet = new PublicKey('USER_WALLET');
const [positionPda] = findPositionPda(marketPda, userWallet, programId);
Finding Current Market
function getCurrentMarketId(durationSeconds: number = 900): bigint {
const now = Math.floor(Date.now() / 1000);
return BigInt(Math.floor(now / durationSeconds));
}
function getMarketTime(
marketId: bigint,
durationSeconds: number = 900
): { start: Date; end: Date } {
const startSec = Number(marketId) * durationSeconds;
return {
start: new Date(startSec * 1000),
end: new Date((startSec + 900) * 1000),
};
}
Fetching Accounts
Single Account
import { Connection } from '@solana/web3.js';
import { parseMarket } from '@seesaw/sdk';
const connection = new Connection('https://api.mainnet-beta.solana.com');
async function getMarket(marketPda: PublicKey) {
const accountInfo = await connection.getAccountInfo(marketPda);
if (!accountInfo) {
throw new Error('Market not found');
}
return parseMarket(accountInfo.data);
}
Multiple Accounts
import { parseMarket, parseOrderbook, parsePosition } from '@seesaw/sdk';
async function getMarketData(marketPda: PublicKey, userWallet: PublicKey) {
const [orderbookPda] = findOrderbookPda(marketPda, programId);
const [positionPda] = findPositionPda(marketPda, userWallet, programId);
const accounts = await connection.getMultipleAccountsInfo([marketPda, orderbookPda, positionPda]);
return {
market: accounts[0] ? parseMarket(accounts[0].data) : null,
orderbook: accounts[1] ? parseOrderbook(accounts[1].data) : null,
position: accounts[2] ? parsePosition(accounts[2].data) : null,
};
}
Parsing Market State
Market Account
interface MarketAccount {
marketId: bigint;
pythFeed: PublicKey;
settlementMint: PublicKey;
tStart: bigint;
tEnd: bigint;
startPrice: bigint; // 0 if not captured
startPriceConf: bigint;
startPriceExpo: number;
startPriceTimestamp: bigint;
endPrice: bigint; // 0 if not captured
endPriceConf: bigint;
endPriceExpo: number;
endPriceTimestamp: bigint;
outcome: number; // 0=None, 1=Up, 2=Down
totalYesShares: bigint;
totalNoShares: bigint;
totalCollateral: bigint;
totalPositions: number;
settledPositions: number;
totalVolume: bigint;
totalTrades: number;
bump: number;
}
Deriving Market State
enum MarketState {
Created, // No start snapshot
Trading, // Start captured, no end
Settling, // Both snapshots, no outcome
Resolved, // Outcome determined
}
function getMarketState(market: MarketAccount): MarketState {
if (market.startPrice === 0n) {
return MarketState.Created;
}
if (market.endPrice === 0n) {
return MarketState.Trading;
}
if (market.outcome === 0) {
return MarketState.Settling;
}
return MarketState.Resolved;
}
Parsing Order Book
Orderbook Account
interface OrderbookAccount {
market: PublicKey;
bidCount: number;
askCount: number;
nextOrderId: bigint;
bestBidPrice: number;
bestAskPrice: number;
bids: Order[];
asks: Order[];
bump: number;
}
interface Order {
orderId: bigint;
owner: PublicKey;
priceBps: number;
quantity: bigint;
originalQuantity: bigint;
timestamp: bigint;
originalSide: number;
isActive: boolean;
}
Analyzing Order Book
function analyzeOrderbook(orderbook: OrderbookAccount) {
const activeBids = orderbook.bids.filter((o) => o.isActive);
const activeAsks = orderbook.asks.filter((o) => o.isActive);
// Calculate total depth
const bidDepth = activeBids.reduce((sum, o) => sum + o.quantity, 0n);
const askDepth = activeAsks.reduce((sum, o) => sum + o.quantity, 0n);
// Calculate spread
const spread = orderbook.bestAskPrice - orderbook.bestBidPrice;
// Calculate mid price
const mid = (orderbook.bestBidPrice + orderbook.bestAskPrice) / 2;
return {
bidCount: activeBids.length,
askCount: activeAsks.length,
bidDepth,
askDepth,
spread,
midPrice: mid,
bestBid: orderbook.bestBidPrice,
bestAsk: orderbook.bestAskPrice,
};
}
Finding User Orders
function getUserOrders(orderbook: OrderbookAccount, user: PublicKey): Order[] {
const userBids = orderbook.bids.filter((o) => o.isActive && o.owner.equals(user));
const userAsks = orderbook.asks.filter((o) => o.isActive && o.owner.equals(user));
return [...userBids, ...userAsks];
}
Parsing Position
Position Account
interface PositionAccount {
market: PublicKey;
owner: PublicKey;
yesShares: bigint;
noShares: bigint;
lockedYesShares: bigint;
lockedNoShares: bigint;
collateralDeposited: bigint;
collateralLocked: bigint;
settled: boolean;
payout: bigint;
totalBought: bigint;
totalSold: bigint;
totalFeesPaid: bigint;
totalRebatesEarned: bigint;
orderCount: number;
firstTradeAt: bigint;
lastTradeAt: bigint;
bump: number;
}
Position Analytics
function analyzePosition(position: PositionAccount, currentYesPrice: number) {
// Available shares
const availableYes = position.yesShares - position.lockedYesShares;
const availableNo = position.noShares - position.lockedNoShares;
// Position value at current prices
const yesValue = (Number(position.yesShares) * currentYesPrice) / 10000;
const noValue = (Number(position.noShares) * (10000 - currentYesPrice)) / 10000;
const totalValue = yesValue + noValue;
// Net fees
const netFees = position.totalFeesPaid - position.totalRebatesEarned;
return {
availableYes,
availableNo,
yesValue,
noValue,
totalValue,
netFees,
orderCount: position.orderCount,
};
}
Subscription Patterns
Polling
async function pollMarket(marketPda: PublicKey, interval: number) {
let lastState: MarketAccount | null = null;
setInterval(async () => {
const account = await connection.getAccountInfo(marketPda);
if (!account) return;
const market = parseMarket(account.data);
// Detect state changes
if (lastState && getMarketState(market) !== getMarketState(lastState)) {
console.log('State changed:', getMarketState(market));
}
lastState = market;
}, interval);
}
WebSocket Subscription
function subscribeToMarket(marketPda: PublicKey, callback: (market: MarketAccount) => void) {
const subscriptionId = connection.onAccountChange(
marketPda,
(accountInfo) => {
const market = parseMarket(accountInfo.data);
callback(market);
},
'confirmed'
);
return () => connection.removeAccountChangeListener(subscriptionId);
}
// Usage
const unsubscribe = subscribeToMarket(marketPda, (market) => {
console.log('Market updated:', market);
});
// Later: unsubscribe()
Batch Subscription
function subscribeToMultiple(pdas: PublicKey[], callback: (updates: Map<string, Buffer>) => void) {
const subscriptions: number[] = [];
const updates = new Map<string, Buffer>();
for (const pda of pdas) {
const subId = connection.onAccountChange(
pda,
(accountInfo) => {
updates.set(pda.toBase58(), accountInfo.data);
callback(updates);
},
'confirmed'
);
subscriptions.push(subId);
}
return () => {
for (const subId of subscriptions) {
connection.removeAccountChangeListener(subId);
}
};
}
Reading Oracle Data
Pyth Price Feed
import { PythHttpClient, getPythProgramKeyForCluster } from '@pythnetwork/client';
async function getPythPrice(pythFeed: PublicKey) {
const pythClient = new PythHttpClient(connection, getPythProgramKeyForCluster('mainnet-beta'));
const data = await pythClient.getData();
const priceData = data.productPrice.get(pythFeed.toBase58());
if (!priceData || !priceData.price) {
throw new Error('Price not available');
}
return {
price: priceData.price,
confidence: priceData.confidence,
publishTime: priceData.publishTime,
};
}
Caching Patterns
Simple Cache
class MarketCache {
private cache = new Map<string, { data: MarketAccount; fetchedAt: number }>();
private ttl = 5000; // 5 seconds
async get(marketPda: PublicKey): Promise<MarketAccount> {
const key = marketPda.toBase58();
const cached = this.cache.get(key);
if (cached && Date.now() - cached.fetchedAt < this.ttl) {
return cached.data;
}
const account = await connection.getAccountInfo(marketPda);
if (!account) throw new Error('Market not found');
const data = parseMarket(account.data);
this.cache.set(key, { data, fetchedAt: Date.now() });
return data;
}
invalidate(marketPda: PublicKey) {
this.cache.delete(marketPda.toBase58());
}
}
Next Steps
- Learn Building Transactions
- See complete Examples