diff --git a/apps/developer-hub/content/docs/price-feeds/core/meta.json b/apps/developer-hub/content/docs/price-feeds/core/meta.json index 4b6f6e87fa..5cbf0aa373 100644 --- a/apps/developer-hub/content/docs/price-feeds/core/meta.json +++ b/apps/developer-hub/content/docs/price-feeds/core/meta.json @@ -1,6 +1,7 @@ { "pages": [ "getting-started", + "upgrade", "---Tutorials---", "create-your-first-pyth-app", "---How-To Guides---", diff --git a/apps/developer-hub/content/docs/price-feeds/core/upgrade/contracts.mdx b/apps/developer-hub/content/docs/price-feeds/core/upgrade/contracts.mdx new file mode 100644 index 0000000000..040ebb6edb --- /dev/null +++ b/apps/developer-hub/content/docs/price-feeds/core/upgrade/contracts.mdx @@ -0,0 +1,25 @@ +--- +title: "Upgraded Pyth Core Contract Addresses" +description: "Per-chain addresses for the upgraded Pyth Core Contract." +full: true +--- + +import { Callout } from "fumadocs-ui/components/callout"; +import MigrationContractsTable from "../../../../../src/components/MigrationContractsTable"; + +These are the **upgraded Pyth Core Contract** addresses on each chain. To upgrade your Pyth Core integration, swap your existing Pyth contract address for the one listed below for your chain. The interface is unchanged — no other code changes are needed. + +See the [upgrade guide](/price-feeds/core/upgrade) for the full upgrade path. + +## Mainnets + + + +## Testnets + + + + + **Don't see your chain?** We're adding chains regularly and can discuss + custom arrangements. [Contact the team →](mailto:contact@pyth.network) + diff --git a/apps/developer-hub/content/docs/price-feeds/core/upgrade/how-it-works.mdx b/apps/developer-hub/content/docs/price-feeds/core/upgrade/how-it-works.mdx new file mode 100644 index 0000000000..920a60b97a --- /dev/null +++ b/apps/developer-hub/content/docs/price-feeds/core/upgrade/how-it-works.mdx @@ -0,0 +1,53 @@ +--- +title: How the Pyth Core upgrade works +description: A technical look at the signers, data flow, and contracts behind the upgrade. +full: true +--- + +The Pyth Core upgrade preserves the existing contract interface and Hermes API surface, so existing integrations work without code changes. +Only the signers and the source of the data change. This page explains the new architecture and how it works. + +## Architecture + +```mermaid +flowchart LR + R1[Router 1] --> H[Upgraded Hermes endpoint] + R2[Router 2] --> H + R3[Router 3] --> H + R4[Router 4] --> H + R5[Router 5] --> H + H --> C[Consumer] + C --> P[Upgraded Pyth Core Contract] +``` + +## Data flow + +Each tick, routers take the latest aggregated prices from Pyth Pro and construct a Merkle tree using the same leaf format Pythnet uses for Pyth Core. Each router signs the Merkle root independently. The upgraded Hermes endpoint collects roots and price messages from all routers and serves the latest update — the signed root plus per-price Merkle proofs. It does this through the same HTTP and streaming endpoints as Hermes. Consumers fetch the update and submit it to the upgraded Pyth Core Contract, which verifies the router signatures meet quorum and then verifies each price against the root using its Merkle proof. + +The shape of this flow mirrors Pyth Core. The difference is where the Merkle root comes from and who signs it: on the existing system, Pythnet produces the root and Wormhole guardians sign it; in the upgrade, the routers do both. + +## Components + +### Routers + +Routers are the same routers in Pyth Pro. To learn more visit [insert link here]. + +Routers build a Merkle tree over the upgraded price aggregate each tick and sign the root. The leaf format matches Pythnet's exactly, which is what makes the resulting update payload byte-compatible with Pyth Core. Five routers, operated independently, each sign the root using the same signature scheme Wormhole guardians use on the existing system. + +On the existing Pyth Core, Wormhole guardians observe a Merkle root produced on Pythnet and sign it. With the upgrade, the routers both produce and sign the root. + +### Upgraded Hermes endpoint + +The upgraded Hermes endpoint exposes the same API as traditional Hermes — the same endpoints and the same response shapes — at a different URL. It collects signed roots and price messages from all five routers and serves the latest update with the signed root and per-price Merkle proofs. +Existing Hermes clients work unchanged when pointed at the upgraded endpoint. +Having an API key is required for upgraded Hermes. + +### Upgraded Pyth Core Contract + +New contracts are deployed at new addresses on the same chains as the existing Pyth Core contracts. They expose the same ABI, so existing integrations work without code changes. The contracts are configured to accept signatures from the five routers with a **3/5** quorum, compared to the existing system's **13/19** Wormhole guardian quorum. + +## What this means for consumers + +Upgrading your Pyth Core integration requires two changes: point the client at the upgraded Hermes endpoint, and use the upgraded Pyth Core Contract address in place of the existing Pyth Core contract address. SDK code, payload parsing, and update submission all stay the same. + +See the [upgrade guide](/price-feeds/core/upgrade) for the full upgrade path, or the [upgraded Pyth Core Contract addresses](/price-feeds/core/upgrade/contracts) for per-chain addresses. diff --git a/apps/developer-hub/content/docs/price-feeds/core/upgrade/index.mdx b/apps/developer-hub/content/docs/price-feeds/core/upgrade/index.mdx new file mode 100644 index 0000000000..e7cb1560c8 --- /dev/null +++ b/apps/developer-hub/content/docs/price-feeds/core/upgrade/index.mdx @@ -0,0 +1,238 @@ +--- +title: "Preparing for the Pyth Core upgrade" +description: "Everything you need to upgrade your Pyth Core integration before July 31, 2026." +icon: RocketLaunch +full: true +--- + +import { Button } from "@pythnetwork/component-library/Button"; +import { Accordion, Accordions } from "fumadocs-ui/components/accordion"; +import { Callout } from "fumadocs-ui/components/callout"; +import { Card, Cards } from "fumadocs-ui/components/card"; +import { Step, Steps } from "fumadocs-ui/components/steps"; +import { Tab, Tabs } from "fumadocs-ui/components/tabs"; +import { + ActiveStepHighlighter, + BranchSection, + BranchToggle, +} from "../../../../../src/components/MigrationFlow"; + + + +## What's changing + +Pyth Network is upgrading [Pyth Core](/price-feeds/core) on **July 31, 2026**. +This upgrade replaces Pyth Core's underlying data infrastructure with an improved version, enabling new features: + +- Higher-frequency updates +- Customizable channels +- Access to Pyth Terminal + +Your existing integration keeps working: the on-chain Pyth contract is upgraded by the Pyth DAO on July 31, and [Hermes](/api-reference/pyth-core/hermes) requests are redirected to the upgraded backend automatically. +The one new requirement is authentication on Hermes — every Hermes user needs a Pyth API Key by **July 31**. + +For a walkthrough of the signers, data flow, and contracts behind the upgrade, see [How the Pyth Core upgrade works](/price-feeds/core/upgrade/how-it-works). + + + **All Hermes users need a Pyth API Key by July 31, 2026** — the upgraded + Hermes endpoint requires authentication, even for users who wait for the + automatic upgrade. [Register at Pyth Terminal →](https://pythdata.app/signup) + + +## Does this apply to you? + +- **Your app calls [`hermes.pyth.network`](https://hermes.pyth.network/docs/)?** + Get a Pyth API Key in Step 1. +- **You use [Pyth Core contracts](/price-feeds/core/contract-addresses) on-chain?** + Choose: swap addresses now, or wait for the automatic upgrade (see the decision section below). +- **You only use a protocol that already integrates Pyth?** + No action needed from your side. + +## Your upgrade path + + + +### Step 1: Get a Pyth API Key + +Required for everyone who calls Hermes. +Sign up at Pyth Terminal: a free trial is included, paid plans cover ongoing use. + + + +### Step 2: Upgrade now or wait for automatic? + +After Step 1, choose how and when to move to the upgraded infrastructure. Your choice is saved in the URL so you can share or bookmark a specific path. + + + +| | Upgrade now (recommended) | Wait for automatic | +| ---------------- | ------------------------------------------ | -------------------------------------------------------------------- | +| Timing | You choose | July 31, 2026 | +| Downtime | Up to you | Brief, during the switch | +| Hermes endpoint | Switch to the upgraded URL now | Start using `hermes.pyth.network` with an API key before the cutover | +| Contract address | You swap to the upgraded Pyth Core Contract | DAO upgrades the current Pyth Core Contract for you | + + +**Upgrade now (recommended)**: Continue with Steps 2 and 3 below. You move to the [upgraded Hermes endpoint](https://pyth.dourolabs.app/hermes) and the [upgraded Pyth Core Contract](/price-feeds/core/upgrade/contracts) today, on your own schedule, with zero downtime. + + + +**Wait for automatic**: You're done after Step 1. On July 31, the Pyth DAO upgrades your contract and `hermes.pyth.network` starts requiring authentication. You'll need to push a code change adding the `Authorization: Bearer $PYTH_API_KEY` header at that point. For a safer rollout, start using the authenticated Hermes API (`https://pyth.dourolabs.app/hermes`) before cutover as a fallback path. Plan a deploy around the cutover. + + + + +## If you're upgrading early + + + + +### Move your Hermes calls to the upgraded endpoint + +Switch your Hermes base URL from `hermes.pyth.network` to `pyth.dourolabs.app/hermes` and add your API key as a bearer token. The two URLs serve the same data today. The upgraded endpoint requires authentication. + + + + +```diff +- url: "https://hermes.pyth.network" ++ url: "https://pyth.dourolabs.app/hermes" ++ Authorization: "Bearer $PYTH_API_KEY" +``` + + + + +```ts +import { HermesClient } from "@pythnetwork/hermes-client"; + +const client = new HermesClient("https://pyth.dourolabs.app/hermes", { + headers: { + Authorization: `Bearer ${process.env.PYTH_API_KEY}`, + }, +}); +``` + + + + + + **Most resilient: use both URLs as primary + fallback.** Keeps you live + through the July 31 cutover regardless of which endpoint is reachable at + any moment. + + ```ts + const primary = new HermesClient("https://pyth.dourolabs.app/hermes", { + headers: { Authorization: `Bearer ${process.env.PYTH_API_KEY}` }, + }); + const fallback = new HermesClient("https://hermes.pyth.network"); + + async function getLatestPriceUpdates(ids: string[]) { + try { + return await primary.getLatestPriceUpdates(ids); + } catch { + return await fallback.getLatestPriceUpdates(ids); + } + } + ``` + + +Routes and response shapes are unchanged. +The upgraded endpoint is a drop-in replacement. See the [Hermes API reference](https://pyth.dourolabs.app/docs/?urls.primaryName=Hermes+API#/) for the full surface. + + + + +### Swap your contract address + +Swap your existing Pyth contract address for the [upgraded Pyth Core Contract](/price-feeds/core/upgrade/contracts) on your chain. The upgraded Pyth Core Contract preserves the Pyth Core interface. No other code changes are needed. + +View all upgraded [Pyth Core Contract addresses](/price-feeds/core/upgrade/contracts). + + + + + + +## Chain support + +Pyth Core will be supported on the chains listed on the [upgraded Pyth Core Contract addresses page](/price-feeds/core/upgrade/contracts). If your chain isn't listed, [contact the team](#get-help) — additional support is available by custom arrangement. + +### Feed support + +Nearly all current Pyth Core feeds remain available after the upgrade, with new ones added. Look up your specific feeds on the [feed explorer](https://pythdata.app/explore). If a feed you depend on isn't listed, [contact the team](#get-help). + +## FAQ + + + + On July 31, 2026, the Pyth DAO upgrades your contract for you and `hermes.pyth.network` starts requiring authentication. Two things you still have to do: + + 1. Have a Pyth API Key (Step 1) by that date. + 2. Push a code change adding `Authorization: Bearer $PYTH_API_KEY` to your Hermes requests, around the cutover. Until that deploy lands, Hermes calls will fail. + + Most users avoid this risk by upgrading early (see Steps 2 and 3) and switching to the upgraded Hermes endpoint, which already accepts the bearer header today. + + + On-chain code is fine: the upgrade is fully backward-compatible with the existing Pyth Core interface via the upgraded Pyth Core Contract. + + Hermes code breaks at the cutover *if* your client is still pointed at `hermes.pyth.network` without a bearer header. Either upgrade early (Step 2) or plan a deploy around July 31 to add the header. + + + On July 31, 2026. Exact switch timing is announced closer to the date. From that point, `hermes.pyth.network` serves the upgraded data and requires bearer authentication — unauthenticated requests will be rejected. + + + Yes. The upgraded endpoint (`pyth.dourolabs.app/hermes`) and the upgraded Pyth Core Contract are both available on testnets today. See the chain support section above for testnet contract addresses. + + + Yes. The upgrade introduces a subscription-based plan for ongoing Hermes access. A free trial is included on signup; paid plans cover continued use. Custom plans are available — [contact the team](#get-help). This is the most significant change for many Pyth Core users. + + + Contact the team. Several chains are in active discussion and may be supported by July 31. For others, custom arrangements are possible. + + + Nearly all current Pyth Core feeds remain available after the upgrade, with new ones added. Look up your specific feeds on the [feed explorer](https://pythdata.app/explore). If a feed you depend on isn't listed, [contact the team](#get-help). + + + No. The API key is only required for Hermes (REST/SSE) requests. If your integration reads prices directly from the Pyth contract on-chain and never calls Hermes, you don't need to register. Your contract will be upgraded automatically on July 31, or you can swap it early (Step 3). + + + Routes and response shapes are unchanged — the upgraded endpoint is a drop-in replacement. The differences are *which URL* you call and *whether you send a bearer header*: + + - **Upgrade early (recommended):** switch your base URL to `pyth.dourolabs.app/hermes` and add `Authorization: Bearer $PYTH_API_KEY` today. The data is identical; only auth changes. + - **Wait:** stay on `hermes.pyth.network` today (unauthenticated). On July 31, push a deploy that adds the bearer header on the same URL. + + See the [Hermes API reference](https://pyth.dourolabs.app/docs/?urls.primaryName=Hermes+API#/) for the full surface. + + + +## Get help + + + + + + diff --git a/apps/developer-hub/next.config.js b/apps/developer-hub/next.config.js index 2dad8b5a40..2158a1b065 100644 --- a/apps/developer-hub/next.config.js +++ b/apps/developer-hub/next.config.js @@ -258,7 +258,7 @@ const config = { }, { - source: String.raw`/price-feeds/:path((?!core(?:/|$|\.mdx?$)|pro(?:/|$|\.mdx?$)|hip-3-service(?:/|$|\.mdx?$)).*)`, + source: String.raw`/price-feeds/:path((?!core(?:/|$|\.mdx?$)|pro(?:/|$|\.mdx?$)|hip-3-service(?:/|$|\.mdx?$)|migration(?:/|$|\.mdx?$)).*)`, destination: "/price-feeds/core/:path", permanent: true, }, diff --git a/apps/developer-hub/src/app/(docs)/[section]/layout.tsx b/apps/developer-hub/src/app/(docs)/[section]/layout.tsx index 0d1c35c105..5a2d0f65a3 100644 --- a/apps/developer-hub/src/app/(docs)/[section]/layout.tsx +++ b/apps/developer-hub/src/app/(docs)/[section]/layout.tsx @@ -4,6 +4,7 @@ import Link from "next/link"; import type { ReactNode } from "react"; import { docsOptions } from "../../../config/layout.config"; +import { MigrationBanner } from "../../../components/MigrationBanner"; export default async function Layout({ children, @@ -36,6 +37,7 @@ export default async function Layout({ )} + {section === "price-feeds" && } {children} ); diff --git a/apps/developer-hub/src/app/(homepage)/layout.tsx b/apps/developer-hub/src/app/(homepage)/layout.tsx index 85274122b3..d49ff458c3 100644 --- a/apps/developer-hub/src/app/(homepage)/layout.tsx +++ b/apps/developer-hub/src/app/(homepage)/layout.tsx @@ -1,8 +1,14 @@ import { HomeLayout } from "fumadocs-ui/layouts/home"; import type { ReactNode } from "react"; +import { MigrationBanner } from "../../components/MigrationBanner"; import { baseOptions } from "../../config/layout.config"; export default function Layout({ children }: { children: ReactNode }) { - return {children}; + return ( + <> + + {children} + + ); } diff --git a/apps/developer-hub/src/app/llms-price-feeds-core.txt/route.ts b/apps/developer-hub/src/app/llms-price-feeds-core.txt/route.ts index f8b36d0007..caec42a367 100644 --- a/apps/developer-hub/src/app/llms-price-feeds-core.txt/route.ts +++ b/apps/developer-hub/src/app/llms-price-feeds-core.txt/route.ts @@ -2,7 +2,8 @@ import { NextResponse } from "next/server"; export const revalidate = false; -const CONTENT = `# Pyth Core — Quick Start +const CONTENT = `\` +function () { return (\`# Pyth Core — Quick Start > Decentralized pull-based price oracle delivering 500+ feeds with 400ms updates across 100+ chains. > This file contains a curated quick-start. For full docs, fetch individual pages below. @@ -20,22 +21,22 @@ Supported chains: EVM, Solana, Sui, Aptos, CosmWasm, NEAR, Starknet, Fuel, IOTA, **Pull Oracle Model**: Your app requests price updates from Hermes (off-chain API), submits them on-chain for verification, and reads the result — all in one transaction. Cheaper and fresher than push oracles. **Price Structure**: Each price contains four fields: -- \`price\` (int64) — Price as integer, multiply by 10^expo for actual value -- \`conf\` (uint64) — Confidence interval (same scale as price) -- \`expo\` (int32) — Exponent, typically -8 (divide by 10^8) -- \`publishTime\` (uint) — Unix timestamp of publication +- \\`price\\` (int64) — Price as integer, multiply by 10^expo for actual value +- \\`conf\\` (uint64) — Confidence interval (same scale as price) +- \\`expo\\` (int32) — Exponent, typically -8 (divide by 10^8) +- \\`publishTime\\` (uint) — Unix timestamp of publication **Confidence Intervals**: Statistical range where the true price likely falls (95% coverage). Use (price - conf) for conservative valuations, (price + conf) for liability protection. Pause activity if conf/price ratio exceeds your threshold. -**Staleness**: Prices become stale if not recently updated or outside market hours. Always use \`getPriceNoOlderThan()\` with an appropriate age threshold rather than \`getPriceUnsafe()\`. +**Staleness**: Prices become stale if not recently updated or outside market hours. Always use \\`getPriceNoOlderThan()\\` with an appropriate age threshold rather than \\`getPriceUnsafe()\\`. -**Update Fees**: Each on-chain price update costs a small fee (typically 1 wei on EVM). Call \`getUpdateFee()\` to get the exact amount before submitting. +**Update Fees**: Each on-chain price update costs a small fee (typically 1 wei on EVM). Call \\`getUpdateFee()\\` to get the exact amount before submitting. ## Integration Code ### EVM (Solidity) -\`\`\`solidity +\\`\\`\\`solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; @@ -62,13 +63,14 @@ contract MyContract { return (p.price, p.conf, p.expo); } } -\`\`\` +\\`\\`\\` -Install: \`npm install @pythnetwork/pyth-sdk-solidity\` +Install: \\`npm install @pythnetwork/pyth-sdk-solidity\\` ### TypeScript (Hermes Client) -\`\`\`typescript +\\`\\`\\`typescript + import { HermesClient } from "@pythnetwork/hermes-client"; const client = new HermesClient("https://hermes.pyth.network"); @@ -82,24 +84,24 @@ console.log("Confidence:", parsed.price.conf); // The binary data to submit on-chain const updateData = priceUpdates.binary.data; -\`\`\` +\\`\\`\\` -Install: \`npm install @pythnetwork/hermes-client\` +Install: \\`npm install @pythnetwork/hermes-client\\` ### Streaming (Server-Sent Events) -\`\`\`typescript +\\`\\`\\`typescript const eventSource = await client.getPriceUpdatesStream([ETH_USD]); eventSource.onMessage((update) => { console.log("New price:", update.parsed[0].price.price); }); // Note: SSE streams auto-close after 24 hours — implement reconnection logic -\`\`\` +\\`\\`\\` ## Contract Addresses ### EVM Mainnet -- Ethereum: 0x4305FB66699C3B2702D4d05CF36551390A4c69C6 +- Ethereum: **New Address Here** - Arbitrum: 0xff1a0f4744e8582DF1aE09D5611b887B6a12925C - Base: 0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a - Optimism: 0xff1a0f4744e8582DF1aE09D5611b887B6a12925C @@ -140,7 +142,7 @@ Full list: https://docs.pyth.network/price-feeds/core/contract-addresses ## Common Patterns ### Stale Price Handling -Always use \`getPriceNoOlderThan(priceId, maxAge)\` instead of \`getPrice()\` or \`getPriceUnsafe()\`. Recommended maxAge values: +Always use \\`getPriceNoOlderThan(priceId, maxAge)\\` instead of \\`getPrice()\\` or \\`getPriceUnsafe()\\`. Recommended maxAge values: - DeFi lending/borrowing: 60 seconds - Derivatives/perpetuals: 10–30 seconds - Display/analytics: 300+ seconds @@ -152,12 +154,12 @@ For lending protocols, use conservative pricing: Pause activity if the confidence-to-price ratio exceeds your threshold (e.g., conf/price > 2%). ### Multi-Feed Batch Updates -Submit multiple feeds in a single \`updatePriceFeeds()\` call to save gas: -\`\`\`typescript +Submit multiple feeds in a single \\`updatePriceFeeds()\\` call to save gas: +\\`\\`\\`typescript const ids = [ETH_USD, BTC_USD, SOL_USD]; const updates = await client.getLatestPriceUpdates(ids); // Submit updates.binary.data on-chain — one transaction updates all feeds -\`\`\` +\\`\\`\\` ### Delayed Settlement for Derivatives Separate order commitment from execution to prevent latency exploitation: @@ -167,22 +169,22 @@ Separate order commitment from execution to prevent latency exploitation: ## Troubleshooting -### StalePrice (0x19abf40e) +### StalePrice (0x19_ab_f4_0e) Price hasn't been updated within the specified age parameter. -**Fix**: Call \`updatePriceFeeds()\` with fresh data from Hermes before reading the price. +**Fix**: Call \\`updatePriceFeeds()\\` with fresh data from Hermes before reading the price. -### PriceFeedNotFound (0x14aebe68) +### PriceFeedNotFound (0x14_ae_be_68) Price feed doesn't exist on-chain or the feed ID is wrong. -**Fix**: Verify the price feed ID is correct. Call \`updatePriceFeeds()\` first. Verify the Pyth contract address matches the chain. +**Fix**: Verify the price feed ID is correct. Call \\`updatePriceFeeds()\\` first. Verify the Pyth contract address matches the chain. ### InsufficientFee (0x025dbdd4) Not enough native token sent for the update fee. -**Fix**: Call \`getUpdateFee(priceUpdateData)\` and pass the result as \`msg.value\`. +**Fix**: Call \\`getUpdateFee(priceUpdateData)\\` and pass the result as \\`msg.value\\`. ### Anchor Version Mismatch (Solana) -\`E0277: PriceUpdateV2: anchor_lang::AccountDeserialize not satisfied\` +\\`E0277: PriceUpdateV2: anchor_lang::AccountDeserialize not satisfied\\` **Fix**: Align anchor-lang versions between your program and pyth-solana-receiver-sdk: -\`cargo update -p anchor-lang@[SDK_VERSION] --precise [YOUR_VERSION]\` +\\`cargo update -p anchor-lang@[SDK_VERSION] --precise [YOUR_VERSION]\\` ## Deep Dive Pages @@ -197,9 +199,13 @@ For complete documentation, fetch any page as plain markdown: - https://docs.pyth.network/price-feeds/core/troubleshoot/evm.mdx — EVM error reference - https://docs.pyth.network/price-feeds/core/troubleshoot/svm.mdx — Solana error reference - https://docs.pyth.network/price-feeds/core/current-fees.mdx — Fee schedule by chain -- https://docs.pyth.network/price-feeds/core/price-feeds/price-feed-ids.mdx — Complete feed catalog +- https://docs.pyth.network/price-feeds/core/price-feed-ids.mdx — Complete feed catalog - https://docs.pyth.network/price-feeds/core/create-your-first-pyth-app/index.mdx — Step-by-step EVM tutorial - https://docs.pyth.network/price-feeds/core/migrate-an-app-to-pyth/chainlink.mdx — Chainlink migration guide +\`) }; + + + `; export function GET() { diff --git a/apps/developer-hub/src/components/MigrationBanner/index.tsx b/apps/developer-hub/src/components/MigrationBanner/index.tsx new file mode 100644 index 0000000000..e2f93fc31a --- /dev/null +++ b/apps/developer-hub/src/components/MigrationBanner/index.tsx @@ -0,0 +1,29 @@ +"use client"; + +import { Banner } from "fumadocs-ui/components/banner"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; + +const isMigrationBannerRoute = (pathname: string): boolean => { + if (pathname === "/") return true; + if (pathname.startsWith("/price-feeds/core")) return true; + return false; +}; + +export const MigrationBanner = () => { + const pathname = usePathname(); + if (!isMigrationBannerRoute(pathname)) { + // eslint-disable-next-line unicorn/no-null + return null; + } + return ( + + + Pyth Core is upgrading on July 31, 2026 — see how to prepare → + + + ); +}; diff --git a/apps/developer-hub/src/components/MigrationContractsTable/deployments-config.ts b/apps/developer-hub/src/components/MigrationContractsTable/deployments-config.ts new file mode 100644 index 0000000000..4b449ce23d --- /dev/null +++ b/apps/developer-hub/src/components/MigrationContractsTable/deployments-config.ts @@ -0,0 +1,30 @@ +export type ChainOverride = { + name?: string; + explorer?: string; +}; + +export const MigrationDeploymentsConfig: Record = { + "4217": { + name: "Tempo", + }, + + "4326": { + name: "MegaETH", + explorer: "https://www.megaexplorer.xyz", + }, + + "42431": { + name: "Tempo Testnet", + }, + + "57054": { + explorer: "https://testnet.sonicscan.org", + }, + + // chainid.network has a stale "Wanchain Testnet" entry for network ID 999; + // Hyperliquid EVM Mainnet has taken over that ID. + "999": { + name: "HyperEVM", + explorer: "https://hyperevmscan.io", + }, +}; diff --git a/apps/developer-hub/src/components/MigrationContractsTable/index.tsx b/apps/developer-hub/src/components/MigrationContractsTable/index.tsx new file mode 100644 index 0000000000..08f1c3ffa3 --- /dev/null +++ b/apps/developer-hub/src/components/MigrationContractsTable/index.tsx @@ -0,0 +1,126 @@ +import { + evmChains, + evmPriceFeedContracts, +} from "@pythnetwork/contract-manager/utils/utils"; + +import CopyAddress from "../CopyAddress"; +import { MigrationDeploymentsConfig } from "./deployments-config"; + +type UpstreamChain = { + chainId: number; + name: string; + explorers?: { url: string }[]; +}; + +type MigrationDeployment = { + chainId: string; + networkId: number; + name: string; + address: string; + explorer?: string; +}; + +const HIDDEN_CHAIN_IDS = new Set(); + +const CHAIN_REGISTRY_URL = "https://chainid.network/chains.json"; +const CHAIN_REGISTRY_REVALIDATE_SECONDS = 60 * 60 * 24; + +const humanize = (chainId: string): string => + chainId + .split("_") + .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) + .join(" "); + +const fetchChainRegistry = async (): Promise> => { + try { + const response = await fetch(CHAIN_REGISTRY_URL, { + next: { revalidate: CHAIN_REGISTRY_REVALIDATE_SECONDS }, + }); + if (!response.ok) return new Map(); + const chains = (await response.json()) as UpstreamChain[]; + return new Map(chains.map((c) => [c.chainId, c])); + } catch { + return new Map(); + } +}; + +const buildDeployments = ( + isMainnet: boolean, + registry: Map, +): MigrationDeployment[] => { + const deployments: MigrationDeployment[] = []; + + for (const contract of evmPriceFeedContracts) { + if ( + !("deploymentType" in contract) || + contract.deploymentType !== "lazer-prod" + ) { + continue; + } + if (HIDDEN_CHAIN_IDS.has(contract.chain)) continue; + + const chain = evmChains.find((c) => c.id === contract.chain); + if (!chain || chain.mainnet !== isMainnet) continue; + + const upstream = registry.get(chain.networkId); + const override = MigrationDeploymentsConfig[String(chain.networkId)]; + const explorer = override?.explorer ?? upstream?.explorers?.[0]?.url; + + deployments.push({ + address: contract.address, + chainId: chain.id, + name: override?.name ?? upstream?.name ?? humanize(chain.id), + networkId: chain.networkId, + ...(explorer ? { explorer } : {}), + }); + } + + return deployments.sort((a, b) => a.name.localeCompare(b.name)); +}; + +const MigrationContractsTable = async ({ + isMainnet, +}: { + isMainnet: boolean; +}) => { + const registry = await fetchChainRegistry(); + const deployments = buildDeployments(isMainnet, registry); + + if (deployments.length === 0) { + return ( +

+ No contracts published yet for this network type. +

+ ); + } + + return ( + + + + + + + + + {deployments.map((d) => ( + + + + + ))} + +
NetworkUpgraded Pyth Core Contract
{d.name} + {d.explorer ? ( + + ) : ( + + )} +
+ ); +}; + +export default MigrationContractsTable; diff --git a/apps/developer-hub/src/components/MigrationFlow/ActiveStepHighlighter.tsx b/apps/developer-hub/src/components/MigrationFlow/ActiveStepHighlighter.tsx new file mode 100644 index 0000000000..107c668d44 --- /dev/null +++ b/apps/developer-hub/src/components/MigrationFlow/ActiveStepHighlighter.tsx @@ -0,0 +1,24 @@ +"use client"; + +import { useEffect } from "react"; + +export const ActiveStepHighlighter = () => { + useEffect(() => { + const params = new URLSearchParams(window.location.search); + const step = params.get("step"); + if (!step) return; + const element = document.getElementById(step); + if (!element) return; + element.scrollIntoView({ behavior: "smooth", block: "start" }); + element.classList.add("migration-step-active"); + const timeout = setTimeout(() => { + element.classList.remove("migration-step-active"); + }, 4000); + return () => { + clearTimeout(timeout); + }; + }, []); + + // eslint-disable-next-line unicorn/no-null + return null; +}; diff --git a/apps/developer-hub/src/components/MigrationFlow/BranchSection.tsx b/apps/developer-hub/src/components/MigrationFlow/BranchSection.tsx new file mode 100644 index 0000000000..b6d4152ba5 --- /dev/null +++ b/apps/developer-hub/src/components/MigrationFlow/BranchSection.tsx @@ -0,0 +1,26 @@ +"use client"; + +import clsx from "clsx"; +import type { ReactNode } from "react"; + +import styles from "./index.module.scss"; +import type { MigrationPath } from "./path-store"; +import { useMigrationPath } from "./path-store"; + +type Props = { + path: MigrationPath; + children: ReactNode; +}; + +export const BranchSection = ({ path: forPath, children }: Props) => { + const [activePath] = useMigrationPath(); + const isActive = activePath === forPath; + return ( +
+ {children} +
+ ); +}; diff --git a/apps/developer-hub/src/components/MigrationFlow/BranchToggle.tsx b/apps/developer-hub/src/components/MigrationFlow/BranchToggle.tsx new file mode 100644 index 0000000000..beb696845b --- /dev/null +++ b/apps/developer-hub/src/components/MigrationFlow/BranchToggle.tsx @@ -0,0 +1,52 @@ +"use client"; + +import clsx from "clsx"; + +import styles from "./index.module.scss"; +import type { MigrationPath } from "./path-store"; +import { useMigrationPath } from "./path-store"; + +type Option = { + value: MigrationPath; + title: string; + caption: string; +}; + +const OPTIONS: Option[] = [ + { + value: "now", + title: "Upgrade now", + caption: "Recommended · zero downtime · ~15 min", + }, + { + value: "wait", + title: "Wait for automatic", + caption: "DAO upgrades contract on July 31", + }, +]; + +export const BranchToggle = () => { + const [path, setPath] = useMigrationPath(); + return ( +
+ {OPTIONS.map((option) => { + const selected = path === option.value; + return ( + + ); + })} +
+ ); +}; diff --git a/apps/developer-hub/src/components/MigrationFlow/index.module.scss b/apps/developer-hub/src/components/MigrationFlow/index.module.scss new file mode 100644 index 0000000000..2827ddf497 --- /dev/null +++ b/apps/developer-hub/src/components/MigrationFlow/index.module.scss @@ -0,0 +1,70 @@ +@use "@pythnetwork/component-library/theme"; + +.toggle { + display: grid; + grid-template-columns: 1fr; + gap: theme.spacing(3); + margin: theme.spacing(6) 0; + + @include theme.breakpoint("md") { + grid-template-columns: 1fr 1fr; + } +} + +.toggleOption { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: theme.spacing(1); + padding: theme.spacing(4) theme.spacing(5); + border-radius: theme.border-radius("xl"); + border: 1px solid theme.color("border"); + background-color: theme.color("background", "primary"); + text-align: left; + cursor: pointer; + transition: border-color 120ms ease, background-color 120ms ease; + font: inherit; + + &:hover { + border-color: theme.color("states", "data", "normal"); + } + + &:focus-visible { + outline: 2px solid theme.color("states", "data", "normal"); + outline-offset: 2px; + } +} + +.toggleOptionSelected { + border-color: theme.color("states", "data", "normal"); + background-color: theme.color("background", "card-highlight"); + box-shadow: inset 0 0 0 1px theme.color("states", "data", "normal"); +} + +.toggleTitle { + @include theme.text("base", "semibold"); + + color: theme.color("heading"); +} + +.toggleCaption { + @include theme.text("sm", "normal"); + + color: theme.color("paragraph"); +} + +.branchSection { + transition: opacity 200ms ease; + border-left: 3px solid transparent; + padding-left: theme.spacing(4); + margin-left: calc(theme.spacing(4) * -1); +} + +.branchActive { + opacity: 1; + border-left-color: theme.color("states", "data", "normal"); +} + +.branchMuted { + opacity: 0.55; +} diff --git a/apps/developer-hub/src/components/MigrationFlow/index.ts b/apps/developer-hub/src/components/MigrationFlow/index.ts new file mode 100644 index 0000000000..6e7e74a53c --- /dev/null +++ b/apps/developer-hub/src/components/MigrationFlow/index.ts @@ -0,0 +1,3 @@ +export { ActiveStepHighlighter } from "./ActiveStepHighlighter"; +export { BranchSection } from "./BranchSection"; +export { BranchToggle } from "./BranchToggle"; diff --git a/apps/developer-hub/src/components/MigrationFlow/path-store.ts b/apps/developer-hub/src/components/MigrationFlow/path-store.ts new file mode 100644 index 0000000000..a5d53e8070 --- /dev/null +++ b/apps/developer-hub/src/components/MigrationFlow/path-store.ts @@ -0,0 +1,15 @@ +"use client"; + +import { + parseAsStringLiteral, + useQueryState, +} from "@pythnetwork/react-hooks/nuqs"; + +export const MIGRATION_PATHS = ["now", "wait"] as const; +export type MigrationPath = (typeof MIGRATION_PATHS)[number]; + +export const useMigrationPath = () => + useQueryState( + "path", + parseAsStringLiteral(MIGRATION_PATHS).withDefault("now"), + ); diff --git a/apps/developer-hub/src/components/Pages/Homepage/index.module.scss b/apps/developer-hub/src/components/Pages/Homepage/index.module.scss index 4a691f00d8..e94749c780 100644 --- a/apps/developer-hub/src/components/Pages/Homepage/index.module.scss +++ b/apps/developer-hub/src/components/Pages/Homepage/index.module.scss @@ -7,6 +7,61 @@ background-color: var(--color-fd-background); } +.sectionMigration { + @include theme.max-width; +} + +.migrationFeature { + display: flex; + flex-direction: column; + gap: theme.spacing(6); + align-items: flex-start; + justify-content: space-between; + padding: theme.spacing(6) theme.spacing(8); + border-radius: theme.border-radius("2xl"); + border: 1px solid theme.color("border"); + background-color: theme.color("background", "card-highlight"); + border-left: 3px solid theme.color("states", "data", "normal"); + + @include theme.breakpoint("md") { + flex-direction: row; + align-items: center; + gap: theme.spacing(10); + } +} + +.migrationFeatureText { + display: flex; + flex-direction: column; + gap: theme.spacing(2); + flex: 1; +} + +.migrationFeatureTitle { + @include theme.text("2xl", "semibold"); + + margin: 0; + letter-spacing: theme.letter-spacing("tight"); + color: theme.color("heading"); +} + +.migrationFeatureBody { + @include theme.text("base", "normal"); + + margin: 0; + color: theme.color("paragraph"); + line-height: 1.55; + + strong { + color: theme.color("heading"); + font-weight: theme.font-weight("semibold"); + } +} + +.migrationFeatureActions { + flex-shrink: 0; +} + .sectionHero { position: relative; border-bottom: 1px solid theme.color("border"); @@ -21,9 +76,9 @@ .sectionHeroContent { display: flex; flex-direction: column; - gap: theme.spacing(10); - padding-top: theme.spacing(18); - padding-bottom: theme.spacing(18); + gap: theme.spacing(8); + padding-top: theme.spacing(10); + padding-bottom: theme.spacing(10); @include theme.max-width; @@ -48,7 +103,7 @@ } .heroTitle { - @include theme.text("5xl", "semibold"); + @include theme.text("4xl", "semibold"); line-height: 115%; letter-spacing: theme.letter-spacing("tight"); @@ -57,7 +112,7 @@ } .heroSubtitle { - @include theme.text("2xl", "normal"); + @include theme.text("xl", "normal"); line-height: 140%; color: theme.color("paragraph"); diff --git a/apps/developer-hub/src/components/Pages/Homepage/index.tsx b/apps/developer-hub/src/components/Pages/Homepage/index.tsx index 6a075e40d1..dd5e9f2dcf 100644 --- a/apps/developer-hub/src/components/Pages/Homepage/index.tsx +++ b/apps/developer-hub/src/components/Pages/Homepage/index.tsx @@ -1,3 +1,5 @@ +import { Button } from "@pythnetwork/component-library/Button"; + import { ProductCard } from "../../ProductCard"; import styles from "./index.module.scss"; import ResourcesForBuildersImage from "./resources-for-builders.svg"; @@ -19,6 +21,25 @@ export const Homepage = () => { +
+
+
+

+ Changes are coming to Pyth Core +

+

+ Existing integrations need three small updates by{" "} + July 31, 2026 to keep working. +

+
+
+ +
+
+
+

Products

diff --git a/apps/developer-hub/src/components/ProductCard/index.module.scss b/apps/developer-hub/src/components/ProductCard/index.module.scss index 8206acdbac..899ef3a08d 100644 --- a/apps/developer-hub/src/components/ProductCard/index.module.scss +++ b/apps/developer-hub/src/components/ProductCard/index.module.scss @@ -2,7 +2,7 @@ .card { flex: 1; - padding: theme.spacing(6); + padding: theme.spacing(5); border: 1px solid var(--color-fd-border); border-radius: theme.border-radius("3xl"); background-color: var(--color-fd-card); @@ -12,7 +12,7 @@ .content { display: flex; flex-direction: column; - gap: theme.spacing(8); + gap: theme.spacing(5); height: 100%; flex: 1; } @@ -20,16 +20,16 @@ .mainContent { display: flex; flex-direction: column; - gap: theme.spacing(12); + gap: theme.spacing(6); flex: 1; } .header { display: flex; flex-direction: column; - gap: theme.spacing(3); - min-height: 7rem; // 88px - fixed height to keep FEATURES at consistent position - height: 7rem; // 88px - fixed height + gap: theme.spacing(2); + min-height: 5rem; + height: 5rem; } .title { @@ -60,15 +60,13 @@ .featuresSection { display: flex; flex-direction: column; - gap: theme.spacing(6); - min-height: 12rem; - max-height: 12rem; + gap: theme.spacing(3); } .quickLinksSection { display: flex; flex-direction: column; - gap: theme.spacing(6); + gap: theme.spacing(3); } .sectionLabel { diff --git a/apps/developer-hub/src/components/Root/global.css b/apps/developer-hub/src/components/Root/global.css index 28195fdacf..d70217401d 100644 --- a/apps/developer-hub/src/components/Root/global.css +++ b/apps/developer-hub/src/components/Root/global.css @@ -77,3 +77,25 @@ --color-fd-accent-foreground: hsl(250, 18%, 87%); --color-fd-ring: hsla(262, 83%, 58%, 1); } + +/* Deep-link highlight for migration steps — fades after ~4s. */ +.migration-step-active { + background-color: hsla(263, 70%, 50%, 0.08); + outline: 2px solid hsla(263, 70%, 50%, 0.5); + outline-offset: 8px; + transition: background-color 600ms ease, outline-color 600ms ease; +} + +/* Solid violet ribbon for the Pyth Core upgrade entry in the sidebar. + Only applied when not the active page — fumadocs already styles active. + White text + icon inherits via currentColor. */ +a[data-active="false"][href="/price-feeds/core/upgrade"] { + background-color: var(--color-fd-primary); + color: white; + font-weight: 500; +} + +a[data-active="false"][href="/price-feeds/core/upgrade"]:hover { + background-color: color-mix(in oklab, var(--color-fd-primary) 88%, black); + color: white; +} diff --git a/apps/developer-hub/src/data/llm-token-counts.json b/apps/developer-hub/src/data/llm-token-counts.json index 03a21496e3..96a64f8b72 100644 --- a/apps/developer-hub/src/data/llm-token-counts.json +++ b/apps/developer-hub/src/data/llm-token-counts.json @@ -1,24 +1,24 @@ { "files": { "/llms.txt": { - "bytes": 2188, - "hash": "sha256:9466b04dabe56cf1ee46ca869026a667b80baec76e2b5e51d0b92cc27cc2d563", - "tokens": 518 + "bytes": 2459, + "hash": "sha256:37b381ad467d7dbcf9d012cfc4318a3435e107aa709635cfdde02baf31acf442", + "tokens": 587 }, "/llms-price-feeds-core.txt": { - "bytes": 8793, - "hash": "sha256:8b6069bfc1a2a732e8c8ff79efe50a83372cbd259cb817eca5dea2108d5107b7", - "tokens": 2473 + "bytes": 8862, + "hash": "sha256:70935f7b42db2e741dc38b5201564f2ab949907c4bb1921e8b2fa9c72ce48604", + "tokens": 2497 }, "/llms-price-feeds-pro.txt": { - "bytes": 8728, - "hash": "sha256:ef8a9f4cc49773a78c89890c99e8319590821ea8c1b301bb7a7db622805995c4", - "tokens": 2261 + "bytes": 10139, + "hash": "sha256:9ba763c0493126a1bb57ac80516c574f5385895439948419ca286fba9c2208c7", + "tokens": 2587 }, "/llms-price-feeds.txt": { - "bytes": 2753, - "hash": "sha256:1cdcc7378f7aa5f2e95bd85f36d21bbfb2d5a9e992545d012f9f3254cba41c6b", - "tokens": 705 + "bytes": 3117, + "hash": "sha256:6609421a266b1fcd616bc2c8e8326a1b10da2f6083c1259b00a56d2c0a488963", + "tokens": 795 }, "/llms-entropy.txt": { "bytes": 8116, @@ -36,7 +36,7 @@ "tokens": 2890 } }, - "generated_at": "2026-02-16T19:49:45.026Z", + "generated_at": "2026-05-20T19:24:36.910Z", "tokenizer": "cl100k_base", "tokenizer_note": "Token counts are approximate. Actual counts vary by model." } diff --git a/contract_manager/src/utils/utils.ts b/contract_manager/src/utils/utils.ts index 21ce1c717b..0b077a3202 100644 --- a/contract_manager/src/utils/utils.ts +++ b/contract_manager/src/utils/utils.ts @@ -17,10 +17,14 @@ export const allEvmChainIds: number[] = evmChainsData.map((c) => c.networkId); export type EvmChainEntry = (typeof evmChainsData)[number]; export type EvmLazerContractEntry = (typeof evmLazerContractsData)[number]; +export type EvmPriceFeedContractEntry = + (typeof evmPriceFeedContractsData)[number]; export const evmChains: readonly EvmChainEntry[] = evmChainsData; export const evmLazerContracts: readonly EvmLazerContractEntry[] = evmLazerContractsData; +export const evmPriceFeedContracts: readonly EvmPriceFeedContractEntry[] = + evmPriceFeedContractsData; export const getEvmPriceFeedContractAddress = ( chainId: number,