-
Notifications
You must be signed in to change notification settings - Fork 39
chore(sponsored-feeds): use JSON format for the sponsored feed data for EVM #746
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
dacbf0f
cf9d95f
bc00d71
d62f8b2
d50cbb7
c7cd51a
d814ff0
66eabcb
1c733c4
6dc21ee
49728da
00fb7c1
9647761
1e62312
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) => { | ||
|
||
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> | ||
); | ||
|
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 | ||
} | ||
] |
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
formatTimeUnit
in 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