Skip to main content

Price feed

Trade calls require a signed Pyth Lazer payload. The cleanest way to wire this up is a tiny backend proxy that fronts the Pyth Lazer REST API: your frontend asks the proxy for a feed, the proxy adds the API token, and your client never sees the credential. This page is a minimum working example.

Get an API token

Sign up at pyth.network/lazer and grab your token. Store it as a server-side secret. Never ship it to the browser.

The Pyth Lazer request

Pyth Lazer exposes a POST /v1/latest_price endpoint. The request body asks for one or more feeds and which encodings to return; for Stellar trades you want the solana format because its Ed25519 signature scheme matches what the on-chain price verifier expects.

{
"channel": "fixed_rate@1000ms",
"properties": ["price", "exponent", "confidence"],
"formats": ["solana"],
"priceFeedIds": [1],
"jsonBinaryEncoding": "hex"
}

Feed IDs map to assets (1 = BTC, 2 = ETH, 23 = XLM). The full list is in the Pyth Lazer dashboard. Pyth runs three redundant nodes (pyth-lazer-0.dourolabs.app, -1, -2); fail over between them on error.

Minimum working proxy

A Cloudflare Worker using Hono gets you to a working price feed in about thirty lines. The same shape works on any other Node-style runtime.

import { Hono } from 'hono';

const NODES = [
'https://pyth-lazer-0.dourolabs.app/v1/latest_price',
'https://pyth-lazer-1.dourolabs.app/v1/latest_price',
'https://pyth-lazer-2.dourolabs.app/v1/latest_price',
];

const app = new Hono<{ Bindings: { PYTH_LAZER_TOKEN: string } }>();

app.get('/prices/:feedId', async (c) => {
const feedId = Number(c.req.param('feedId'));
if (!Number.isInteger(feedId)) {
return c.json({ error: 'invalid feed id' }, 400);
}

const body = JSON.stringify({
channel: 'fixed_rate@1000ms',
properties: ['price', 'exponent', 'confidence'],
formats: ['solana'],
priceFeedIds: [feedId],
jsonBinaryEncoding: 'hex',
});

for (const url of NODES) {
try {
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${c.env.PYTH_LAZER_TOKEN}`,
},
body,
});
if (!res.ok) continue;

const json = await res.json() as {
parsed?: { timestampUs?: string; priceFeeds?: { price: string; exponent: number; confidence: number }[] };
solana?: { data?: string };
};
const hex = json.solana?.data;
if (!hex) continue;

const feed = json.parsed?.priceFeeds?.[0];
return c.json({
data: hex,
price: feed?.price ?? '0',
exponent: feed?.exponent ?? 0,
confidence: feed?.confidence ?? 0,
timestamp: Math.floor(Number(json.parsed?.timestampUs ?? '0') / 1_000_000),
});
} catch {
// try the next node
}
}

return c.json({ error: 'all Pyth Lazer nodes failed' }, 502);
});

export default app;

Your frontend hits GET /prices/1, gets back { data, price, exponent, confidence, timestamp }, decodes data from hex into a Uint8Array, and passes that to the SDK as the price arg on a trade call.

Caching

Pyth Lazer is fast but not free. A 500 ms in-memory cache per feed is enough to absorb rapid polling from a frontend without ever serving a payload that's outside the contract's staleness window. Add an expiresAt field to the response, check it before fetching, and store the last response in a Map<number, ...>. For Cloudflare Workers, the same map persists across requests inside a single isolate.

When to skip the proxy

If your application is a backend service (a trading bot, a market maker, an automation) you can call Pyth Lazer directly with the API token. The proxy pattern exists to keep the token server-side when the client is a browser. Choose accordingly.