Skip to content

Commit fdbd0ad

Browse files
committed
feat(dev-hub): pyth-pro-table
1 parent 24ba767 commit fdbd0ad

File tree

3 files changed

+195
-0
lines changed

3 files changed

+195
-0
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
title: Price Feed IDs
3+
description: List of price feed IDs for all the assets supported by Pyth Pro
4+
slug: /price-feeds/pro/price-feed-ids
5+
---
6+
7+
import { Suspense } from "react";
8+
import { PriceFeedIdsProTable } from "../../../../src/components/PriceFeedIdsProTable";
9+
10+
<Suspense>
11+
<PriceFeedIdsProTable />
12+
</Suspense>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@use "@pythnetwork/component-library/theme";
2+
3+
.searchInput {
4+
width: 100%;
5+
}
6+
7+
.paginator {
8+
margin-bottom: theme.spacing(8);
9+
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
"use client";
2+
3+
import { Paginator } from "@pythnetwork/component-library/Paginator";
4+
import { SearchInput } from "@pythnetwork/component-library/SearchInput";
5+
import type { ColumnConfig } from "@pythnetwork/component-library/Table";
6+
import { Table } from "@pythnetwork/component-library/Table";
7+
import { useQueryParamFilterPagination } from "@pythnetwork/component-library/useQueryParamsPagination";
8+
import { Callout } from "fumadocs-ui/components/callout";
9+
import { matchSorter } from "match-sorter";
10+
import { useEffect, useState } from "react";
11+
import { z } from "zod";
12+
13+
import styles from "./index.module.scss";
14+
15+
export const PriceFeedIdsProTable = () => {
16+
const [state, setState] = useState<State>(State.NotLoaded());
17+
useEffect(() => {
18+
setState(State.Loading());
19+
getPythProFeeds()
20+
.then((feeds) => {
21+
setState(State.Loaded(feeds));
22+
})
23+
.catch((error: unknown) => {
24+
setState(State.Failed(error));
25+
});
26+
}, []);
27+
28+
const columns: ColumnConfig<Col>[] = [
29+
{ id: "Asset Type", name: "Asset Type", isRowHeader: true },
30+
{ id: "Description", name: "Description" },
31+
{ id: "Name", name: "Name" },
32+
{ id: "Symbol", name: "Symbol" },
33+
{ id: "Pyth Pro ID", name: "Pyth Pro ID" },
34+
{ id: "Exponent", name: "Exponent" },
35+
];
36+
37+
const {
38+
search,
39+
sortDescriptor,
40+
page,
41+
pageSize,
42+
updateSearch,
43+
updateSortDescriptor,
44+
updatePage,
45+
updatePageSize,
46+
paginatedItems,
47+
numPages,
48+
mkPageLink,
49+
} = useQueryParamFilterPagination(
50+
state.type === StateType.Loaded ? state.feeds : [],
51+
() => true,
52+
() => 1,
53+
(items, searchString) => {
54+
return matchSorter(items, searchString, {
55+
keys: [
56+
"Asset Type",
57+
"Description",
58+
"Name",
59+
"Symbol",
60+
"Pyth Pro ID",
61+
"Exponent",
62+
],
63+
});
64+
},
65+
{ defaultSort: "Pyth Pro ID", defaultPageSize: 20 },
66+
);
67+
68+
if (state.type === StateType.Error) {
69+
return <Callout type="error">{errorToString(state.error)}</Callout>;
70+
}
71+
72+
const isLoading =
73+
state.type === StateType.Loading || state.type === StateType.NotLoaded;
74+
75+
const rows = paginatedItems.map((feed) => ({
76+
id: feed.pyth_lazer_id,
77+
data: {
78+
"Asset Type": feed.asset_type,
79+
Description: feed.description,
80+
Name: feed.name,
81+
Symbol: feed.symbol,
82+
"Pyth Pro ID": feed.pyth_lazer_id,
83+
Exponent: feed.exponent,
84+
},
85+
}));
86+
87+
return (
88+
<>
89+
<SearchInput
90+
label="Search price feeds"
91+
placeholder="Search by symbol or feed id"
92+
value={search}
93+
onChange={updateSearch}
94+
className={styles.searchInput ?? ""}
95+
/>
96+
97+
<Table<Col>
98+
{...(isLoading ? { isLoading: true } : { isLoading: false, rows })}
99+
label="Price feed ids"
100+
columns={columns}
101+
onSortChange={updateSortDescriptor}
102+
sortDescriptor={sortDescriptor}
103+
stickyHeader="top"
104+
fill
105+
rounded
106+
/>
107+
<Paginator
108+
numPages={numPages}
109+
currentPage={page}
110+
onPageChange={updatePage}
111+
pageSize={pageSize}
112+
onPageSizeChange={updatePageSize}
113+
mkPageLink={mkPageLink}
114+
className={styles.paginator ?? ""}
115+
/>
116+
</>
117+
);
118+
};
119+
120+
const errorToString = (error: unknown) => {
121+
if (error instanceof Error) {
122+
return error.message;
123+
} else if (typeof error === "string") {
124+
return error;
125+
} else {
126+
return "An error occurred, please try again";
127+
}
128+
};
129+
130+
enum StateType {
131+
NotLoaded,
132+
Loading,
133+
Loaded,
134+
Error,
135+
}
136+
137+
const State = {
138+
NotLoaded: () => ({ type: StateType.NotLoaded as const }),
139+
Loading: () => ({ type: StateType.Loading as const }),
140+
Loaded: (feeds: Awaited<ReturnType<typeof getPythProFeeds>>) => ({
141+
type: StateType.Loaded as const,
142+
feeds,
143+
}),
144+
Failed: (error: unknown) => ({ type: StateType.Error as const, error }),
145+
};
146+
type State = ReturnType<(typeof State)[keyof typeof State]>;
147+
148+
const getPythProFeeds = async () => {
149+
const result: Response = await fetch(
150+
"https://history.pyth-lazer.dourolabs.app/history/v1/symbols",
151+
);
152+
const data = pythProSchema.parse(await result.json());
153+
return data;
154+
};
155+
156+
const pythProSchema = z.array(
157+
z.object({
158+
asset_type: z.string(),
159+
description: z.string(),
160+
name: z.string(),
161+
symbol: z.string(),
162+
pyth_lazer_id: z.number(),
163+
priceFeedId: z.number(),
164+
exponent: z.number(),
165+
}),
166+
);
167+
168+
type Col =
169+
| "Asset Type"
170+
| "Description"
171+
| "Name"
172+
| "Symbol"
173+
| "Pyth Pro ID"
174+
| "Exponent";

0 commit comments

Comments
 (0)