Skip to main content

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