Skip to content

Commit 39fa737

Browse files
chore(sponsored-feeds): use JSON format for the sponsored feed data for EVM (#746)
* use yaml format for the sponsored feed data for evm * changes to hyperEVM * individual yaml file for each chain for easy of use * comments edit * fix precommit from merged changes * linter fix * use json with static imports, remove yaml files * use json directly in the mdx file, no need of network key * precommit apply * remove yaml packages * make updateParams as react component * use Intl namespace * use duration.formatToParts * not use Readt.FS, remove type cast and no polyfil
1 parent e2d62f7 commit 39fa737

14 files changed

+1121
-1493
lines changed

components/SponsoredFeedsTable.tsx renamed to components/SponsoredFeedsTableWithData.tsx

Lines changed: 95 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,121 @@
1-
import { useState } from "react";
1+
import React from "react";
22
import CopyIcon from "./icons/CopyIcon";
33
import { mapValues } from "../utils/ObjectHelpers";
4+
import { useCopyToClipboard } from "../utils/useCopyToClipboard";
45

5-
interface UpdateParameters {
6-
heartbeatLength: number;
7-
heartbeatUnit: "second" | "minute" | "hour";
8-
priceDeviation: number;
9-
}
10-
6+
// SponsoredFeed interface has the same structure as defined in deployment yaml/json files
117
interface SponsoredFeed {
12-
name: string;
13-
priceFeedId: string;
14-
updateParameters: UpdateParameters;
8+
alias: string; // name of the feed
9+
id: string; // price feed id
10+
time_difference: number; // in seconds
11+
price_deviation: number;
12+
confidence_ratio: number;
1513
}
1614

1715
interface SponsoredFeedsTableProps {
1816
feeds: SponsoredFeed[];
1917
networkName: string;
2018
}
2119

20+
interface UpdateParamsProps {
21+
feed: SponsoredFeed;
22+
isDefault: boolean;
23+
}
24+
2225
/**
2326
* Helper functions
2427
*/
28+
// Convert time_difference (seconds) to human readable format
29+
const formatTimeUnit = (seconds: number): { value: number; unit: string } => {
30+
// @ts-expect-error - Intl.DurationFormat is not a standard type
31+
const duration = new Intl.DurationFormat("en", {
32+
style: "long",
33+
numeric: "auto",
34+
});
35+
let durationObj: { hours?: number; minutes?: number; seconds?: number };
36+
37+
if (seconds >= 3600) {
38+
durationObj = { hours: Math.floor(seconds / 3600) };
39+
} else if (seconds >= 60) {
40+
durationObj = { minutes: Math.floor(seconds / 60) };
41+
} else {
42+
durationObj = { seconds };
43+
}
44+
45+
const parts = duration.formatToParts(durationObj);
46+
const intPart = parts.find((p: any) => p.type === "integer");
47+
if (intPart) {
48+
return { value: Number(intPart.value), unit: intPart.unit };
49+
} else {
50+
// fallback in case formatting fails
51+
return { value: seconds, unit: "second" };
52+
}
53+
};
2554

2655
// Format update parameters as a string for grouping
27-
const formatUpdateParams = (params: UpdateParameters): string => {
28-
return `${params.heartbeatLength} ${params.heartbeatUnit} heartbeat / ${params.priceDeviation}% price deviation`;
56+
const formatUpdateParams = (feed: SponsoredFeed): string => {
57+
const timeFormat = formatTimeUnit(feed.time_difference);
58+
const timeStr = `${timeFormat.value} ${timeFormat.unit}${
59+
timeFormat.value !== 1 ? "s" : ""
60+
}`;
61+
return `${timeStr} heartbeat / ${feed.price_deviation}% price deviation`;
2962
};
3063

31-
// Render update parameters with proper styling
32-
const renderUpdateParams = (params: UpdateParameters, isDefault: boolean) => (
33-
<div className="flex items-start gap-1.5">
34-
<div
35-
className={`w-1.5 h-1.5 rounded-full mt-1 flex-shrink-0 ${
36-
isDefault ? "bg-green-500" : "bg-orange-500"
37-
}`}
38-
></div>
39-
<span
40-
className={`text-xs leading-relaxed font-medium ${
41-
isDefault
42-
? "text-gray-700 dark:text-gray-300"
43-
: "text-orange-600 dark:text-orange-400"
44-
}`}
45-
>
46-
<strong>{params.heartbeatLength}</strong> {params.heartbeatUnit} heartbeat
47-
<br />
48-
<strong>{params.priceDeviation}%</strong> price deviation
49-
</span>
50-
</div>
51-
);
64+
const UpdateParams = ({ feed, isDefault }: UpdateParamsProps) => {
65+
const timeFormat = formatTimeUnit(feed.time_difference);
66+
const timeStr =
67+
timeFormat.value === 1 ? timeFormat.unit : `${timeFormat.unit}s`;
68+
69+
return (
70+
<div className="flex items-start gap-1.5">
71+
<div
72+
className={`w-1.5 h-1.5 rounded-full mt-1 flex-shrink-0 ${
73+
isDefault ? "bg-green-500" : "bg-orange-500"
74+
}`}
75+
></div>
76+
<span
77+
className={`text-xs leading-relaxed font-medium ${
78+
isDefault
79+
? "text-gray-700 dark:text-gray-300"
80+
: "text-orange-600 dark:text-orange-400"
81+
}`}
82+
>
83+
<strong>{timeFormat.value}</strong> {timeStr} heartbeat
84+
<br />
85+
<strong>{feed.price_deviation}%</strong> price deviation
86+
</span>
87+
</div>
88+
);
89+
};
5290

5391
export const SponsoredFeedsTable = ({
5492
feeds,
5593
networkName,
5694
}: SponsoredFeedsTableProps) => {
57-
const [copiedId, setCopiedId] = useState<string | null>(null);
95+
const { copiedText, copyToClipboard } = useCopyToClipboard();
5896

59-
const copyToClipboard = (text: string) => {
60-
navigator.clipboard.writeText(text).then(() => {
61-
setCopiedId(text);
62-
setTimeout(() => setCopiedId(null), 2000);
63-
});
64-
};
97+
// Handle empty feeds
98+
if (feeds.length === 0) {
99+
return (
100+
<div className="my-6">
101+
<p className="mb-3">
102+
No sponsored price feeds are currently available for{" "}
103+
<strong>{networkName}</strong>.
104+
</p>
105+
</div>
106+
);
107+
}
65108

66109
// Calculate parameter statistics
67110
const paramCounts = mapValues(
68-
Object.groupBy(feeds, (feed) => formatUpdateParams(feed.updateParameters)),
111+
Object.groupBy(feeds, (feed) => formatUpdateParams(feed)),
69112
(feeds: SponsoredFeed[]) => feeds.length
70113
);
71114

72-
const defaultParams = Object.entries(paramCounts).sort(
115+
const paramEntries = Object.entries(paramCounts).sort(
73116
([, a], [, b]) => b - a
74-
)[0][0];
117+
);
118+
const defaultParams = paramEntries.length > 0 ? paramEntries[0][0] : "";
75119

76120
return (
77121
<div className="my-6">
@@ -123,33 +167,31 @@ export const SponsoredFeedsTable = ({
123167
</tr>
124168
</thead>
125169
<tbody className="bg-white dark:bg-gray-900">
126-
{feeds.map((feed, index) => {
127-
const formattedParams = formatUpdateParams(
128-
feed.updateParameters
129-
);
170+
{feeds.map((feed) => {
171+
const formattedParams = formatUpdateParams(feed);
130172
const isDefault = formattedParams === defaultParams;
131173

132174
return (
133175
<tr
134-
key={feed.priceFeedId}
176+
key={feed.id}
135177
className="border-b border-gray-100 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800/30"
136178
>
137179
<td className="px-3 py-2 align-top">
138180
<span className="font-medium text-gray-900 dark:text-gray-100">
139-
{feed.name}
181+
{feed.alias}
140182
</span>
141183
</td>
142184
<td className="px-3 py-2 align-top">
143185
<div className="flex items-start gap-2">
144186
<code className="text-xs font-mono text-gray-600 dark:text-gray-400 flex-1 break-all leading-relaxed">
145-
{feed.priceFeedId}
187+
{feed.id}
146188
</code>
147189
<button
148-
onClick={() => copyToClipboard(feed.priceFeedId)}
190+
onClick={() => copyToClipboard(feed.id)}
149191
className="p-1 hover:bg-gray-200 dark:hover:bg-gray-600 rounded flex-shrink-0 mt-0.5"
150192
title="Copy Price Feed ID"
151193
>
152-
{copiedId === feed.priceFeedId ? (
194+
{copiedText === feed.id ? (
153195
<span className="text-green-500 text-xs font-bold">
154196
155197
</span>
@@ -160,7 +202,7 @@ export const SponsoredFeedsTable = ({
160202
</div>
161203
</td>
162204
<td className="px-3 py-2 align-top">
163-
{renderUpdateParams(feed.updateParameters, isDefault)}
205+
<UpdateParams feed={feed} isDefault={isDefault} />
164206
</td>
165207
</tr>
166208
);

package-lock.json

Lines changed: 1 addition & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
[
2+
{
3+
"alias": "USDC/USD",
4+
"id": "eaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a",
5+
"time_difference": 3600,
6+
"price_deviation": 1,
7+
"confidence_ratio": 100
8+
},
9+
{
10+
"alias": "ETH/USD",
11+
"id": "ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace",
12+
"time_difference": 3600,
13+
"price_deviation": 1,
14+
"confidence_ratio": 100
15+
},
16+
{
17+
"alias": "WETH/USD",
18+
"id": "9d4294bbcd1174d6f2003ec365831e64cc31d9f6f15a2b85399db8d5000960f6",
19+
"time_difference": 3600,
20+
"price_deviation": 1,
21+
"confidence_ratio": 100
22+
},
23+
{
24+
"alias": "PUFETH/USD",
25+
"id": "e5801530292c348f322b7b4a48c1c0d59ab629846cce1c816fc27aee2054b560",
26+
"time_difference": 3600,
27+
"price_deviation": 1,
28+
"confidence_ratio": 100
29+
},
30+
{
31+
"alias": "WEETH/USD",
32+
"id": "9ee4e7c60b940440a261eb54b6d8149c23b580ed7da3139f7f08f4ea29dad395",
33+
"time_difference": 3600,
34+
"price_deviation": 1,
35+
"confidence_ratio": 100
36+
},
37+
{
38+
"alias": "EZETH/USD",
39+
"id": "06c217a791f5c4f988b36629af4cb88fad827b2485400a358f3b02886b54de92",
40+
"time_difference": 3600,
41+
"price_deviation": 1,
42+
"confidence_ratio": 100
43+
},
44+
{
45+
"alias": "CBETH/USD",
46+
"id": "15ecddd26d49e1a8f1de9376ebebc03916ede873447c1255d2d5891b92ce5717",
47+
"time_difference": 3600,
48+
"price_deviation": 1,
49+
"confidence_ratio": 100
50+
},
51+
{
52+
"alias": "WSTETH/USD",
53+
"id": "6df640f3b8963d8f8358f791f352b8364513f6ab1cca5ed3f1f7b5448980e784",
54+
"time_difference": 3600,
55+
"price_deviation": 1,
56+
"confidence_ratio": 100
57+
},
58+
{
59+
"alias": "RSETH/USD",
60+
"id": "0caec284d34d836ca325cf7b3256c078c597bc052fbd3c0283d52b581d68d71f",
61+
"time_difference": 3600,
62+
"price_deviation": 1,
63+
"confidence_ratio": 100
64+
},
65+
{
66+
"alias": "PYTH/USD",
67+
"id": "0bbf28e9a841a1cc788f6a361b17ca072d0ea3098a1e5df1c3922d06719579ff",
68+
"time_difference": 3600,
69+
"price_deviation": 1,
70+
"confidence_ratio": 100
71+
},
72+
{
73+
"alias": "SUI/USD",
74+
"id": "23d7315113f5b1d3ba7a83604c44b94d79f4fd69af77f804fc7f920a6dc65744",
75+
"time_difference": 3600,
76+
"price_deviation": 1,
77+
"confidence_ratio": 100
78+
},
79+
{
80+
"alias": "XRP/USD",
81+
"id": "ec5d399846a9209f3fe5881d70aae9268c94339ff9817e8d18ff19fa05eea1c8",
82+
"time_difference": 3600,
83+
"price_deviation": 1,
84+
"confidence_ratio": 100
85+
},
86+
{
87+
"alias": "USR/USD",
88+
"id": "10b013adec14c0fe839ca0fe54cec9e4d0b6c1585ac6d7e70010dac015e57f9c",
89+
"time_difference": 3600,
90+
"price_deviation": 1,
91+
"confidence_ratio": 100
92+
},
93+
{
94+
"alias": "USR/USD.RR",
95+
"id": "512a79cc65f49531f0bbb72956353e79ecdc1e4a6e5241847196c1f9a11d8a52",
96+
"time_difference": 3600,
97+
"price_deviation": 1,
98+
"confidence_ratio": 100
99+
},
100+
{
101+
"alias": "RLP/USD",
102+
"id": "7265d5cf8ee0e7b5266f75ff19c42c5b7697a9756c9304aa78b6be4fbb8d823d",
103+
"time_difference": 3600,
104+
"price_deviation": 1,
105+
"confidence_ratio": 100
106+
},
107+
{
108+
"alias": "RLP/USD.RR",
109+
"id": "796bcb684fdfbba2b071c165251511ab61f08c8949afd9e05665a26f69d9a839",
110+
"time_difference": 3600,
111+
"price_deviation": 1,
112+
"confidence_ratio": 100
113+
},
114+
{
115+
"alias": "WSTUSR/USR.RR",
116+
"id": "b74c2bc175c2dab850ce5a5451608501c293fe8410cb4aba7449dd1c355ab706",
117+
"time_difference": 3600,
118+
"price_deviation": 1,
119+
"confidence_ratio": 100
120+
}
121+
]

0 commit comments

Comments
 (0)