|
|
@@ -13,10 +13,12 @@ import {
|
|
|
Table,
|
|
|
} from "@pythnetwork/component-library/Table";
|
|
|
import { useQueryState, parseAsString } from "nuqs";
|
|
|
-import { type ReactNode, Suspense, useCallback, useMemo } from "react";
|
|
|
+import { Suspense, useCallback, useMemo } from "react";
|
|
|
import { useFilter, useCollator } from "react-aria";
|
|
|
|
|
|
+import { usePriceFeeds } from "../../hooks/use-price-feeds";
|
|
|
import { useQueryParamFilterPagination } from "../../hooks/use-query-param-filter-pagination";
|
|
|
+import { AssetClassTag } from "../AssetClassTag";
|
|
|
import { FeedKey } from "../FeedKey";
|
|
|
import {
|
|
|
SKELETON_WIDTH,
|
|
|
@@ -34,11 +36,7 @@ type Props = {
|
|
|
};
|
|
|
|
|
|
type PriceFeed = {
|
|
|
- icon: ReactNode;
|
|
|
symbol: string;
|
|
|
- id: string;
|
|
|
- displaySymbol: string;
|
|
|
- assetClass: string;
|
|
|
exponent: number;
|
|
|
numQuoters: number;
|
|
|
};
|
|
|
@@ -57,12 +55,32 @@ const ResolvedPriceFeedsCard = ({ priceFeeds, ...props }: Props) => {
|
|
|
"assetClass",
|
|
|
parseAsString.withDefault(""),
|
|
|
);
|
|
|
+ const feeds = usePriceFeeds();
|
|
|
+ const priceFeedsWithContextInfo = useMemo(
|
|
|
+ () =>
|
|
|
+ priceFeeds.map((feed) => {
|
|
|
+ const contextFeed = feeds.get(feed.symbol);
|
|
|
+ if (contextFeed) {
|
|
|
+ return {
|
|
|
+ ...feed,
|
|
|
+ assetClass: contextFeed.assetClass,
|
|
|
+ displaySymbol: contextFeed.displaySymbol,
|
|
|
+ key: contextFeed.key,
|
|
|
+ };
|
|
|
+ } else {
|
|
|
+ throw new NoSuchFeedError(feed.symbol);
|
|
|
+ }
|
|
|
+ }),
|
|
|
+ [feeds, priceFeeds],
|
|
|
+ );
|
|
|
const feedsFilteredByAssetClass = useMemo(
|
|
|
() =>
|
|
|
assetClass
|
|
|
- ? priceFeeds.filter((feed) => feed.assetClass === assetClass)
|
|
|
- : priceFeeds,
|
|
|
- [assetClass, priceFeeds],
|
|
|
+ ? priceFeedsWithContextInfo.filter(
|
|
|
+ (feed) => feed.assetClass === assetClass,
|
|
|
+ )
|
|
|
+ : priceFeedsWithContextInfo,
|
|
|
+ [assetClass, priceFeedsWithContextInfo],
|
|
|
);
|
|
|
const {
|
|
|
search,
|
|
|
@@ -100,54 +118,27 @@ const ResolvedPriceFeedsCard = ({ priceFeeds, ...props }: Props) => {
|
|
|
|
|
|
const rows = useMemo(
|
|
|
() =>
|
|
|
- paginatedItems.map(
|
|
|
- ({
|
|
|
- icon,
|
|
|
- id,
|
|
|
- symbol,
|
|
|
- displaySymbol,
|
|
|
- exponent,
|
|
|
- numQuoters,
|
|
|
- assetClass,
|
|
|
- }) => ({
|
|
|
- id,
|
|
|
- href: `/price-feeds/${encodeURIComponent(symbol)}`,
|
|
|
- data: {
|
|
|
- exponent: (
|
|
|
- <LiveValue
|
|
|
- field="exponent"
|
|
|
- feedKey={id}
|
|
|
- defaultValue={exponent}
|
|
|
- />
|
|
|
- ),
|
|
|
- numPublishers: (
|
|
|
- <LiveValue
|
|
|
- field="numQuoters"
|
|
|
- feedKey={id}
|
|
|
- defaultValue={numQuoters}
|
|
|
- />
|
|
|
- ),
|
|
|
- price: <LivePrice feedKey={id} />,
|
|
|
- confidenceInterval: <LiveConfidence feedKey={id} />,
|
|
|
- priceFeedName: (
|
|
|
- <PriceFeedTag compact symbol={displaySymbol} icon={icon} />
|
|
|
- ),
|
|
|
- assetClass: (
|
|
|
- <Badge variant="neutral" style="outline" size="xs">
|
|
|
- {assetClass.toUpperCase()}
|
|
|
- </Badge>
|
|
|
- ),
|
|
|
- priceFeedId: (
|
|
|
- <FeedKey
|
|
|
- // className={styles.feedKey ?? ""}
|
|
|
- size="xs"
|
|
|
- variant="ghost"
|
|
|
- feedKey={id}
|
|
|
- />
|
|
|
- ),
|
|
|
- },
|
|
|
- }),
|
|
|
- ),
|
|
|
+ paginatedItems.map(({ symbol, exponent, numQuoters, key }) => ({
|
|
|
+ id: symbol,
|
|
|
+ href: `/price-feeds/${encodeURIComponent(symbol)}`,
|
|
|
+ data: {
|
|
|
+ exponent: (
|
|
|
+ <LiveValue field="exponent" feedKey={key} defaultValue={exponent} />
|
|
|
+ ),
|
|
|
+ numPublishers: (
|
|
|
+ <LiveValue
|
|
|
+ field="numQuoters"
|
|
|
+ feedKey={key}
|
|
|
+ defaultValue={numQuoters}
|
|
|
+ />
|
|
|
+ ),
|
|
|
+ price: <LivePrice feedKey={key} />,
|
|
|
+ confidenceInterval: <LiveConfidence feedKey={key} />,
|
|
|
+ priceFeedName: <PriceFeedTag compact symbol={symbol} />,
|
|
|
+ assetClass: <AssetClassTag symbol={symbol} />,
|
|
|
+ priceFeedId: <FeedKey size="xs" variant="ghost" feedKey={key} />,
|
|
|
+ },
|
|
|
+ })),
|
|
|
[paginatedItems],
|
|
|
);
|
|
|
|
|
|
@@ -163,10 +154,10 @@ const ResolvedPriceFeedsCard = ({ priceFeeds, ...props }: Props) => {
|
|
|
|
|
|
const assetClasses = useMemo(
|
|
|
() =>
|
|
|
- [...new Set(priceFeeds.map((feed) => feed.assetClass))].sort((a, b) =>
|
|
|
- collator.compare(a, b),
|
|
|
- ),
|
|
|
- [priceFeeds, collator],
|
|
|
+ [
|
|
|
+ ...new Set(priceFeedsWithContextInfo.map((feed) => feed.assetClass)),
|
|
|
+ ].sort((a, b) => collator.compare(a, b)),
|
|
|
+ [priceFeedsWithContextInfo, collator],
|
|
|
);
|
|
|
|
|
|
return (
|
|
|
@@ -362,3 +353,10 @@ const PriceFeedsCardContents = ({ id, ...props }: PriceFeedsCardContents) => (
|
|
|
/>
|
|
|
</Card>
|
|
|
);
|
|
|
+
|
|
|
+class NoSuchFeedError extends Error {
|
|
|
+ constructor(symbol: string) {
|
|
|
+ super(`No feed exists named ${symbol}`);
|
|
|
+ this.name = "NoSuchFeedError";
|
|
|
+ }
|
|
|
+}
|