> ## Documentation Index
> Fetch the complete documentation index at: https://docs.phoenix.trade/llms.txt
> Use this file to discover all available pages before exploring further.

# Exchange Data

> Use the Rise SDK exchange cache, market metadata, market stats, and orderbook streams.

Use this page when your integration needs market params, public market metadata, live mark prices, L2 orderbooks, or commodity/equity calendar state.

The exchange cache should be the default source for market params in both SDKs. It resolves the PerpAssetMap, global exchange accounts, market accounts, spline accounts, fee params, risk params, and public market metadata such as logos. Use REST to bootstrap, then let the SDK keep a local cache fresh instead of repeatedly polling the same params.

## Exchange Cache

In TypeScript, configure `createPhoenixClient(...)` with exchange metadata streaming, wait for `client.exchange.ready()`, then read markets from the cache.

<CodeGroup>
  ```ts TypeScript theme={null}
  import { createPhoenixClient } from "@ellipsis-labs/rise";

  const client = createPhoenixClient({
    apiUrl: "https://perp-api.phoenix.trade",
    rpcUrl: "https://api.mainnet-beta.solana.com",
    ws: { connectMode: "eager" },
    exchangeMetadata: { stream: true },
  });

  const snapshot = await client.exchange.ready();
  const sol = client.exchange.market("SOL");
  const solMetadata = client.exchange.marketMetadata("SOL");

  console.log(snapshot.markets.length);
  console.log(sol?.assetId, sol?.tickSize, sol?.baseLotsDecimals);
  console.log(solMetadata?.logoUri);

  const unsubscribe = client.exchange.onEvent((event) => {
    if (event.type === "marketUpdated") {
      console.log(event.symbol, event.change);
    }
  });
  ```

  ```rust Rust theme={null}
  use phoenix_rise::{PhoenixHttpClient, PhoenixMetadata};

  let http = PhoenixHttpClient::new_from_env()?;
  let exchange = http.get_exchange().await?.into();
  let mut metadata = PhoenixMetadata::new(exchange);

  let sol = metadata
      .get_market("SOL")
      .expect("SOL market should exist in exchange metadata");

  println!("asset id: {}", sol.asset_id);
  println!("market: {}", sol.market_pubkey);
  println!("spline: {}", sol.spline_pubkey);
  ```
</CodeGroup>

The TypeScript order-packet builders and `client.ixs` instruction builders read from `client.exchange`. The Rust `PhoenixTxBuilder` reads from `PhoenixMetadata`. Keep those caches warm before building orders so price, tick, base-lot, account, and risk-param conversions use the same market view as the rest of your app.

References:

