Skip to main content
streamAccountUpdates() emits full account snapshots for one or more traders. It combines subgraph polling, Alchemy contract-log overlays, and live price repricing so market opens and full closes can appear before subgraph indexing catches up.
const client = await OstiumClient.createSelfAndSelf({
  traderPrivateKey: process.env.TRADER_PRIVATE_KEY as `0x${string}`,
  rpcUrl: process.env.ARB_RPC_URL!,
  alchemyApiKey: process.env.ALCHEMY_API_KEY!,
});

const stream = client.streamAccountUpdates();

stream.onUpdate(snapshot => {
  const account = snapshot[client.getTraderAddress().toLowerCase()];
  console.log(account.positions);
  console.log(account.orders);
  console.log(account.limits);
});

stream.onError(error => console.error(error));
Read-only clients and OstiumSubgraphClient require a user address array.
const stream = client.streamAccountUpdates({
  user: ['0xTraderAddress'],
  alchemyApiKey: process.env.ALCHEMY_API_KEY!,
  pollIntervalMs: 700,
});

Multiple addresses

Pass one or more addresses in user to subscribe to accounts on a single stream. The stream uses one WebSocket, one poll loop, and one price feed regardless of how many addresses you watch — far cheaper than opening one stream per trader.
const stream = client.streamAccountUpdates({
  user: ['0xTraderA', '0xTraderB', '0xTraderC'],
  alchemyApiKey: process.env.ALCHEMY_API_KEY!,
});

stream.onUpdate(snapshot => {
  console.log(snapshot['0xtradera'].positions);
  console.log(snapshot['0xtraderb'].orders);
});
The snapshot is keyed by normalized trader address. Each trader bucket contains positions, orders, and limits. Duplicate addresses are de-duplicated, and stream.users returns the subscribed addresses.

Optimistic opens

For market opens, add an optimistic overlay immediately after submission and reconcile it once you know the on-chain order id.
import { extractOrderIdFromReceipt } from '@ostium/builder-sdk';

const result = await client.openTrade(params);
const localId = stream.addOptimisticOpen(params, result);

const receipt = await publicClient.waitForTransactionReceipt({
  hash: result.txHash,
});
const orderId = extractOrderIdFromReceipt(receipt);

if (orderId) {
  stream.attachOrderId(localId, orderId, {
    initiatedTx: result.txHash,
  });
}
addOptimisticOpen() only supports market open orders. When streaming multiple addresses, pass the owning address as the third argument (addOptimisticOpen(params, result, '0xTraderA')); it is optional and defaults to the sole subscribed address when streaming one trader.

Parameters

interface StreamAccountUpdatesParams {
  /** One or more addresses to stream on one connection. */
  user?: `0x${string}`[];
  alchemyApiKey?: string;
  pollIntervalMs?: number;
}
alchemyApiKey can be provided when creating the client or when starting the stream. The stream throws if no Alchemy API key is available.

Response schema

interface OstiumAccountUpdatesStream {
  /** Trader addresses this stream is subscribed to, in the order supplied. */
  readonly users: `0x${string}`[];
  onUpdate(handler: (snapshot: AccountUpdatesSnapshot) => void): () => void;
  onError(handler: (error: Error) => void): this;
  getCurrent(): AccountUpdatesSnapshot;
  addOptimisticOpen(
    params: OpenTradeParams,
    submission?: SubmissionResult,
    user?: `0x${string}`,
  ): string;
  attachOrderId(
    localId: string,
    orderId: string,
    metadata?: { initiatedTx?: string; initiatedBlock?: string },
  ): void;
  close(): void;
}

interface AccountUpdatesForTrader {
  /** Open positions, formatted like getOpenPositions().pairPositions. */
  positions: PairPosition[];
  /** Pending market orders only. */
  orders: Order[];
  /** Active limit and stop orders. */
  limits: OpenOrder[];
}

type AccountUpdatesSnapshot = Record<string, AccountUpdatesForTrader>;
positions, orders, and limits use the same SDK-formatted objects returned by getOpenPositions, getOrders, and getOpenOrders.