Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Live Prices

This page covers showing prices that move: LivePrices, a stream of every market’s composed Pyth price with freshness and venue flags; reading Pyth Pro directly; and PriceService, the engine under both the stream and the keeper.

Prices on the chain and prices on screen differ. A snapshot reports the pair’s stored price, which moves only when a transaction stores a new one; a live price is Pyth’s newest print, composed the way the pair’s oracle would compose it. Every on-chain price check uses the print a call carries (see The 3-Second Price Window).

The samples assume bz connected as in Connecting with a Pyth Pro price source, and eth a market. pythToken is the page’s source of short-lived tokens (Tokens for a Browser).

LivePrices

const prices = await bz.livePrices(); // every listed market, each streamed as its own set of feeds
await prices.ready; // the first request for each market's latest print has answered

const unsubscribe = prices.subscribe((changed) => {
  for (const [, p] of changed) {
    const flag = p.venueClosed ? "closed" : p.fresh ? "live" : `${p.age} s old`;
    render(`${formatUnits18(p.spot, 2)} ± ${p.confidenceBp.toFixed(1)} bp (${flag})`);
  }
}); // every watched market; pass [eth] as a second argument for one

const now = prices.price(eth); // from memory: null while a feed has no print yet
prices.fresh(eth); // inside the pair's 3 s window, and the confidence clears the 2% cap
prices.venueClosed(eth); // a trading-hours market whose feed stopped generating prices

// When the view goes away:
unsubscribe(); // one subscriber
prices.stop(); // the stream itself, for good

bz.livePrices(options?) lists the connection’s markets and streams them through the connection’s Pyth Pro client: one subscription per market, on every stream endpoint at once, so each market’s signed update carries exactly its own feeds (both legs of a composite in one update) and a dropped endpoint interrupts nothing. Before the stream it makes one request for every feed’s latest print, so every market shows a price at once; ready settles when that request has answered or failed. When the stream fails three times in a row, it polls for a minute, then tries the stream again.

The stream runs over a WebSocket. Browsers and Node 22 and later have one; on Node 20 give the client a constructor (new ProClient({ WebSocket }), with the ws package’s, say).

Everything a render needs is read from memory:

MethodReturns
price(market)The market’s MarketPrice, or null while a feed has no print yet (or the market is not watched).
all()A Map from address to MarketPrice, for every market with one.
fresh(market)Whether the price is inside the pair’s 3-second window and its confidence clears the 2% cap.
venueClosed(market)Whether a trading-hours market’s venue is closed. Always false for a 24/7 market.
subscribe(listener, markets?)Calls listener with a Map of the markets whose price changed after each batch of prints, and of those whose fresh or venueClosed flipped as time passed (checked every tickMs). Only markets when given. Returns the function that unsubscribes.
add(markets)Streams these markets too (already watched ones are ignored).
markets()The markets being streamed.
stop()Ends the stream, the ticker and every subscription, for good.
serviceThe PriceService underneath: its healthy, lastError and raw latest prints are public.

A listener is called synchronously, with a map it must not keep; a listener that throws does not stop the others or the stream. Call stop() when the view unmounts (in a React effect’s cleanup, say).

Each MarketPrice (18 decimals):

FieldMeaning
marketThe pair’s address.
spotThe composed price.
low, highSpot less and plus the confidence: what a long and a short are valued at when they withdraw.
confidenceThe band’s wider half: max(high − spot, spot − low).
confidenceBpconfidence / spot in basis points, a number for display.
confidenceOkEvery leg, and the composed band, sit inside the oracle’s 2% confidence cap.
publishTimeThe second the older leg’s price was generated in (Unix seconds): what the pair’s window judges.
ageSeconds since publishTime, at the service’s clock.
freshEvery leg’s price was generated at most 3 seconds from now, and confidenceOk: a call carrying this print would be accepted if it landed now.
venueClosedA trading-hours market whose feed has generated no price for closedAfter seconds while the endpoint delivers.
legsThe feeds’ prints (base first for a composite).

A price’s age is when it was generated. While a venue is shut Pyth keeps sending updates, each stamped with the current time and carrying the last price forward. The update’s own timestamp says nothing about the price: publishTime is the second the price was generated in (Pyth’s feed update timestamp), which is what the pair’s 3-second window judges and what fresh, age and venueClosed use. A carried-forward price reads as old, as it should.

How the oracle composes a price, and why a closed venue matters, is in Oracle.

Options

const tuned = await bz.livePrices({
  closedAfter: 30, // seconds without a newly generated price before a trading-hours venue reads closed (default 15)
  tickMs: 500, // how often fresh/venueClosed flips are re-checked for subscribers (default 1000)
  reuseAge: 1, // a price generated this recently is reused as a call's update (default 1 s)
  stream: true, // the WebSocket stream, falling back to polling (default true)
  pollInterval: 2, // seconds between polls while not streaming (default 1)
  maxFutureSkew: 120, // seconds a print may be dated ahead of this clock (default here 120)
  logger: null, // silence endpoint warnings (default console)
});

