11import { useState , useEffect } from "react" ;
2- import { TextField , Select , MenuItem , Box } from "@mui/material" ;
3- import { HermesClient } from "@pythnetwork/hermes-client" ;
2+ import { TextField , Select , MenuItem } from "@mui/material" ;
3+ import { HermesClient , PriceFeedMetadata } from "@pythnetwork/hermes-client" ;
44import { Table , Td , Th , Tr , CopyToClipboard } from "nextra/components" ;
55
6- interface PriceFeed {
7- id : string ;
8- name : string ;
9- assetType : string ;
6+ const fetchStablePriceFeeds = async ( ) => {
7+ const priceFeeds = await new HermesClient (
8+ "https://hermes.pyth.network"
9+ ) . getPriceFeeds ( ) ;
10+ const assetTypes = Array . from (
11+ new Set ( priceFeeds . map ( ( feed ) => feed . attributes . asset_type ?? "" ) )
12+ ) ;
13+ console . log ( priceFeeds ) ;
14+ return { priceFeeds, assetTypes } ;
15+ } ;
16+
17+ const fetchBetaPriceFeeds = async ( ) => {
18+ const priceFeeds = await new HermesClient (
19+ "https://hermes-beta.pyth.network"
20+ ) . getPriceFeeds ( ) ;
21+ const assetTypes = Array . from (
22+ new Set ( priceFeeds . map ( ( feed ) => feed . attributes . asset_type ?? "" ) )
23+ ) ;
24+ console . log ( priceFeeds ) ;
25+ return { priceFeeds, assetTypes } ;
26+ } ;
27+
28+ type AssetTypesSelectorProps = {
29+ priceFeedsState : PriceFeedsState ;
30+ selectedAssetType : string ;
31+ setSelectedAssetType : ( assetType : string ) => void ;
32+ } ;
33+
34+ enum PriceFeedsStateType {
35+ NotLoaded ,
36+ Loading ,
37+ Loaded ,
38+ Error ,
1039}
1140
12- export function PriceFeedTable ( ) {
13- const [ priceFeeds , setPriceFeeds ] = useState < PriceFeed [ ] > ( [ ] ) ;
14- const [ searchTerm , setSearchTerm ] = useState ( "" ) ;
15- const [ selectedAssetType , setSelectedAssetType ] = useState ( "All" ) ;
41+ const PriceFeedsState = {
42+ NotLoaded : ( ) => ( { type : PriceFeedsStateType . NotLoaded as const } ) ,
43+ Loading : ( ) => ( { type : PriceFeedsStateType . Loading as const } ) ,
44+ Loaded : ( priceFeeds : PriceFeedMetadata [ ] , assetTypes : string [ ] ) => ( {
45+ type : PriceFeedsStateType . Loaded as const ,
46+ priceFeeds,
47+ assetTypes,
48+ } ) ,
49+ Error : ( error : unknown ) => ( {
50+ type : PriceFeedsStateType . Error as const ,
51+ error,
52+ } ) ,
53+ } ;
54+
55+ type PriceFeedsState = ReturnType <
56+ typeof PriceFeedsState [ keyof typeof PriceFeedsState ]
57+ > ;
1658
17- // enum AssetTypesStateType {
18- // NotLoaded,
19- // Loading,
20- // Loaded,
21- // Error,
22- // }
23-
24- // const AssetTypesState = {
25- // NotLoaded: () => ({ type: AssetTypesStateType.NotLoaded as const }),
26- // Loading: () => ({ type: AssetTypesStateType.Loading as const }),
27- // Loaded: (assetTypes: string[]) => ({
28- // type: AssetTypesStateType.Loaded as const,
29- // assetTypes,
30- // }),
31- // Error: (error: unknown) => ({
32- // type: AssetTypesStateType.Error as const,
33- // error,
34- // }),
35- // };
36-
37- const fetchAssetTypes = async ( ) => {
38- const hermesClient = new HermesClient ( "https://hermes.pyth.network" ) ;
39- const feeds = await hermesClient . getPriceFeeds ( ) ;
40- return Array . from (
41- new Set ( feeds . map ( ( feed ) => feed . attributes . asset_type ?? "" ) )
42- ) ;
43- } ;
44-
45- // const useAssetTypes = (): typeof AssetTypesState => {
46- // const [assetTypes, setAssetTypes] = useState<typeof AssetTypesState>(
47- // AssetTypesState.NotLoaded()
48- // );
49-
50- // useEffect(() => {
51- // setAssetTypes(AssetTypesState.Loading());
52- // }, []);
53-
54- // return assetTypes;
55- // };
56-
57- // type AssetTypesSelectorProps = {
58- // selectedAssetType: string;
59- // setSelectedAssetType: (assetType: string) => void;
60- // };
61-
62- // const AssetTypesSelector = ({
63- // selectedAssetType,
64- // setSelectedAssetType,
65- // }: AssetTypesSelectorProps) => {
66- // const assetTypes = useAssetTypes();
67- // };
59+ const usePriceFeeds = ( ) : PriceFeedsState => {
60+ const [ priceFeeds , setPriceFeeds ] = useState < PriceFeedsState > (
61+ PriceFeedsState . NotLoaded ( )
62+ ) ;
6863
6964 useEffect ( ( ) => {
70- const fetchPriceFeeds = async ( ) => {
71- const hermesClient = new HermesClient ( "https://hermes.pyth.network" ) ;
72- const feeds = await hermesClient . getPriceFeeds ( ) ;
73- const transformedFeeds = feeds . map ( ( feed ) => ( {
74- id : feed . id ,
75- name : feed . attributes . display_symbol || "" ,
76- assetType : feed . attributes . asset_type || "" ,
77- } ) ) ;
78- setPriceFeeds ( transformedFeeds ) ;
79- } ;
80-
81- fetchPriceFeeds ( ) ;
65+ setPriceFeeds ( PriceFeedsState . Loading ( ) ) ;
66+ fetchStablePriceFeeds ( )
67+ . then ( ( { priceFeeds, assetTypes } ) => {
68+ setPriceFeeds ( PriceFeedsState . Loaded ( priceFeeds , assetTypes ) ) ;
69+ } )
70+ . catch ( ( error ) => {
71+ setPriceFeeds ( PriceFeedsState . Error ( error ) ) ;
72+ } ) ;
8273 } , [ ] ) ;
8374
84- const filteredData = priceFeeds . filter ( ( feed ) => {
85- const matchesSearch =
86- feed . id . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ||
87- feed . name . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ;
88- const matchesAssetType =
89- selectedAssetType === "All" || feed . assetType === selectedAssetType ;
90- return matchesSearch && matchesAssetType ;
91- } ) ;
92- return (
93- < Box >
94- < Box sx = { { mb : 3 , display : "flex" , gap : 2 } } >
95- < TextField
96- label = "Search price feeds"
97- variant = "outlined"
98- size = "small"
99- value = { searchTerm }
100- onChange = { ( e ) => setSearchTerm ( e . target . value ) }
101- sx = { { width : 300 } }
102- />
75+ return priceFeeds ;
76+ } ;
77+
78+ const AssetTypesSelect = ( {
79+ priceFeedsState,
80+ selectedAssetType,
81+ setSelectedAssetType,
82+ } : AssetTypesSelectorProps ) => {
83+ const priceFeeds = usePriceFeeds ( ) ;
84+
85+ switch ( priceFeeds . type ) {
86+ case PriceFeedsStateType . NotLoaded :
87+ case PriceFeedsStateType . Loading :
88+ case PriceFeedsStateType . Error :
89+ return < p > Error loading asset types</ p > ;
90+ case PriceFeedsStateType . Loaded :
91+ return (
10392 < Select
10493 value = { selectedAssetType }
10594 onChange = { ( e ) => setSelectedAssetType ( e . target . value ) }
10695 size = "small"
10796 sx = { { width : 200 } }
10897 >
10998 < MenuItem value = "All" > All Asset Types</ MenuItem >
110- { Array . from ( new Set ( priceFeeds . map ( ( feed ) => feed . assetType ) ) ) . map (
111- ( type ) => (
112- < MenuItem key = { type } value = { type } >
113- { type || "Uncategorized" }
114- </ MenuItem >
115- )
116- ) }
99+ { priceFeeds . assetTypes . map ( ( type ) => (
100+ < MenuItem key = { type } value = { type } >
101+ { type ?? "Uncategorized" }
102+ </ MenuItem >
103+ ) ) }
117104 </ Select >
118- </ Box >
105+ ) ;
106+ }
107+ } ;
108+
109+ export function PriceFeedTable ( ) {
110+ const [ searchTerm , setSearchTerm ] = useState ( "" ) ;
111+ const [ selectedAssetType , setSelectedAssetType ] = useState ( "All" ) ;
112+ const priceFeedsState = usePriceFeeds ( ) ;
113+
114+ < AssetTypesSelect
115+ priceFeedsState = { priceFeedsState }
116+ selectedAssetType = { selectedAssetType }
117+ setSelectedAssetType = { setSelectedAssetType }
118+ /> ;
119+
120+ let filteredData : PriceFeedMetadata [ ] = [ ] ;
121+ if ( priceFeedsState . type === PriceFeedsStateType . Loaded ) {
122+ filteredData = priceFeedsState . priceFeeds . filter ( ( feed ) => {
123+ const matchesSearch =
124+ feed . id . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ||
125+ feed . attributes . display_symbol
126+ . toLowerCase ( )
127+ . includes ( searchTerm . toLowerCase ( ) ) ;
128+ const matchesAssetType =
129+ selectedAssetType === "All" ||
130+ feed . attributes . asset_type === selectedAssetType ;
131+ return matchesSearch && matchesAssetType ;
132+ } ) ;
133+ }
134+
135+ return (
136+ < >
137+ < TextField
138+ label = "Search price feeds"
139+ variant = "outlined"
140+ size = "small"
141+ value = { searchTerm }
142+ onChange = { ( e ) => setSearchTerm ( e . target . value ) }
143+ sx = { { width : 300 } }
144+ />
145+ < Select
146+ value = { selectedAssetType }
147+ onChange = { ( e ) => setSelectedAssetType ( e . target . value ) }
148+ size = "small"
149+ sx = { { width : 200 } }
150+ >
151+ < MenuItem value = "All" > All Asset Types</ MenuItem >
152+ { priceFeedsState . type === PriceFeedsStateType . Loaded &&
153+ priceFeedsState . assetTypes . map ( ( type ) => (
154+ < MenuItem key = { type } value = { type } >
155+ { type || "Uncategorized" }
156+ </ MenuItem >
157+ ) ) }
158+ </ Select >
119159
120160 < Table >
121161 < thead >
@@ -128,16 +168,16 @@ export function PriceFeedTable() {
128168 < tbody >
129169 { filteredData . map ( ( feed ) => (
130170 < Tr key = { feed . id } >
131- < Td > { feed . name } </ Td >
132- < Td > { feed . assetType } </ Td >
133- < Td >
171+ < Td > { feed . attributes . display_symbol } </ Td >
172+ < Td > { feed . attributes . asset_type } </ Td >
173+ < Td className = "font-mono whitespace-nowrap" >
134174 { feed . id }
135- < CopyToClipboard getValue = { ( ) => feed . id } />
175+ < CopyToClipboard className = "ml-2" getValue = { ( ) => feed . id } />
136176 </ Td >
137177 </ Tr >
138178 ) ) }
139179 </ tbody >
140180 </ Table >
141- </ Box >
181+ </ >
142182 ) ;
143183}
0 commit comments