From 5799db68d32c4a74a6fc061e3bcdadfc90b99aff Mon Sep 17 00:00:00 2001 From: devin distefano Date: Thu, 28 May 2026 17:15:49 -0500 Subject: [PATCH 01/10] hide very risk feeds, fix monad smartdata --- src/db/feedCategories.ts | 15 ++++- src/features/data/chains.ts | 5 +- src/features/feeds/components/FeedList.tsx | 20 +++++- src/features/feeds/components/Tables.tsx | 71 ++++++++++++++-------- src/features/feeds/utils/feedVisibility.ts | 11 +++- 5 files changed, 86 insertions(+), 36 deletions(-) diff --git a/src/db/feedCategories.ts b/src/db/feedCategories.ts index 396b0a120b8..93b30880cba 100644 --- a/src/db/feedCategories.ts +++ b/src/db/feedCategories.ts @@ -103,13 +103,23 @@ const resolveRiskStatus = ( shutdownDate?: string, fallbackCategory?: string ): string | null => { - if (dbTier != null) return dbTier + // Deprecating feeds always show the deprecating icon, even when a DB risk tier exists. if (shutdownDate) return "deprecating" + if (dbTier != null) return dbTier if (fallbackCategory && FALLBACK_ONLY_CATEGORIES.has(fallbackCategory.toLowerCase())) return fallbackCategory.toLowerCase() return null } +/** Client-side helper for resolving the displayed feed category. */ +export function resolveFeedCategory( + dbTier: string | null | undefined, + shutdownDate?: string, + fallbackCategory?: string +): string | null { + return resolveRiskStatus(dbTier, shutdownDate, fallbackCategory) +} + const defaultCategoryList = () => Object.values(FEED_CATEGORY_CONFIG).map(({ key, name }) => ({ key, name })) /* =========================== @@ -148,7 +158,8 @@ export async function getFeedCategories() { /** * Batch lookup: returns a Map of `${address}-${network}` → { final }. - * Uses DB risk_status when present. If absent, infers "deprecating" from shutdownDate. + * Uses DB risk_status when present, unless the feed has a shutdownDate (deprecating). + * If absent, infers "deprecating" from shutdownDate. * Returns null when neither is available. */ export async function getFeedRiskTiersBatch(feedRequests: FeedRequest[]): Promise> { diff --git a/src/features/data/chains.ts b/src/features/data/chains.ts index 3e58e2e2d72..93f4cc9f364 100644 --- a/src/features/data/chains.ts +++ b/src/features/data/chains.ts @@ -486,6 +486,7 @@ export const CHAINS: Chain[] = [ networkType: "mainnet", rddUrl: "https://reference-data-directory.vercel.app/feeds-monad-mainnet.json", queryString: "monad-mainnet", + tags: ["smartData"], }, { name: "Monad Testnet", @@ -493,6 +494,7 @@ export const CHAINS: Chain[] = [ networkType: "testnet", rddUrl: "https://reference-data-directory.vercel.app/feeds-monad-testnet.json", queryString: "monad-testnet", + tags: ["smartData"], }, ], }, @@ -679,7 +681,7 @@ export const CHAINS: Chain[] = [ title: "Solana Data Feeds", img: "/assets/chains/solana.svg", networkStatusUrl: "https://status.solana.com/", - tags: ["default", "smartData"], + tags: ["default"], supportedFeatures: ["feeds"], networks: [ { @@ -688,7 +690,6 @@ export const CHAINS: Chain[] = [ networkType: "mainnet", rddUrl: "https://reference-data-directory.vercel.app/feeds-solana-mainnet.json", queryString: "solana-mainnet", - tags: ["smartData"], }, { name: "Solana Devnet", diff --git a/src/features/feeds/components/FeedList.tsx b/src/features/feeds/components/FeedList.tsx index 7fcd8124c72..4b10af5e9c2 100644 --- a/src/features/feeds/components/FeedList.tsx +++ b/src/features/feeds/components/FeedList.tsx @@ -498,14 +498,30 @@ export const FeedList = ({ const metadataCache = chainMetadata.cache ?? initialCache const selectableChains = useMemo(() => { - if (!isDeprecating || isStreams || !metadataCache) return filteredChainsByTag + if (isStreams || !metadataCache) return filteredChainsByTag + + const filterByVisibleFeeds = + isDeprecating || isSmartData || isRates || isUSGovernmentMacroeconomicData || dataFeedType === "tokenizedEquity" + + if (!filterByVisibleFeeds) return filteredChainsByTag return filteredChainsByTag.filter((chain) => chainHasVisibleFeeds((metadataCache as Record)[chain.page], dataFeedType, ecosystem, { tokenizedEquityProvider, }) ) - }, [filteredChainsByTag, metadataCache, isDeprecating, isStreams, dataFeedType, ecosystem, tokenizedEquityProvider]) + }, [ + filteredChainsByTag, + metadataCache, + isDeprecating, + isStreams, + isSmartData, + isRates, + isUSGovernmentMacroeconomicData, + dataFeedType, + ecosystem, + tokenizedEquityProvider, + ]) const availableChainsForSelection = selectableChains.length > 0 ? selectableChains : filteredChainsByTag diff --git a/src/features/feeds/components/Tables.tsx b/src/features/feeds/components/Tables.tsx index c08f40a4091..a9a32a508da 100644 --- a/src/features/feeds/components/Tables.tsx +++ b/src/features/feeds/components/Tables.tsx @@ -9,7 +9,7 @@ import button from "@chainlink/design-system/button.module.css" import { CheckHeartbeat } from "./pause-notice/CheckHeartbeat.tsx" import { monitoredFeeds, FeedDataItem } from "~/features/data/index.ts" import { StreamsNetworksData, type NetworkData } from "../data/StreamsNetworksData.ts" -import { FEED_CATEGORY_CONFIG } from "../../../db/feedCategories.js" +import { FEED_CATEGORY_CONFIG, resolveFeedCategory } from "../../../db/feedCategories.js" import { REPORT_SCHEMA_DEFINITIONS, type SchemaDefinition } from "./reportSchemaData.ts" import { useBatchedFeedCategories, getFeedCategoryFromBatch, getNetworkIdentifier } from "./useBatchedFeedCategories.ts" import { isSharedSVR, isAaveSVR } from "~/features/feeds/utils/svrDetection.ts" @@ -347,7 +347,7 @@ const DefaultTr = ({ network, metadata, showExtraDetails, batchedCategoryData, d // Any feed with a calculated price, or one explicitly listed in CONTACT_EMAIL_PROXY_ADDRESSES, // should have its address hidden and show a contact email instead. - const hideAddress = shouldHideAddress(metadata) + const hideAddress = shouldHideAddress(metadata, finalTier) // Stablecoin price-bound note: only when the source marks the feed as explicitly capped const stablecoinBound = @@ -610,6 +610,8 @@ const SmartDataTr = ({ network, metadata, showExtraDetails, batchedCategoryData ? getMaxSubmissionValueBound(metadata.maxSubmissionValue, metadata.decimals) : null + const hideAddress = shouldHideAddress(metadata, finalTier) + return ( @@ -685,29 +687,40 @@ const SmartDataTr = ({ network, metadata, showExtraDetails, batchedCategoryData
- - {metadata.proxyAddress ?? metadata.transmissionsAccount} - - + {hideAddress ? ( + + Contact us:{" "} + + {TOKENIZED_EQUITY_CONTACT_EMAIL} + + + ) : ( + <> + + {metadata.proxyAddress ?? metadata.transmissionsAccount} + + + + )}
@@ -1444,11 +1457,13 @@ export const MainnetTable = ({ const contractAddress = isAptos ? metadata.proxyAddress : metadata.contractAddress || metadata.proxyAddress const networkIdentifier = getNetworkIdentifier(network) - const finalCategory = + const batchFinal = contractAddress && batchedCategoryData?.size ? getFeedCategoryFromBatch(batchedCategoryData, contractAddress, networkIdentifier).final : null + const finalCategory = resolveFeedCategory(batchFinal, metadata.docs?.shutdownDate, metadata.feedCategory) + return { ...metadata, finalCategory } }) @@ -1695,11 +1710,13 @@ export const TestnetTable = ({ const contractAddress = isAptos ? metadata.proxyAddress : metadata.contractAddress || metadata.proxyAddress const networkIdentifier = getNetworkIdentifier(network) - const finalCategory = + const batchFinal = contractAddress && batchedCategoryData?.size ? getFeedCategoryFromBatch(batchedCategoryData, contractAddress, networkIdentifier).final : null + const finalCategory = resolveFeedCategory(batchFinal, metadata.docs?.shutdownDate, metadata.feedCategory) + return { ...metadata, finalCategory } }) diff --git a/src/features/feeds/utils/feedVisibility.ts b/src/features/feeds/utils/feedVisibility.ts index 8293a0e5653..2ba282e9d51 100644 --- a/src/features/feeds/utils/feedVisibility.ts +++ b/src/features/feeds/utils/feedVisibility.ts @@ -19,21 +19,26 @@ export const CONTACT_EMAIL_PROXY_ADDRESSES = new Set([ "0x0101166b3b000332000000000000000000000000000000000000000000000000", ]) +const normalizeCategoryKey = (value?: string | null): string | undefined => value?.toLowerCase().replace(/\s+/g, "") + /** * Returns true when the feed's contract address should be hidden and replaced * with the data-feeds contact email in the UI. * - * Two conditions trigger hiding: + * Three conditions trigger hiding: * 1. The feed's productSubType is "calculatedPrice" (blanket rule for all * calculated-price feeds). * 2. The feed's proxyAddress appears in CONTACT_EMAIL_PROXY_ADDRESSES (used * for one-off overrides on a per-feed basis). + * 3. The feed's resolved risk tier is "very high" (Supabase risk_status or + * equivalent final category). */ // eslint-disable-next-line @typescript-eslint/no-explicit-any -export function shouldHideAddress(feed: any): boolean { +export function shouldHideAddress(feed: any, riskTier?: string | null): boolean { if (feed.docs?.productSubType === "calculatedPrice") return true const proxy: string | null | undefined = feed.proxyAddress - return proxy != null && CONTACT_EMAIL_PROXY_ADDRESSES.has(proxy.toLowerCase()) + if (proxy != null && CONTACT_EMAIL_PROXY_ADDRESSES.has(proxy.toLowerCase())) return true + return normalizeCategoryKey(riskTier) === "veryhigh" } /** From 27ccee01739038f25f8ba79797dddd1f7f1b3ce2 Mon Sep 17 00:00:00 2001 From: devin distefano Date: Thu, 28 May 2026 17:48:09 -0500 Subject: [PATCH 02/10] table refactor + redesign --- .../ChainSelector/ChainSelector.tsx | 14 +- src/features/feeds/README.md | 52 ++ .../feeds/components/FeedList.module.css | 109 ++- src/features/feeds/components/FeedList.tsx | 635 +++++++++--------- src/features/feeds/components/FeedPage.astro | 196 +++--- src/features/feeds/components/Tables.tsx | 347 ++-------- src/features/feeds/constants.ts | 27 + .../feeds/hooks/useFilteredFeedMetadata.ts | 29 + src/features/feeds/types.ts | 51 ++ src/features/feeds/utils/chainFilters.ts | 65 ++ src/features/feeds/utils/feedMetadata.ts | 146 ++++ src/features/feeds/utils/feedVisibility.ts | 92 +-- src/features/feeds/utils/svrDetection.ts | 31 + src/features/feeds/utils/tableFilters.ts | 59 ++ 14 files changed, 1025 insertions(+), 828 deletions(-) create mode 100644 src/features/feeds/README.md create mode 100644 src/features/feeds/constants.ts create mode 100644 src/features/feeds/hooks/useFilteredFeedMetadata.ts create mode 100644 src/features/feeds/types.ts create mode 100644 src/features/feeds/utils/chainFilters.ts create mode 100644 src/features/feeds/utils/feedMetadata.ts create mode 100644 src/features/feeds/utils/tableFilters.ts diff --git a/src/components/ChainSelector/ChainSelector.tsx b/src/components/ChainSelector/ChainSelector.tsx index 44006774200..84d754dbc6c 100644 --- a/src/components/ChainSelector/ChainSelector.tsx +++ b/src/components/ChainSelector/ChainSelector.tsx @@ -2,6 +2,7 @@ import { useState, useRef, useEffect } from "preact/hooks" import { clsx } from "~/lib/clsx/clsx.ts" import { Chain } from "~/features/data/chains.ts" +import { chainMatchesFeedTypeTag } from "~/features/feeds/utils/chainFilters.ts" import styles from "./ChainSelector.module.css" interface ChainSelectorProps { @@ -33,18 +34,7 @@ export function ChainSelector({ // Filter chains based on dataFeedType and search term const filteredChains = chains.filter((chain) => { - // Filter by dataFeedType first - const matchesDataFeedType = (() => { - if (dataFeedType.includes("streams")) return chain.tags?.includes("streams") ?? false - if (dataFeedType === "smartdata") return chain.tags?.includes("smartData") ?? false - if (dataFeedType === "rates") return chain.tags?.includes("rates") ?? false - if (dataFeedType === "usGovernmentMacroeconomicData") - return chain.tags?.includes("usGovernmentMacroeconomicData") ?? false - if (dataFeedType === "tokenizedEquity") return chain.tags?.includes("tokenizedEquity") ?? false - return chain.tags?.includes("default") ?? false - })() - - // Filter by search term + const matchesDataFeedType = chainMatchesFeedTypeTag(chain, dataFeedType as never) const matchesSearch = !searchTerm || chain.label.toLowerCase().includes(searchTerm.toLowerCase()) return matchesDataFeedType && matchesSearch diff --git a/src/features/feeds/README.md b/src/features/feeds/README.md new file mode 100644 index 00000000000..cc04b64758f --- /dev/null +++ b/src/features/feeds/README.md @@ -0,0 +1,52 @@ +# Feeds UI architecture + +This folder powers the data feed address tables (`FeedList`, `Tables`) and shared filtering logic. + +## Where to start + +| File | Responsibility | +| ---------------------------------- | --------------------------------------------------------------------------------------- | +| `components/FeedList.tsx` | Page shell: chain selector, URL state, filters, renders `MainnetTable` / `TestnetTable` | +| `components/Tables.tsx` | Row rendering (DefaultTr, SmartDataTr, StreamsTr) and table layout | +| `utils/feedVisibility.ts` | **Source of truth** for whether a feed belongs on a given page | +| `utils/tableFilters.ts` | Shared mainnet/testnet row pipeline (enrich → filter → search) | +| `utils/feedMetadata.ts` | Schema version, category enrichment, search/category helpers | +| `utils/chainFilters.ts` | Chain/network tag matching for dropdowns and section visibility | +| `types.ts` | `DataFeedType` and `getFeedTypeFlags()` | +| `hooks/useFilteredFeedMetadata.ts` | Hook used by both feed tables | + +## Data flow + +``` +chains.ts (static config + RDD URLs) + ↓ +useGetChainMetadata → network.metadata[] + ↓ +useFilteredFeedMetadata / isFeedVisible + ↓ +MainnetTable / TestnetTable → DefaultTr / SmartDataTr / StreamsTr +``` + +## Common change patterns + +**Add a new feed page type** + +1. Add to `DataFeedType` in `types.ts` +2. Add visibility rules in `isFeedVisible()` (`feedVisibility.ts`) +3. Add chain tag mapping in `chainFilters.ts` if using tags +4. Wire filters in `FeedList.tsx` if needed + +**Change which feeds appear on a page** +→ Edit `isFeedVisible()` only. Avoid duplicating logic in components. + +**Hide a feed address (show contact email)** +→ `shouldHideAddress()` in `feedVisibility.ts` + +**Risk category icons** +→ Supabase batch lookup via `useBatchedFeedCategories` + `enrichFeedWithCategory()` in `feedMetadata.ts` + +## Known tech debt + +- `chains.ts` tags duplicate what `isFeedVisible()` already knows from RDD metadata +- `FeedList.tsx` is still large (URL sync, stream-specific UI) — candidate for splitting into hooks +- `Tables.tsx` row components could move to `components/tableRows/` when touched next diff --git a/src/features/feeds/components/FeedList.module.css b/src/features/feeds/components/FeedList.module.css index 5ade30ead15..f49d0919330 100644 --- a/src/features/feeds/components/FeedList.module.css +++ b/src/features/feeds/components/FeedList.module.css @@ -54,10 +54,50 @@ div.shutDate > hr { } .tableFilters { + display: flex; + flex-direction: column; + align-items: stretch; + gap: var(--space-3x); +} + +.filterControls { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-4x); + min-width: 0; +} + +.filterControls:empty { + display: none; +} + +.tableSearch { + position: relative; + display: flex; + align-items: center; + gap: var(--space-3x); + width: 100%; + min-width: 0; + order: -1; +} + +.tableSearch .filterDropdown_searchInput { + border: 1px solid var(--gray-300); + border-radius: var(--border-radius-primary); + background-color: var(--white); + box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06); + font-size: 0.9375rem; +} + +.tableSearch .filterDropdown_searchInput:focus { + outline: none; + border-color: var(--blue-500); + box-shadow: 0 0 0 3px var(--blue-100); +} + +.tableSearch .clearFilterBtn { + margin-left: 0; } .streamNetworkSelector { @@ -137,12 +177,6 @@ div.shutDate > hr { opacity: 0.65; } -.searchAndCheckbox { - display: flex; - align-items: center; - gap: var(--space-4x); -} - .filterDropdown_details { display: inline-block; position: relative; @@ -153,9 +187,9 @@ div.shutDate > hr { .filterDropdown_search { position: relative; - max-width: 420px; display: flex; align-items: center; + width: 100%; } .filterDropdown_searchInput { @@ -172,18 +206,68 @@ div.shutDate > hr { .detailsLabel { margin: 0; - display: inline-block; + display: inline-flex; + align-items: center; + gap: var(--space-2x); cursor: pointer; + user-select: none; + color: var(--color-text-primary); + font-size: 0.875rem; + white-space: nowrap; +} + +.filterCheckboxGroup { + display: inline-flex; + align-items: center; + gap: var(--space-1x); + white-space: nowrap; +} + +.filterHelpLink { + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + border-radius: 50%; + border: 1px solid var(--gray-400); + color: var(--gray-500); + font-size: 11px; + font-weight: 600; + line-height: 1; + text-decoration: none; + flex-shrink: 0; +} + +.filterHelpLink:hover { + color: var(--gray-700); + border-color: var(--gray-500); +} + +.feedCheckbox { + appearance: auto; + -webkit-appearance: checkbox; + width: var(--space-4x); + height: var(--space-4x); + min-width: var(--space-4x); + flex-shrink: 0; + cursor: pointer; + accent-color: var(--color-fill-highlight); } .checkboxContainer { display: flex; - gap: 16px; + flex-wrap: nowrap; + gap: var(--space-4x); align-items: center; - margin: 8px 0; + margin: 0; } @media screen and (max-width: 450px) { + .filterControls, + .checkboxContainer { + flex-wrap: wrap; + } .filterDropdown_details, .filterDropdown_search { padding-left: 0px; @@ -194,13 +278,12 @@ div.shutDate > hr { gap: var(--space-2x); } - .tableFilters > * { + .filterControls { width: 100%; - max-width: 100%; } .clearFilterBtn { - margin-left: var(--space-2x); + margin-left: 0; } .filterDropdown_search { diff --git a/src/features/feeds/components/FeedList.tsx b/src/features/feeds/components/FeedList.tsx index 4b10af5e9c2..4e4b282f1dd 100644 --- a/src/features/feeds/components/FeedList.tsx +++ b/src/features/feeds/components/FeedList.tsx @@ -15,23 +15,29 @@ import button from "@chainlink/design-system/button.module.css" import { updateTableOfContents } from "~/components/TableOfContents/tocStore.ts" import { ChainSelector } from "~/components/ChainSelector/ChainSelector.tsx" import { chainHasVisibleFeeds, isFeedVisible, networkHasVisibleFeeds } from "../utils/feedVisibility.ts" +import { chainHasSvrFeeds } from "../utils/svrDetection.ts" +import { + filterChainsByFeedTypeTag, + networkMatchesFeedTypeTag, + shouldFilterSelectableChainsByVisibleFeeds, + shouldRenderNetworkSection, +} from "../utils/chainFilters.ts" +import { getSchemaVersion } from "../utils/feedMetadata.ts" +import { + DEFAULT_FEED_CATEGORY_OPTIONS, + getAddrPerPage, + SMART_DATA_CATEGORY_OPTIONS, +} from "../constants.ts" +import { + type DataFeedType, + type SchemaFilterValue, + type StreamsRwaFeedTypeValue, + type TradingHoursFilterValue, + getFeedTypeFlags, +} from "../types.ts" import { updateUrlClean } from "./urlStateHelpers.ts" -export type DataFeedType = - | "default" - | "smartdata" - | "rates" - | "usGovernmentMacroeconomicData" - | "tokenizedEquity" - | "streamsCrypto" - | "streamsRwa" - | "streamsNav" - | "streamsExRate" - | "streamsBacked" - -type SchemaFilterValue = "all" | "v8" | "v11" -type StreamsRwaFeedTypeValue = "all" | "datalink" | "equities" | "forex" -type TradingHoursFilterValue = "all" | "regular" | "extended" | "overnight" +export type { DataFeedType } from "../types.ts" type FilterOption = { label: string @@ -82,20 +88,7 @@ type StreamFeedMetadata = ChainMetadata & { } } -const getStreamSchemaVersion = (feed: StreamFeedMetadata): string | undefined => { - if (feed.docs?.schema) return feed.docs.schema - - const clicProductName = feed.docs?.clicProductName - if (!clicProductName) return undefined - - const match = clicProductName.match(/-0(\d{2})$/) - if (!match) return undefined - - if (match[1] === "04" || match[1] === "08") return "v8" - if (match[1] === "11") return "v11" - - return undefined -} +const getStreamSchemaVersion = (feed: StreamFeedMetadata): string | undefined => getSchemaVersion(feed) const is24x5StreamFeed = (feed: StreamFeedMetadata): boolean => { const schemaVersion = getStreamSchemaVersion(feed) @@ -177,16 +170,9 @@ export const FeedList = ({ forceStreamCategoryFilter?: StreamsRwaFeedTypeValue tokenizedEquityProvider?: string }) => { - const isStreams = - dataFeedType === "streamsCrypto" || - dataFeedType === "streamsRwa" || - dataFeedType === "streamsNav" || - dataFeedType === "streamsExRate" || - dataFeedType === "streamsBacked" + const feedTypeFlags = getFeedTypeFlags(dataFeedType) + const { isStreams, isSmartData, isRates, isUSGovernmentMacroeconomicData } = feedTypeFlags const isDeprecating = ecosystem === "deprecating" - const isRates = dataFeedType === "rates" - const isSmartData = dataFeedType === "smartdata" - const isUSGovernmentMacroeconomicData = dataFeedType === "usGovernmentMacroeconomicData" const chains = isDeprecating && isStreams ? ALL_CHAINS : CHAINS // Get network from URL parameters or fall back to initialNetwork @@ -422,7 +408,7 @@ export const FeedList = ({ setCurrentPage(pageStr) updateUrlClean({ page: pageNumber === 1 ? undefined : pageNumber }) } - const addrPerPage = ecosystem === "deprecating" && isStreams ? 10 : ecosystem === "deprecating" ? 10000 : 8 + const addrPerPage = getAddrPerPage(ecosystem, isStreams) const currentPageNum = Number(currentPage) || 1 const lastAddr = currentPageNum * addrPerPage const firstAddr = lastAddr - addrPerPage @@ -434,20 +420,14 @@ export const FeedList = ({ setTestnetCurrentPage(pageStr) updateUrlClean({ testnetPage: pageNumber === 1 ? undefined : pageNumber }) } - const testnetAddrPerPage = ecosystem === "deprecating" && isStreams ? 10 : ecosystem === "deprecating" ? 10000 : 8 + const testnetAddrPerPage = getAddrPerPage(ecosystem, isStreams) const testnetPageNum = Number(testnetCurrentPage) || 1 const testnetLastAddr = testnetPageNum * testnetAddrPerPage const testnetFirstAddr = testnetLastAddr - testnetAddrPerPage // Dynamic feed categories loaded from Supabase - const [dataFeedCategory, setDataFeedCategory] = useState([ - { key: "low", name: "Low Market Risk" }, - { key: "medium", name: "Medium Market Risk" }, - { key: "high", name: "High Market Risk" }, - { key: "veryhigh", name: "Very High Market Risk" }, - { key: "custom", name: "Custom" }, - { key: "new", name: "New Token" }, - { key: "deprecating", name: "Deprecating" }, + const [dataFeedCategory, setDataFeedCategory] = useState<{ key: string; name: string }[]>([ + ...DEFAULT_FEED_CATEGORY_OPTIONS, ]) // Load dynamic categories from Supabase on component mount @@ -461,27 +441,14 @@ export const FeedList = ({ loadCategories() }, []) - const smartDataTypes = [ - { key: "Proof of Reserve", name: "Proof of Reserve" }, - { key: "NAVLink", name: "NAVLink" }, - { key: "SmartAUM", name: "SmartAUM" }, - { key: "Stablecoin Stability Assessment", name: "Stablecoin Stability Assessment" }, - ] + const smartDataTypes = [...SMART_DATA_CATEGORY_OPTIONS] const [streamsChain] = useState(initialNetwork) const activeChain = isStreams ? streamsChain : currentNetwork - // Filter chains by dataFeedType tag to get only chains that support this feed type - const filteredChainsByTag = useMemo(() => { - return chains.filter((chain) => { - if (dataFeedType.includes("streams")) return chain.tags?.includes("streams") ?? false - if (dataFeedType === "smartdata") return chain.tags?.includes("smartData") ?? false - if (dataFeedType === "rates") return chain.tags?.includes("rates") ?? false - if (dataFeedType === "usGovernmentMacroeconomicData") - return chain.tags?.includes("usGovernmentMacroeconomicData") ?? false - if (dataFeedType === "tokenizedEquity") return chain.tags?.includes("tokenizedEquity") ?? false - return chain.tags?.includes("default") ?? false - }) - }, [chains, dataFeedType]) + const filteredChainsByTag = useMemo( + () => filterChainsByFeedTypeTag(chains, dataFeedType), + [chains, dataFeedType] + ) const requestedChain = useMemo(() => { const requestedNetwork = @@ -500,28 +467,16 @@ export const FeedList = ({ const selectableChains = useMemo(() => { if (isStreams || !metadataCache) return filteredChainsByTag - const filterByVisibleFeeds = - isDeprecating || isSmartData || isRates || isUSGovernmentMacroeconomicData || dataFeedType === "tokenizedEquity" - - if (!filterByVisibleFeeds) return filteredChainsByTag + if (!shouldFilterSelectableChainsByVisibleFeeds(dataFeedType, ecosystem)) { + return filteredChainsByTag + } return filteredChainsByTag.filter((chain) => chainHasVisibleFeeds((metadataCache as Record)[chain.page], dataFeedType, ecosystem, { tokenizedEquityProvider, }) ) - }, [ - filteredChainsByTag, - metadataCache, - isDeprecating, - isStreams, - isSmartData, - isRates, - isUSGovernmentMacroeconomicData, - dataFeedType, - ecosystem, - tokenizedEquityProvider, - ]) + }, [filteredChainsByTag, metadataCache, isStreams, dataFeedType, ecosystem, tokenizedEquityProvider]) const availableChainsForSelection = selectableChains.length > 0 ? selectableChains : filteredChainsByTag @@ -547,6 +502,30 @@ export const FeedList = ({ ...chainMetadata, processedData: selectedChainProcessedData, } + + const chainHasSvr = useMemo(() => { + if (isStreams || isSmartData || isUSGovernmentMacroeconomicData) return false + if (!currentChainMetadata.processedData) return false + + return chainHasSvrFeeds(currentChainMetadata.processedData, dataFeedType, ecosystem, { + tokenizedEquityProvider, + }) + }, [ + currentChainMetadata.processedData, + isStreams, + isSmartData, + isUSGovernmentMacroeconomicData, + dataFeedType, + ecosystem, + tokenizedEquityProvider, + ]) + + useEffect(() => { + if (!chainHasSvr && showOnlySVR) { + setShowOnlySVR(false) + } + }, [chainHasSvr, showOnlySVR]) + const wrapperRef = useRef(null) // scroll handler @@ -766,14 +745,7 @@ export const FeedList = ({ // Filter networks by feed type const filteredNetworks = currentChainMetadata.processedData.networks - .filter((network) => { - if (isStreams) return network.tags?.includes("streams") - if (isSmartData) return network.tags?.includes("smartData") - if (isRates) return network.tags?.includes("rates") - if (isUSGovernmentMacroeconomicData) return network.tags?.includes("usGovernmentMacroeconomicData") - - return true - }) + .filter((network) => networkMatchesFeedTypeTag(network, dataFeedType)) .filter((network) => { // Ensure the network has at least one visible feed for the current dataFeedType const visibilityOptions = { @@ -1046,18 +1018,21 @@ export const FeedList = ({ {currentChainMetadata.error &&

There was an error loading the streams...

} -
- { - setSearchValue((event.target as HTMLInputElement).value) - setCurrentPage("1") - }} - /> -
+
+
+ { + setSearchValue((event.target as HTMLInputElement).value) + setCurrentPage("1") + }} + /> +
+
{filteredMainnetStreams.length > 0 ? ( <>
@@ -1100,18 +1075,21 @@ export const FeedList = ({ -
- { - setTestnetSearchValue((event.target as HTMLInputElement).value) - setTestnetCurrentPage("1") - }} - /> -
+
+
+ { + setTestnetSearchValue((event.target as HTMLInputElement).value) + setTestnetCurrentPage("1") + }} + /> +
+
{filteredTestnetStreams.length > 0 ? ( <>
@@ -1225,25 +1203,13 @@ export const FeedList = ({ idOverride={streamsMainnetSectionTitle.toLowerCase().replace(/\s+/g, "-")} >
-
- { - closeAllDropdowns() - setSearchValue((event.target as HTMLInputElement).value) - setCurrentPage("1") - }} - /> -
+
{dataFeedType === "streamsCrypto" && (
+
+ { + closeAllDropdowns() + setSearchValue((event.target as HTMLInputElement).value) + setCurrentPage("1") + }} + /> +
{currentChainMetadata.loading || !currentChainMetadata.processedData ? (

Loading...

@@ -1413,25 +1394,13 @@ export const FeedList = ({ idOverride={streamsTestnetSectionTitle.toLowerCase().replace(/\s+/g, "-")} >
-
- { - closeAllDropdowns() - setTestnetSearchValue((event.target as HTMLInputElement).value) - setTestnetCurrentPage("1") - }} - /> -
+
{dataFeedType === "streamsCrypto" && (
+
+ { + closeAllDropdowns() + setTestnetSearchValue((event.target as HTMLInputElement).value) + setTestnetCurrentPage("1") + }} + /> +
{currentChainMetadata.loading || !currentChainMetadata.processedData ? (

Loading...

@@ -1628,30 +1612,22 @@ export const FeedList = ({ {(() => { // Handle regular network processing return currentChainMetadata.processedData?.networks - ?.filter((network: { metadata: unknown[]; tags: string | string[]; networkType: string }) => { - if (isDeprecating) { - const foundDeprecated = networkHasVisibleFeeds(network, dataFeedType, ecosystem, { - tokenizedEquityProvider, - }) - if (foundDeprecated && network.networkType === selectedNetworkType) { - netCount++ - } - return foundDeprecated && network.networkType === selectedNetworkType - } - - if (isStreams) return network.tags?.includes("streams") && network.networkType === selectedNetworkType - - if (isSmartData) return network.tags?.includes("smartData") && network.networkType === selectedNetworkType - - if (isRates) return network.tags?.includes("rates") && network.networkType === selectedNetworkType + ?.filter((network: ChainNetwork) => { + const hasVisibleFeeds = networkHasVisibleFeeds(network, dataFeedType, ecosystem, { + tokenizedEquityProvider, + }) - if (isUSGovernmentMacroeconomicData) - return ( - network.tags?.includes("usGovernmentMacroeconomicData") && network.networkType === selectedNetworkType - ) + if (isDeprecating && hasVisibleFeeds && network.networkType === selectedNetworkType) { + netCount++ + } - // Filter by selected network type (mainnet/testnet) - return network.networkType === selectedNetworkType + return shouldRenderNetworkSection( + network, + dataFeedType, + selectedNetworkType, + isDeprecating, + hasVisibleFeeds + ) }) .map((network: ChainNetwork) => { return ( @@ -1719,125 +1695,134 @@ export const FeedList = ({ )}
- {!isStreams && !isSmartData && ( -
- setShowCategoriesDropdown((prev) => !prev)}> - Data Feed Categories - - -
- )} - {isSmartData && ( -
- setShowCategoriesDropdown((prev) => !prev)}> - SmartData Type - - -
- )} -
-
- { - closeAllDropdowns() - setSearchValue((event.target as HTMLInputElement).value) - setCurrentPage("1") - }} - /> - {searchValue && ( - - )} -
+
+ {!isStreams && !isSmartData && ( +
+ setShowCategoriesDropdown((prev) => !prev)}> + Data Feed Categories + + +
+ )} + {isSmartData && ( +
+ setShowCategoriesDropdown((prev) => !prev)}> + SmartData Type + + +
+ )} {!isStreams && ( )} -
-
{!isStreams && isSmartData && ( )} - {!isStreams && !isSmartData && !isUSGovernmentMacroeconomicData && ( - + {!isStreams && !isSmartData && !isUSGovernmentMacroeconomicData && chainHasSvr && ( + + + + ? + + )}
+
+ { + closeAllDropdowns() + setSearchValue((event.target as HTMLInputElement).value) + setCurrentPage("1") + }} + /> + {searchValue && ( + + )} +
- {isSmartData && ( -
- setShowCategoriesDropdown((prev) => !prev)}> - SmartData Type - - -
- )} -
-
- { - setTestnetSearchValue((event.target as HTMLInputElement).value) - setTestnetCurrentPage("1") - }} - /> - {testnetSearchValue && ( - - )} -
+
+ {isSmartData && ( +
+ setShowCategoriesDropdown((prev) => !prev)}> + SmartData Type + + +
+ )} -
-
{!isStreams && isSmartData && ( )}
+
+ { + setTestnetSearchValue((event.target as HTMLInputElement).value) + setTestnetCurrentPage("1") + }} + /> + {testnetSearchValue && ( + + )} +
)} {isStreams && (
-
+ { setTestnetSearchValue((event.target as HTMLInputElement).value) setTestnetCurrentPage("1") diff --git a/src/features/feeds/components/FeedPage.astro b/src/features/feeds/components/FeedPage.astro index 04ccfd26192..d05d5660900 100644 --- a/src/features/feeds/components/FeedPage.astro +++ b/src/features/feeds/components/FeedPage.astro @@ -19,13 +19,14 @@ export type Props = { defaultNetworkTableExpanded?: boolean } import { getServerSideChainMetadata } from "~/features/data/api/backend" -import { CHAINS } from "~/features/data/chains" +import { CHAINS, ALL_CHAINS } from "~/features/data/chains" import { CheckHeartbeat } from "./pause-notice/CheckHeartbeat" import { FeedDataItem, monitoredFeeds } from "~/features/data" const { initialNetwork, ecosystem, dataFeedType, allowNetworkTableExpansion, defaultNetworkTableExpanded } = Astro.props -const initialCache = await getServerSideChainMetadata(CHAINS) +const isDeprecating = ecosystem === "deprecating" +const initialCache = await getServerSideChainMetadata([...CHAINS, ...ALL_CHAINS], isDeprecating) const feedItems: FeedDataItem[] = monitoredFeeds.mainnet --- @@ -76,105 +77,99 @@ const feedItems: FeedDataItem[] = monitoredFeeds.mainnet prior to the date of deprecation.

+ ) : dataFeedType === "streamsCrypto" || + dataFeedType === "streamsRwa" || + dataFeedType === "streamsNav" || + dataFeedType === "streamsExRate" || + dataFeedType === "streamsBacked" ? ( + <> +

+ To learn how to use Data Streams, see the{" "} + Fetch and decode reports tutorial. +

+

+ LINK Token Contracts |{" "} + Supported networks & verifier proxies |{" "} + Retrieve stream IDs via API +

+ + + ) : dataFeedType === "smartdata" ? ( + <> + +

+ SmartData Feeds documentation |{" "} + LINK token addresses & faucets{" "} +

+

Before using feeds, review the risk considerations and disclaimer below.

+ + ) : dataFeedType === "rates" ? ( + <> +

+ Learn to use data feeds |{" "} + LINK token addresses & faucets{" "} +

+ + ) : dataFeedType === "usGovernmentMacroeconomicData" ? ( + <> + +

+ About these feeds{" "} + | Learn to use data feeds |{" "} + LINK token addresses & faucets{" "} +

+

+ Before using feeds in production, review the best practices below. +

+ ) : ( <> - {dataFeedType === "streamsCrypto" || - dataFeedType === "streamsRwa" || - dataFeedType === "streamsNav" || - dataFeedType === "streamsExRate" || - dataFeedType === "streamsBacked" ? ( - <> -

- To learn how to use Data Streams, see the{" "} - Fetch and decode reports tutorial. -

-

- For LINK token and Faucet details, see the{" "} - LINK Token Contracts page. -

- - - - ) : dataFeedType === "smartdata" ? ( - <> -

- To learn how to use these feeds, see the SmartData Feeds documentation. -

-

- For LINK token and Faucet details, see the{" "} - LINK Token Contracts page. -

- - - - ) : dataFeedType === "rates" ? ( - <> -

- To learn how to use these feeds, see the Using Data Feeds guide. -

-

- For LINK token and Faucet details, see the{" "} - LINK Token Contracts page. -

- - ) : dataFeedType === "usGovernmentMacroeconomicData" ? ( - <> -

- To learn more about the U.S. Government Macroeconomic data feeds, see the{" "} - blog article. -

-

- To learn how to use these feeds, see the Using Data Feeds guide. -

-

- For LINK token and Faucet details, see the{" "} - LINK Token Contracts page. -

- - - - - ) : ( - <> -

- To learn how to use these feeds, see the Using Data Feeds guide. -

-

- For LINK token and Faucet details, see the{" "} - LINK Token Contracts page. -

- - - - - )} + +

+ Learn to use data feeds |{" "} + LINK token addresses & faucets{" "} +

+

+ Before using feeds in production, review the best practices below. +

) } + + { dataFeedType === "smartdata" ? ( <> +