* [TypeScript unified client example](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/examples/phoenix-client-example.ts)
* [TypeScript limit-order ix example](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/examples/03-build-limit-order-ix.ts)
* [Rust HTTP client example](https://github.com/Ellipsis-Labs/rise-public/blob/master/rust/sdk/examples/http_client.rs)
* [Rust limit-order example](https://github.com/Ellipsis-Labs/rise-public/blob/master/rust/sdk/examples/send_limit_order.rs)

## Market Stats

Market stats carry live mark price, mid price, oracle price, funding, volume, and open interest. Subscribe for current values; do not poll market stats in a tight loop.

<CodeGroup>
  ```ts TypeScript theme={null}
  const controller = new AbortController();

  for await (const update of client.streams!.marketStats("SOL", controller.signal)) {
    console.log(
      update.symbol,
      update.stats.markPrice,
      update.stats.currentFundingRate
    );
  }
  ```

  ```rust Rust theme={null}
  use phoenix_rise::api::{PhoenixMetadata, PhoenixWSClient};

  let ws = PhoenixWSClient::new_from_env()?;
  let (mut rx, _handle) = ws.subscribe_to_market("SOL".to_string())?;

  while let Some(stats) = rx.recv().await {
      metadata.apply_market_stats(&stats)?;
      println!("{} mark=${}", stats.symbol, stats.mark_price);
  }
  ```
</CodeGroup>

In Rust, `metadata.apply_market_stats(&stats)` updates the cached mark price inputs used by margin calculations.

The TypeScript stream adapter converts the raw WebSocket payload into camelCase. `timestamp` is a `bigint` in TypeScript and is shown as a string below for display:

```json theme={null}
{
  "symbol": "SOL",
  "stats": {
    "timestamp": "1719868800000",
    "openInterest": 1000.0,
    "markPrice": 150.1,
    "oraclePrice": 150.0,
    "prevDayMarkPrice": 148.7,
    "dayVolumeUsd": 2500000.0,
    "dayVolumeBase": 16655.1,
    "currentFundingRate": 0.0000125,
    "eightHourFundingRate": 0.0001,
    "annualizedFundingRate": 0.1095
  }
}
```

For raw WebSocket payloads, see [WebSocket market stats](/api/websocket#market-stats). For historical stats, use the REST route [`GET /v1/market/{symbol}/stats`](/api/get-market-stats-history).

References:

* [TypeScript WebSocket example](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/examples/phoenix-ws-example.ts)
* [Rust market-stats example](https://github.com/Ellipsis-Labs/rise-public/blob/master/rust/sdk/examples/subscribe_market_stats.rs)

## L2 Orderbook

Use the L2 stream when you need current book levels. Keep one subscription per symbol and fan out internally to your UI or strategy modules.

<CodeGroup>
  ```ts TypeScript theme={null}
  const controller = new AbortController();

  for await (const update of client.streams!.l2Book("SOL", controller.signal)) {
    console.log(update.symbol, update.bids[0], update.asks[0]);
  }
  ```

  ```rust Rust theme={null}
  use phoenix_rise::api::PhoenixWSClient;
  use phoenix_rise::types::l2book::L2Book;

  let ws = PhoenixWSClient::new_from_env()?;
  let (mut rx, _handle) =
      ws.subscribe_to_orderbook_with_options("SOL".to_string(), false)?;
  let mut book = L2Book::new("SOL".to_string());

  while let Some(update) = rx.recv().await {
      book.apply_update(&update);
      println!("bid={:?} ask={:?}", book.best_bid(), book.best_ask());
  }
  ```
</CodeGroup>

Reference: [Rust L2 book example](https://github.com/Ellipsis-Labs/rise-public/blob/master/rust/sdk/examples/subscribe_l2_book.rs).

## API Polling

Polling is appropriate for startup snapshots, periodic reconciliation, or services that do not need live updates. It is not a substitute for WebSocket streams in a trading UI or market-making service.

<CodeGroup>
  ```ts TypeScript theme={null}
  const snapshot = await client.api.exchange().getSnapshot();
  const markets = await client.api.markets().getMarkets();
  const sol = await client.api.markets().getMarket("SOL");

  console.log(snapshot.markets.length, markets.length, sol.symbol);
  ```

  ```rust Rust theme={null}
  let exchange = http.get_exchange().await?.into();
  let metadata = PhoenixMetadata::new(exchange);
  let markets = http.markets().get_markets().await?;
  let sol = http.markets().get_market("SOL").await?;

  println!("{} {}", metadata.exchange().markets.len(), markets.len());
  println!("{}", sol.symbol);
  ```
</CodeGroup>

If you need to poll exchange params, poll on a slow interval and replace the local cache atomically. In TypeScript, prefer `exchangeMetadata: { stream: true }` when WebSocket access is available. In Rust, rebuild `PhoenixMetadata` from a fresh `get_exchange()` snapshot after reconnects or scheduled reconciliation, then continue applying market stats.

## Market Metadata And Calendars

The exchange cache includes public market metadata used by apps, including labels and logo URIs where available. Treat these fields as display metadata; trading logic should use the market params and risk params from the cache.

Commodity and equity markets can have session windows, after-hours bounds, reopen windows, and stale index behavior. The exchange cache exposes lightweight calendar metadata with the rest of the market snapshot so your UI can show the market's current calendar source and the next known transition without another API call.

<CodeGroup>
  ```ts TypeScript theme={null}
  await client.exchange.ready();

  const sol = client.exchange.market("SOL");
  const publicMetadata = client.exchange.marketMetadata("SOL");
  const cachedCalendar = sol?.metadata?.calendar ?? publicMetadata?.calendar;

  console.log(publicMetadata?.logoUri);
  console.log(cachedCalendar?.id);
  console.log(cachedCalendar?.calendarUri);
  console.log(cachedCalendar?.nextMarketTransitionUtc);
  ```

  ```rust Rust theme={null}
  let sol = metadata
      .get_market("SOL")
      .expect("SOL market should exist in exchange metadata");

  let cached_calendar = sol
      .metadata
      .as_ref()
      .and_then(|metadata| metadata.calendar.as_ref());

  println!("{:?}", sol.metadata.as_ref().and_then(|m| m.logo_uri.as_ref()));
  println!("{:?}", cached_calendar.map(|calendar| &calendar.id));
  println!("{:?}", cached_calendar.map(|calendar| &calendar.calendar_uri));
  println!("{:?}", cached_calendar.and_then(|calendar| {
      calendar.next_market_transition_utc.as_ref()
  }));
  ```
</CodeGroup>

Use the API clients when you need the full weekly schedule, date overrides, or current/next open state computed by the API.

<CodeGroup>
  ```ts TypeScript theme={null}
  const calendar = await client.api.markets().getMarketCalendar("SOL");
  const nextTransition =
    await client.api.markets().getNextMarketCalendarTransition("SOL");
  const nextCommodityTransition =
    await client.api.markets().getNextCommodityMarketTransition();
  const commodityCalendar =
    await client.api.markets().getCommodityMarketCalendar();

  console.log(calendar.marketCalendarId, calendar.calendar?.weeklySchedule);
  console.log(nextTransition.currentState, nextTransition.utcNextTransition);
  console.log(nextCommodityTransition.market, nextCommodityTransition.nextMarketState);
  console.log(commodityCalendar.calendar.weeklySchedule);

  if (calendar.marketCalendarId) {
    const calendarRecord =
      await client.api.markets().getMarketCalendarById(calendar.marketCalendarId);
    console.log(calendarRecord.calendar?.weeklySchedule);
  }
  ```

  ```rust Rust theme={null}
  let calendar = http.markets().get_market_calendar("SOL").await?;
  let next_transition = http
      .markets()
      .get_next_market_calendar_transition("SOL")
      .await?;
  let next_commodity_transition = http
      .markets()
      .get_next_commodity_market_transition()
      .await?;
  let commodity_calendar = http.markets().get_commodity_market_calendar().await?;

  println!("{}", calendar.market_calendar_id);
  println!("{:?}", calendar.calendar.as_ref().map(|c| &c.weekly_schedule));
  println!("{:?}", next_transition.current_state);
  println!("{:?}", next_transition.utc_next_transition);
  println!("{:?}", next_commodity_transition.utc_next_transition);
  println!("{:?}", commodity_calendar.calendar.weekly_schedule);
  ```
</CodeGroup>

TypeScript also exposes `getMarketCalendarById(id)` when you start from the calendar id stored in exchange metadata. Rust currently exposes the symbol and commodity calendar helpers.

API reference:

* [`GET /v1/market/{symbol}/market-calendar`](/api/get-market-calendar)
* [`GET /v1/market-calendar/{market_calendar_id}`](/api/get-market-calendar-by-id)
* [`GET /v1/market/{symbol}/next-market-calendar-transition`](/api/get-next-market-calendar-transition)
* [`GET /v1/market/next-commodity-market-transition`](/api/get-next-commodity-market-transition)
* [`GET /v1/market/commodity-calendar`](/api/get-commodity-market-calendar)

Related Phoenix docs:

* [RWA index price](/phoenix/real-world-assets/index-price)
* [RWA mark price and bounds](/phoenix/real-world-assets/mark-price-and-bounds)
* [Market specs](/phoenix/real-world-assets/market-specs)
