-
Notifications
You must be signed in to change notification settings - Fork 36
Feat(pricefeed/price feed id) add data #445
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 9 commits
bd4dd97
abffe78
bbae1ca
845fdf3
a307c5d
ac05f76
9191ced
8420aec
09e8501
5a3980e
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,119 @@ | ||
import { useState, useCallback } from "react"; | ||
import { StyledTd } from "./Table"; | ||
|
||
interface PriceFeed { | ||
ids: string; | ||
assetType: string; | ||
name: string; | ||
} | ||
|
||
interface TableColumnWidths { | ||
assetType: string; | ||
name: string; | ||
ids: any; | ||
} | ||
|
||
const columnWidths: TableColumnWidths = { | ||
assetType: "w-3/10", | ||
name: "w-1/5", | ||
ids: "w-1/2", | ||
}; | ||
|
||
const PriceFeedTable = ({ priceFeeds }: { priceFeeds: PriceFeed[] }) => { | ||
const [selectedAssetType, setSelectedAssetType] = useState<string>("All"); | ||
const [copiedId, setCopiedId] = useState<string | null>(null); | ||
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 generally recommend using |
||
|
||
const assetTypes = [ | ||
"All", | ||
...Array.from(new Set(priceFeeds.map((feed) => feed.assetType))), | ||
]; | ||
|
||
const filteredFeeds = | ||
selectedAssetType === "All" | ||
? priceFeeds | ||
: priceFeeds.filter((feed) => feed.assetType === selectedAssetType); | ||
|
||
const copyToClipboard = useCallback((text: string) => { | ||
navigator.clipboard | ||
.writeText(text) | ||
.then(() => { | ||
setCopiedId(text); | ||
setTimeout(() => setCopiedId(null), 2000); // Hide the popup after 2 seconds | ||
}) | ||
.catch((err) => { | ||
console.error("Failed to copy: ", err); | ||
}); | ||
}, []); | ||
|
||
return ( | ||
<div> | ||
<div className="mb-4"> | ||
<label htmlFor="assetTypeFilter" className="mr-2"> | ||
Filter by Asset Type: | ||
</label> | ||
<select | ||
id="assetTypeFilter" | ||
value={selectedAssetType} | ||
onChange={(e) => setSelectedAssetType(e.target.value)} | ||
className="p-2 border rounded" | ||
> | ||
{assetTypes.map((type) => ( | ||
<option key={type} value={type}> | ||
{type} | ||
</option> | ||
))} | ||
</select> | ||
</div> | ||
<table> | ||
<thead> | ||
<tr> | ||
<th className={columnWidths.assetType}>Asset Type</th> | ||
<th className={columnWidths.name}>Name</th> | ||
<th className={columnWidths.ids}>Feed ID</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{filteredFeeds.map((feed, index) => ( | ||
<tr key={index}> | ||
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 should use something that's unique and not index based here, e.g. |
||
<StyledTd>{feed.assetType}</StyledTd> | ||
<StyledTd>{feed.name}</StyledTd> | ||
<StyledTd> | ||
<div className="relative"> | ||
<button | ||
onClick={() => copyToClipboard(feed.ids)} | ||
className="flex items-center space-x-2 px-2 py-1 bg-gray-100 hover:bg-gray-200 dark:bg-darkGray2 dark:hover:bg-darkGray3 rounded transition duration-200" | ||
> | ||
<code className="dark:text-darkLinks text-lightLinks dark:bg-darkGray2 bg-gray-100 px-2 py-1 rounded"> | ||
{feed.ids} | ||
</code> | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
className="h-4 w-4" | ||
fill="none" | ||
viewBox="0 0 24 24" | ||
stroke="currentColor" | ||
> | ||
<path | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
strokeWidth={2} | ||
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" | ||
/> | ||
</svg> | ||
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'm assuming that this is a copy icon right? Why not use the |
||
</button> | ||
{copiedId === feed.ids && ( | ||
<div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-gray-800 text-white text-sm rounded shadow"> | ||
Copied to clipboard | ||
</div> | ||
)} | ||
</div> | ||
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. A lot of this component is doing the same stuff that's already in Nextra in import { Code, Pre, Table, Td, Th, Tr } from 'nextra/components';
const PriceFeedTable = ({ priceFeeds }: { priceFeeds: PriceFeed[] }) => {
// .... filtering stuff
return (
<Table>
<thead>
<Tr>
<Th className="w-3/10">Asset Type</Th>
<Th className="w-1/5">Name</Th>
<Th className="w-1/2">Feed ID</Th>
</Tr>
</thead>
<tbody>
{filteredFeeds.map(({ assetType, name, ids }) => (
<Tr key={name}>
<Td>{assetType}</Td>
<Td>{name}</Td>
<Td>
<Pre data-copy="">
<Code>
{ids}
</Code>
</Pre>
</Td>
</Tr>
))}
</thead>
</Table>
);
}; Note Not only will this drastically reduce the amount of custom code here, but you'll also make things much more consistent with the rest of the site. |
||
</StyledTd> | ||
</tr> | ||
))} | ||
</tbody> | ||
</table> | ||
</div> | ||
); | ||
}; | ||
|
||
export default PriceFeedTable; |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# Price Feeds | ||
|
||
Pyth Price Feeds provide real-time, first-party, market data for a wide range of assets. | ||
|
||
Every price feed has a **unique ID**, representing the specific pair of assets being priced. | ||
These specific pairs are part of an asset class, which is a broader category of assets. | ||
|
||
Anyone can fetch available price feeds and their IDs via [Hermes API](https://hermes.pyth.network/docs/#/rest/price_feeds_metadata). | ||
|
||
## Asset Classes | ||
|
||
Every price feed belongs to an asset class. These asset classes distinguish between different types of assets, such as crypto, US equities, and metals. | ||
|
||
Refer to the [Asset Classes](./price-feeds/asset-classes.md) page to learn more about the existing asset classes. | ||
|
||
## Price Feed IDs | ||
|
||
Price Feed IDs are unique identifiers for each specific pair of assets being priced (e.g. BTC/USD). | ||
Every price update is tagged with the corresponding price feed ID. | ||
|
||
Applications need to store the IDs of the feeds they wish to read. | ||
However, the IDs may be represented in different formats (e.g. hex or base58) depending on the blockchain. | ||
Price feeds also have different IDs in the Stable and Beta channels. | ||
|
||
Refer to the [Price Feed ID reference catalog](./price-feeds/price-feed-ids.md) to identify a feed's ID in your chosen ecosystem. | ||
|
||
### Solana Price Feed Accounts | ||
|
||
On Solana, each feed additionally has a collection of **price feed accounts** containing the feed's data. | ||
The addresses of these accounts are programmatically derived from the feed id and a shard id, which is simply a 16-bit number. | ||
See [How to Use Real-Time Data on Solana](./use-real-time-data/solana#price-feed-accounts) for more information on price feed accounts. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"asset-classes": "Asset Classes", | ||
"price-feed-ids": "Price Feed IDs" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# Asset Classes | ||
|
||
[Pyth price feeds](https://www.pyth.network/price-feeds) provide market data for the following asset classes: | ||
|
||
| Asset Class | Subclass | Definition | | ||
| ----------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| Crypto | Spot Prices | Real-time prices for cryptocurrencies and digital assets | | ||
| | Redemption Rates | Real-time swap rates derived from smart contracts for the redemption of liquid staking and liquid restaking tokens (LSTs and LRTs), liquidity provider tokens (LP Tokens) and interest-bearing assets, including tokenised notes | | ||
| | Indices | Real-time prices that measure the performance of baskets of cryptocurrencies and digital assets | | ||
| US Equities | Spot Prices | Real-time prices for US equities | | ||
| FX | Spot Prices | Real-time prices for fiat currency pairs | | ||
| Metals | Spot Prices | Real-time prices for precious metals | | ||
| Rates | Future Prices | Real-time prices for fixed income products, including bond futures | | ||
| Commodities | Futures Prices | Real-time prices for commodity futures | |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import PriceFeedTable from "../../../components/PriceFeedTable"; | ||
import { useState, useEffect } from 'react'; | ||
|
||
export const PriceFeedData = () => { | ||
const [priceFeeds, setPriceFeeds] = useState([]); | ||
|
||
useEffect(() => { | ||
const fetchPriceFeeds = async () => { | ||
try { | ||
const response = await fetch('https://hermes.pyth.network/v2/price_feeds'); | ||
const data = await response.json(); | ||
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. Ideally, you should validate the response type here. This is where Alternatively if you use the Hermes js client, it already has validation and everything built in |
||
|
||
// Transform the data to match our PriceFeed interface | ||
const transformedData = data.map(feed => ({ | ||
assetType: feed.attributes.asset_type, | ||
name: feed.attributes.display_symbol, | ||
ids: feed.id | ||
})); | ||
|
||
setPriceFeeds(transformedData); | ||
} catch (error) { | ||
console.error('Error fetching price feeds:', error); | ||
} | ||
}; | ||
|
||
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. You should ideally pass a canceler here using You can also extract out the You should also add loading and error states. Finally I'd suggest putting all the component code in an actual .tsx file and just importing it here, rather than keeping it inline. Your formatting isn't handled by prettier, you don't get linting, etc, when you have the code inline. Putting this all together would look something like this: import { useState, useEffect } from 'react';
import { z } from "zod";
import PriceFeedTable from "../../../components/PriceFeedTable";
export const PriceFeedData = () => {
const priceFeedData = usePriceFeedData();
switch (state.type) {
case StateType.NotLoaded:
case StateType.Loading: {
return <Spinner /> // TODO implement a spinner / loader...
}
case StateType.Error: {
return <p>Oh no! An error occurred: {state.error}</p>
}
case StateType.Loaded: {
return <PriceFeedTable priceFeeds={state.data} />;
}
}
};
const usePriceFeedData = (): State => {
const [state, setState] = useState<State>(State.NotLoaded());
useEffect(() => {
setState(State.Loading());
const controller = new AbortController();
fetchPriceFeeds(controller)
.then(data => setState(State.Loaded(data)))
.catch((error: unknown) => {
console.error('Error fetching price feeds:', error);
setState(State.Error(error));
});
return () => controller.abort();
}, []);
return state;
}
const dataSchema = z.array(z.object({
asset_type: z.string(),
display_symbol: z.string(),
ids: z.array() // TODO: I don't know what this shape should be but you should type this correctly!
}).transform(({ asset_type, display_symbol, id }) => ({
assetType: asset_type,
name: display_symbol,
ids: id
})));
type Data = z.infer<typeof dataSchema>;
enum StateType = {
NotLoaded,
Loading,
Error,
Loaded
}
const State = {
NotLoaded: () => ({ type: StateType.NotLoaded as const }),
Loading: () => ({ type: StateType.Loading as const }),
Error: (error: unknown) => ({ type: StateType.Error as const, error }),
Loaded: (data: Data) => ({ type: StateType.Loaded as const, data }),
}
type State = ReturnType<typeof State[keyof typeof State]>;
const fetchPriceFeeds = async (signal: AbortSignal): Promise<Data> => {
const response = await fetch('https://hermes.pyth.network/v2/price_feeds', { signal });
return dataSchema.parse(await response.json());
}; Note this is untested and still needs a few TODO items filled in so don't copy-paste blindly! |
||
|
||
}, []); | ||
|
||
return <PriceFeedTable priceFeeds={priceFeeds} />; | ||
}; | ||
|
||
# Price Feed IDs | ||
|
||
Below is a table of all available price feed IDs: | ||
|
||
<PriceFeedData /> |
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.
I don't think there's any reason to extract all this out, just put the tailwind classes in at the call sites where they're being consumed (i.e. lines 70 - 72)