-
Notifications
You must be signed in to change notification settings - Fork 35
feat(pricefeed) Migrate Price Feed IDs to documentation #493
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 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(); | ||
}, []); | ||
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]; 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? 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 }}> | ||
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. The |
||
<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> | ||
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" | ||
/> | ||
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. You can avoid having to use this component by using the |
||
</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.