Skip to content

Add pro/frontend-jwt-auth Next.js example for Pyth Pro JWT flow - #131

Merged
aditya520 merged 5 commits into
mainfrom
sw-djpmrwuj
Jul 27, 2026
Merged

Add pro/frontend-jwt-auth Next.js example for Pyth Pro JWT flow#131
aditya520 merged 5 commits into
mainfrom
sw-djpmrwuj

Conversation

@aditya520

Copy link
Copy Markdown
Member

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

  • Server-side mint route (src/app/api/pyth-token/route.ts): POST /api/pyth-token forwards to POST https://pyth.dourolabs.app/auth/token with Authorization: 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_KEY is read from process.env in a server-only file — never referenced from NEXT_PUBLIC_* or client code.
  • Browser token cache (src/lib/pythToken.ts): matches the caching pattern in the docs — reuses the JWT until it's within 30s of expiry, then re-mints via POST /api/pyth-token.
  • History client (src/lib/pythClient.ts): fetchHistory({ symbol, from, to, resolution })GET https://pyth.dourolabs.app/v1/fixed_rate@200ms/history?… with Authorization: Bearer <jwt>, invalidates the cache and retries once on 401.
  • Chart UI: main page renders three lightweight-charts candlestick charts (BTC/USD, ETH/USD, SOL/USD), last 24h at 1-minute resolution, refreshed every 30s.
  • README + .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.
  • CI: .github/workflows/ci-pro-frontend-jwt-auth.yml — pnpm 9 / Node 18, runs test: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/symbols catalog on https://pyth.dourolabs.app exposes crypto spots as Crypto.BTC/USD (fully-qualified symbol) — passing the bare BTC/USD returns HTTP 404 symbol not found. The example uses Crypto.{BTC,ETH,SOL}/USD for API calls and displays the shorter form (BTC/USD, …) as the card label.

Test plan

  • pnpm install --frozen-lockfile
  • pnpm run test:format
  • pnpm run test:types
  • pnpm run build (without PRO_API_KEY — succeeds)
  • grep -rn PRO_API_KEY .next/static → no matches
  • POST /api/pyth-token with no PRO_API_KEY → 500 with setup message
  • POST /api/pyth-token with a bogus PRO_API_KEY → mirrors upstream 401 + body
  • Live GET /v1/fixed_rate@200ms/history matches the HistoryResponse shape used in src/lib/pythClient.ts
  • End-to-end browser render with a real PRO_API_KEY — not verified (no key available in this environment); see validation comment on the tracking issue

Out of scope

  • No wallet integration, on-chain posting, or trading UI.
  • No production hardening of /api/pyth-token (auth, rate limit) — called out in the README instead.
  • No test suite beyond typecheck + build.

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).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +15 to +16
const PYTH_API_BASE_URL =
process.env.NEXT_PUBLIC_PYTH_API_BASE_URL ?? DEFAULT_PYTH_API_BASE_URL;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

aditya520 and others added 4 commits July 27, 2026 12:09
…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]>
@morancj
morancj requested review from morancj and removed request for morancj July 27, 2026 16:38
@aditya520
aditya520 merged commit 36b85e5 into main Jul 27, 2026
7 checks passed
@aditya520
aditya520 deleted the sw-djpmrwuj branch July 27, 2026 16:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants