-
Notifications
You must be signed in to change notification settings - Fork 38
feat(pricefeed) Migrate Price Feed IDs to documentation #493
Changes from 2 commits
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 |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| import { useState, useEffect } from "react"; | ||
| import { | ||
| Table, | ||
| TableHead, | ||
| TableBody, | ||
| TableRow, | ||
| TableCell, | ||
| TextField, | ||
| Select, | ||
| MenuItem, | ||
| Box, | ||
| Snackbar, | ||
| } from "@mui/material"; | ||
| import { HermesClient } from "@pythnetwork/hermes-client"; | ||
| import copy from "copy-to-clipboard"; | ||
|
|
||
| interface PriceFeed { | ||
| id: string; | ||
| name: string; | ||
| assetType: string; | ||
| } | ||
|
|
||
| export function PriceFeedTable() { | ||
| const [priceFeeds, setPriceFeeds] = useState<PriceFeed[]>([]); | ||
| const [searchTerm, setSearchTerm] = useState(""); | ||
| const [selectedAssetType, setSelectedAssetType] = useState("All"); | ||
| const [openSnackbar, setOpenSnackbar] = useState(false); | ||
|
|
||
| useEffect(() => { | ||
| const fetchPriceFeeds = async () => { | ||
| const hermesClient = new HermesClient("https://hermes.pyth.network"); | ||
| const feeds = await hermesClient.getPriceFeeds(); | ||
| const transformedFeeds = feeds.map((feed) => ({ | ||
| id: feed.id, | ||
| name: feed.attributes.display_symbol || "", | ||
| assetType: feed.attributes.asset_type || "", | ||
| })); | ||
| setPriceFeeds(transformedFeeds); | ||
| }; | ||
|
|
||
| fetchPriceFeeds(); | ||
| }, []); | ||
|
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. There's a few issues with this effect and the state it creates:
Putting all this together you can change the component to something like this: export const PriceFeedTable = () => {
const [selectedAssetType, setSelectedAssetType] = useState("All");
// ...
<AssetTypesSelect
selectedAssetType={selectedAssetType}
setSelectedAssetType={setSelectedAssetType}
/>
}
type AssetTypesSelectProps = {
selectedAssetType: string;
setSelectedAssetType: (value: string) => void;
}
const AssetTypesSelect = ({ selectedAssetType, setSelectedAssetType }: AssetTypesSelectProps) => {
const assetTypes = useAssetTypes();
switch (state.type) {
case AssetTypesStateType.NotLoaded:
case AssetTypesStateType.Loading: {
// use any spinner we're already using in the app anywhere else...
return <Spinner />
}
case AssetTypesStateType.Error: {
return <p>Error loading asset types!</p>;
}
case AssetTypesStateType.Loaded: {
return (
<Select
value={selectedAssetType}
onChange={(e) => setSelectedAssetType(e.target.value)}
size="small"
sx={{ width: 200 }}
>
<MenuItem value="All">All Asset Types</MenuItem>
{state.assetTypes.map(assetType => (
<MenuItem key={assetType} value={assetType}>
{assetType ?? "Uncategorized"}
</MenuItem>
))}
</Select>
);
}
}
};
const useAssetTypes = (): AssetTypesState => {
const [assetTypes, setAssetTypes] = useState<AssetTypesState>(AssetTypesState.NotLoaded());
useEffect(() => {
setAssetTypes(AssetTypesState.Loading());
fetchAssetTypes()
.then(assetTypes => setAssetTypes(AssetTypesState.Loaded(assetTypes)))
.catch(error => setAssetTypes(AssetTypesState.Error(error)));
}, []);
return assetTypes;
}
const fetchAssetTypes = async () => {
const hermesClient = new HermesClient("https://hermes.pyth.network");
const feeds = await hermesClient.getPriceFeeds();
return [...new Set(feeds.map(feed => feed.attributes.asset_type ?? ""))];
};
enum AssetTypesStateType {
NotLoaded,
Loading,
Loaded,
Error
}
const AssetTypesState = {
NotLoaded: () => ({ type: AssetTypesStateType.NotLoaded as const }),
Loading: () => ({ type: AssetTypesStateType.Loading as const }),
Loaded: (assetTypes: string[]) => ({ type: AssetTypesStateType.Loaded as const, assetTypes }),
Error: (error: unknown) => ({ type: AssetTypesStateType.Error as const, error }),
}
type AssetTypesState = typeof AssetTypesState[keyof typeof AssetTypesState];
Member
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. Replying to point 3. and then to display the data. What should be the best way around?
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. Ah, apologies, I was so focused on the asset types that I didn't think about the other usage of the feeds. In my suggested code, just replace const fetchPriceFeeds = async () => {
const priceFeeds = await new HermesClient("https://hermes.pyth.network").getPriceFeeds();
const assetTypes = [...new Set(priceFeeds.map(feed => feed.attributes.asset_type ?? ""))];
return { priceFeeds, assetTypes };
};Then update variable names etc as appropriate to reflect that the state is storing the price feeds and not just asset types (e.g. rename export const PriceFeedTable = () => {
const [selectedAssetType, setSelectedAssetType] = useState("All");
const priceFeedsState = usePriceFeeds();
// ...
<AssetTypesSelect
priceFeedsState={priceFeedsState}
selectedAssetType={selectedAssetType}
setSelectedAssetType={setSelectedAssetType}
/>
}
type AssetTypesSelectProps = {
priceFeedsState: PriceFeedsState;
selectedAssetType: string;
setSelectedAssetType: (value: string) => void;
}
const AssetTypesSelect = ({ priceFeedsState, selectedAssetType, setSelectedAssetType }: AssetTypesSelectProps) => {
switch (priceFeedsState.type) {
// ...
}
}Let me know if this makes sense. |
||
|
|
||
| const filteredData = priceFeeds.filter((feed) => { | ||
| const matchesSearch = | ||
| feed.id.toLowerCase().includes(searchTerm.toLowerCase()) || | ||
| feed.name.toLowerCase().includes(searchTerm.toLowerCase()); | ||
| const matchesAssetType = | ||
| selectedAssetType === "All" || feed.assetType === selectedAssetType; | ||
| return matchesSearch && matchesAssetType; | ||
| }); | ||
|
|
||
| const handleCopy = (id: string) => { | ||
| copy(id); | ||
| setOpenSnackbar(true); | ||
| }; | ||
|
|
||
| return ( | ||
| <Box> | ||
| <Box sx={{ mb: 3, display: "flex", gap: 2 }}> | ||
|
||
| <TextField | ||
| label="Search price feeds" | ||
| variant="outlined" | ||
| size="small" | ||
| value={searchTerm} | ||
| onChange={(e) => setSearchTerm(e.target.value)} | ||
| sx={{ width: 300 }} | ||
| /> | ||
| <Select | ||
| value={selectedAssetType} | ||
| onChange={(e) => setSelectedAssetType(e.target.value)} | ||
| size="small" | ||
| sx={{ width: 200 }} | ||
| > | ||
| <MenuItem value="All">All Asset Types</MenuItem> | ||
| {Array.from(new Set(priceFeeds.map((feed) => feed.assetType))).map( | ||
| (type) => ( | ||
| <MenuItem key={type} value={type}> | ||
| {type || "Uncategorized"} | ||
| </MenuItem> | ||
| ) | ||
| )} | ||
| </Select> | ||
|
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. I don't think there's a great reason to use MUI for text fields or selects; we already have other components in the codebase for both cases, see e.g. https://docs.pyth.network/price-feeds/api-reference/cosmwasm/query-price-feed and the components on that page ( |
||
| </Box> | ||
|
|
||
| <Table> | ||
| <TableHead> | ||
| <TableRow> | ||
| <TableCell>Ticker</TableCell> | ||
| <TableCell>Asset Type</TableCell> | ||
| <TableCell>Feed ID</TableCell> | ||
| </TableRow> | ||
| </TableHead> | ||
| <TableBody> | ||
| {filteredData.map((feed) => ( | ||
| <TableRow key={feed.id}> | ||
| <TableCell>{feed.name}</TableCell> | ||
| <TableCell>{feed.assetType}</TableCell> | ||
| <TableCell | ||
| onClick={() => handleCopy(feed.id)} | ||
| sx={{ | ||
| cursor: "pointer", | ||
| "&:hover": { | ||
| backgroundColor: "#BB86FC", | ||
| }, | ||
| }} | ||
| > | ||
| {feed.id} | ||
| </TableCell> | ||
| </TableRow> | ||
| ))} | ||
| </TableBody> | ||
| </Table> | ||
|
|
||
| <Snackbar | ||
| open={openSnackbar} | ||
| autoHideDuration={2000} | ||
| onClose={() => setOpenSnackbar(false)} | ||
| message="Feed ID copied to clipboard" | ||
| /> | ||
|
||
| </Box> | ||
| ); | ||
| } | ||
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.
Mentioned this in slack but we should try to use built-in nextra components where possible. I don't really have an issue with using mui for stuff that isn't in nextra, but I also don't think you get much out of the stuff you're using here and can probably do away with mui entirely.