// Some markets only, with a Pyth Pro client of their own, without a connection:
const some = await livePrices([eth], { pro: { token: pythToken, channel: "fixed_rate@1000ms" } });
OptionDefaultMeaning
closedAfter15Seconds without a newly generated price after which a trading-hours market reads closed.
tickMs1000Milliseconds between re-checks of fresh and venueClosed for subscribers (0: never).
reuseAge1A stream update whose price was generated at most this many seconds ago is reused for a call (see Prices for Calls).
starttrueStart streaming at once; with false, call start().
streamtrueFollow the stream, falling back to polling when it fails. false polls only.
streamIdleTimeout10Seconds the stream may bring no new update before it is dropped and reconnected. null or 0 waits forever.
pollInterval1Seconds between polls when not streaming.
maxFutureSkew120Seconds a print may be dated ahead of this device’s clock before it is ignored. See below.
clockthe system clockThe time freshness is judged at (Unix seconds).
loggerconsoleWhere endpoint failures are reported (warn); null for silence.
prothe connection’sOptions for a ProClient of the stream’s own (another token, another channel).
clientthe connection’sA ProClient (or any PriceSource) to share.

PriceService’s sendMargin, ethUsdMaxAge and ethUsdBounds are accepted too; they do not change what LivePrices reports.

The channel. A client streams on one channel (default fixed_rate@200ms: an update every 200 ms per subscription and endpoint). A page that only shows prices can stream fixed_rate@1000ms and still fetch a fresh update when it builds a call. When the connection checks signers (the default), each new update costs one signature recovery, so a slower channel is also lighter on a phone.

The future-print skew. A print dated far ahead of this device’s clock would hold its feed until a newer print arrived, so prints dated more than maxFutureSkew seconds ahead are ignored, with a warning once per feed. The keeper’s bound is 5 seconds; a browser’s clock can run a minute slow, so LivePrices allows 120 seconds (BROWSER_FUTURE_SKEW). A device clock that is far off also makes age and fresh wrong: fresh judges against this device’s clock, the pair against the block’s.

Without a Connection

livePrices(markets, options) streams the given markets (MarketInfos, from a market list you already have) through client, or through a client it makes from pro; with a context instead of markets it lists them first, as bz.livePrices does. new LivePrices(markets, source, options) is the constructor underneath. A connection whose price source is not a Pyth Pro source has no client to stream from: pass pro or client, or livePrices throws a ConnectError.

Prices for Snapshots

Pass a LivePrices as the prices option of marketSnapshot, accountSnapshot, portfolio, watchMarket and watchAccount: they then value positions at the live price and judge venues from the stream, with no request of their own.

Prices for Calls

A LivePrices is also a PriceUpdateSource. Its latest(feeds) returns the stream’s own update for those feeds when every price in it was generated at most reuseAge seconds ago (no request), and fetches one otherwise. Give it to a facade over the same context, and that facade’s builders attach the stream’s updates:

// A LivePrices is also a PriceUpdateSource: give it to a second facade over the same context, and its
// order builders reuse the stream's updates instead of asking the endpoint again.
const trading = Bazaar.from({ ...bz.ctx, prices: tuned });
// ...then trading.trade.createOrder(...) attaches the stream's update when its price is at most 1 s old.

The copied context has its own caches (it lists the markets again once), and its price source is not a proPriceSource: keep the original bz for reads, which then still get the symbols registry and venue checks from the connection’s client. updateFor(market) returns the stream’s current update for one market, for code that encodes calls itself.

Pyth Pro Directly

// Pyth Pro directly: prints read from the signed update itself, never from the JSON beside it.
const source = proPriceSource({ token: pythToken });
const [print] = await source.prints(eth.legs); // feedId, price, conf, expo, publishTime, generatedUs, blob
const update = await source.latest(eth.legs); // { update: Hex[], publishTime }: what a call attaches

const client = new ProClient({ token: pythToken, timeout: 5 });
const ctl = new AbortController();
// One set of feeds: every update carries exactly the market's legs, from whichever endpoint is first.
for await (const batch of client.stream(eth.legs, ctl.signal, { idleTimeout: 10 })) {
  render(`${batch.length} prints`);
  ctl.abort(); // or break: the stream ends at once
}

proPriceSource(options | client) is the connection’s price source (see Connecting); its prints(feedIds) returns the parsed prints, for showing a price without a stream. ProClient is the client underneath:

MemberWhat it does
new ProClient(options)The options of Connecting: token, channel, the URLs, WebSocket, isTrustedSigner. A token or header with a line break is refused at once.
latest(ids)The latest prints for these feeds, all from one signed update (a feed without a usable live price is left out).
latestUpdate(ids)The same update whole, as a ProUpdate: blob, signer, timestampUs, channel, every feed’s entry (feeds), and its prints.
atTick(ids, second)The one-second channel’s update stamped exactly second, or null when there is none: what settlement proofs are made of.
symbols({ query?, assetType? })Pyth Pro’s symbols registry: each feed’s number, symbol, asset type, fastest channel, state and schedule. Needs no token.
stream(feeds, signal?, { idleTimeout?, onNotice? })An async iterator of print batches from every stream endpoint, each update yielded once. feeds is one set of feeds or a list of sets; each set is its own subscription. Aborting signal, breaking out of the loop or close() ends it. It throws a ProError when no endpoint can be reached, and when no new update arrives for idleTimeout seconds (default 10).
close()Ends every open stream.
displayUrlThe request API’s base URL with any key masked.

