This repository was archived by the owner on Nov 28, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 38
chore(sponsored-feeds): use JSON format for the sponsored feed data for EVM #746
Merged
Merged
Changes from 10 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
dacbf0f
use yaml format for the sponsored feed data for evm
nidhi-singh02 cf9d95f
changes to hyperEVM
nidhi-singh02 bc00d71
individual yaml file for each chain for easy of use
nidhi-singh02 d62f8b2
comments edit
nidhi-singh02 d50cbb7
fix precommit from merged changes
nidhi-singh02 c7cd51a
linter fix
nidhi-singh02 d814ff0
use json with static imports, remove yaml files
nidhi-singh02 66eabcb
use json directly in the mdx file, no need of network key
nidhi-singh02 1c733c4
precommit apply
nidhi-singh02 6dc21ee
remove yaml packages
nidhi-singh02 49728da
make updateParams as react component
nidhi-singh02 00fb7c1
use Intl namespace
nidhi-singh02 9647761
use duration.formatToParts
nidhi-singh02 1e62312
not use Readt.FS, remove type cast and no polyfil
nidhi-singh02 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,15 @@ | ||
| import { useState } from "react"; | ||
| import React from "react"; | ||
| import CopyIcon from "./icons/CopyIcon"; | ||
| import { mapValues } from "../utils/ObjectHelpers"; | ||
| import { useCopyToClipboard } from "../utils/useCopyToClipboard"; | ||
|
|
||
| interface UpdateParameters { | ||
| heartbeatLength: number; | ||
| heartbeatUnit: "second" | "minute" | "hour"; | ||
| priceDeviation: number; | ||
| } | ||
|
|
||
| // SponsoredFeed interface has the same structure as defined in deployment yaml/json files | ||
| interface SponsoredFeed { | ||
| name: string; | ||
| priceFeedId: string; | ||
| updateParameters: UpdateParameters; | ||
| alias: string; // name of the feed | ||
| id: string; // price feed id | ||
| time_difference: number; // in seconds | ||
| price_deviation: number; | ||
| confidence_ratio: number; | ||
| } | ||
|
|
||
| interface SponsoredFeedsTableProps { | ||
|
|
@@ -22,56 +20,82 @@ interface SponsoredFeedsTableProps { | |
| /** | ||
| * Helper functions | ||
| */ | ||
| // Convert time_difference (seconds) to human readable format | ||
| const formatTimeUnit = (seconds: number): { value: number; unit: string } => { | ||
| if (seconds >= 3600) { | ||
| return { value: seconds / 3600, unit: "hour" }; | ||
| } else if (seconds >= 60) { | ||
| return { value: seconds / 60, unit: "minute" }; | ||
| } else { | ||
| return { value: seconds, unit: "second" }; | ||
| } | ||
| }; | ||
|
|
||
| // Format update parameters as a string for grouping | ||
| const formatUpdateParams = (params: UpdateParameters): string => { | ||
| return `${params.heartbeatLength} ${params.heartbeatUnit} heartbeat / ${params.priceDeviation}% price deviation`; | ||
| const formatUpdateParams = (feed: SponsoredFeed): string => { | ||
| const timeFormat = formatTimeUnit(feed.time_difference); | ||
| const timeStr = `${timeFormat.value} ${timeFormat.unit}${ | ||
| timeFormat.value !== 1 ? "s" : "" | ||
| }`; | ||
| return `${timeStr} heartbeat / ${feed.price_deviation}% price deviation`; | ||
| }; | ||
|
|
||
| // Render update parameters with proper styling | ||
| const renderUpdateParams = (params: UpdateParameters, isDefault: boolean) => ( | ||
| <div className="flex items-start gap-1.5"> | ||
| <div | ||
| className={`w-1.5 h-1.5 rounded-full mt-1 flex-shrink-0 ${ | ||
| isDefault ? "bg-green-500" : "bg-orange-500" | ||
| }`} | ||
| ></div> | ||
| <span | ||
| className={`text-xs leading-relaxed font-medium ${ | ||
| isDefault | ||
| ? "text-gray-700 dark:text-gray-300" | ||
| : "text-orange-600 dark:text-orange-400" | ||
| }`} | ||
| > | ||
| <strong>{params.heartbeatLength}</strong> {params.heartbeatUnit} heartbeat | ||
| <br /> | ||
| <strong>{params.priceDeviation}%</strong> price deviation | ||
| </span> | ||
| </div> | ||
| ); | ||
| const renderUpdateParams = (feed: SponsoredFeed, isDefault: boolean) => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be a component since it returns jsx, and the only change would be renaming it from By using a component, React can optimize rerenders and the component tree. By using a function, react is not aware of this subtree as a component and applies no render optimizations.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for explaining the rationale. |
||
| const timeFormat = formatTimeUnit(feed.time_difference); | ||
| const timeStr = | ||
| timeFormat.value === 1 ? timeFormat.unit : `${timeFormat.unit}s`; | ||
|
|
||
| return ( | ||
| <div className="flex items-start gap-1.5"> | ||
| <div | ||
| className={`w-1.5 h-1.5 rounded-full mt-1 flex-shrink-0 ${ | ||
| isDefault ? "bg-green-500" : "bg-orange-500" | ||
| }`} | ||
| ></div> | ||
| <span | ||
| className={`text-xs leading-relaxed font-medium ${ | ||
| isDefault | ||
| ? "text-gray-700 dark:text-gray-300" | ||
| : "text-orange-600 dark:text-orange-400" | ||
| }`} | ||
| > | ||
| <strong>{timeFormat.value}</strong> {timeStr} heartbeat | ||
| <br /> | ||
| <strong>{feed.price_deviation}%</strong> price deviation | ||
| </span> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export const SponsoredFeedsTable = ({ | ||
| feeds, | ||
| networkName, | ||
| }: SponsoredFeedsTableProps) => { | ||
| const [copiedId, setCopiedId] = useState<string | null>(null); | ||
| const { copiedText, copyToClipboard } = useCopyToClipboard(); | ||
|
|
||
| const copyToClipboard = (text: string) => { | ||
| navigator.clipboard.writeText(text).then(() => { | ||
| setCopiedId(text); | ||
| setTimeout(() => setCopiedId(null), 2000); | ||
| }); | ||
| }; | ||
| // Handle empty feeds | ||
| if (feeds.length === 0) { | ||
| return ( | ||
| <div className="my-6"> | ||
| <p className="mb-3"> | ||
| No sponsored price feeds are currently available for{" "} | ||
| <strong>{networkName}</strong>. | ||
| </p> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| // Calculate parameter statistics | ||
| const paramCounts = mapValues( | ||
| Object.groupBy(feeds, (feed) => formatUpdateParams(feed.updateParameters)), | ||
| Object.groupBy(feeds, (feed) => formatUpdateParams(feed)), | ||
| (feeds: SponsoredFeed[]) => feeds.length | ||
| ); | ||
|
|
||
| const defaultParams = Object.entries(paramCounts).sort( | ||
| const paramEntries = Object.entries(paramCounts).sort( | ||
| ([, a], [, b]) => b - a | ||
| )[0][0]; | ||
| ); | ||
| const defaultParams = paramEntries.length > 0 ? paramEntries[0][0] : ""; | ||
|
|
||
| return ( | ||
| <div className="my-6"> | ||
|
|
@@ -123,33 +147,31 @@ export const SponsoredFeedsTable = ({ | |
| </tr> | ||
| </thead> | ||
| <tbody className="bg-white dark:bg-gray-900"> | ||
| {feeds.map((feed, index) => { | ||
| const formattedParams = formatUpdateParams( | ||
| feed.updateParameters | ||
| ); | ||
| {feeds.map((feed) => { | ||
| const formattedParams = formatUpdateParams(feed); | ||
| const isDefault = formattedParams === defaultParams; | ||
|
|
||
| return ( | ||
| <tr | ||
| key={feed.priceFeedId} | ||
| key={feed.id} | ||
| className="border-b border-gray-100 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800/30" | ||
| > | ||
| <td className="px-3 py-2 align-top"> | ||
| <span className="font-medium text-gray-900 dark:text-gray-100"> | ||
| {feed.name} | ||
| {feed.alias} | ||
| </span> | ||
| </td> | ||
| <td className="px-3 py-2 align-top"> | ||
| <div className="flex items-start gap-2"> | ||
| <code className="text-xs font-mono text-gray-600 dark:text-gray-400 flex-1 break-all leading-relaxed"> | ||
| {feed.priceFeedId} | ||
| {feed.id} | ||
| </code> | ||
| <button | ||
| onClick={() => copyToClipboard(feed.priceFeedId)} | ||
| onClick={() => copyToClipboard(feed.id)} | ||
| className="p-1 hover:bg-gray-200 dark:hover:bg-gray-600 rounded flex-shrink-0 mt-0.5" | ||
| title="Copy Price Feed ID" | ||
| > | ||
| {copiedId === feed.priceFeedId ? ( | ||
| {copiedText === feed.id ? ( | ||
| <span className="text-green-500 text-xs font-bold"> | ||
| ✓ | ||
| </span> | ||
|
|
@@ -160,7 +182,7 @@ export const SponsoredFeedsTable = ({ | |
| </div> | ||
| </td> | ||
| <td className="px-3 py-2 align-top"> | ||
| {renderUpdateParams(feed.updateParameters, isDefault)} | ||
| {renderUpdateParams(feed, isDefault)} | ||
| </td> | ||
| </tr> | ||
| ); | ||
|
|
||
121 changes: 121 additions & 0 deletions
121
pages/price-feeds/sponsored-feeds/data/evm/base_mainnet.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| [ | ||
| { | ||
| "alias": "USDC/USD", | ||
| "id": "eaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a", | ||
| "time_difference": 3600, | ||
| "price_deviation": 1, | ||
| "confidence_ratio": 100 | ||
| }, | ||
| { | ||
| "alias": "ETH/USD", | ||
| "id": "ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace", | ||
| "time_difference": 3600, | ||
| "price_deviation": 1, | ||
| "confidence_ratio": 100 | ||
| }, | ||
| { | ||
| "alias": "WETH/USD", | ||
| "id": "9d4294bbcd1174d6f2003ec365831e64cc31d9f6f15a2b85399db8d5000960f6", | ||
| "time_difference": 3600, | ||
| "price_deviation": 1, | ||
| "confidence_ratio": 100 | ||
| }, | ||
| { | ||
| "alias": "PUFETH/USD", | ||
| "id": "e5801530292c348f322b7b4a48c1c0d59ab629846cce1c816fc27aee2054b560", | ||
| "time_difference": 3600, | ||
| "price_deviation": 1, | ||
| "confidence_ratio": 100 | ||
| }, | ||
| { | ||
| "alias": "WEETH/USD", | ||
| "id": "9ee4e7c60b940440a261eb54b6d8149c23b580ed7da3139f7f08f4ea29dad395", | ||
| "time_difference": 3600, | ||
| "price_deviation": 1, | ||
| "confidence_ratio": 100 | ||
| }, | ||
| { | ||
| "alias": "EZETH/USD", | ||
| "id": "06c217a791f5c4f988b36629af4cb88fad827b2485400a358f3b02886b54de92", | ||
| "time_difference": 3600, | ||
| "price_deviation": 1, | ||
| "confidence_ratio": 100 | ||
| }, | ||
| { | ||
| "alias": "CBETH/USD", | ||
| "id": "15ecddd26d49e1a8f1de9376ebebc03916ede873447c1255d2d5891b92ce5717", | ||
| "time_difference": 3600, | ||
| "price_deviation": 1, | ||
| "confidence_ratio": 100 | ||
| }, | ||
| { | ||
| "alias": "WSTETH/USD", | ||
| "id": "6df640f3b8963d8f8358f791f352b8364513f6ab1cca5ed3f1f7b5448980e784", | ||
| "time_difference": 3600, | ||
| "price_deviation": 1, | ||
| "confidence_ratio": 100 | ||
| }, | ||
| { | ||
| "alias": "RSETH/USD", | ||
| "id": "0caec284d34d836ca325cf7b3256c078c597bc052fbd3c0283d52b581d68d71f", | ||
| "time_difference": 3600, | ||
| "price_deviation": 1, | ||
| "confidence_ratio": 100 | ||
| }, | ||
| { | ||
| "alias": "PYTH/USD", | ||
| "id": "0bbf28e9a841a1cc788f6a361b17ca072d0ea3098a1e5df1c3922d06719579ff", | ||
| "time_difference": 3600, | ||
| "price_deviation": 1, | ||
| "confidence_ratio": 100 | ||
| }, | ||
| { | ||
| "alias": "SUI/USD", | ||
| "id": "23d7315113f5b1d3ba7a83604c44b94d79f4fd69af77f804fc7f920a6dc65744", | ||
| "time_difference": 3600, | ||
| "price_deviation": 1, | ||
| "confidence_ratio": 100 | ||
| }, | ||
| { | ||
| "alias": "XRP/USD", | ||
| "id": "ec5d399846a9209f3fe5881d70aae9268c94339ff9817e8d18ff19fa05eea1c8", | ||
| "time_difference": 3600, | ||
| "price_deviation": 1, | ||
| "confidence_ratio": 100 | ||
| }, | ||
| { | ||
| "alias": "USR/USD", | ||
| "id": "10b013adec14c0fe839ca0fe54cec9e4d0b6c1585ac6d7e70010dac015e57f9c", | ||
| "time_difference": 3600, | ||
| "price_deviation": 1, | ||
| "confidence_ratio": 100 | ||
| }, | ||
| { | ||
| "alias": "USR/USD.RR", | ||
| "id": "512a79cc65f49531f0bbb72956353e79ecdc1e4a6e5241847196c1f9a11d8a52", | ||
| "time_difference": 3600, | ||
| "price_deviation": 1, | ||
| "confidence_ratio": 100 | ||
| }, | ||
| { | ||
| "alias": "RLP/USD", | ||
| "id": "7265d5cf8ee0e7b5266f75ff19c42c5b7697a9756c9304aa78b6be4fbb8d823d", | ||
| "time_difference": 3600, | ||
| "price_deviation": 1, | ||
| "confidence_ratio": 100 | ||
| }, | ||
| { | ||
| "alias": "RLP/USD.RR", | ||
| "id": "796bcb684fdfbba2b071c165251511ab61f08c8949afd9e05665a26f69d9a839", | ||
| "time_difference": 3600, | ||
| "price_deviation": 1, | ||
| "confidence_ratio": 100 | ||
| }, | ||
| { | ||
| "alias": "WSTUSR/USR.RR", | ||
| "id": "b74c2bc175c2dab850ce5a5451608501c293fe8410cb4aba7449dd1c355ab706", | ||
| "time_difference": 3600, | ||
| "price_deviation": 1, | ||
| "confidence_ratio": 100 | ||
| } | ||
| ] |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DurationFormat for this. It's a bit overkill for our needs as we don't plan to localize pretty much ever, certainly not any time soon, but in general it's probably wise to stick with standards whenever possible.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, using Intl.DurationFormat seems like a overkill, the code earlier was easy to read imo. Though I agree with your point of sticking around with standards. So added Intl.DurationFormat in
formatTimeUnitin commit 00fb7c1Was using regex with
Intl.DurationFormat, so instead using https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DurationFormat/formatToParts as this returns an array with unit and value. Check the latest commit 9647761