Add pro/frontend-jwt-auth Next.js example for Pyth Pro JWT flow - #131
Conversation
New top-level `pro/frontend-jwt-auth/` example demonstrating the JWT auth flow required for the Pyth Pro History API. A Next.js App Router route (`POST /api/pyth-token`) mints short-lived JWTs on the server using `PRO_API_KEY`, the browser caches them 30s before `expires_at`, and a lightweight-charts UI renders 24h of 1m OHLC for BTC/USD, ETH/USD, and SOL/USD from the Pyth Pro History API, polling every 30s. Also adds `.github/workflows/ci-pro-frontend-jwt-auth.yml` — a paths-scoped CI job that runs prettier, tsc --noEmit, and next build (no secrets required).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ee6df77732
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (!series) return; | ||
| const latest = candlesFromHistory(history); | ||
| for (const candle of latest) { | ||
| series.update(toChartCandle(candle)); |
There was a problem hiding this comment.
Update only the newest candle during polling
After the initial setData, the series already ends at the newest candle, but each refresh requests two minutes of history and iterates from the older candle first. lightweight-charts rejects a normal series.update whose timestamp precedes the series' latest timestamp, so the first poll throws before applying any new data; the charts remain stale and display an error. Update only the latest candle or explicitly perform supported historical updates.
Useful? React with 👍 / 👎.
| const PYTH_API_BASE_URL = | ||
| process.env.NEXT_PUBLIC_PYTH_API_BASE_URL ?? DEFAULT_PYTH_API_BASE_URL; |
There was a problem hiding this comment.
Keep the browser and mint endpoints on the same base URL
When a user follows .env.example and sets only PYTH_API_BASE_URL for a non-production environment, the server mints the token there while this browser client still sends history requests to the production default. Consequently the override does not actually move the app's data requests to non-prod and may also use a token against the wrong environment. The configuration should set/document the corresponding public URL as part of the same override.
Useful? React with 👍 / 👎.
…le no_data - PythChart: series.update() in lightweight-charts v4 rejects bars older than the last one, which made every 30s refresh throw "Cannot update oldest data" and permanently freeze the charts after initial load. Track the newest applied bar time and skip older candles. - pythToken: share one in-flight mint across concurrent callers so a page load performs a single upstream /auth/token call instead of one per chart. Also expose getTokenStatus() for the UI. - pythClient: a no_data history response may omit the OHLC arrays, which crashed candlesFromHistory. Treat missing arrays as an empty candle set. Co-Authored-By: Claude Fable 5 <[email protected]>
- Rewrite the README as a step-by-step tutorial: why the JWT exchange exists, a mermaid sequence diagram, a walkthrough of the four files, the symbol-name gotcha, and a troubleshooting table. - Add an in-page "How these charts authenticate" panel that teaches the flow from the auth-required announcement and shows a live view of the token cache (expiry countdown, mints this session). Co-Authored-By: Claude Fable 5 <[email protected]>
…and JWT snippet - Document in .env.example and the README that PYTH_API_BASE_URL and NEXT_PUBLIC_PYTH_API_BASE_URL must be overridden together, since the server mints tokens against one while the browser fetches history from the other (addresses the Codex P2 review comment). - Add an app screenshot to the README and the bearer-token attach snippet to the walkthrough, so the full JWT flow is visible in the docs. Co-Authored-By: Claude Fable 5 <[email protected]>
Greenlight requires .github/greenlight.yml with enabled: true at the PR head before it will review. Minimal config, no auto-approve paths. Co-Authored-By: Claude Fable 5 <[email protected]>
Summary
Adds a new top-level
pro/frontend-jwt-auth/example demonstrating the JWT auth flow required for the Pyth Pro History API since 2026-07-24. Also adds a paths-scoped GitHub Actions workflow that runs prettier + typecheck + build on changes to the example.What it does
src/app/api/pyth-token/route.ts):POST /api/pyth-tokenforwards toPOST https://pyth.dourolabs.app/auth/tokenwithAuthorization: Bearer $PRO_API_KEY, mirrors the upstream status + body on error, and returns the{ access_token, expires_at, token_type }JSON on success.PRO_API_KEYis read fromprocess.envin a server-only file — never referenced fromNEXT_PUBLIC_*or client code.src/lib/pythToken.ts): matches the caching pattern in the docs — reuses the JWT until it's within 30s of expiry, then re-mints viaPOST /api/pyth-token.src/lib/pythClient.ts):fetchHistory({ symbol, from, to, resolution })→GET https://pyth.dourolabs.app/v1/fixed_rate@200ms/history?…withAuthorization: Bearer <jwt>, invalidates the cache and retries once on401.lightweight-chartscandlestick charts (BTC/USD, ETH/USD, SOL/USD), last 24h at 1-minute resolution, refreshed every 30s..env.example: quick-start (Node 18+, pnpm, one env var), "how it works" section, security notes calling out that the example intentionally ships without request auth or a rate limit on/api/pyth-token..github/workflows/ci-pro-frontend-jwt-auth.yml— pnpm 9 / Node 18, runstest:format,test:types,build. Path-scoped so it only fires on changes to the example or the workflow itself. No secrets required to build.Discrepancy vs. docs
The docs table lists symbols like
BTC/USD, but the live/v1/symbolscatalog onhttps://pyth.dourolabs.appexposes crypto spots asCrypto.BTC/USD(fully-qualifiedsymbol) — passing the bareBTC/USDreturnsHTTP 404 symbol not found. The example usesCrypto.{BTC,ETH,SOL}/USDfor API calls and displays the shorter form (BTC/USD, …) as the card label.Test plan
pnpm install --frozen-lockfilepnpm run test:formatpnpm run test:typespnpm run build(withoutPRO_API_KEY— succeeds)grep -rn PRO_API_KEY .next/static→ no matchesPOST /api/pyth-tokenwith noPRO_API_KEY→ 500 with setup messagePOST /api/pyth-tokenwith a bogusPRO_API_KEY→ mirrors upstream 401 + bodyGET /v1/fixed_rate@200ms/historymatches theHistoryResponseshape used insrc/lib/pythClient.tsPRO_API_KEY— not verified (no key available in this environment); see validation comment on the tracking issueOut of scope
/api/pyth-token(auth, rate limit) — called out in the README instead.