A Print has feedId (the bytes32 form), price, conf, expo, publishTime (the second the price was generated in), generatedUs (the same instant in microseconds), timestampUs and channel (the carrying update’s), signer (when signers are checked), blob (the signed update that carries it) and received. Every field but received and signer is read from the update’s signed payload, never from the JSON an endpoint sends beside it; signer is recovered from its signature.

A feed gets a print only when its entry holds a usable live price: a price, an exponent, a confidence above zero and a generation time. The oracle refuses a live update without any of them, so the SDK never offers one to a call; whyNotLive(entry) says which is missing.

A response over 16 MiB, or a stream message over 1 MiB, is refused. Errors mask the token, header secrets and any key in a URL.

PriceService

// PriceService: the engine under LivePrices and the keeper, for code that wants the raw parts.
const service = new PriceService(client, { stream: true, maxFutureSkew: 120 });
const model = marketFromStatic(eth); // the per-market functions take the SDK's Market model
service.watchMarkets([model]);
const stopListening = service.onPrints((feeds) => {
  const pp = service.price(model); // spot, low, high, publishTime, confidenceOk
  if (pp) render(`${feeds.size} feeds updated, spot ${formatUnits18(pp.spot, 2)}, fresh: ${service.fresh(model, null, 0)}`);
});
void service.run(); // follows the stream (or polls) until stop(); never rejects
// ... later
stopListening();
service.stop();

PriceService is the engine under LivePrices and the keeper’s price loop: it holds the newest print per feed and the newest update per watched set of feeds, and builds per-market prices and updates from them. Use it when LivePrices is too much (a server that tracks a few feeds) or too little (custom freshness margins).

MemberWhat it does
new PriceService(source, options?)source is a ProClient or any PriceSource (latest and stream). Options as in the table above, plus sendMargin (default 1). The default maxFutureSkew here is 5 seconds.
watchMarkets(markets), watchSets(sets), watch(feeds)Watch each market’s legs (or each set) as one subscription, or each feed on its own; the stream resubscribes at once. ETH/USD is always watched (the keeper prices gas with it).
run()Follow the stream (or poll) until stop(), through any failure: three in a row mark it unhealthy and switch to polling for a minute. Never rejects.
fetch(feeds?)One request for the latest prints, outside the loop.
ingest(prints)Take in prints from anywhere.
onPrints(listener)Called after each batch with the feeds that got a newly generated price; returns the remover.
nextPrints()Waits for the next newly generated price (one consumer only).
price(market), legs(market)The composed PairPrice (spot, low, high, publishTime, confidenceOk) and the prints, or null.
fresh(market, now?, margin?)Every leg’s price generated within 3 − margin seconds of now (default margin: sendMargin), and the confidence clears the cap.
closed(market, now?, after?)No price generated for after seconds (default 15) while the service is healthy.
updateFor(market)The signed update a call on the market attaches: the one update that carries its legs.
printsFor(feeds)The prints a call reading these feeds together would attach.
ethUsd(now?)USD per ETH from the newest ETH/USD print, or null when stale or out of bounds.
missing()Watched feeds with no print yet.
healthy, lastError, lastUpdate, latestState, readable.
stop()Ends run and its stream.

A market prices from its own set’s update, so a call attaches one update and pays one verification fee. Only while its set has none, or lags another update’s price for one of its legs by a second or more, does it fall back to each leg’s newest print (one update per leg).

The per-market methods take the SDK’s Market model: marketFromStatic(info) makes one from a MarketInfo.

What Can Go Wrong

WhatWhy
ConnectError: live prices need a Pyth Pro source: ...The connection’s price source is not a Pyth Pro source and no pro or client was given.
ProError: this runtime has no WebSocket ...Node before 22: give the client a WebSocket constructor.
price() stays nullA feed has no print: Pyth has no usable price for it (a warning names the feed), or ready has not settled.
Warning: price endpoint sent no print for feed 0x...The feed ID is wrong, the token does not cover the feed, the feed is not served on the channel, or Pyth has no price or no confidence for it. Other feeds are unaffected.
Warning: price stream refused the subscription for feed(s) ...What Pyth answered to a subscription, passed on once.
Warning: price stream endpoint ... dropped (...): reconnectingOne endpoint went away; the others keep delivering.
Warning: ignored a print for feed 0x... dated N s ahead of the clockThis device’s clock is behind, or the endpoint is wrong.
fresh is false while prices moveThis device’s clock is off, or the confidence is above 2% (confidenceOk).
ProError: price endpoint refused the request (HTTP 401); check the token (prices.token) ...The token is wrong, expired, or not entitled to these feeds or this channel.
ProError: a price update is signed by 0x..., which is not a signer Pyth's verifier trusts: refusing itThe update did not come from Pyth, or the deployment’s verifier no longer trusts that signer.
service.healthy is falseThree failures in a row; service.lastError says why. It keeps retrying.