From efa7b651c70f405e03440d121523a2c812c3ca51 Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Wed, 6 Aug 2025 16:08:02 -0700 Subject: [PATCH 01/33] dynamic category sourcing: supabase --- .env.local.template | 6 + .gitignore | 1 + package-lock.json | 97 +++++ package.json | 1 + src/db/feedCategories.ts | 214 +++++++++++ src/db/feedEnhancement.ts | 50 +++ src/db/supabase.ts | 16 + src/features/feeds/components/FeedList.tsx | 21 +- src/features/feeds/components/Tables.tsx | 395 +++++++++++---------- src/pages/supabase-test.astro | 146 ++++++++ 10 files changed, 756 insertions(+), 191 deletions(-) create mode 100644 .env.local.template create mode 100644 src/db/feedCategories.ts create mode 100644 src/db/feedEnhancement.ts create mode 100644 src/db/supabase.ts create mode 100644 src/pages/supabase-test.astro diff --git a/.env.local.template b/.env.local.template new file mode 100644 index 00000000000..2bd0f285b63 --- /dev/null +++ b/.env.local.template @@ -0,0 +1,6 @@ +# Copy this file to .env.local and replace with your actual Supabase credentials +# Get these from your Supabase project dashboard: https://supabase.com/dashboard/project/{your-project}/settings/api +# Note: PUBLIC_ prefix is required for client-side access in Astro + +# PUBLIC_SUPABASE_URL=https://your-project-id.supabase.co +# PUBLIC_SUPABASE_KEY=your-supabase-anon-key diff --git a/.gitignore b/.gitignore index f373bba6d84..577732b5e09 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ server.log # environment variables .env +.env.local .env.production # macOS-specific files diff --git a/package-lock.json b/package-lock.json index 1d28077acd9..640ef240202 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "@nanostores/preact": "^0.5.2", "@nanostores/react": "^0.8.4", "@openzeppelin/contracts": "^4.9.6", + "@supabase/supabase-js": "^2.53.0", "astro": "^5.12.8", "bignumber.js": "^9.3.1", "clipboard": "^2.0.11", @@ -10932,6 +10933,81 @@ "antlr4ts": "^0.5.0-alpha.4" } }, + "node_modules/@supabase/auth-js": { + "version": "2.71.1", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.71.1.tgz", + "integrity": "sha512-mMIQHBRc+SKpZFRB2qtupuzulaUhFYupNyxqDj5Jp/LyPvcWvjaJzZzObv6URtL/O6lPxkanASnotGtNpS3H2Q==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.5.tgz", + "integrity": "sha512-v5GSqb9zbosquTo6gBwIiq7W9eQ7rE5QazsK/ezNiQXdCbY+bH8D9qEaBIkhVvX4ZRW5rP03gEfw5yw9tiq4EQ==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.19.4.tgz", + "integrity": "sha512-O4soKqKtZIW3olqmbXXbKugUtByD2jPa8kL2m2c1oozAO11uCcGrRhkZL0kVxjBLrXHE0mdSkFsMj7jDSfyNpw==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.11.15", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.11.15.tgz", + "integrity": "sha512-HQKRnwAqdVqJW/P9TjKVK+/ETpW4yQ8tyDPPtRMKOH4Uh3vQD74vmj353CYs8+YwVBKubeUOOEpI9CT8mT4obw==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.13", + "@types/phoenix": "^1.6.6", + "@types/ws": "^8.18.1", + "isows": "^1.0.7", + "ws": "^8.18.2" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.10.4", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.10.4.tgz", + "integrity": "sha512-cvL02GarJVFcNoWe36VBybQqTVRq6wQSOCvTS64C+eyuxOruFIm1utZAY0xi2qKtHJO3EjKaj8iWJKySusDmAQ==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.53.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.53.0.tgz", + "integrity": "sha512-Vg9sl0oFn55cCPaEOsDsRDbxOVccxRrK/cikjL1XbywHEOfyA5SOOEypidMvQLwgoAfnC2S4D9BQwJDcZs7/TQ==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.71.1", + "@supabase/functions-js": "2.4.5", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "1.19.4", + "@supabase/realtime-js": "2.11.15", + "@supabase/storage-js": "^2.10.4" + } + }, "node_modules/@swagger-api/apidom-ast": { "version": "1.0.0-beta.42", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.0.0-beta.42.tgz", @@ -12005,6 +12081,12 @@ "@types/node": "*" } }, + "node_modules/@types/phoenix": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", + "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==", + "license": "MIT" + }, "node_modules/@types/prettier": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", @@ -22018,6 +22100,21 @@ "ws": "*" } }, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", diff --git a/package.json b/package.json index 647b80ad8ab..c44e002b0d2 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "@nanostores/preact": "^0.5.2", "@nanostores/react": "^0.8.4", "@openzeppelin/contracts": "^4.9.6", + "@supabase/supabase-js": "^2.53.0", "astro": "^5.12.8", "bignumber.js": "^9.3.1", "clipboard": "^2.0.11", diff --git a/src/db/feedCategories.ts b/src/db/feedCategories.ts new file mode 100644 index 00000000000..0757e13fe6b --- /dev/null +++ b/src/db/feedCategories.ts @@ -0,0 +1,214 @@ +import { supabase } from "./supabase.js" + +// Centralized category configuration +export const FEED_CATEGORY_CONFIG = { + low: { + key: "low", + name: "Low Market Risk", + icon: "🟢", + title: "Low Market Risk - Feeds that deliver a market price for liquid assets with robust market structure.", + link: "/data-feeds/selecting-data-feeds#-low-market-risk-feeds", + }, + medium: { + key: "medium", + name: "Medium Market Risk", + icon: "🟡", + title: + "Medium Market Risk - Feeds that deliver a market price for assets that show signs of liquidity-related risk or other market structure-related risk.", + link: "/data-feeds/selecting-data-feeds#-medium-market-risk-feeds", + }, + high: { + key: "high", + name: "High Market Risk", + icon: "🔴", + title: + "High Market Risk - Feeds that deliver a heightened degree of some of the risk factors associated with Medium Market Risk Feeds, or a separate risk that makes the market price subject to uncertainty or volatile. In using a high market risk data feed you acknowledge that you understand the risks associated with such a feed and that you are solely responsible for monitoring and mitigating such risks.", + link: "/data-feeds/selecting-data-feeds#-high-market-risk-feeds", + }, + new: { + key: "new", + name: "New Token", + icon: "🟠", + title: + "New Token - Tokens without the historical data required to implement a risk assessment framework may be launched in this category. Users must understand the additional market and volatility risks inherent with such assets. Users of New Token Feeds are responsible for independently verifying the liquidity and stability of the assets priced by feeds that they use.", + link: "/data-feeds/selecting-data-feeds#-new-token-feeds", + }, + custom: { + key: "custom", + name: "Custom", + icon: "🔵", + title: + "Custom - Feeds built to serve a specific use case or rely on external contracts or data sources. These might not be suitable for general use or your use case's risk parameters. Users must evaluate the properties of a feed to make sure it aligns with their intended use case.", + link: "/data-feeds/selecting-data-feeds#-custom-feeds", + }, + deprecating: { + key: "deprecating", + name: "Deprecating", + icon: "⭕", + title: + "Deprecating - These feeds are scheduled for deprecation. See the [Deprecation](/data-feeds/deprecating-feeds) page to learn more.", + link: "/data-feeds/deprecating-feeds", + }, +} as const + +export const getDefaultCategories = () => Object.values(FEED_CATEGORY_CONFIG) + +export async function getFeedRiskData(proxyAddress?: string, network?: string) { + try { + if (!supabase) { + console.warn("Supabase client not available") + return null + } + + let query = supabase.from("docs_feeds_risk").select("*") + + if (proxyAddress) { + query = query.eq("proxy_address", proxyAddress) + } + + if (network) { + query = query.eq("network", network) + } + + const { data, error } = await query + + if (error) { + console.warn("Error fetching feed risk data:", error.message) + return null + } + + return data + } catch (e) { + console.error("Error fetching feed risk data:", e) + return null + } +} + +export async function getFeedRiskTier(proxyAddress: string, network: string, fallbackCategory?: string) { + try { + if (!supabase) { + console.warn("Supabase client not available, using fallback") + return fallbackCategory || null + } + + const { data, error } = await supabase + .from("docs_feeds_risk") + .select("*") + .eq("proxy_address", proxyAddress) + .eq("network", network) + .single() + + if (error) { + console.warn("Error fetching feed risk tier:", error.message) + return fallbackCategory || null + } + + return data?.risk_status || fallbackCategory || null + } catch (e) { + console.error("Error fetching feed risk tier:", e) + return fallbackCategory || null + } +} + +export async function getFeedCategories() { + try { + if (!supabase) { + console.warn("Supabase client not available, using fallback categories") + return Object.values(FEED_CATEGORY_CONFIG).map((config) => ({ + key: config.key, + name: config.name, + })) + } + + const { data, error } = await supabase + .from("docs_feeds_risk") + .select("risk_status") + .not("risk_status", "is", null) + .neq("risk_status", "hidden") + + if (error) { + console.warn("Error fetching feed categories:", error.message) + return Object.values(FEED_CATEGORY_CONFIG).map((config) => ({ + key: config.key, + name: config.name, + })) + } + + const uniqueStatuses = Array.from(new Set(data.map((item) => item.risk_status).filter(Boolean))).filter( + (status) => status.toLowerCase() !== "hidden" + ) + + const dynamicCategories = uniqueStatuses.map((status) => { + const config = FEED_CATEGORY_CONFIG[status.toLowerCase()] + return { + key: status.toLowerCase(), + name: config?.name || status, + } + }) + + const defaultCategories = Object.values(FEED_CATEGORY_CONFIG).map((config) => ({ + key: config.key, + name: config.name, + })) + + const allCategories = [...defaultCategories] + dynamicCategories.forEach((dynCat) => { + if (!allCategories.find((cat) => cat.key === dynCat.key)) { + allCategories.push(dynCat) + } + }) + + return allCategories + } catch (e) { + console.error("Error fetching feed categories:", e) + return Object.values(FEED_CATEGORY_CONFIG).map((config) => ({ + key: config.key, + name: config.name, + })) + } +} + +export async function getFeedRiskTierWithFallback( + proxyAddress: string, + network: string, + fallbackCategory?: string +): Promise { + try { + if (typeof window === "undefined") { + const riskTier = await getFeedRiskTier(proxyAddress, network, fallbackCategory) + return riskTier || fallbackCategory + } else { + console.log("Client-side context: using fallback category instead of Supabase") + return fallbackCategory + } + } catch (error) { + console.warn("Failed to fetch risk tier from Supabase, using fallback:", error) + return fallbackCategory + } +} + +export async function testSupabaseConnection() { + try { + if (!supabase) { + return { + success: false, + data: null, + error: "Supabase client not available", + } + } + + const { data, error } = await supabase.from("docs_feeds_risk").select("*").limit(1) + + return { + success: !error || error.code === "PGRST116", + data, + error: error?.message, + } + } catch (e) { + return { + success: false, + data: null, + error: e instanceof Error ? e.message : "Unknown error", + } + } +} diff --git a/src/db/feedEnhancement.ts b/src/db/feedEnhancement.ts new file mode 100644 index 00000000000..09b92f37b1e --- /dev/null +++ b/src/db/feedEnhancement.ts @@ -0,0 +1,50 @@ +import { getFeedRiskTier } from "./feedCategories.js" + +interface FeedData { + name: string + contractAddress?: string + proxyAddress?: string + feedCategory?: string + [key: string]: unknown +} + +/** + * Enhanced feed processing function for Supabase risk tier data + */ +export async function enhanceFeedWithRiskTier(feed: FeedData, network: string): Promise { + try { + const feedRiskTier = await getFeedRiskTier( + feed.contractAddress || feed.proxyAddress || "", + network, + feed.feedCategory + ) + + // Return the enhanced feed object with risk tier from Supabase + const feedWithRiskTier = { + ...feed, + feedCategory: feedRiskTier, + } + + return feedWithRiskTier + } catch (error) { + console.warn(`Failed to enhance feed ${feed.name}:`, error) + // Return original feed if lookup fails + return feed + } +} + +/** + * Process multiple feeds with Supabase enhancement + */ +export async function enhanceFeedsWithRiskTiers(feeds: FeedData[], network: string): Promise { + const enhancedFeeds = await Promise.allSettled(feeds.map((feed) => enhanceFeedWithRiskTier(feed, network))) + + return enhancedFeeds.map((result, index) => { + if (result.status === "fulfilled") { + return result.value + } else { + console.warn(`Failed to enhance feed at index ${index}:`, result.reason) + return feeds[index] // return original feed if enhancement fails + } + }) +} diff --git a/src/db/supabase.ts b/src/db/supabase.ts new file mode 100644 index 00000000000..2510c5e8fdb --- /dev/null +++ b/src/db/supabase.ts @@ -0,0 +1,16 @@ +import { createClient } from "@supabase/supabase-js" + +const supabaseUrl = import.meta.env.PUBLIC_SUPABASE_URL +const supabaseKey = import.meta.env.PUBLIC_SUPABASE_KEY + +// Export a function that safely creates the client +export function getSupabaseClient() { + if (!supabaseUrl || !supabaseKey) { + console.warn("Supabase environment variables not found. Supabase features will be disabled.") + return null + } + return createClient(supabaseUrl, supabaseKey) +} + +// Export the client instance (may be null) +export const supabase = getSupabaseClient() diff --git a/src/features/feeds/components/FeedList.tsx b/src/features/feeds/components/FeedList.tsx index 863ce9d1556..57399dfeca9 100644 --- a/src/features/feeds/components/FeedList.tsx +++ b/src/features/feeds/components/FeedList.tsx @@ -9,6 +9,7 @@ import { useGetChainMetadata } from "./useGetChainMetadata.ts" import { ChainMetadata } from "~/features/data/api/index.ts" import useQueryString from "~/hooks/useQueryString.ts" import { RefObject } from "preact" +import { getFeedCategories } from "../../../db/feedCategories.js" import SectionWrapper from "~/components/SectionWrapper/SectionWrapper.tsx" import button from "@chainlink/design-system/button.module.css" import { updateTableOfContents } from "~/components/TableOfContents/tocStore.ts" @@ -123,14 +124,28 @@ export const FeedList = ({ const testnetLastAddr = Number(testnetCurrentPage) * testnetAddrPerPage const testnetFirstAddr = testnetLastAddr - testnetAddrPerPage - const dataFeedCategory = [ + // 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: "custom", name: "Custom" }, { key: "new", name: "New Token" }, - { key: "deprecating", name: "Deprecating" }, - ] + ]) + + // Load dynamic categories from Supabase on component mount + useEffect(() => { + const loadCategories = async () => { + try { + const categories = await getFeedCategories() + setDataFeedCategory(categories) + } catch (error) { + console.warn("Failed to load categories from Supabase:", error) + } + } + + loadCategories() + }, []) const smartDataTypes = [ { key: "Proof of Reserve", name: "Proof of Reserve" }, { key: "NAVLink", name: "NAVLink" }, diff --git a/src/features/feeds/components/Tables.tsx b/src/features/feeds/components/Tables.tsx index 6c31fd090d5..3668994f944 100644 --- a/src/features/feeds/components/Tables.tsx +++ b/src/features/feeds/components/Tables.tsx @@ -1,5 +1,5 @@ /** @jsxImportSource preact */ -import { useState } from "preact/hooks" +import { useState, useEffect } from "preact/hooks" import { Fragment } from "preact" import feedList from "./FeedList.module.css" import { clsx } from "~/lib/clsx/clsx.ts" @@ -9,74 +9,37 @@ 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 { type Docs } from "~/features/data/api/index.ts" +import { FEED_CATEGORY_CONFIG, getFeedRiskTierWithFallback } from "../../../db/feedCategories.js" const feedItems = monitoredFeeds.mainnet -const feedCategories = { - low: ( - - - 🟢 - - - ), - medium: ( - - - 🟡 - - - ), - high: ( - - - 🔴 - - - ), - new: ( - - - 🟠 - - - ), - custom: ( - - - 🔵 - - - ), - deprecating: ( - - - ⭕ + +// Centralized function to get feed category element using the shared config +const getFeedCategoryElement = (riskTier: string | undefined) => { + if (!riskTier) return "" + + const lowerTier = riskTier.toLowerCase() + const category = FEED_CATEGORY_CONFIG[lowerTier] + + if (!category) return "" + + return ( + + + {category.icon} - ), + ) +} + +// ✅ Dynamic Supabase feed categories implementation +// Feed categories are sourced from Supabase with graceful fallback +const feedCategories = { + low: getFeedCategoryElement("low"), + medium: getFeedCategoryElement("medium"), + high: getFeedCategoryElement("high"), + new: getFeedCategoryElement("new"), + custom: getFeedCategoryElement("custom"), + deprecating: getFeedCategoryElement("deprecating"), } const Pagination = ({ addrPerPage, totalAddr, paginate, currentPage, firstAddr, lastAddr }) => { @@ -199,124 +162,92 @@ const DefaultTHead = ({ showExtraDetails, networkName }: { showExtraDetails: boo ) } -const DefaultTr = ({ network, metadata, showExtraDetails }) => ( - - -
-
- {feedCategories[metadata.feedCategory?.toLowerCase()] || ""} - {metadata.name} -
- {metadata.secondaryProxyAddress && ( - - )} -
- {metadata.docs.shutdownDate && ( -
-
- Deprecating: -
- {metadata.docs.shutdownDate} -
- )} - - {metadata.threshold ? metadata.threshold + "%" : "N/A"} - {metadata.heartbeat ? metadata.heartbeat + "s" : "N/A"} - {metadata.decimals ? metadata.decimals : "N/A"} - -
-
-
- {metadata.secondaryProxyAddress && ( -
- Standard Proxy: -
- )} -
-
- - - {metadata.proxyAddress ?? metadata.transmissionsAccount} - -
-
+const DefaultTr = ({ network, metadata, showExtraDetails }) => { + // Enhanced feed category logic with async Supabase lookup + const [dynamicFeedCategory, setDynamicFeedCategory] = useState(metadata.feedCategory) + + // Effect to fetch risk tier from Supabase on component mount + useEffect(() => { + const fetchRiskTier = async () => { + if (metadata.proxyAddress && network?.referenceDataDirectorySchema) { + try { + const supabaseRiskTier = await getFeedRiskTierWithFallback( + metadata.proxyAddress, + network.referenceDataDirectorySchema, + metadata.feedCategory + ) + + setDynamicFeedCategory(supabaseRiskTier || metadata.feedCategory) + } catch (error) { + console.warn("Failed to fetch risk tier for", metadata.name, error) + setDynamicFeedCategory(metadata.feedCategory) + } + } + } + + fetchRiskTier() + }, [metadata.proxyAddress, metadata.feedCategory, network?.referenceDataDirectorySchema, metadata.name]) + + const getFinalFeedCategory = () => { + // Use dynamically fetched category + const categoryKey = dynamicFeedCategory?.toLowerCase() + return (categoryKey && feedCategories[categoryKey]) || getFeedCategoryElement(dynamicFeedCategory) || "" + } + + return ( + + +
+
+ {getFinalFeedCategory()} + {metadata.name}
- {metadata.assetName && ( -
-
- Asset name: -
-
{metadata.assetName}
-
- )} - {metadata.feedType && ( -
-
- Asset type: -
-
- {metadata.feedType} - {metadata.docs.assetSubClass === "UK" ? " - " + metadata.docs.assetSubClass : ""} -
+ {metadata.secondaryProxyAddress && ( + )} - {metadata.docs.marketHours && ( +
+ {metadata.docs.shutdownDate && ( +
+
+ Deprecating: +
+ {metadata.docs.shutdownDate} +
+ )} + + {metadata.threshold ? metadata.threshold + "%" : "N/A"} + {metadata.heartbeat ? metadata.heartbeat + "s" : "N/A"} + {metadata.decimals ? metadata.decimals : "N/A"} + +
+
-
- Market hours: -
-
- - {metadata.docs.marketHours} - -
-
- )} - {metadata.secondaryProxyAddress && ( - <> -
-
+ {metadata.secondaryProxyAddress && (
- AAVE SVR Proxy: + Standard Proxy:
-
+ )} +
+
- {metadata.secondaryProxyAddress} + {metadata.proxyAddress ?? metadata.transmissionsAccount} +
+
+
+ {metadata.assetName && ( +
+
+ Asset name: +
+
{metadata.assetName}
+
+ )} + {metadata.feedType && ( +
+
+ Asset type: +
+
+ {metadata.feedType} + {metadata.docs.assetSubClass === "UK" ? " - " + metadata.docs.assetSubClass : ""}
-
- ⚠️ Aave Dedicated Feed: This SVR proxy feed is dedicated exclusively for use by the - Aave protocol. Learn more about{" "} - - SVR-enabled Feeds - - . + )} + {metadata.docs.marketHours && ( +
+
+ Market hours: +
+
+ + {metadata.docs.marketHours} + +
- - )} -
-
- - -) + )} + {metadata.secondaryProxyAddress && ( + <> +
+
+
+ AAVE SVR Proxy: +
+
+ + + {metadata.secondaryProxyAddress} + +
+
+
+ ⚠️ Aave Dedicated Feed: This SVR proxy feed is dedicated exclusively for use by the + Aave protocol. Learn more about{" "} + + SVR-enabled Feeds + + . +
+ + )} +
+
+ + + ) +} const SmartDataTHead = ({ showExtraDetails }: { showExtraDetails: boolean }) => ( @@ -367,6 +363,29 @@ const SmartDataTr = ({ network, metadata, showExtraDetails }) => { // Only show MVR badge if explicitly flagged as MVR const finalIsMVRFeed = isMVRFlagSet && hasDecoding + // Dynamic feed category lookup for SmartData feeds + const [dynamicFeedCategory, setDynamicFeedCategory] = useState(metadata.feedCategory) + + useEffect(() => { + const fetchRiskTier = async () => { + if (metadata.proxyAddress && network?.referenceDataDirectorySchema) { + try { + const supabaseRiskTier = await getFeedRiskTierWithFallback( + metadata.proxyAddress, + network.referenceDataDirectorySchema, + metadata.feedCategory + ) + setDynamicFeedCategory(supabaseRiskTier || metadata.feedCategory) + } catch (error) { + console.warn("Failed to fetch risk tier for SmartData feed", metadata.name, error) + setDynamicFeedCategory(metadata.feedCategory) + } + } + } + + fetchRiskTier() + }, [metadata.proxyAddress, metadata.feedCategory, network?.referenceDataDirectorySchema, metadata.name]) + return ( @@ -386,7 +405,7 @@ const SmartDataTr = ({ network, metadata, showExtraDetails }) => { return "" })}
- {feedCategories[metadata.feedCategory?.toLowerCase()] || ""} {metadata.name} + {getFeedCategoryElement(dynamicFeedCategory) || ""} {metadata.name}
{metadata.docs.shutdownDate && (
@@ -1095,7 +1114,7 @@ export const TestnetTable = ({ lastAddr = 1000, addrPerPage = 8, currentPage = 1, - paginate = (_page: number) => { + paginate = () => { /* Default no-op function */ }, searchValue = "", diff --git a/src/pages/supabase-test.astro b/src/pages/supabase-test.astro new file mode 100644 index 00000000000..c9ecf7025a2 --- /dev/null +++ b/src/pages/supabase-test.astro @@ -0,0 +1,146 @@ +--- +import { supabase } from "../db/supabase" +import { getFeedCategories, testSupabaseConnection } from "../db/feedCategories" + +let connectionStatus = "Testing connection..." +let data: any = null +let error: string | null = null +let feedCategoriesData: any = null + +try { + // Test the connection by trying to fetch from the actual docs_feeds_risk table + // This will fail gracefully if the table doesn't exist or if credentials are wrong + if (!supabase) { + connectionStatus = "Connection failed" + error = "Supabase client is not initialized." + } else { + const { data: testData, error: testError } = await supabase.from("docs_feeds_risk").select("*").limit(5) + + if (testError) { + // If it's a table not found error, that's actually good - it means we connected + if ( + testError.code === "PGRST116" || + testError.message.includes("relation") || + testError.message.includes("does not exist") + ) { + connectionStatus = 'Connection successful! (Table "docs_feeds_risk" does not exist, but connection is working)' + } else { + connectionStatus = "Connection error" + error = testError.message + } + } else { + connectionStatus = "Connection successful!" + data = testData + + // If we successfully got data, also try to get feed categories + try { + feedCategoriesData = await getFeedCategories() + } catch (e) { + console.warn("Could not fetch feed categories:", e) + } + } + } +} catch (e) { + connectionStatus = "Connection failed" + error = e.message +} +--- + + + + + + Supabase Connection Test + + + +

Supabase Connection Test

+ +
+

Status: {connectionStatus}

+
+ + { + error && ( +
+

Error Details:

+
{error}
+
+ ) + } + + { + data && ( +
+

Sample docs_feeds_risk Data (first 5 records):

+
{JSON.stringify(data, null, 2)}
+
+ ) + } + + { + feedCategoriesData && ( +
+

Feed Categories Analysis:

+
{JSON.stringify(feedCategoriesData, null, 2)}
+
+ ) + } + +
+

Environment Variables Check:

+
    +
  • PUBLIC_SUPABASE_URL: {import.meta.env.PUBLIC_SUPABASE_URL ? "✓ Set" : "✗ Not set"}
  • +
  • PUBLIC_SUPABASE_KEY: {import.meta.env.PUBLIC_SUPABASE_KEY ? "✓ Set" : "✗ Not set"}
  • +
+
+ +
+

Next Steps:

+
    +
  1. If you haven't already, create a .env.local file based on .env.local.template
  2. +
  3. Add your actual Supabase URL and anon key to the .env.local file
  4. +
  5. Make sure your Supabase project has the docs_feeds_risk table
  6. +
  7. Restart the dev server after adding environment variables
  8. +
  9. + If successful, you should see sample data from the docs_feeds_risk table and risk category analysis +
  10. +
+ +

Expected table structure for docs_feeds_risk:

+
    +
  • proxy_address - Feed contract address
  • +
  • network - Network identifier
  • +
  • risk_status - Risk category (low, medium, high, etc.)
  • +
+
+ + From 8b7fc4264f66c36ec715ce721aa07a428d501b20 Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Thu, 7 Aug 2025 15:45:26 -0700 Subject: [PATCH 02/33] fix + tests --- .env.local.template | 11 + src/db/feedCategories.ts | 255 ++++++++++++++++--- src/features/feeds/components/Tables.tsx | 308 ++++++++++++++++++++--- 3 files changed, 511 insertions(+), 63 deletions(-) diff --git a/.env.local.template b/.env.local.template index 2bd0f285b63..7a607d0cfa9 100644 --- a/.env.local.template +++ b/.env.local.template @@ -4,3 +4,14 @@ # PUBLIC_SUPABASE_URL=https://your-project-id.supabase.co # PUBLIC_SUPABASE_KEY=your-supabase-anon-key + +# DEVELOPER TEST MODE: +# To test Supabase integration and see category comparisons, open your browser console and run: +# window.CHAINLINK_DEV_MODE = true +# +# This will: +# 1. Show a warning banner above data feed tables +# 2. Display category comparisons like: 🔴 → 🔵 (original → supabase) +# 3. Enable enhanced console logging +# +# To disable: window.CHAINLINK_DEV_MODE = false diff --git a/src/db/feedCategories.ts b/src/db/feedCategories.ts index 0757e13fe6b..547e394d406 100644 --- a/src/db/feedCategories.ts +++ b/src/db/feedCategories.ts @@ -1,5 +1,26 @@ import { supabase } from "./supabase.js" +// Development debug function +function isDevelopmentDebug(): boolean { + return ( + typeof window !== "undefined" && (window as unknown as { CHAINLINK_DEV_MODE?: boolean }).CHAINLINK_DEV_MODE === true + ) +} + +// Development flag getter +function getDevTestFlag(): boolean { + return ( + typeof window !== "undefined" && (window as unknown as { CHAINLINK_DEV_MODE?: boolean }).CHAINLINK_DEV_MODE === true + ) +} + +// Type for the docs_feeds_risk table +type FeedRiskData = { + proxy_address: string + network: string + risk_status: string +} + // Centralized category configuration export const FEED_CATEGORY_CONFIG = { low: { @@ -53,59 +74,94 @@ export const FEED_CATEGORY_CONFIG = { export const getDefaultCategories = () => Object.values(FEED_CATEGORY_CONFIG) -export async function getFeedRiskData(proxyAddress?: string, network?: string) { - try { - if (!supabase) { - console.warn("Supabase client not available") - return null - } +export async function getFeedRiskData(network?: string): Promise { + if (!supabase) { + console.warn("getFeedRiskData: Supabase client not available") + return [] + } + try { let query = supabase.from("docs_feeds_risk").select("*") - if (proxyAddress) { - query = query.eq("proxy_address", proxyAddress) - } - if (network) { query = query.eq("network", network) + + if (isDevelopmentDebug()) { + console.log(`[DEBUG] getFeedRiskData: Using network: "${network}"`) + } } - const { data, error } = await query + const { data, error } = await query.limit(1000) // Use reasonable limit instead of .single() if (error) { - console.warn("Error fetching feed risk data:", error.message) - return null + console.error("Supabase query error:", error) + return [] } - return data - } catch (e) { - console.error("Error fetching feed risk data:", e) - return null + if (isDevelopmentDebug()) { + console.log(`[DEBUG] getFeedRiskData: Loaded ${data?.length || 0} records for network ${network}`) + } + + return data || [] + } catch (error) { + console.error("Error in getFeedRiskData:", error) + return [] } } -export async function getFeedRiskTier(proxyAddress: string, network: string, fallbackCategory?: string) { +export async function getFeedRiskTier(contractAddress: string, network: string, fallbackCategory?: string) { try { + // Always fetch from Supabase if available, even in non-dev mode if (!supabase) { console.warn("Supabase client not available, using fallback") return fallbackCategory || null } + // Map the network identifier to match the database schema const { data, error } = await supabase .from("docs_feeds_risk") .select("*") - .eq("proxy_address", proxyAddress) + .eq("proxy_address", contractAddress) .eq("network", network) - .single() + .limit(1) if (error) { console.warn("Error fetching feed risk tier:", error.message) return fallbackCategory || null } - return data?.risk_status || fallbackCategory || null - } catch (e) { - console.error("Error fetching feed risk tier:", e) + // Handle empty results - no matching records found + if (!data || data.length === 0) { + const devMode = getDevTestFlag() + if (devMode) { + console.log("🔬 DEV MODE: No Supabase data found for:", { + contractAddress, + network, + fallback: fallbackCategory, + }) + } + return fallbackCategory || null + } + + const supabaseRiskTier = data[0]?.risk_status || null + const finalCategory = supabaseRiskTier || fallbackCategory || null + + // Enhanced logging when dev mode is enabled + const devMode = getDevTestFlag() + if (devMode && (supabaseRiskTier || fallbackCategory)) { + console.log("🔬 DEV MODE: Feed category comparison:", { + contractAddress, + network, + original: fallbackCategory, + supabase: supabaseRiskTier, + final: finalCategory, + changed: supabaseRiskTier && supabaseRiskTier !== fallbackCategory, + }) + } + + return finalCategory + } catch (error) { + console.error("Error in getFeedRiskTier:", error) return fallbackCategory || null } } @@ -135,13 +191,13 @@ export async function getFeedCategories() { } const uniqueStatuses = Array.from(new Set(data.map((item) => item.risk_status).filter(Boolean))).filter( - (status) => status.toLowerCase() !== "hidden" + (status: string) => status.toLowerCase() !== "hidden" ) - const dynamicCategories = uniqueStatuses.map((status) => { - const config = FEED_CATEGORY_CONFIG[status.toLowerCase()] + const dynamicCategories = uniqueStatuses.map((status: string) => { + const config = FEED_CATEGORY_CONFIG[status.toLowerCase() as keyof typeof FEED_CATEGORY_CONFIG] return { - key: status.toLowerCase(), + key: status.toLowerCase() as keyof typeof FEED_CATEGORY_CONFIG, name: config?.name || status, } }) @@ -154,7 +210,7 @@ export async function getFeedCategories() { const allCategories = [...defaultCategories] dynamicCategories.forEach((dynCat) => { if (!allCategories.find((cat) => cat.key === dynCat.key)) { - allCategories.push(dynCat) + allCategories.push(dynCat as (typeof allCategories)[0]) } }) @@ -168,14 +224,153 @@ export async function getFeedCategories() { } } +// Enhanced function for dev mode that returns comparison data +export async function getFeedRiskTierWithComparison( + contractAddress: string, + network: string, + fallbackCategory?: string +): Promise<{ + final: string | null + original: string | null + supabase: string | null + changed: boolean + devMode: boolean +}> { + const devMode = getDevTestFlag() + const original = fallbackCategory || null + + // COMPREHENSIVE DEBUG LOGGING FOR ALL CALLS + if (devMode) { + console.group(`🔍 getFeedRiskTierWithComparison DEBUG - ${contractAddress}`) + console.log("📥 Input Parameters:", { + contractAddress, + network, + fallbackCategory, + }) + console.log("🏁 Original Value:", original) + console.log("🎯 Dev Mode Active:", devMode) + console.log("🔍 Network identifier analysis:", { + network, + networkType: typeof network, + networkLength: network?.length, + }) + } + + try { + if (!supabase) { + const result = { + final: original, + original, + supabase: null, + changed: false, + devMode, + } + if (devMode) { + console.log("❌ No Supabase client, returning:", result) + console.groupEnd() + } + return result + } + + if (devMode) { + console.log("🔍 Querying Supabase with:", { + contractAddress, + network, + }) + } + + const { data, error } = await supabase + .from("docs_feeds_risk") + .select("*") + .eq("proxy_address", contractAddress) + .eq("network", network) + .limit(1) + + if (devMode) { + console.log("📡 Supabase Query Result:", { data, error }) + } + + if (error) { + const result = { + final: original, + original, + supabase: null, + changed: false, + devMode, + } + if (devMode) { + console.log("❌ Supabase error, returning:", result) + console.groupEnd() + } + return result + } + + // Handle empty results - no matching records found + if (!data || data.length === 0) { + const result = { + final: original, + original, + supabase: null, + changed: false, + devMode, + } + if (devMode) { + console.log("📝 No Supabase data found, returning:", result) + console.groupEnd() + } + return result + } + + const supabaseRiskTier = data[0]?.risk_status || null + const final = supabaseRiskTier || original + const changed = supabaseRiskTier !== null && supabaseRiskTier !== original + + const result = { + final, + original, + supabase: supabaseRiskTier, + changed, + devMode, + } + + if (devMode) { + console.log("🎯 Final Comparison Result:", result) + console.log("🔄 Change Detection:", { + supabaseRiskTier, + original, + isNotNull: supabaseRiskTier !== null, + isDifferent: supabaseRiskTier !== original, + changed, + }) + console.groupEnd() + } + + return result + } catch (error) { + console.error("Error in getFeedRiskTierWithComparison:", error) + const result = { + final: original, + original, + supabase: null, + changed: false, + devMode, + } + if (devMode) { + console.log("❌ Exception occurred, returning:", result) + console.groupEnd() + } + return result + } +} + export async function getFeedRiskTierWithFallback( - proxyAddress: string, + contractAddress: string, network: string, fallbackCategory?: string ): Promise { try { if (typeof window === "undefined") { - const riskTier = await getFeedRiskTier(proxyAddress, network, fallbackCategory) + const riskTier = await getFeedRiskTier(contractAddress, network, fallbackCategory) return riskTier || fallbackCategory } else { console.log("Client-side context: using fallback category instead of Supabase") diff --git a/src/features/feeds/components/Tables.tsx b/src/features/feeds/components/Tables.tsx index 3668994f944..3e20ab7cfde 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, getFeedRiskTierWithFallback } from "../../../db/feedCategories.js" +import { FEED_CATEGORY_CONFIG, getFeedRiskTierWithComparison } from "../../../db/feedCategories.js" const feedItems = monitoredFeeds.mainnet @@ -31,15 +31,107 @@ const getFeedCategoryElement = (riskTier: string | undefined) => { ) } -// ✅ Dynamic Supabase feed categories implementation -// Feed categories are sourced from Supabase with graceful fallback -const feedCategories = { - low: getFeedCategoryElement("low"), - medium: getFeedCategoryElement("medium"), - high: getFeedCategoryElement("high"), - new: getFeedCategoryElement("new"), - custom: getFeedCategoryElement("custom"), - deprecating: getFeedCategoryElement("deprecating"), +// Dev mode warning banner +const DevModeWarning = () => { + const [showDevMode, setShowDevMode] = useState(false) + + useEffect(() => { + const checkDevMode = () => { + const devMode = + typeof window !== "undefined" && + (window as unknown as { CHAINLINK_DEV_MODE?: boolean }).CHAINLINK_DEV_MODE === true + console.log("🔍 DevModeWarning checking dev mode:", devMode) + setShowDevMode(devMode) + } + + checkDevMode() + // Check every 2 seconds in case dev mode is toggled + const interval = setInterval(checkDevMode, 2000) + return () => clearInterval(interval) + }, []) + + console.log("🎯 DevModeWarning render - showDevMode:", showDevMode) + + if (!showDevMode) return null + + return ( +
+ 🔬 DEVELOPER TEST MODE ACTIVE - Categories show original → Supabase comparison. Disable with{" "} + + window.CHAINLINK_DEV_MODE = false + +
+ ) +} +const getFeedCategoryWithComparison = (comparisonData: { + final: string | null + original: string | null + supabase: string | null + changed: boolean + devMode: boolean +}) => { + const { final, devMode } = comparisonData + + // Add debugging + if (devMode) { + console.log("🔬 UI: getFeedCategoryWithComparison called with:", comparisonData) + } + + // Always show the final category icon + return getFeedCategoryElement(final || undefined) +} + +// New function to show comparison text under feed name +const getComparisonText = (comparisonData: { + final: string | null + original: string | null + supabase: string | null + changed: boolean + devMode: boolean +}) => { + const { original, supabase, changed, devMode } = comparisonData + + if (!devMode || !changed) { + return null + } + + const getIconForCategory = (category: string | null) => { + if (!category) return "❓" + const config = FEED_CATEGORY_CONFIG[category.toLowerCase()] + return config?.icon || "❓" + } + + const originalIcon = getIconForCategory(original) + const supabaseIcon = getIconForCategory(supabase) + + return ( +
+ 🔬 +
+ {originalIcon} {original || "none"} → {supabaseIcon} {supabase || "none"} +
+ ) } const Pagination = ({ addrPerPage, totalAddr, paginate, currentPage, firstAddr, lastAddr }) => { @@ -163,35 +255,117 @@ const DefaultTHead = ({ showExtraDetails, networkName }: { showExtraDetails: boo } const DefaultTr = ({ network, metadata, showExtraDetails }) => { - // Enhanced feed category logic with async Supabase lookup - const [dynamicFeedCategory, setDynamicFeedCategory] = useState(metadata.feedCategory) + // Enhanced feed category logic with dev mode comparison + const [comparisonData, setComparisonData] = useState<{ + final: string | null + original: string | null + supabase: string | null + changed: boolean + devMode: boolean + }>({ + final: metadata.feedCategory, + original: metadata.feedCategory, + supabase: null, + changed: false, + devMode: false, + }) + + // BASIC TEST - Log when component renders + console.log(`🚀 DefaultTr component loaded for ${metadata.name}`) - // Effect to fetch risk tier from Supabase on component mount + // Effect to fetch risk tier comparison data useEffect(() => { + console.log(`🔧 useEffect triggered for ${metadata.name}`) + const fetchRiskTier = async () => { - if (metadata.proxyAddress && network?.referenceDataDirectorySchema) { + console.log(`🔧 fetchRiskTier starting for ${metadata.name}`, { + proxyAddress: metadata.proxyAddress, + networkSchema: network?.referenceDataDirectorySchema, + hasProxyAddress: !!metadata.proxyAddress, + hasNetworkSchema: !!network?.referenceDataDirectorySchema, + }) + + // DEBUG: Let's see what the network object actually contains + console.log(`🌐 Full network object for ${metadata.name}:`, network) + + // Use the network type as the network identifier (mainnet, testnet, etc.) + const networkIdentifier = network?.networkType || "unknown" + + console.log("🌍 NETWORK DEBUG:", { + network, + queryString: network?.queryString, + name: network?.name, + networkIdentifier, + allNetworkProps: Object.keys(network || {}), + }) + + // TEMP: Call directly with any proxy address in dev mode + if (metadata.proxyAddress) { try { - const supabaseRiskTier = await getFeedRiskTierWithFallback( - metadata.proxyAddress, - network.referenceDataDirectorySchema, + console.log(`📞 About to call getFeedRiskTierWithComparison for ${metadata.name}`) + const result = await getFeedRiskTierWithComparison( + metadata.contractAddress || metadata.proxyAddress, + networkIdentifier, metadata.feedCategory ) + setComparisonData(result) + + // Console diff logging for dev mode + if (result.devMode) { + const hasChanges = result.changed + const diffInfo = { + feedName: metadata.name, + proxyAddress: metadata.proxyAddress, + network: network.referenceDataDirectorySchema, + original: result.original, + supabase: result.supabase, + final: result.final, + changed: hasChanges, + devMode: result.devMode, + } - setDynamicFeedCategory(supabaseRiskTier || metadata.feedCategory) + // COMPREHENSIVE DATA DUMP + console.group(`🔍 FULL DATA DUMP - ${metadata.name}`) + console.log("📋 Feed Metadata:", { + name: metadata.name, + proxyAddress: metadata.proxyAddress, + feedCategory: metadata.feedCategory, + assetName: metadata.assetName, + feedType: metadata.feedType, + }) + console.log("🌐 Network Info:", { + networkName: network.name, + referenceDataDirectorySchema: network.networkType, + }) + console.log("📊 Comparison Result:", diffInfo) + console.log("🔄 Raw Result Object:", result) + + if (hasChanges) { + console.log(`📊 DIFF DETECTED - ${metadata.name}:`, diffInfo) + console.log(` Original: ${result.original} → Supabase: ${result.supabase}`) + } else { + console.log(`✅ NO DIFF - ${metadata.name}:`, diffInfo) + } + console.groupEnd() + } } catch (error) { console.warn("Failed to fetch risk tier for", metadata.name, error) - setDynamicFeedCategory(metadata.feedCategory) + setComparisonData({ + final: metadata.feedCategory, + original: metadata.feedCategory, + supabase: null, + changed: false, + devMode: false, + }) } } } fetchRiskTier() - }, [metadata.proxyAddress, metadata.feedCategory, network?.referenceDataDirectorySchema, metadata.name]) + }, [metadata.proxyAddress, metadata.feedCategory, network?.networkType, metadata.name]) const getFinalFeedCategory = () => { - // Use dynamically fetched category - const categoryKey = dynamicFeedCategory?.toLowerCase() - return (categoryKey && feedCategories[categoryKey]) || getFeedCategoryElement(dynamicFeedCategory) || "" + return getFeedCategoryWithComparison(comparisonData) } return ( @@ -202,6 +376,7 @@ const DefaultTr = ({ network, metadata, showExtraDetails }) => { {getFinalFeedCategory()} {metadata.name}
+ {getComparisonText(comparisonData)} {metadata.secondaryProxyAddress && (
{ // Only show MVR badge if explicitly flagged as MVR const finalIsMVRFeed = isMVRFlagSet && hasDecoding - // Dynamic feed category lookup for SmartData feeds - const [dynamicFeedCategory, setDynamicFeedCategory] = useState(metadata.feedCategory) + // Dynamic feed category lookup for SmartData feeds with comparison + const [comparisonData, setComparisonData] = useState<{ + final: string | null + original: string | null + supabase: string | null + changed: boolean + devMode: boolean + }>({ + final: metadata.feedCategory, + original: metadata.feedCategory, + supabase: null, + changed: false, + devMode: false, + }) useEffect(() => { const fetchRiskTier = async () => { - if (metadata.proxyAddress && network?.referenceDataDirectorySchema) { + // Use the network type as the network identifier (mainnet, testnet, etc.) + const networkIdentifier = network?.networkType || "unknown" + + if (metadata.proxyAddress && network) { try { - const supabaseRiskTier = await getFeedRiskTierWithFallback( - metadata.proxyAddress, - network.referenceDataDirectorySchema, + const result = await getFeedRiskTierWithComparison( + metadata.contractAddress || metadata.proxyAddress, + networkIdentifier, metadata.feedCategory ) - setDynamicFeedCategory(supabaseRiskTier || metadata.feedCategory) + setComparisonData(result) + + // Console diff logging for dev mode + if (result.devMode) { + const hasChanges = result.changed + const diffInfo = { + feedName: metadata.name, + proxyAddress: metadata.proxyAddress, + network: network.referenceDataDirectorySchema, + original: result.original, + supabase: result.supabase, + final: result.final, + changed: hasChanges, + devMode: result.devMode, + } + + // COMPREHENSIVE DATA DUMP FOR SMARTDATA + console.group(`🔍 SMARTDATA FULL DATA DUMP - ${metadata.name}`) + console.log("📋 SmartData Feed Metadata:", { + name: metadata.name, + proxyAddress: metadata.proxyAddress, + feedCategory: metadata.feedCategory, + assetName: metadata.assetName, + productType: metadata.docs?.productType, + isMVR: metadata.docs?.isMVR, + }) + console.log("🌐 Network Info:", { + networkName: network.name, + referenceDataDirectorySchema: network.networkType, + }) + console.log("📊 Comparison Result:", diffInfo) + console.log("🔄 Raw Result Object:", result) + + if (hasChanges) { + console.log(`📊 SMARTDATA DIFF DETECTED - ${metadata.name}:`, diffInfo) + console.log(` Original: ${result.original} → Supabase: ${result.supabase}`) + } else { + console.log(`✅ SMARTDATA NO DIFF - ${metadata.name}:`, diffInfo) + } + console.groupEnd() + } } catch (error) { console.warn("Failed to fetch risk tier for SmartData feed", metadata.name, error) - setDynamicFeedCategory(metadata.feedCategory) + setComparisonData({ + final: metadata.feedCategory, + original: metadata.feedCategory, + supabase: null, + changed: false, + devMode: false, + }) } } } fetchRiskTier() - }, [metadata.proxyAddress, metadata.feedCategory, network?.referenceDataDirectorySchema, metadata.name]) + }, [metadata.proxyAddress, metadata.feedCategory, network?.networkType, metadata.name]) + + const getSmartDataFeedCategory = () => { + return getFeedCategoryWithComparison(comparisonData) + } return ( @@ -405,8 +645,9 @@ const SmartDataTr = ({ network, metadata, showExtraDetails }) => { return "" })}
- {getFeedCategoryElement(dynamicFeedCategory) || ""} {metadata.name} + {getSmartDataFeedCategory()} {metadata.name}
+ {getComparisonText(comparisonData)} {metadata.docs.shutdownDate && (

@@ -1054,6 +1295,7 @@ export const MainnetTable = ({ return ( <> +
{slicedFilteredMetadata.length === 0 ? ( From d6036904c8c9d1b3cda72fdb75b802d9da5b104c Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Thu, 7 Aug 2025 19:27:45 -0700 Subject: [PATCH 03/33] more table updates --- src/features/feeds/components/Tables.tsx | 158 +++++++---------------- 1 file changed, 45 insertions(+), 113 deletions(-) diff --git a/src/features/feeds/components/Tables.tsx b/src/features/feeds/components/Tables.tsx index 3e20ab7cfde..6636b3dade4 100644 --- a/src/features/feeds/components/Tables.tsx +++ b/src/features/feeds/components/Tables.tsx @@ -14,7 +14,16 @@ import { FEED_CATEGORY_CONFIG, getFeedRiskTierWithComparison } from "../../../db const feedItems = monitoredFeeds.mainnet // Centralized function to get feed category element using the shared config -const getFeedCategoryElement = (riskTier: string | undefined) => { +const getFeedCategoryElement = (riskTier: string | undefined, isLoading: boolean = false) => { + // Show gray circle while loading + if (isLoading) { + return ( + + ⚪ + + ) + } + if (!riskTier) return "" const lowerTier = riskTier.toLowerCase() @@ -40,7 +49,6 @@ const DevModeWarning = () => { const devMode = typeof window !== "undefined" && (window as unknown as { CHAINLINK_DEV_MODE?: boolean }).CHAINLINK_DEV_MODE === true - console.log("🔍 DevModeWarning checking dev mode:", devMode) setShowDevMode(devMode) } @@ -50,8 +58,6 @@ const DevModeWarning = () => { return () => clearInterval(interval) }, []) - console.log("🎯 DevModeWarning render - showDevMode:", showDevMode) - if (!showDevMode) return null return ( @@ -80,16 +86,11 @@ const getFeedCategoryWithComparison = (comparisonData: { supabase: string | null changed: boolean devMode: boolean -}) => { - const { final, devMode } = comparisonData - - // Add debugging - if (devMode) { - console.log("🔬 UI: getFeedCategoryWithComparison called with:", comparisonData) - } +}, isLoading: boolean = false) => { + const { final } = comparisonData - // Always show the final category icon - return getFeedCategoryElement(final || undefined) + // Always show the final category icon (or loading state) + return getFeedCategoryElement(final || undefined, isLoading) } // New function to show comparison text under feed name @@ -270,83 +271,32 @@ const DefaultTr = ({ network, metadata, showExtraDetails }) => { devMode: false, }) - // BASIC TEST - Log when component renders - console.log(`🚀 DefaultTr component loaded for ${metadata.name}`) + // Add loading state for risk category - start as false, set to true when API starts + const [isLoadingRisk, setIsLoadingRisk] = useState(false) // Effect to fetch risk tier comparison data useEffect(() => { - console.log(`🔧 useEffect triggered for ${metadata.name}`) - const fetchRiskTier = async () => { - console.log(`🔧 fetchRiskTier starting for ${metadata.name}`, { - proxyAddress: metadata.proxyAddress, - networkSchema: network?.referenceDataDirectorySchema, - hasProxyAddress: !!metadata.proxyAddress, - hasNetworkSchema: !!network?.referenceDataDirectorySchema, - }) - - // DEBUG: Let's see what the network object actually contains - console.log(`🌐 Full network object for ${metadata.name}:`, network) - // Use the network type as the network identifier (mainnet, testnet, etc.) const networkIdentifier = network?.networkType || "unknown" - console.log("🌍 NETWORK DEBUG:", { - network, - queryString: network?.queryString, - name: network?.name, - networkIdentifier, - allNetworkProps: Object.keys(network || {}), - }) - // TEMP: Call directly with any proxy address in dev mode if (metadata.proxyAddress) { + // Ensure loading state is true at the start of API call + setIsLoadingRisk(true) + try { - console.log(`📞 About to call getFeedRiskTierWithComparison for ${metadata.name}`) const result = await getFeedRiskTierWithComparison( metadata.contractAddress || metadata.proxyAddress, networkIdentifier, metadata.feedCategory ) setComparisonData(result) + setIsLoadingRisk(false) // Risk data loaded successfully - // Console diff logging for dev mode - if (result.devMode) { - const hasChanges = result.changed - const diffInfo = { - feedName: metadata.name, - proxyAddress: metadata.proxyAddress, - network: network.referenceDataDirectorySchema, - original: result.original, - supabase: result.supabase, - final: result.final, - changed: hasChanges, - devMode: result.devMode, - } - - // COMPREHENSIVE DATA DUMP - console.group(`🔍 FULL DATA DUMP - ${metadata.name}`) - console.log("📋 Feed Metadata:", { - name: metadata.name, - proxyAddress: metadata.proxyAddress, - feedCategory: metadata.feedCategory, - assetName: metadata.assetName, - feedType: metadata.feedType, - }) - console.log("🌐 Network Info:", { - networkName: network.name, - referenceDataDirectorySchema: network.networkType, - }) - console.log("📊 Comparison Result:", diffInfo) - console.log("🔄 Raw Result Object:", result) - - if (hasChanges) { - console.log(`📊 DIFF DETECTED - ${metadata.name}:`, diffInfo) - console.log(` Original: ${result.original} → Supabase: ${result.supabase}`) - } else { - console.log(`✅ NO DIFF - ${metadata.name}:`, diffInfo) - } - console.groupEnd() + // Only log differences in dev mode + if (result.devMode && result.changed) { + console.log(`📊 ${metadata.name}: ${result.original} → ${result.supabase}`) } } catch (error) { console.warn("Failed to fetch risk tier for", metadata.name, error) @@ -357,7 +307,11 @@ const DefaultTr = ({ network, metadata, showExtraDetails }) => { changed: false, devMode: false, }) + setIsLoadingRisk(false) // Stop loading even on error } + } else { + // No proxy address, no API call needed + setIsLoadingRisk(false) } } @@ -365,7 +319,7 @@ const DefaultTr = ({ network, metadata, showExtraDetails }) => { }, [metadata.proxyAddress, metadata.feedCategory, network?.networkType, metadata.name]) const getFinalFeedCategory = () => { - return getFeedCategoryWithComparison(comparisonData) + return getFeedCategoryWithComparison(comparisonData, isLoadingRisk) } return ( @@ -553,8 +507,19 @@ const SmartDataTr = ({ network, metadata, showExtraDetails }) => { devMode: false, }) + // Add loading state for risk category - start as false, set to true when API starts + const [isLoadingRisk, setIsLoadingRisk] = useState(false) + useEffect(() => { const fetchRiskTier = async () => { + // Only load if we have a proxy address + if (!metadata.proxyAddress) { + setIsLoadingRisk(false) + return + } + + // Ensure loading state is true at the start of API call + setIsLoadingRisk(true) // Use the network type as the network identifier (mainnet, testnet, etc.) const networkIdentifier = network?.networkType || "unknown" @@ -566,45 +531,11 @@ const SmartDataTr = ({ network, metadata, showExtraDetails }) => { metadata.feedCategory ) setComparisonData(result) + setIsLoadingRisk(false) // Risk data loaded successfully - // Console diff logging for dev mode - if (result.devMode) { - const hasChanges = result.changed - const diffInfo = { - feedName: metadata.name, - proxyAddress: metadata.proxyAddress, - network: network.referenceDataDirectorySchema, - original: result.original, - supabase: result.supabase, - final: result.final, - changed: hasChanges, - devMode: result.devMode, - } - - // COMPREHENSIVE DATA DUMP FOR SMARTDATA - console.group(`🔍 SMARTDATA FULL DATA DUMP - ${metadata.name}`) - console.log("📋 SmartData Feed Metadata:", { - name: metadata.name, - proxyAddress: metadata.proxyAddress, - feedCategory: metadata.feedCategory, - assetName: metadata.assetName, - productType: metadata.docs?.productType, - isMVR: metadata.docs?.isMVR, - }) - console.log("🌐 Network Info:", { - networkName: network.name, - referenceDataDirectorySchema: network.networkType, - }) - console.log("📊 Comparison Result:", diffInfo) - console.log("🔄 Raw Result Object:", result) - - if (hasChanges) { - console.log(`📊 SMARTDATA DIFF DETECTED - ${metadata.name}:`, diffInfo) - console.log(` Original: ${result.original} → Supabase: ${result.supabase}`) - } else { - console.log(`✅ SMARTDATA NO DIFF - ${metadata.name}:`, diffInfo) - } - console.groupEnd() + // Only log differences in dev mode + if (result.devMode && result.changed) { + console.log(`📊 SmartData ${metadata.name}: ${result.original} → ${result.supabase}`) } } catch (error) { console.warn("Failed to fetch risk tier for SmartData feed", metadata.name, error) @@ -615,6 +546,7 @@ const SmartDataTr = ({ network, metadata, showExtraDetails }) => { changed: false, devMode: false, }) + setIsLoadingRisk(false) // Stop loading even on error } } } @@ -623,7 +555,7 @@ const SmartDataTr = ({ network, metadata, showExtraDetails }) => { }, [metadata.proxyAddress, metadata.feedCategory, network?.networkType, metadata.name]) const getSmartDataFeedCategory = () => { - return getFeedCategoryWithComparison(comparisonData) + return getFeedCategoryWithComparison(comparisonData, isLoadingRisk) } return ( From a648f7e3905b2aa7159a14b49070f27d705f785d Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Thu, 7 Aug 2025 22:19:47 -0700 Subject: [PATCH 04/33] batched feed approach --- src/db/feedCategories.ts | 151 ++++++++++++ src/features/feeds/components/Tables.tsx | 217 +++++++++--------- .../components/useBatchedFeedCategories.ts | 150 ++++++++++++ 3 files changed, 414 insertions(+), 104 deletions(-) create mode 100644 src/features/feeds/components/useBatchedFeedCategories.ts diff --git a/src/db/feedCategories.ts b/src/db/feedCategories.ts index 547e394d406..cf7d660be1c 100644 --- a/src/db/feedCategories.ts +++ b/src/db/feedCategories.ts @@ -363,6 +363,157 @@ export async function getFeedRiskTierWithComparison( } } +// Batched version for improved performance +export async function getFeedRiskTiersBatch( + feedRequests: Array<{ + contractAddress: string + network: string + fallbackCategory?: string + }> +): Promise< + Map< + string, + { + final: string | null + original: string | null + supabase: string | null + changed: boolean + devMode: boolean + } + > +> { + const devMode = getDevTestFlag() + const resultMap = new Map< + string, + { + final: string | null + original: string | null + supabase: string | null + changed: boolean + devMode: boolean + } + >() + + // If no Supabase client, return fallback values for all requests + if (!supabase) { + feedRequests.forEach(({ contractAddress, network, fallbackCategory }) => { + const key = `${contractAddress}-${network}` + resultMap.set(key, { + final: fallbackCategory || null, + original: fallbackCategory || null, + supabase: null, + changed: false, + devMode, + }) + }) + return resultMap + } + + // Get unique networks and contract addresses + const uniqueNetworks = Array.from(new Set(feedRequests.map((req) => req.network))) + const uniqueAddresses = Array.from(new Set(feedRequests.map((req) => req.contractAddress))) + + if (devMode) { + console.group(`🚀 BATCH getFeedRiskTiersBatch - ${feedRequests.length} requests`) + console.log("📊 Batch Stats:", { + totalRequests: feedRequests.length, + uniqueNetworks: uniqueNetworks.length, + uniqueAddresses: uniqueAddresses.length, + networks: uniqueNetworks, + }) + } + + try { + // Single query for all addresses and networks + const { data, error } = await supabase + .from("docs_feeds_risk") + .select("*") + .in("proxy_address", uniqueAddresses) + .in("network", uniqueNetworks) + .limit(1000) // Reasonable limit for batch operations + + if (error) { + console.error("Batch Supabase query error:", error) + // Return fallback values for all requests + feedRequests.forEach(({ contractAddress, network, fallbackCategory }) => { + const key = `${contractAddress}-${network}` + resultMap.set(key, { + final: fallbackCategory || null, + original: fallbackCategory || null, + supabase: null, + changed: false, + devMode, + }) + }) + return resultMap + } + + // Create a lookup map from the batch results + const supabaseData = new Map() + data?.forEach((row) => { + const key = `${row.proxy_address}-${row.network}` + supabaseData.set(key, row.risk_status) + }) + + if (devMode) { + console.log("📡 Batch Query Results:", { + supabaseRecords: data?.length || 0, + lookupEntries: supabaseData.size, + }) + } + + // Process each request using the batch data + feedRequests.forEach(({ contractAddress, network, fallbackCategory }) => { + const key = `${contractAddress}-${network}` + const original = fallbackCategory || null + const supabaseRiskTier = supabaseData.get(key) || null + const final = supabaseRiskTier || original + const changed = supabaseRiskTier !== null && supabaseRiskTier !== original + + resultMap.set(key, { + final, + original, + supabase: supabaseRiskTier, + changed, + devMode, + }) + + // Log individual changes in dev mode + if (devMode && changed) { + console.log(`🔄 ${contractAddress}: ${original} → ${supabaseRiskTier}`) + } + }) + + if (devMode) { + const changedCount = Array.from(resultMap.values()).filter((result) => result.changed).length + console.log("🎯 Batch Processing Complete:", { + totalProcessed: resultMap.size, + changedFeeds: changedCount, + unchangedFeeds: resultMap.size - changedCount, + }) + console.groupEnd() + } + } catch (error) { + console.error("Error in getFeedRiskTiersBatch:", error) + // Return fallback values for all requests on error + feedRequests.forEach(({ contractAddress, network, fallbackCategory }) => { + const key = `${contractAddress}-${network}` + resultMap.set(key, { + final: fallbackCategory || null, + original: fallbackCategory || null, + supabase: null, + changed: false, + devMode, + }) + }) + if (devMode) { + console.groupEnd() + } + } + + return resultMap +} + export async function getFeedRiskTierWithFallback( contractAddress: string, network: string, diff --git a/src/features/feeds/components/Tables.tsx b/src/features/feeds/components/Tables.tsx index 6636b3dade4..e5f93b9c65b 100644 --- a/src/features/feeds/components/Tables.tsx +++ b/src/features/feeds/components/Tables.tsx @@ -9,7 +9,8 @@ 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, getFeedRiskTierWithComparison } from "../../../db/feedCategories.js" +import { FEED_CATEGORY_CONFIG } from "../../../db/feedCategories.js" +import { useBatchedFeedCategories, getFeedCategoryFromBatch, type FeedCategoryData } from "./useBatchedFeedCategories.ts" const feedItems = monitoredFeeds.mainnet @@ -255,15 +256,9 @@ const DefaultTHead = ({ showExtraDetails, networkName }: { showExtraDetails: boo ) } -const DefaultTr = ({ network, metadata, showExtraDetails }) => { - // Enhanced feed category logic with dev mode comparison - const [comparisonData, setComparisonData] = useState<{ - final: string | null - original: string | null - supabase: string | null - changed: boolean - devMode: boolean - }>({ +const DefaultTr = ({ network, metadata, showExtraDetails, batchedCategoryData }) => { + // Use batched category data instead of individual API calls + const [comparisonData, setComparisonData] = useState({ final: metadata.feedCategory, original: metadata.feedCategory, supabase: null, @@ -271,55 +266,54 @@ const DefaultTr = ({ network, metadata, showExtraDetails }) => { devMode: false, }) - // Add loading state for risk category - start as false, set to true when API starts - const [isLoadingRisk, setIsLoadingRisk] = useState(false) + // No longer need loading state since batch loading is handled at network level - // Effect to fetch risk tier comparison data + // Effect to get data from batch results when they're available useEffect(() => { - const fetchRiskTier = async () => { - // Use the network type as the network identifier (mainnet, testnet, etc.) + // For testnet or networks without batch data, just use fallback + if (!batchedCategoryData || batchedCategoryData.size === 0) { + setComparisonData({ + final: metadata.feedCategory, + original: metadata.feedCategory, + supabase: null, + changed: false, + devMode: false, + }) + return + } + + if (metadata.proxyAddress || metadata.contractAddress) { + const contractAddress = metadata.contractAddress || metadata.proxyAddress const networkIdentifier = network?.networkType || "unknown" + + if (contractAddress) { + const batchResult = getFeedCategoryFromBatch( + batchedCategoryData, + contractAddress, + networkIdentifier, + metadata.feedCategory + ) + setComparisonData(batchResult) - // TEMP: Call directly with any proxy address in dev mode - if (metadata.proxyAddress) { - // Ensure loading state is true at the start of API call - setIsLoadingRisk(true) - - try { - const result = await getFeedRiskTierWithComparison( - metadata.contractAddress || metadata.proxyAddress, - networkIdentifier, - metadata.feedCategory - ) - setComparisonData(result) - setIsLoadingRisk(false) // Risk data loaded successfully - - // Only log differences in dev mode - if (result.devMode && result.changed) { - console.log(`📊 ${metadata.name}: ${result.original} → ${result.supabase}`) - } - } catch (error) { - console.warn("Failed to fetch risk tier for", metadata.name, error) - setComparisonData({ - final: metadata.feedCategory, - original: metadata.feedCategory, - supabase: null, - changed: false, - devMode: false, - }) - setIsLoadingRisk(false) // Stop loading even on error + // Only log differences in dev mode + if (batchResult.devMode && batchResult.changed) { + console.log(`📊 ${metadata.name}: ${batchResult.original} → ${batchResult.supabase}`) } - } else { - // No proxy address, no API call needed - setIsLoadingRisk(false) } + } else { + // Fallback to original category if no proxy address + setComparisonData({ + final: metadata.feedCategory, + original: metadata.feedCategory, + supabase: null, + changed: false, + devMode: false, + }) } - - fetchRiskTier() - }, [metadata.proxyAddress, metadata.feedCategory, network?.networkType, metadata.name]) + }, [batchedCategoryData, metadata.proxyAddress, metadata.contractAddress, metadata.feedCategory, network?.networkType, metadata.name]) const getFinalFeedCategory = () => { - return getFeedCategoryWithComparison(comparisonData, isLoadingRisk) + return getFeedCategoryWithComparison(comparisonData, false) } return ( @@ -484,7 +478,7 @@ const SmartDataTHead = ({ showExtraDetails }: { showExtraDetails: boolean }) => ) -const SmartDataTr = ({ network, metadata, showExtraDetails }) => { +const SmartDataTr = ({ network, metadata, showExtraDetails, batchedCategoryData }) => { // Check if this is an MVR feed const hasDecoding = Array.isArray(metadata.docs?.decoding) && metadata.docs.decoding.length > 0 const isMVRFlagSet = metadata.docs?.isMVR === true @@ -492,14 +486,8 @@ const SmartDataTr = ({ network, metadata, showExtraDetails }) => { // Only show MVR badge if explicitly flagged as MVR const finalIsMVRFeed = isMVRFlagSet && hasDecoding - // Dynamic feed category lookup for SmartData feeds with comparison - const [comparisonData, setComparisonData] = useState<{ - final: string | null - original: string | null - supabase: string | null - changed: boolean - devMode: boolean - }>({ + // Use batched category data instead of individual API calls + const [comparisonData, setComparisonData] = useState({ final: metadata.feedCategory, original: metadata.feedCategory, supabase: null, @@ -507,55 +495,41 @@ const SmartDataTr = ({ network, metadata, showExtraDetails }) => { devMode: false, }) - // Add loading state for risk category - start as false, set to true when API starts - const [isLoadingRisk, setIsLoadingRisk] = useState(false) + // No longer need loading state since batch loading is handled at network level useEffect(() => { - const fetchRiskTier = async () => { - // Only load if we have a proxy address - if (!metadata.proxyAddress) { - setIsLoadingRisk(false) - return - } - - // Ensure loading state is true at the start of API call - setIsLoadingRisk(true) - // Use the network type as the network identifier (mainnet, testnet, etc.) + if (batchedCategoryData && metadata.proxyAddress) { const networkIdentifier = network?.networkType || "unknown" + const contractAddress = metadata.contractAddress || metadata.proxyAddress + + if (contractAddress) { + const batchResult = getFeedCategoryFromBatch( + batchedCategoryData, + contractAddress, + networkIdentifier, + metadata.feedCategory + ) + setComparisonData(batchResult) - if (metadata.proxyAddress && network) { - try { - const result = await getFeedRiskTierWithComparison( - metadata.contractAddress || metadata.proxyAddress, - networkIdentifier, - metadata.feedCategory - ) - setComparisonData(result) - setIsLoadingRisk(false) // Risk data loaded successfully - - // Only log differences in dev mode - if (result.devMode && result.changed) { - console.log(`📊 SmartData ${metadata.name}: ${result.original} → ${result.supabase}`) - } - } catch (error) { - console.warn("Failed to fetch risk tier for SmartData feed", metadata.name, error) - setComparisonData({ - final: metadata.feedCategory, - original: metadata.feedCategory, - supabase: null, - changed: false, - devMode: false, - }) - setIsLoadingRisk(false) // Stop loading even on error + // Only log differences in dev mode + if (batchResult.devMode && batchResult.changed) { + console.log(`📊 SmartData ${metadata.name}: ${batchResult.original} → ${batchResult.supabase}`) } } + } else { + // Fallback to original category if no batch data + setComparisonData({ + final: metadata.feedCategory, + original: metadata.feedCategory, + supabase: null, + changed: false, + devMode: false, + }) } - - fetchRiskTier() - }, [metadata.proxyAddress, metadata.feedCategory, network?.networkType, metadata.name]) + }, [batchedCategoryData, metadata.proxyAddress, metadata.contractAddress, metadata.feedCategory, network?.networkType, metadata.name]) const getSmartDataFeedCategory = () => { - return getFeedCategoryWithComparison(comparisonData, isLoadingRisk) + return getFeedCategoryWithComparison(comparisonData, false) } return ( @@ -1124,6 +1098,9 @@ export const MainnetTable = ({ }) => { if (!network.metadata) return null + // Use batched feed category loading for this network + const { data: batchedCategoryData, isLoading: isBatchLoading } = useBatchedFeedCategories(network) + const isStreams = dataFeedType === "streamsCrypto" || dataFeedType === "streamsRwa" const isSmartData = dataFeedType === "smartdata" const isDefault = !isStreams && !isSmartData @@ -1228,6 +1205,7 @@ export const MainnetTable = ({ return ( <> + {isBatchLoading &&

Loading...

}
{slicedFilteredMetadata.length === 0 ? ( @@ -1253,10 +1231,20 @@ export const MainnetTable = ({ <> {isStreams && } {isSmartData && ( - + )} {isDefault && ( - + )} ))} @@ -1310,6 +1298,9 @@ export const TestnetTable = ({ }) => { if (!network.metadata) return null + // Use batched feed category loading for this network + const { data: batchedCategoryData, isLoading: isBatchLoading } = useBatchedFeedCategories(network) + const isStreams = dataFeedType === "streamsCrypto" || dataFeedType === "streamsRwa" const isSmartData = dataFeedType === "smartdata" const isRates = dataFeedType === "rates" @@ -1396,6 +1387,7 @@ export const TestnetTable = ({ return ( <> + {isBatchLoading &&

Loading...

}
{slicedFilteredMetadata.length === 0 ? ( @@ -1422,12 +1414,29 @@ export const TestnetTable = ({ <> {isStreams && } {isSmartData && ( - + )} {isDefault && ( - + + )} + {isRates && ( + )} - {isRates && } ))} diff --git a/src/features/feeds/components/useBatchedFeedCategories.ts b/src/features/feeds/components/useBatchedFeedCategories.ts new file mode 100644 index 00000000000..aa3e992d3d3 --- /dev/null +++ b/src/features/feeds/components/useBatchedFeedCategories.ts @@ -0,0 +1,150 @@ +import { useEffect, useState } from "preact/hooks" +import { getFeedRiskTiersBatch } from "~/db/feedCategories.js" +import { ChainNetwork } from "~/features/data/chains.ts" + +// Type definition for the comparison data +export type FeedCategoryData = { + final: string | null + original: string | null + supabase: string | null + changed: boolean + devMode: boolean +} + +// Type for the batched feed category hook +type BatchedFeedCategoriesState = { + data: Map + isLoading: boolean + error: string | null +} + +/** + * Custom hook to batch-load feed category data for all feeds in a network + * This replaces individual API calls with a single batched request per network + */ +export function useBatchedFeedCategories(network: ChainNetwork | null): BatchedFeedCategoriesState { + const [state, setState] = useState({ + data: new Map(), + isLoading: false, + error: null, + }) + + useEffect(() => { + if (!network || !network.metadata) { + setState({ + data: new Map(), + isLoading: false, + error: null, + }) + return + } + + // Only load batch data for mainnet networks - testnet doesn't have risk categories + if (network.networkType !== "mainnet") { + setState({ + data: new Map(), + isLoading: false, + error: null, + }) + return + } + + const loadBatchedCategories = async () => { + setState((prev) => ({ + ...prev, + isLoading: true, + error: null, + })) + + try { + // Collect all feed requests for this network + const feedRequests: Array<{ + contractAddress: string + network: string + fallbackCategory?: string + }> = [] + + if (network.metadata) { + network.metadata.forEach((metadata) => { + // Only process feeds that have proxy addresses + const contractAddress = metadata.contractAddress || metadata.proxyAddress + if (contractAddress) { + feedRequests.push({ + contractAddress, + network: network.networkType || "unknown", + fallbackCategory: metadata.feedCategory, + }) + } + }) + } + + // Skip batch request if no feeds with addresses + if (feedRequests.length === 0) { + setState({ + data: new Map(), + isLoading: false, + error: null, + }) + return + } + + // Make the batched API call + const batchResults = await getFeedRiskTiersBatch(feedRequests) + + setState({ + data: batchResults, + isLoading: false, + error: null, + }) + } catch (error) { + console.error("Failed to load batched feed categories:", error) + setState((prev) => ({ + ...prev, + isLoading: false, + error: error instanceof Error ? error.message : "Unknown error occurred", + })) + } + } + + loadBatchedCategories() + }, [network?.name, network?.networkType, network?.metadata?.length]) + + return state +} + +/** + * Helper function to get feed category data from batched results + */ +export function getFeedCategoryFromBatch( + batchData: Map, + contractAddress: string, + network: string, + fallbackCategory?: string +): FeedCategoryData { + // If no batch data (e.g., testnet), return fallback immediately + if (!batchData || batchData.size === 0) { + return { + final: fallbackCategory || null, + original: fallbackCategory || null, + supabase: null, + changed: false, + devMode: false, + } + } + + const key = `${contractAddress}-${network}` + const batchResult = batchData.get(key) + + if (batchResult) { + return batchResult + } + + // Return fallback if not found in batch + return { + final: fallbackCategory || null, + original: fallbackCategory || null, + supabase: null, + changed: false, + devMode: false, + } +} From 7c8ba117bb630a6173aec9889ef0776e6287df08 Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Thu, 7 Aug 2025 22:29:47 -0700 Subject: [PATCH 05/33] removed verbose logging --- src/db/feedCategories.ts | 137 ++++----------------------------------- 1 file changed, 11 insertions(+), 126 deletions(-) diff --git a/src/db/feedCategories.ts b/src/db/feedCategories.ts index cf7d660be1c..a9da0a55dc6 100644 --- a/src/db/feedCategories.ts +++ b/src/db/feedCategories.ts @@ -1,12 +1,5 @@ import { supabase } from "./supabase.js" -// Development debug function -function isDevelopmentDebug(): boolean { - return ( - typeof window !== "undefined" && (window as unknown as { CHAINLINK_DEV_MODE?: boolean }).CHAINLINK_DEV_MODE === true - ) -} - // Development flag getter function getDevTestFlag(): boolean { return ( @@ -85,10 +78,6 @@ export async function getFeedRiskData(network?: string): Promise if (network) { query = query.eq("network", network) - - if (isDevelopmentDebug()) { - console.log(`[DEBUG] getFeedRiskData: Using network: "${network}"`) - } } const { data, error } = await query.limit(1000) // Use reasonable limit instead of .single() @@ -98,10 +87,6 @@ export async function getFeedRiskData(network?: string): Promise return [] } - if (isDevelopmentDebug()) { - console.log(`[DEBUG] getFeedRiskData: Loaded ${data?.length || 0} records for network ${network}`) - } - return data || [] } catch (error) { console.error("Error in getFeedRiskData:", error) @@ -132,31 +117,16 @@ export async function getFeedRiskTier(contractAddress: string, network: string, // Handle empty results - no matching records found if (!data || data.length === 0) { - const devMode = getDevTestFlag() - if (devMode) { - console.log("🔬 DEV MODE: No Supabase data found for:", { - contractAddress, - network, - fallback: fallbackCategory, - }) - } return fallbackCategory || null } const supabaseRiskTier = data[0]?.risk_status || null const finalCategory = supabaseRiskTier || fallbackCategory || null - // Enhanced logging when dev mode is enabled + // Only log diffs when dev mode is enabled const devMode = getDevTestFlag() - if (devMode && (supabaseRiskTier || fallbackCategory)) { - console.log("🔬 DEV MODE: Feed category comparison:", { - contractAddress, - network, - original: fallbackCategory, - supabase: supabaseRiskTier, - final: finalCategory, - changed: supabaseRiskTier && supabaseRiskTier !== fallbackCategory, - }) + if (devMode && supabaseRiskTier && supabaseRiskTier !== fallbackCategory) { + console.log(`� ${contractAddress}: ${fallbackCategory} → ${supabaseRiskTier}`) } return finalCategory @@ -239,44 +209,15 @@ export async function getFeedRiskTierWithComparison( const devMode = getDevTestFlag() const original = fallbackCategory || null - // COMPREHENSIVE DEBUG LOGGING FOR ALL CALLS - if (devMode) { - console.group(`🔍 getFeedRiskTierWithComparison DEBUG - ${contractAddress}`) - console.log("📥 Input Parameters:", { - contractAddress, - network, - fallbackCategory, - }) - console.log("🏁 Original Value:", original) - console.log("🎯 Dev Mode Active:", devMode) - console.log("🔍 Network identifier analysis:", { - network, - networkType: typeof network, - networkLength: network?.length, - }) - } - try { if (!supabase) { - const result = { + return { final: original, original, supabase: null, changed: false, devMode, } - if (devMode) { - console.log("❌ No Supabase client, returning:", result) - console.groupEnd() - } - return result - } - - if (devMode) { - console.log("🔍 Querying Supabase with:", { - contractAddress, - network, - }) } const { data, error } = await supabase @@ -286,39 +227,25 @@ export async function getFeedRiskTierWithComparison( .eq("network", network) .limit(1) - if (devMode) { - console.log("📡 Supabase Query Result:", { data, error }) - } - if (error) { - const result = { + return { final: original, original, supabase: null, changed: false, devMode, } - if (devMode) { - console.log("❌ Supabase error, returning:", result) - console.groupEnd() - } - return result } // Handle empty results - no matching records found if (!data || data.length === 0) { - const result = { + return { final: original, original, supabase: null, changed: false, devMode, } - if (devMode) { - console.log("📝 No Supabase data found, returning:", result) - console.groupEnd() - } - return result } const supabaseRiskTier = data[0]?.risk_status || null @@ -333,33 +260,21 @@ export async function getFeedRiskTierWithComparison( devMode, } - if (devMode) { - console.log("🎯 Final Comparison Result:", result) - console.log("🔄 Change Detection:", { - supabaseRiskTier, - original, - isNotNull: supabaseRiskTier !== null, - isDifferent: supabaseRiskTier !== original, - changed, - }) - console.groupEnd() + // Only log diffs in dev mode + if (devMode && changed) { + console.log(`🔄 ${contractAddress}: ${original} → ${supabaseRiskTier}`) } return result } catch (error) { console.error("Error in getFeedRiskTierWithComparison:", error) - const result = { + return { final: original, original, supabase: null, changed: false, devMode, } - if (devMode) { - console.log("❌ Exception occurred, returning:", result) - console.groupEnd() - } - return result } } @@ -413,16 +328,6 @@ export async function getFeedRiskTiersBatch( const uniqueNetworks = Array.from(new Set(feedRequests.map((req) => req.network))) const uniqueAddresses = Array.from(new Set(feedRequests.map((req) => req.contractAddress))) - if (devMode) { - console.group(`🚀 BATCH getFeedRiskTiersBatch - ${feedRequests.length} requests`) - console.log("📊 Batch Stats:", { - totalRequests: feedRequests.length, - uniqueNetworks: uniqueNetworks.length, - uniqueAddresses: uniqueAddresses.length, - networks: uniqueNetworks, - }) - } - try { // Single query for all addresses and networks const { data, error } = await supabase @@ -455,13 +360,6 @@ export async function getFeedRiskTiersBatch( supabaseData.set(key, row.risk_status) }) - if (devMode) { - console.log("📡 Batch Query Results:", { - supabaseRecords: data?.length || 0, - lookupEntries: supabaseData.size, - }) - } - // Process each request using the batch data feedRequests.forEach(({ contractAddress, network, fallbackCategory }) => { const key = `${contractAddress}-${network}` @@ -478,21 +376,11 @@ export async function getFeedRiskTiersBatch( devMode, }) - // Log individual changes in dev mode + // Only log diffs in dev mode if (devMode && changed) { console.log(`🔄 ${contractAddress}: ${original} → ${supabaseRiskTier}`) } }) - - if (devMode) { - const changedCount = Array.from(resultMap.values()).filter((result) => result.changed).length - console.log("🎯 Batch Processing Complete:", { - totalProcessed: resultMap.size, - changedFeeds: changedCount, - unchangedFeeds: resultMap.size - changedCount, - }) - console.groupEnd() - } } catch (error) { console.error("Error in getFeedRiskTiersBatch:", error) // Return fallback values for all requests on error @@ -506,9 +394,6 @@ export async function getFeedRiskTiersBatch( devMode, }) }) - if (devMode) { - console.groupEnd() - } } return resultMap From 7b1f3274b4325a6dd3cd0cbfe8b358cee82a9483 Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Tue, 12 Aug 2025 18:17:36 -0700 Subject: [PATCH 06/33] updated key and test --- .github/workflows/test.yml | 3 +++ src/db/supabase.ts | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 41e12fd819e..4369dadde8a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -184,6 +184,9 @@ jobs: typecheck: needs: [setup] runs-on: ubuntu-latest + env: + SUPABASE_URL: ${{ secrets.SUPABASE_URL }} + SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} steps: - name: Checkout Repo uses: actions/checkout@v4 diff --git a/src/db/supabase.ts b/src/db/supabase.ts index 2510c5e8fdb..094aeb4dad5 100644 --- a/src/db/supabase.ts +++ b/src/db/supabase.ts @@ -1,7 +1,7 @@ import { createClient } from "@supabase/supabase-js" -const supabaseUrl = import.meta.env.PUBLIC_SUPABASE_URL -const supabaseKey = import.meta.env.PUBLIC_SUPABASE_KEY +const supabaseUrl = import.meta.env.SUPABASE_URL +const supabaseKey = import.meta.env.SUPABASE_ANON_KEY // Export a function that safely creates the client export function getSupabaseClient() { From 808d4401f7c6376708eb2f9d8a8aa731d4c4c98b Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Tue, 12 Aug 2025 18:26:05 -0700 Subject: [PATCH 07/33] lint fix --- src/features/feeds/components/Tables.tsx | 79 +++++++++++++++--------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/src/features/feeds/components/Tables.tsx b/src/features/feeds/components/Tables.tsx index e5f93b9c65b..1e476069cbb 100644 --- a/src/features/feeds/components/Tables.tsx +++ b/src/features/feeds/components/Tables.tsx @@ -10,17 +10,21 @@ 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 { useBatchedFeedCategories, getFeedCategoryFromBatch, type FeedCategoryData } from "./useBatchedFeedCategories.ts" +import { + useBatchedFeedCategories, + getFeedCategoryFromBatch, + type FeedCategoryData, +} from "./useBatchedFeedCategories.ts" const feedItems = monitoredFeeds.mainnet // Centralized function to get feed category element using the shared config -const getFeedCategoryElement = (riskTier: string | undefined, isLoading: boolean = false) => { +const getFeedCategoryElement = (riskTier: string | undefined, isLoading = false) => { // Show gray circle while loading if (isLoading) { return ( - ⚪ + ⚪ ) } @@ -81,13 +85,16 @@ const DevModeWarning = () => { ) } -const getFeedCategoryWithComparison = (comparisonData: { - final: string | null - original: string | null - supabase: string | null - changed: boolean - devMode: boolean -}, isLoading: boolean = false) => { +const getFeedCategoryWithComparison = ( + comparisonData: { + final: string | null + original: string | null + supabase: string | null + changed: boolean + devMode: boolean + }, + isLoading = false +) => { const { final } = comparisonData // Always show the final category icon (or loading state) @@ -285,7 +292,7 @@ const DefaultTr = ({ network, metadata, showExtraDetails, batchedCategoryData }) if (metadata.proxyAddress || metadata.contractAddress) { const contractAddress = metadata.contractAddress || metadata.proxyAddress const networkIdentifier = network?.networkType || "unknown" - + if (contractAddress) { const batchResult = getFeedCategoryFromBatch( batchedCategoryData, @@ -310,7 +317,14 @@ const DefaultTr = ({ network, metadata, showExtraDetails, batchedCategoryData }) devMode: false, }) } - }, [batchedCategoryData, metadata.proxyAddress, metadata.contractAddress, metadata.feedCategory, network?.networkType, metadata.name]) + }, [ + batchedCategoryData, + metadata.proxyAddress, + metadata.contractAddress, + metadata.feedCategory, + network?.networkType, + metadata.name, + ]) const getFinalFeedCategory = () => { return getFeedCategoryWithComparison(comparisonData, false) @@ -526,7 +540,14 @@ const SmartDataTr = ({ network, metadata, showExtraDetails, batchedCategoryData devMode: false, }) } - }, [batchedCategoryData, metadata.proxyAddress, metadata.contractAddress, metadata.feedCategory, network?.networkType, metadata.name]) + }, [ + batchedCategoryData, + metadata.proxyAddress, + metadata.contractAddress, + metadata.feedCategory, + network?.networkType, + metadata.name, + ]) const getSmartDataFeedCategory = () => { return getFeedCategoryWithComparison(comparisonData, false) @@ -1231,17 +1252,17 @@ export const MainnetTable = ({ <> {isStreams && } {isSmartData && ( - )} {isDefault && ( - @@ -1414,25 +1435,25 @@ export const TestnetTable = ({ <> {isStreams && } {isSmartData && ( - )} {isDefault && ( - )} {isRates && ( - From b51d49eeb5d3fde22408e7a2d825b778626b6ae1 Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Tue, 12 Aug 2025 18:54:09 -0700 Subject: [PATCH 08/33] env naming --- src/db/supabase.ts | 4 ++-- src/pages/supabase-test.astro | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/db/supabase.ts b/src/db/supabase.ts index 094aeb4dad5..7c7edc926ea 100644 --- a/src/db/supabase.ts +++ b/src/db/supabase.ts @@ -1,7 +1,7 @@ import { createClient } from "@supabase/supabase-js" -const supabaseUrl = import.meta.env.SUPABASE_URL -const supabaseKey = import.meta.env.SUPABASE_ANON_KEY +const supabaseUrl = import.meta.env.PUBLIC_SUPABASE_URL +const supabaseKey = import.meta.env.PUBLIC_SUPABASE_ANON_KEY // Export a function that safely creates the client export function getSupabaseClient() { diff --git a/src/pages/supabase-test.astro b/src/pages/supabase-test.astro index c9ecf7025a2..e221b38c2a5 100644 --- a/src/pages/supabase-test.astro +++ b/src/pages/supabase-test.astro @@ -119,7 +119,7 @@ try {

Environment Variables Check:

  • PUBLIC_SUPABASE_URL: {import.meta.env.PUBLIC_SUPABASE_URL ? "✓ Set" : "✗ Not set"}
  • -
  • PUBLIC_SUPABASE_KEY: {import.meta.env.PUBLIC_SUPABASE_KEY ? "✓ Set" : "✗ Not set"}
  • +
  • PUBLIC_SUPABASE_KEY: {import.meta.env.PUBLIC_SUPABASE_ANON_KEY ? "✓ Set" : "✗ Not set"}
From 444d022e7b7811e361c3ba81508a96ec6e5afc11 Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Tue, 26 Aug 2025 01:03:13 -0700 Subject: [PATCH 09/33] removed tests --- src/db/feedCategories.ts | 279 +++++++-------------------------------- 1 file changed, 48 insertions(+), 231 deletions(-) diff --git a/src/db/feedCategories.ts b/src/db/feedCategories.ts index a9da0a55dc6..999823d0f45 100644 --- a/src/db/feedCategories.ts +++ b/src/db/feedCategories.ts @@ -1,12 +1,5 @@ import { supabase } from "./supabase.js" -// Development flag getter -function getDevTestFlag(): boolean { - return ( - typeof window !== "undefined" && (window as unknown as { CHAINLINK_DEV_MODE?: boolean }).CHAINLINK_DEV_MODE === true - ) -} - // Type for the docs_feeds_risk table type FeedRiskData = { proxy_address: string @@ -68,70 +61,40 @@ export const FEED_CATEGORY_CONFIG = { export const getDefaultCategories = () => Object.values(FEED_CATEGORY_CONFIG) export async function getFeedRiskData(network?: string): Promise { - if (!supabase) { - console.warn("getFeedRiskData: Supabase client not available") - return [] - } + if (!supabase) return [] try { let query = supabase.from("docs_feeds_risk").select("*") + if (network) query = query.eq("network", network) - if (network) { - query = query.eq("network", network) - } - - const { data, error } = await query.limit(1000) // Use reasonable limit instead of .single() - - if (error) { - console.error("Supabase query error:", error) - return [] - } - + const { data, error } = await query.limit(1000) + if (error) return [] return data || [] - } catch (error) { - console.error("Error in getFeedRiskData:", error) + } catch { return [] } } -export async function getFeedRiskTier(contractAddress: string, network: string, fallbackCategory?: string) { +export async function getFeedRiskTier( + contractAddress: string, + network: string, + fallbackCategory?: string +): Promise { try { - // Always fetch from Supabase if available, even in non-dev mode - if (!supabase) { - console.warn("Supabase client not available, using fallback") - return fallbackCategory || null - } + if (!supabase) return fallbackCategory || null - // Map the network identifier to match the database schema const { data, error } = await supabase .from("docs_feeds_risk") - .select("*") + .select("risk_status") .eq("proxy_address", contractAddress) .eq("network", network) .limit(1) - if (error) { - console.warn("Error fetching feed risk tier:", error.message) - return fallbackCategory || null - } + if (error) return fallbackCategory || null + if (!data || data.length === 0) return fallbackCategory || null - // Handle empty results - no matching records found - if (!data || data.length === 0) { - return fallbackCategory || null - } - - const supabaseRiskTier = data[0]?.risk_status || null - const finalCategory = supabaseRiskTier || fallbackCategory || null - - // Only log diffs when dev mode is enabled - const devMode = getDevTestFlag() - if (devMode && supabaseRiskTier && supabaseRiskTier !== fallbackCategory) { - console.log(`� ${contractAddress}: ${fallbackCategory} → ${supabaseRiskTier}`) - } - - return finalCategory - } catch (error) { - console.error("Error in getFeedRiskTier:", error) + return data[0]?.risk_status || fallbackCategory || null + } catch { return fallbackCategory || null } } @@ -139,7 +102,6 @@ export async function getFeedRiskTier(contractAddress: string, network: string, export async function getFeedCategories() { try { if (!supabase) { - console.warn("Supabase client not available, using fallback categories") return Object.values(FEED_CATEGORY_CONFIG).map((config) => ({ key: config.key, name: config.name, @@ -153,7 +115,6 @@ export async function getFeedCategories() { .neq("risk_status", "hidden") if (error) { - console.warn("Error fetching feed categories:", error.message) return Object.values(FEED_CATEGORY_CONFIG).map((config) => ({ key: config.key, name: config.name, @@ -185,8 +146,7 @@ export async function getFeedCategories() { }) return allCategories - } catch (e) { - console.error("Error fetching feed categories:", e) + } catch { return Object.values(FEED_CATEGORY_CONFIG).map((config) => ({ key: config.key, name: config.name, @@ -194,211 +154,70 @@ export async function getFeedCategories() { } } -// Enhanced function for dev mode that returns comparison data -export async function getFeedRiskTierWithComparison( - contractAddress: string, - network: string, - fallbackCategory?: string -): Promise<{ - final: string | null - original: string | null - supabase: string | null - changed: boolean - devMode: boolean -}> { - const devMode = getDevTestFlag() - const original = fallbackCategory || null - - try { - if (!supabase) { - return { - final: original, - original, - supabase: null, - changed: false, - devMode, - } - } +// Minimal batch result type +export type FeedTierResult = { final: string | null } - const { data, error } = await supabase - .from("docs_feeds_risk") - .select("*") - .eq("proxy_address", contractAddress) - .eq("network", network) - .limit(1) - - if (error) { - return { - final: original, - original, - supabase: null, - changed: false, - devMode, - } - } - - // Handle empty results - no matching records found - if (!data || data.length === 0) { - return { - final: original, - original, - supabase: null, - changed: false, - devMode, - } - } - - const supabaseRiskTier = data[0]?.risk_status || null - const final = supabaseRiskTier || original - const changed = supabaseRiskTier !== null && supabaseRiskTier !== original - - const result = { - final, - original, - supabase: supabaseRiskTier, - changed, - devMode, - } - - // Only log diffs in dev mode - if (devMode && changed) { - console.log(`🔄 ${contractAddress}: ${original} → ${supabaseRiskTier}`) - } - - return result - } catch (error) { - console.error("Error in getFeedRiskTierWithComparison:", error) - return { - final: original, - original, - supabase: null, - changed: false, - devMode, - } - } -} - -// Batched version for improved performance +/** + * Batched lookup that returns the final category per (address, network) wrapped in { final }. + * Map key format: `${contractAddress}-${network}` + */ export async function getFeedRiskTiersBatch( feedRequests: Array<{ contractAddress: string network: string fallbackCategory?: string }> -): Promise< - Map< - string, - { - final: string | null - original: string | null - supabase: string | null - changed: boolean - devMode: boolean - } - > -> { - const devMode = getDevTestFlag() - const resultMap = new Map< - string, - { - final: string | null - original: string | null - supabase: string | null - changed: boolean - devMode: boolean - } - >() +): Promise> { + const resultMap = new Map() - // If no Supabase client, return fallback values for all requests if (!supabase) { feedRequests.forEach(({ contractAddress, network, fallbackCategory }) => { - const key = `${contractAddress}-${network}` - resultMap.set(key, { - final: fallbackCategory || null, - original: fallbackCategory || null, - supabase: null, - changed: false, - devMode, - }) + resultMap.set(`${contractAddress}-${network}`, { final: fallbackCategory ?? null }) }) return resultMap } - // Get unique networks and contract addresses - const uniqueNetworks = Array.from(new Set(feedRequests.map((req) => req.network))) - const uniqueAddresses = Array.from(new Set(feedRequests.map((req) => req.contractAddress))) + const uniqueNetworks = Array.from(new Set(feedRequests.map((r) => r.network))) + const uniqueAddresses = Array.from(new Set(feedRequests.map((r) => r.contractAddress))) try { - // Single query for all addresses and networks const { data, error } = await supabase .from("docs_feeds_risk") - .select("*") + .select("proxy_address, network, risk_status") .in("proxy_address", uniqueAddresses) .in("network", uniqueNetworks) - .limit(1000) // Reasonable limit for batch operations + .limit(1000) if (error) { - console.error("Batch Supabase query error:", error) - // Return fallback values for all requests feedRequests.forEach(({ contractAddress, network, fallbackCategory }) => { - const key = `${contractAddress}-${network}` - resultMap.set(key, { - final: fallbackCategory || null, - original: fallbackCategory || null, - supabase: null, - changed: false, - devMode, - }) + resultMap.set(`${contractAddress}-${network}`, { final: fallbackCategory ?? null }) }) return resultMap } - // Create a lookup map from the batch results - const supabaseData = new Map() - data?.forEach((row) => { - const key = `${row.proxy_address}-${row.network}` - supabaseData.set(key, row.risk_status) + const supabaseData = new Map() + data?.forEach((row: any) => { + supabaseData.set(`${row.proxy_address}-${row.network}`, row.risk_status ?? null) }) - // Process each request using the batch data feedRequests.forEach(({ contractAddress, network, fallbackCategory }) => { const key = `${contractAddress}-${network}` - const original = fallbackCategory || null - const supabaseRiskTier = supabaseData.get(key) || null - const final = supabaseRiskTier || original - const changed = supabaseRiskTier !== null && supabaseRiskTier !== original - - resultMap.set(key, { - final, - original, - supabase: supabaseRiskTier, - changed, - devMode, - }) - - // Only log diffs in dev mode - if (devMode && changed) { - console.log(`🔄 ${contractAddress}: ${original} → ${supabaseRiskTier}`) - } + const supaTier = supabaseData.get(key) + resultMap.set(key, { final: supaTier ?? fallbackCategory ?? null }) }) - } catch (error) { - console.error("Error in getFeedRiskTiersBatch:", error) - // Return fallback values for all requests on error + + return resultMap + } catch { feedRequests.forEach(({ contractAddress, network, fallbackCategory }) => { - const key = `${contractAddress}-${network}` - resultMap.set(key, { - final: fallbackCategory || null, - original: fallbackCategory || null, - supabase: null, - changed: false, - devMode, - }) + resultMap.set(`${contractAddress}-${network}`, { final: fallbackCategory ?? null }) }) + return resultMap } - - return resultMap } +/** + * Server-safe helper: uses Supabase on the server; uses fallback on the client. + */ export async function getFeedRiskTierWithFallback( contractAddress: string, network: string, @@ -407,13 +226,11 @@ export async function getFeedRiskTierWithFallback( try { if (typeof window === "undefined") { const riskTier = await getFeedRiskTier(contractAddress, network, fallbackCategory) - return riskTier || fallbackCategory + return riskTier ?? fallbackCategory } else { - console.log("Client-side context: using fallback category instead of Supabase") return fallbackCategory } - } catch (error) { - console.warn("Failed to fetch risk tier from Supabase, using fallback:", error) + } catch { return fallbackCategory } } @@ -431,9 +248,9 @@ export async function testSupabaseConnection() { const { data, error } = await supabase.from("docs_feeds_risk").select("*").limit(1) return { - success: !error || error.code === "PGRST116", + success: !error || (error as any)?.code === "PGRST116", data, - error: error?.message, + error: (error as any)?.message, } } catch (e) { return { From fb9d61ea67344c3a9261059a15de11a14aadb399 Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Tue, 26 Aug 2025 01:09:19 -0700 Subject: [PATCH 10/33] feedCategories refactor --- src/db/feedCategories.ts | 196 ++++++++++++++++++++------------------- 1 file changed, 102 insertions(+), 94 deletions(-) diff --git a/src/db/feedCategories.ts b/src/db/feedCategories.ts index 999823d0f45..e002a3cd929 100644 --- a/src/db/feedCategories.ts +++ b/src/db/feedCategories.ts @@ -1,13 +1,27 @@ import { supabase } from "./supabase.js" -// Type for the docs_feeds_risk table +/* =========================== + Types + =========================== */ + +type FeedRiskRow = { + proxy_address: string + network: string + risk_status: string | null +} + type FeedRiskData = { proxy_address: string network: string risk_status: string } -// Centralized category configuration +export type FeedTierResult = { final: string | null } + +/* =========================== + Category Config + =========================== */ + export const FEED_CATEGORY_CONFIG = { low: { key: "low", @@ -58,108 +72,108 @@ export const FEED_CATEGORY_CONFIG = { }, } as const -export const getDefaultCategories = () => Object.values(FEED_CATEGORY_CONFIG) +export type CategoryKey = keyof typeof FEED_CATEGORY_CONFIG + +/* =========================== + Small helpers + =========================== */ +const TABLE = "docs_feeds_risk" + +const normalizeKey = (v?: string | null): CategoryKey | undefined => { + if (!v) return undefined + const key = v.toLowerCase() as CategoryKey + return key in FEED_CATEGORY_CONFIG ? key : undefined +} + +const chooseTier = (dbTier: string | null | undefined, fallback?: string): string | null => dbTier ?? fallback ?? null + +const defaultCategoryList = () => Object.values(FEED_CATEGORY_CONFIG).map(({ key, name }) => ({ key, name })) + +/** Optional convenience for consumers */ +export function getCategoryMeta(category?: string | null) { + const key = normalizeKey(category) + return key ? FEED_CATEGORY_CONFIG[key] : undefined +} + +/* =========================== + Public API + =========================== */ + +export const getDefaultCategories = defaultCategoryList + +/** Fetch rows (optionally filtered by network). Returns [] on any error. */ export async function getFeedRiskData(network?: string): Promise { if (!supabase) return [] try { - let query = supabase.from("docs_feeds_risk").select("*") + let query = supabase.from(TABLE).select("*") if (network) query = query.eq("network", network) const { data, error } = await query.limit(1000) - if (error) return [] - return data || [] + if (error || !data) return [] + + // Narrow to non-null risk_status to match FeedRiskData type + return (data as FeedRiskRow[]).filter((r): r is FeedRiskData => !!r.risk_status) } catch { return [] } } +/** Single lookup with fallback-first behavior. */ export async function getFeedRiskTier( contractAddress: string, network: string, fallbackCategory?: string ): Promise { try { - if (!supabase) return fallbackCategory || null + if (!supabase) return chooseTier(null, fallbackCategory) const { data, error } = await supabase - .from("docs_feeds_risk") + .from(TABLE) .select("risk_status") .eq("proxy_address", contractAddress) .eq("network", network) .limit(1) - if (error) return fallbackCategory || null - if (!data || data.length === 0) return fallbackCategory || null - - return data[0]?.risk_status || fallbackCategory || null + if (error || !data?.length) return chooseTier(null, fallbackCategory) + return chooseTier(data[0]?.risk_status, fallbackCategory) } catch { - return fallbackCategory || null + return chooseTier(null, fallbackCategory) } } +/** Merge static categories with those dynamically present in the table. */ export async function getFeedCategories() { try { - if (!supabase) { - return Object.values(FEED_CATEGORY_CONFIG).map((config) => ({ - key: config.key, - name: config.name, - })) - } + if (!supabase) return defaultCategoryList() const { data, error } = await supabase - .from("docs_feeds_risk") + .from(TABLE) .select("risk_status") .not("risk_status", "is", null) .neq("risk_status", "hidden") - if (error) { - return Object.values(FEED_CATEGORY_CONFIG).map((config) => ({ - key: config.key, - name: config.name, - })) - } - - const uniqueStatuses = Array.from(new Set(data.map((item) => item.risk_status).filter(Boolean))).filter( - (status: string) => status.toLowerCase() !== "hidden" - ) + if (error || !data) return defaultCategoryList() - const dynamicCategories = uniqueStatuses.map((status: string) => { - const config = FEED_CATEGORY_CONFIG[status.toLowerCase() as keyof typeof FEED_CATEGORY_CONFIG] - return { - key: status.toLowerCase() as keyof typeof FEED_CATEGORY_CONFIG, - name: config?.name || status, - } - }) + const dynamic = Array.from( + new Set(data.map((d) => normalizeKey(d.risk_status)).filter(Boolean) as CategoryKey[]) + ).map((key) => ({ key, name: FEED_CATEGORY_CONFIG[key].name })) - const defaultCategories = Object.values(FEED_CATEGORY_CONFIG).map((config) => ({ - key: config.key, - name: config.name, - })) + // Dedup by key while keeping all defaults first + const byKey = new Map() + defaultCategoryList().forEach((c) => byKey.set(c.key, c)) + dynamic.forEach((c) => byKey.set(c.key, c)) - const allCategories = [...defaultCategories] - dynamicCategories.forEach((dynCat) => { - if (!allCategories.find((cat) => cat.key === dynCat.key)) { - allCategories.push(dynCat as (typeof allCategories)[0]) - } - }) - - return allCategories + return Array.from(byKey.values()) } catch { - return Object.values(FEED_CATEGORY_CONFIG).map((config) => ({ - key: config.key, - name: config.name, - })) + return defaultCategoryList() } } -// Minimal batch result type -export type FeedTierResult = { final: string | null } - /** - * Batched lookup that returns the final category per (address, network) wrapped in { final }. - * Map key format: `${contractAddress}-${network}` + * Batch lookup: returns a Map of `${address}-${network}` → { final }. + * Uses DB value when present; otherwise uses per-item fallback. */ export async function getFeedRiskTiersBatch( feedRequests: Array<{ @@ -168,50 +182,50 @@ export async function getFeedRiskTiersBatch( fallbackCategory?: string }> ): Promise> { - const resultMap = new Map() + const out = new Map() + const keyFor = (addr: string, net: string) => `${addr}-${net}` if (!supabase) { - feedRequests.forEach(({ contractAddress, network, fallbackCategory }) => { - resultMap.set(`${contractAddress}-${network}`, { final: fallbackCategory ?? null }) - }) - return resultMap + feedRequests.forEach(({ contractAddress, network, fallbackCategory }) => + out.set(keyFor(contractAddress, network), { final: chooseTier(null, fallbackCategory) }) + ) + return out } - const uniqueNetworks = Array.from(new Set(feedRequests.map((r) => r.network))) - const uniqueAddresses = Array.from(new Set(feedRequests.map((r) => r.contractAddress))) + const networks = Array.from(new Set(feedRequests.map((r) => r.network))) + const addresses = Array.from(new Set(feedRequests.map((r) => r.contractAddress))) try { const { data, error } = await supabase - .from("docs_feeds_risk") + .from(TABLE) .select("proxy_address, network, risk_status") - .in("proxy_address", uniqueAddresses) - .in("network", uniqueNetworks) + .in("proxy_address", addresses) + .in("network", networks) .limit(1000) if (error) { - feedRequests.forEach(({ contractAddress, network, fallbackCategory }) => { - resultMap.set(`${contractAddress}-${network}`, { final: fallbackCategory ?? null }) - }) - return resultMap + feedRequests.forEach(({ contractAddress, network, fallbackCategory }) => + out.set(keyFor(contractAddress, network), { final: chooseTier(null, fallbackCategory) }) + ) + return out } - const supabaseData = new Map() - data?.forEach((row: any) => { - supabaseData.set(`${row.proxy_address}-${row.network}`, row.risk_status ?? null) - }) + const lookup = new Map() + ;(data as FeedRiskRow[] | null)?.forEach((row) => + lookup.set(keyFor(row.proxy_address, row.network), row.risk_status ?? null) + ) feedRequests.forEach(({ contractAddress, network, fallbackCategory }) => { - const key = `${contractAddress}-${network}` - const supaTier = supabaseData.get(key) - resultMap.set(key, { final: supaTier ?? fallbackCategory ?? null }) + const key = keyFor(contractAddress, network) + out.set(key, { final: chooseTier(lookup.get(key), fallbackCategory) }) }) - return resultMap + return out } catch { - feedRequests.forEach(({ contractAddress, network, fallbackCategory }) => { - resultMap.set(`${contractAddress}-${network}`, { final: fallbackCategory ?? null }) - }) - return resultMap + feedRequests.forEach(({ contractAddress, network, fallbackCategory }) => + out.set(keyFor(contractAddress, network), { final: chooseTier(null, fallbackCategory) }) + ) + return out } } @@ -227,26 +241,20 @@ export async function getFeedRiskTierWithFallback( if (typeof window === "undefined") { const riskTier = await getFeedRiskTier(contractAddress, network, fallbackCategory) return riskTier ?? fallbackCategory - } else { - return fallbackCategory } + return fallbackCategory } catch { return fallbackCategory } } +/** Lightweight connectivity check. */ export async function testSupabaseConnection() { try { if (!supabase) { - return { - success: false, - data: null, - error: "Supabase client not available", - } + return { success: false, data: null, error: "Supabase client not available" } } - - const { data, error } = await supabase.from("docs_feeds_risk").select("*").limit(1) - + const { data, error } = await supabase.from(TABLE).select("*").limit(1) return { success: !error || (error as any)?.code === "PGRST116", data, From 524439be245c46e851c2cf1a741820c52c9a8171 Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Tue, 26 Aug 2025 01:22:14 -0700 Subject: [PATCH 11/33] remove tests + refactor --- src/db/feedEnhancement.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db/feedEnhancement.ts b/src/db/feedEnhancement.ts index 09b92f37b1e..185fa926f99 100644 --- a/src/db/feedEnhancement.ts +++ b/src/db/feedEnhancement.ts @@ -22,7 +22,7 @@ export async function enhanceFeedWithRiskTier(feed: FeedData, network: string): // Return the enhanced feed object with risk tier from Supabase const feedWithRiskTier = { ...feed, - feedCategory: feedRiskTier, + feedCategory: feedRiskTier ?? undefined, } return feedWithRiskTier From 185b191e62ecf7c0d0dc76081e28434deca00048 Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Tue, 26 Aug 2025 02:08:46 -0700 Subject: [PATCH 12/33] tables.tsx cleanup --- src/features/feeds/components/Tables.tsx | 517 +++++------------------ 1 file changed, 106 insertions(+), 411 deletions(-) diff --git a/src/features/feeds/components/Tables.tsx b/src/features/feeds/components/Tables.tsx index 1e476069cbb..6cb692411ee 100644 --- a/src/features/feeds/components/Tables.tsx +++ b/src/features/feeds/components/Tables.tsx @@ -1,5 +1,5 @@ /** @jsxImportSource preact */ -import { useState, useEffect } from "preact/hooks" +import { useState } from "preact/hooks" import { Fragment } from "preact" import feedList from "./FeedList.module.css" import { clsx } from "~/lib/clsx/clsx.ts" @@ -13,29 +13,15 @@ import { FEED_CATEGORY_CONFIG } from "../../../db/feedCategories.js" import { useBatchedFeedCategories, getFeedCategoryFromBatch, - type FeedCategoryData, } from "./useBatchedFeedCategories.ts" const feedItems = monitoredFeeds.mainnet -// Centralized function to get feed category element using the shared config -const getFeedCategoryElement = (riskTier: string | undefined, isLoading = false) => { - // Show gray circle while loading - if (isLoading) { - return ( - - ⚪ - - ) - } - +// Render a category icon/link from the config +const getFeedCategoryElement = (riskTier: string | undefined) => { if (!riskTier) return "" - - const lowerTier = riskTier.toLowerCase() - const category = FEED_CATEGORY_CONFIG[lowerTier] - + const category = FEED_CATEGORY_CONFIG[riskTier.toLowerCase()] if (!category) return "" - return ( @@ -45,110 +31,9 @@ const getFeedCategoryElement = (riskTier: string | undefined, isLoading = false) ) } -// Dev mode warning banner -const DevModeWarning = () => { - const [showDevMode, setShowDevMode] = useState(false) - - useEffect(() => { - const checkDevMode = () => { - const devMode = - typeof window !== "undefined" && - (window as unknown as { CHAINLINK_DEV_MODE?: boolean }).CHAINLINK_DEV_MODE === true - setShowDevMode(devMode) - } - - checkDevMode() - // Check every 2 seconds in case dev mode is toggled - const interval = setInterval(checkDevMode, 2000) - return () => clearInterval(interval) - }, []) - - if (!showDevMode) return null - - return ( -
- 🔬 DEVELOPER TEST MODE ACTIVE - Categories show original → Supabase comparison. Disable with{" "} - - window.CHAINLINK_DEV_MODE = false - -
- ) -} -const getFeedCategoryWithComparison = ( - comparisonData: { - final: string | null - original: string | null - supabase: string | null - changed: boolean - devMode: boolean - }, - isLoading = false -) => { - const { final } = comparisonData - - // Always show the final category icon (or loading state) - return getFeedCategoryElement(final || undefined, isLoading) -} - -// New function to show comparison text under feed name -const getComparisonText = (comparisonData: { - final: string | null - original: string | null - supabase: string | null - changed: boolean - devMode: boolean -}) => { - const { original, supabase, changed, devMode } = comparisonData - - if (!devMode || !changed) { - return null - } - - const getIconForCategory = (category: string | null) => { - if (!category) return "❓" - const config = FEED_CATEGORY_CONFIG[category.toLowerCase()] - return config?.icon || "❓" - } - - const originalIcon = getIconForCategory(original) - const supabaseIcon = getIconForCategory(supabase) - - return ( -
- 🔬 -
- {originalIcon} {original || "none"} → {supabaseIcon} {supabase || "none"} -
- ) -} - const Pagination = ({ addrPerPage, totalAddr, paginate, currentPage, firstAddr, lastAddr }) => { const pageNumbers: number[] = [] - - for (let i = 1; i <= Math.ceil(totalAddr / addrPerPage); i++) { - pageNumbers.push(i) - } + for (let i = 1; i <= Math.ceil(totalAddr / addrPerPage); i++) pageNumbers.push(i) return (
@@ -181,11 +66,7 @@ const Pagination = ({ addrPerPage, totalAddr, paginate, currentPage, firstAddr, const handleClick = (e, additionalInfo) => { e.preventDefault() - - const dataLayerEvent = { - event: "docs_product_interaction", - ...additionalInfo, - } + const dataLayerEvent = { event: "docs_product_interaction", ...additionalInfo } window.dataLayer = window.dataLayer || [] window.dataLayer.push(dataLayerEvent) } @@ -202,7 +83,6 @@ const CopyableAddress = ({ environment: string }) => { if (!address) return null - return (
@@ -264,81 +139,23 @@ const DefaultTHead = ({ showExtraDetails, networkName }: { showExtraDetails: boo } const DefaultTr = ({ network, metadata, showExtraDetails, batchedCategoryData }) => { - // Use batched category data instead of individual API calls - const [comparisonData, setComparisonData] = useState({ - final: metadata.feedCategory, - original: metadata.feedCategory, - supabase: null, - changed: false, - devMode: false, - }) - - // No longer need loading state since batch loading is handled at network level - - // Effect to get data from batch results when they're available - useEffect(() => { - // For testnet or networks without batch data, just use fallback - if (!batchedCategoryData || batchedCategoryData.size === 0) { - setComparisonData({ - final: metadata.feedCategory, - original: metadata.feedCategory, - supabase: null, - changed: false, - devMode: false, - }) - return - } - - if (metadata.proxyAddress || metadata.contractAddress) { - const contractAddress = metadata.contractAddress || metadata.proxyAddress - const networkIdentifier = network?.networkType || "unknown" - - if (contractAddress) { - const batchResult = getFeedCategoryFromBatch( - batchedCategoryData, - contractAddress, - networkIdentifier, - metadata.feedCategory - ) - setComparisonData(batchResult) - - // Only log differences in dev mode - if (batchResult.devMode && batchResult.changed) { - console.log(`📊 ${metadata.name}: ${batchResult.original} → ${batchResult.supabase}`) - } - } - } else { - // Fallback to original category if no proxy address - setComparisonData({ - final: metadata.feedCategory, - original: metadata.feedCategory, - supabase: null, - changed: false, - devMode: false, - }) - } - }, [ - batchedCategoryData, - metadata.proxyAddress, - metadata.contractAddress, - metadata.feedCategory, - network?.networkType, - metadata.name, - ]) - - const getFinalFeedCategory = () => { - return getFeedCategoryWithComparison(comparisonData, false) - } + // Resolve the final category using batched data, falling back to metadata if absent + const contractAddress = metadata.contractAddress || metadata.proxyAddress + const networkIdentifier = network?.networkType || "unknown" + const finalTier = + contractAddress && batchedCategoryData?.size + ? getFeedCategoryFromBatch(batchedCategoryData, contractAddress, networkIdentifier, metadata.feedCategory)?.final ?? + metadata.feedCategory + : metadata.feedCategory return ( @@ -572,9 +342,8 @@ const SmartDataTr = ({ network, metadata, showExtraDetails, batchedCategoryData return "" })}
- {getSmartDataFeedCategory()} {metadata.name} + {getFeedCategoryElement(finalTier || undefined)} {metadata.name}
- {getComparisonText(comparisonData)} {metadata.docs.shutdownDate && (

@@ -742,20 +511,15 @@ export const StreamsNetworkAddressesTable = () => { const [searchValue, setSearchValue] = useState("") const normalizedSearch = searchValue.toLowerCase().replaceAll(" ", "") - const match = (value?: string) => !!value && value.toLowerCase().replaceAll(" ", "").includes(normalizedSearch) const filteredNetworks = StreamsNetworksData.filter((network) => { if (!normalizedSearch) return true - const networkMatch = match(network.network) - const mainnetLabel = network.mainnet?.label const testnetLabel = network.testnet?.label - const mainnetAddr = network.isSolana ? network.mainnet?.verifierProgramId : network.mainnet?.verifierProxy const testnetAddr = network.isSolana ? network.testnet?.verifierProgramId : network.testnet?.verifierProxy - return networkMatch || match(mainnetLabel) || match(testnetLabel) || match(mainnetAddr) || match(testnetAddr) }) @@ -894,12 +658,7 @@ export const StreamsNetworkAddressesTable = () => {
- {getFinalFeedCategory()} + {getFeedCategoryElement(finalTier || undefined)} {metadata.name}
- {getComparisonText(comparisonData)} {metadata.secondaryProxyAddress && (
) const SmartDataTr = ({ network, metadata, showExtraDetails, batchedCategoryData }) => { - // Check if this is an MVR feed + // MVR badge only when explicitly flagged and has decoding schema const hasDecoding = Array.isArray(metadata.docs?.decoding) && metadata.docs.decoding.length > 0 - const isMVRFlagSet = metadata.docs?.isMVR === true - - // Only show MVR badge if explicitly flagged as MVR - const finalIsMVRFeed = isMVRFlagSet && hasDecoding - - // Use batched category data instead of individual API calls - const [comparisonData, setComparisonData] = useState({ - final: metadata.feedCategory, - original: metadata.feedCategory, - supabase: null, - changed: false, - devMode: false, - }) - - // No longer need loading state since batch loading is handled at network level + const finalIsMVRFeed = metadata.docs?.isMVR === true && hasDecoding - useEffect(() => { - if (batchedCategoryData && metadata.proxyAddress) { - const networkIdentifier = network?.networkType || "unknown" - const contractAddress = metadata.contractAddress || metadata.proxyAddress - - if (contractAddress) { - const batchResult = getFeedCategoryFromBatch( - batchedCategoryData, - contractAddress, - networkIdentifier, - metadata.feedCategory - ) - setComparisonData(batchResult) - - // Only log differences in dev mode - if (batchResult.devMode && batchResult.changed) { - console.log(`📊 SmartData ${metadata.name}: ${batchResult.original} → ${batchResult.supabase}`) - } - } - } else { - // Fallback to original category if no batch data - setComparisonData({ - final: metadata.feedCategory, - original: metadata.feedCategory, - supabase: null, - changed: false, - devMode: false, - }) - } - }, [ - batchedCategoryData, - metadata.proxyAddress, - metadata.contractAddress, - metadata.feedCategory, - network?.networkType, - metadata.name, - ]) - - const getSmartDataFeedCategory = () => { - return getFeedCategoryWithComparison(comparisonData, false) - } + // Resolve final category from batch (fallback to metadata) + const contractAddress = metadata.contractAddress || metadata.proxyAddress + const networkIdentifier = network?.networkType || "unknown" + const finalTier = + contractAddress && batchedCategoryData?.size + ? getFeedCategoryFromBatch(batchedCategoryData, contractAddress, networkIdentifier, metadata.feedCategory)?.final ?? + metadata.feedCategory + : metadata.feedCategory return (
@@ -925,14 +684,8 @@ const StreamsTHead = () => ( ) const streamsCategoryMap = { - custom: { - text: "Custom", - link: "/data-streams/developer-responsibilities/#custom-data-streams", - }, - new_token: { - text: "New token", - link: "/data-streams/developer-responsibilities#new-token-data-streams", - }, + custom: { text: "Custom", link: "/data-streams/developer-responsibilities/#custom-data-streams" }, + new_token: { text: "New token", link: "/data-streams/developer-responsibilities#new-token-data-streams" }, } const StreamsTr = ({ metadata, isMainnet }) => ( @@ -941,11 +694,7 @@ const StreamsTr = ({ metadata, isMainnet }) => (
{metadata.pair[0]}/{metadata.pair[1]} {metadata.feedType === "Crypto-DEX" && ( - + DEX State Price )} @@ -983,30 +732,22 @@ const StreamsTr = ({ metadata, isMainnet }) => (
{isMainnet && metadata.docs.clicProductName && (
-
- Full name: -
+
Full name:
{metadata.docs.clicProductName}
)} {metadata.assetName && (
-
- Asset name: -
+
Asset name:
{metadata.assetName}
)} {metadata.docs.assetClass ? (
-
- Asset class: -
+
Asset class:
{metadata.docs.assetClass} - {metadata.docs.assetSubClass && - metadata.docs.assetSubClass !== "Crypto" && - metadata.docs.assetSubClass !== "Equities" + {metadata.docs.assetSubClass && metadata.docs.assetSubClass !== "Crypto" && metadata.docs.assetSubClass !== "Equities" ? " - " + metadata.docs.assetSubClass : ""}
@@ -1014,21 +755,15 @@ const StreamsTr = ({ metadata, isMainnet }) => ( ) : null} {metadata.docs.marketHours ? (
-
- Market hours: -
+
Market hours:
- - {metadata.docs.marketHours} - + {metadata.docs.marketHours}
) : null} {streamsCategoryMap[metadata.feedCategory] ? ( ) : null} {metadata.feedType === "Crypto-DEX" && ( - )}{" "} + )} {metadata.feedType === "Equities" && (
-
- Report Schema: -
+
Report Schema:
RWA Schema (v8) @@ -1119,7 +846,6 @@ export const MainnetTable = ({ }) => { if (!network.metadata) return null - // Use batched feed category loading for this network const { data: batchedCategoryData, isLoading: isBatchLoading } = useBatchedFeedCategories(network) const isStreams = dataFeedType === "streamsCrypto" || dataFeedType === "streamsRwa" @@ -1130,22 +856,14 @@ export const MainnetTable = ({ const filteredMetadata = network.metadata .sort((a, b) => (a.name < b.name ? -1 : 1)) .filter((metadata) => { - if (showOnlySVR && !metadata.secondaryProxyAddress) { - return false - } - + if (showOnlySVR && !metadata.secondaryProxyAddress) return false if (isDeprecating) return !!metadata.docs.shutdownDate if (dataFeedType === "streamsCrypto") { const isValidStreamsFeed = metadata.contractType === "verifier" && (metadata.docs.feedType === "Crypto" || metadata.docs.feedType === "Crypto-DEX") - - if (showOnlyDEXFeeds) { - return isValidStreamsFeed && metadata.docs.feedType === "Crypto-DEX" - } - - return isValidStreamsFeed + return showOnlyDEXFeeds ? isValidStreamsFeed && metadata.docs.feedType === "Crypto-DEX" : isValidStreamsFeed } if (dataFeedType === "streamsRwa") { @@ -1153,10 +871,7 @@ export const MainnetTable = ({ } if (isSmartData) { - if (showOnlyMVRFeeds) { - return !metadata.docs?.hidden && metadata.docs?.isMVR === true - } - + if (showOnlyMVRFeeds) return !metadata.docs?.hidden && metadata.docs?.isMVR === true return ( !metadata.docs?.hidden && (metadata.docs.productType === "Proof of Reserve" || @@ -1166,7 +881,7 @@ export const MainnetTable = ({ ) } - // Exclude MVR feeds from default view + // Default table excludes SmartData feeds return ( !metadata.docs.porType && metadata.contractType !== "verifier" && @@ -1177,16 +892,11 @@ export const MainnetTable = ({ }) .filter((metadata) => { if (isSmartData) { - // Include MVR category in SmartData filter - if (selectedFeedCategories.includes("MVR") && metadata.docs?.isMVR) { - return true - } - - const included = + if (selectedFeedCategories.includes("MVR") && metadata.docs?.isMVR) return true + return ( selectedFeedCategories.length === 0 || (metadata.docs.productType && selectedFeedCategories.includes(metadata.docs.productType)) - - return included + ) } return ( selectedFeedCategories.length === 0 || @@ -1196,28 +906,16 @@ export const MainnetTable = ({ .filter( (metadata) => metadata.name.toLowerCase().replaceAll(" ", "").includes(searchValue.toLowerCase().replaceAll(" ", "")) || - metadata.proxyAddress - ?.toLowerCase() - .replaceAll(" ", "") - .includes(searchValue.toLowerCase().replaceAll(" ", "")) || + metadata.proxyAddress?.toLowerCase().replaceAll(" ", "").includes(searchValue.toLowerCase().replaceAll(" ", "")) || metadata.secondaryProxyAddress ?.toLowerCase() .replaceAll(" ", "") .includes(searchValue.toLowerCase().replaceAll(" ", "")) || metadata.assetName.toLowerCase().replaceAll(" ", "").includes(searchValue.toLowerCase().replaceAll(" ", "")) || metadata.feedType.toLowerCase().replaceAll(" ", "").includes(searchValue.toLowerCase().replaceAll(" ", "")) || - metadata.docs.porType - ?.toLowerCase() - .replaceAll(" ", "") - .includes(searchValue.toLowerCase().replaceAll(" ", "")) || - metadata.docs.porAuditor - ?.toLowerCase() - .replaceAll(" ", "") - .includes(searchValue.toLowerCase().replaceAll(" ", "")) || - metadata.docs.porSource - ?.toLowerCase() - .replaceAll(" ", "") - .includes(searchValue.toLowerCase().replaceAll(" ", "")) || + metadata.docs.porType?.toLowerCase().replaceAll(" ", "").includes(searchValue.toLowerCase().replaceAll(" ", "")) || + metadata.docs.porAuditor?.toLowerCase().replaceAll(" ", "").includes(searchValue.toLowerCase().replaceAll(" ", "")) || + metadata.docs.porSource?.toLowerCase().replaceAll(" ", "").includes(searchValue.toLowerCase().replaceAll(" ", "")) || metadata.feedId?.toLowerCase().replaceAll(" ", "").includes(searchValue.toLowerCase().replaceAll(" ", "")) ) @@ -1225,7 +923,6 @@ export const MainnetTable = ({ return ( <> - {isBatchLoading &&

Loading...

}
@@ -1297,9 +994,7 @@ export const TestnetTable = ({ lastAddr = 1000, addrPerPage = 8, currentPage = 1, - paginate = () => { - /* Default no-op function */ - }, + paginate = () => {}, searchValue = "", showOnlyMVRFeeds, showOnlyDEXFeeds, @@ -1319,7 +1014,6 @@ export const TestnetTable = ({ }) => { if (!network.metadata) return null - // Use batched feed category loading for this network const { data: batchedCategoryData, isLoading: isBatchLoading } = useBatchedFeedCategories(network) const isStreams = dataFeedType === "streamsCrypto" || dataFeedType === "streamsRwa" @@ -1335,25 +1029,15 @@ export const TestnetTable = ({ const isValidStreamsFeed = metadata.contractType === "verifier" && (metadata.feedType === "Crypto" || metadata.feedType === "Crypto-DEX") - - if (showOnlyDEXFeeds) { - return isValidStreamsFeed && metadata.feedType === "Crypto-DEX" - } - - return isValidStreamsFeed + return showOnlyDEXFeeds ? isValidStreamsFeed && metadata.feedType === "Crypto-DEX" : isValidStreamsFeed } - if (dataFeedType === "streamsRwa") { return metadata.contractType === "verifier" && metadata.feedType === "Equities" } } if (isSmartData) { - if (showOnlyMVRFeeds) { - return !metadata.docs?.hidden && metadata.docs?.isMVR === true - } - - // Otherwise, include all SmartData feeds (MVR, PoR, NAVLink, SmartAUM) + if (showOnlyMVRFeeds) return !metadata.docs?.hidden && metadata.docs?.isMVR === true return ( !metadata.docs?.hidden && (metadata.docs.productType === "Proof of Reserve" || @@ -1366,7 +1050,7 @@ export const TestnetTable = ({ if (isRates) return !!(metadata.docs.productType === "Rates" || metadata.docs.productSubType === "Realized Volatility") - // Exclude MVR feeds from default view + // Default table excludes SmartData and Rates return ( !metadata.feedId && !metadata.docs.porType && @@ -1379,15 +1063,11 @@ export const TestnetTable = ({ }) .filter((metadata) => { if (isSmartData) { - if (selectedFeedCategories.includes("MVR") && metadata.docs?.isMVR) { - return true - } - - const included = + if (selectedFeedCategories.includes("MVR") && metadata.docs?.isMVR) return true + return ( selectedFeedCategories.length === 0 || (metadata.docs.productType && selectedFeedCategories.includes(metadata.docs.productType)) - - return included + ) } return ( selectedFeedCategories.length === 0 || @@ -1431,35 +1111,50 @@ export const TestnetTable = ({ {isDefault && } {isRates && } - {slicedFilteredMetadata.map((metadata) => ( - <> - {isStreams && } - {isSmartData && ( - - )} - {isDefault && ( - - )} - {isRates && ( - - )} - - ))} + {slicedFilteredMetadata.map((metadata) => { + // For rows that display category, compute the final tier once + const contractAddress = metadata.contractAddress || metadata.proxyAddress + const networkIdentifier = network?.networkType || "unknown" + const finalTier = + contractAddress && batchedCategoryData?.size + ? getFeedCategoryFromBatch( + batchedCategoryData, + contractAddress, + networkIdentifier, + metadata.feedCategory + )?.final ?? metadata.feedCategory + : metadata.feedCategory + + return ( + <> + {isStreams && } + {isSmartData && ( + + )} + {isDefault && ( + + )} + {isRates && ( + + )} + + ) + })} )} @@ -1475,4 +1170,4 @@ export const TestnetTable = ({ /> ) -} +} \ No newline at end of file From 68aac9375216eeb2e2b39b50e45b7653268a0cc0 Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Tue, 26 Aug 2025 02:13:03 -0700 Subject: [PATCH 13/33] remove tests --- .../components/useBatchedFeedCategories.ts | 93 ++++++------------- 1 file changed, 26 insertions(+), 67 deletions(-) diff --git a/src/features/feeds/components/useBatchedFeedCategories.ts b/src/features/feeds/components/useBatchedFeedCategories.ts index aa3e992d3d3..2f95db2ed9a 100644 --- a/src/features/feeds/components/useBatchedFeedCategories.ts +++ b/src/features/feeds/components/useBatchedFeedCategories.ts @@ -2,16 +2,12 @@ import { useEffect, useState } from "preact/hooks" import { getFeedRiskTiersBatch } from "~/db/feedCategories.js" import { ChainNetwork } from "~/features/data/chains.ts" -// Type definition for the comparison data +// Final category only export type FeedCategoryData = { final: string | null - original: string | null - supabase: string | null - changed: boolean - devMode: boolean } -// Type for the batched feed category hook +// Batched feed category hook state type BatchedFeedCategoriesState = { data: Map isLoading: boolean @@ -19,8 +15,8 @@ type BatchedFeedCategoriesState = { } /** - * Custom hook to batch-load feed category data for all feeds in a network - * This replaces individual API calls with a single batched request per network + * Batch-load feed category data for all feeds in a network. + * Uses DB values when available; falls back to per-item defaults otherwise. */ export function useBatchedFeedCategories(network: ChainNetwork | null): BatchedFeedCategoriesState { const [state, setState] = useState({ @@ -31,64 +27,44 @@ export function useBatchedFeedCategories(network: ChainNetwork | null): BatchedF useEffect(() => { if (!network || !network.metadata) { - setState({ - data: new Map(), - isLoading: false, - error: null, - }) + setState({ data: new Map(), isLoading: false, error: null }) return } - // Only load batch data for mainnet networks - testnet doesn't have risk categories + // Only load batch data for mainnet networks if (network.networkType !== "mainnet") { - setState({ - data: new Map(), - isLoading: false, - error: null, - }) + setState({ data: new Map(), isLoading: false, error: null }) return } const loadBatchedCategories = async () => { - setState((prev) => ({ - ...prev, - isLoading: true, - error: null, - })) + setState((prev) => ({ ...prev, isLoading: true, error: null })) try { - // Collect all feed requests for this network + // Collect requests for this network const feedRequests: Array<{ contractAddress: string network: string fallbackCategory?: string }> = [] - if (network.metadata) { - network.metadata.forEach((metadata) => { - // Only process feeds that have proxy addresses - const contractAddress = metadata.contractAddress || metadata.proxyAddress - if (contractAddress) { - feedRequests.push({ - contractAddress, - network: network.networkType || "unknown", - fallbackCategory: metadata.feedCategory, - }) - } - }) - } + network.metadata?.forEach((metadata) => { + const contractAddress = metadata.contractAddress || metadata.proxyAddress + if (contractAddress) { + feedRequests.push({ + contractAddress, + network: network.networkType || "unknown", + fallbackCategory: metadata.feedCategory, + }) + } + }) - // Skip batch request if no feeds with addresses if (feedRequests.length === 0) { - setState({ - data: new Map(), - isLoading: false, - error: null, - }) + setState({ data: new Map(), isLoading: false, error: null }) return } - // Make the batched API call + // Batched DB lookup (returns Map) const batchResults = await getFeedRiskTiersBatch(feedRequests) setState({ @@ -113,7 +89,7 @@ export function useBatchedFeedCategories(network: ChainNetwork | null): BatchedF } /** - * Helper function to get feed category data from batched results + * Get final category from batched results with fallback. */ export function getFeedCategoryFromBatch( batchData: Map, @@ -121,30 +97,13 @@ export function getFeedCategoryFromBatch( network: string, fallbackCategory?: string ): FeedCategoryData { - // If no batch data (e.g., testnet), return fallback immediately if (!batchData || batchData.size === 0) { - return { - final: fallbackCategory || null, - original: fallbackCategory || null, - supabase: null, - changed: false, - devMode: false, - } + return { final: fallbackCategory ?? null } } const key = `${contractAddress}-${network}` - const batchResult = batchData.get(key) + const found = batchData.get(key) + if (found) return found - if (batchResult) { - return batchResult - } - - // Return fallback if not found in batch - return { - final: fallbackCategory || null, - original: fallbackCategory || null, - supabase: null, - changed: false, - devMode: false, - } + return { final: fallbackCategory ?? null } } From 85980236554748101cbd69ab4e0e0f76886fd1a1 Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Tue, 26 Aug 2025 02:23:54 -0700 Subject: [PATCH 14/33] remove feedenhancement --- src/db/feedEnhancement.ts | 50 --------------------------------------- 1 file changed, 50 deletions(-) delete mode 100644 src/db/feedEnhancement.ts diff --git a/src/db/feedEnhancement.ts b/src/db/feedEnhancement.ts deleted file mode 100644 index 185fa926f99..00000000000 --- a/src/db/feedEnhancement.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { getFeedRiskTier } from "./feedCategories.js" - -interface FeedData { - name: string - contractAddress?: string - proxyAddress?: string - feedCategory?: string - [key: string]: unknown -} - -/** - * Enhanced feed processing function for Supabase risk tier data - */ -export async function enhanceFeedWithRiskTier(feed: FeedData, network: string): Promise { - try { - const feedRiskTier = await getFeedRiskTier( - feed.contractAddress || feed.proxyAddress || "", - network, - feed.feedCategory - ) - - // Return the enhanced feed object with risk tier from Supabase - const feedWithRiskTier = { - ...feed, - feedCategory: feedRiskTier ?? undefined, - } - - return feedWithRiskTier - } catch (error) { - console.warn(`Failed to enhance feed ${feed.name}:`, error) - // Return original feed if lookup fails - return feed - } -} - -/** - * Process multiple feeds with Supabase enhancement - */ -export async function enhanceFeedsWithRiskTiers(feeds: FeedData[], network: string): Promise { - const enhancedFeeds = await Promise.allSettled(feeds.map((feed) => enhanceFeedWithRiskTier(feed, network))) - - return enhancedFeeds.map((result, index) => { - if (result.status === "fulfilled") { - return result.value - } else { - console.warn(`Failed to enhance feed at index ${index}:`, result.reason) - return feeds[index] // return original feed if enhancement fails - } - }) -} From 562b2ba53e93d39f7d73ff2d120abad907abc350 Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Tue, 26 Aug 2025 02:49:38 -0700 Subject: [PATCH 15/33] aptos bugfix --- .../components/useBatchedFeedCategories.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/features/feeds/components/useBatchedFeedCategories.ts b/src/features/feeds/components/useBatchedFeedCategories.ts index 2f95db2ed9a..e70a09da214 100644 --- a/src/features/feeds/components/useBatchedFeedCategories.ts +++ b/src/features/feeds/components/useBatchedFeedCategories.ts @@ -49,10 +49,16 @@ export function useBatchedFeedCategories(network: ChainNetwork | null): BatchedF }> = [] network.metadata?.forEach((metadata) => { - const contractAddress = metadata.contractAddress || metadata.proxyAddress - if (contractAddress) { + // Use proxyAddress for Aptos, contractAddress/proxyAddress for others + let feedKey: string | undefined + if (network.name.toLowerCase().includes("aptos")) { + feedKey = metadata.proxyAddress ?? undefined + } else { + feedKey = metadata.contractAddress ?? metadata.proxyAddress ?? undefined + } + if (feedKey) { feedRequests.push({ - contractAddress, + contractAddress: feedKey, network: network.networkType || "unknown", fallbackCategory: metadata.feedCategory, }) @@ -67,6 +73,13 @@ export function useBatchedFeedCategories(network: ChainNetwork | null): BatchedF // Batched DB lookup (returns Map) const batchResults = await getFeedRiskTiersBatch(feedRequests) + // Debug logging for each feed + feedRequests.forEach(({ contractAddress, network, fallbackCategory }) => { + const key = `${contractAddress}-${network}` + const result = batchResults.get(key) + console.log(`[FeedCategoryDebug] key: ${key}, fallback: ${fallbackCategory}, final: ${result?.final}`) + }) + setState({ data: batchResults, isLoading: false, From f8393ae5e074c0c2c35e55c557fb8f830c998924 Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Tue, 26 Aug 2025 02:54:28 -0700 Subject: [PATCH 16/33] lint:fix --- src/features/feeds/components/Tables.tsx | 96 +++++++++++++++++------- 1 file changed, 68 insertions(+), 28 deletions(-) diff --git a/src/features/feeds/components/Tables.tsx b/src/features/feeds/components/Tables.tsx index 6cb692411ee..49d9d6c9004 100644 --- a/src/features/feeds/components/Tables.tsx +++ b/src/features/feeds/components/Tables.tsx @@ -10,10 +10,7 @@ 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 { - useBatchedFeedCategories, - getFeedCategoryFromBatch, -} from "./useBatchedFeedCategories.ts" +import { useBatchedFeedCategories, getFeedCategoryFromBatch } from "./useBatchedFeedCategories.ts" const feedItems = monitoredFeeds.mainnet @@ -144,8 +141,8 @@ const DefaultTr = ({ network, metadata, showExtraDetails, batchedCategoryData }) const networkIdentifier = network?.networkType || "unknown" const finalTier = contractAddress && batchedCategoryData?.size - ? getFeedCategoryFromBatch(batchedCategoryData, contractAddress, networkIdentifier, metadata.feedCategory)?.final ?? - metadata.feedCategory + ? (getFeedCategoryFromBatch(batchedCategoryData, contractAddress, networkIdentifier, metadata.feedCategory) + ?.final ?? metadata.feedCategory) : metadata.feedCategory return ( @@ -319,8 +316,8 @@ const SmartDataTr = ({ network, metadata, showExtraDetails, batchedCategoryData const networkIdentifier = network?.networkType || "unknown" const finalTier = contractAddress && batchedCategoryData?.size - ? getFeedCategoryFromBatch(batchedCategoryData, contractAddress, networkIdentifier, metadata.feedCategory)?.final ?? - metadata.feedCategory + ? (getFeedCategoryFromBatch(batchedCategoryData, contractAddress, networkIdentifier, metadata.feedCategory) + ?.final ?? metadata.feedCategory) : metadata.feedCategory return ( @@ -658,7 +655,12 @@ export const StreamsNetworkAddressesTable = () => {
@@ -694,7 +696,11 @@ const StreamsTr = ({ metadata, isMainnet }) => (
{metadata.pair[0]}/{metadata.pair[1]} {metadata.feedType === "Crypto-DEX" && ( - + DEX State Price )} @@ -732,22 +738,30 @@ const StreamsTr = ({ metadata, isMainnet }) => (
{isMainnet && metadata.docs.clicProductName && (
-
Full name:
+
+ Full name: +
{metadata.docs.clicProductName}
)} {metadata.assetName && (
-
Asset name:
+
+ Asset name: +
{metadata.assetName}
)} {metadata.docs.assetClass ? (
-
Asset class:
+
+ Asset class: +
{metadata.docs.assetClass} - {metadata.docs.assetSubClass && metadata.docs.assetSubClass !== "Crypto" && metadata.docs.assetSubClass !== "Equities" + {metadata.docs.assetSubClass && + metadata.docs.assetSubClass !== "Crypto" && + metadata.docs.assetSubClass !== "Equities" ? " - " + metadata.docs.assetSubClass : ""}
@@ -755,15 +769,21 @@ const StreamsTr = ({ metadata, isMainnet }) => ( ) : null} {metadata.docs.marketHours ? (
-
Market hours:
+
+ Market hours: +
- {metadata.docs.marketHours} + + {metadata.docs.marketHours} +
) : null} {streamsCategoryMap[metadata.feedCategory] ? ( ) : null} {metadata.feedType === "Crypto-DEX" && (
-
Report Schema:
+
+ Report Schema: +
Crypto Schema - DEX (v3) @@ -789,7 +813,9 @@ const StreamsTr = ({ metadata, isMainnet }) => ( )} {metadata.feedType === "Crypto" && (
-
Report Schema:
+
+ Report Schema: +
Crypto Schema (v3) @@ -799,7 +825,9 @@ const StreamsTr = ({ metadata, isMainnet }) => ( )} {metadata.feedType === "Equities" && (
-
Report Schema:
+
+ Report Schema: +
RWA Schema (v8) @@ -906,16 +934,28 @@ export const MainnetTable = ({ .filter( (metadata) => metadata.name.toLowerCase().replaceAll(" ", "").includes(searchValue.toLowerCase().replaceAll(" ", "")) || - metadata.proxyAddress?.toLowerCase().replaceAll(" ", "").includes(searchValue.toLowerCase().replaceAll(" ", "")) || + metadata.proxyAddress + ?.toLowerCase() + .replaceAll(" ", "") + .includes(searchValue.toLowerCase().replaceAll(" ", "")) || metadata.secondaryProxyAddress ?.toLowerCase() .replaceAll(" ", "") .includes(searchValue.toLowerCase().replaceAll(" ", "")) || metadata.assetName.toLowerCase().replaceAll(" ", "").includes(searchValue.toLowerCase().replaceAll(" ", "")) || metadata.feedType.toLowerCase().replaceAll(" ", "").includes(searchValue.toLowerCase().replaceAll(" ", "")) || - metadata.docs.porType?.toLowerCase().replaceAll(" ", "").includes(searchValue.toLowerCase().replaceAll(" ", "")) || - metadata.docs.porAuditor?.toLowerCase().replaceAll(" ", "").includes(searchValue.toLowerCase().replaceAll(" ", "")) || - metadata.docs.porSource?.toLowerCase().replaceAll(" ", "").includes(searchValue.toLowerCase().replaceAll(" ", "")) || + metadata.docs.porType + ?.toLowerCase() + .replaceAll(" ", "") + .includes(searchValue.toLowerCase().replaceAll(" ", "")) || + metadata.docs.porAuditor + ?.toLowerCase() + .replaceAll(" ", "") + .includes(searchValue.toLowerCase().replaceAll(" ", "")) || + metadata.docs.porSource + ?.toLowerCase() + .replaceAll(" ", "") + .includes(searchValue.toLowerCase().replaceAll(" ", "")) || metadata.feedId?.toLowerCase().replaceAll(" ", "").includes(searchValue.toLowerCase().replaceAll(" ", "")) ) @@ -1117,12 +1157,12 @@ export const TestnetTable = ({ const networkIdentifier = network?.networkType || "unknown" const finalTier = contractAddress && batchedCategoryData?.size - ? getFeedCategoryFromBatch( + ? (getFeedCategoryFromBatch( batchedCategoryData, contractAddress, networkIdentifier, metadata.feedCategory - )?.final ?? metadata.feedCategory + )?.final ?? metadata.feedCategory) : metadata.feedCategory return ( @@ -1170,4 +1210,4 @@ export const TestnetTable = ({ /> ) -} \ No newline at end of file +} From 075c0d009122ef808d98ca5a7632f7726e11522a Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Tue, 26 Aug 2025 02:55:49 -0700 Subject: [PATCH 17/33] remove more tests --- .env.local.template | 17 ---- src/db/supabase.ts | 2 +- src/pages/supabase-test.astro | 146 ---------------------------------- 3 files changed, 1 insertion(+), 164 deletions(-) delete mode 100644 .env.local.template delete mode 100644 src/pages/supabase-test.astro diff --git a/.env.local.template b/.env.local.template deleted file mode 100644 index 7a607d0cfa9..00000000000 --- a/.env.local.template +++ /dev/null @@ -1,17 +0,0 @@ -# Copy this file to .env.local and replace with your actual Supabase credentials -# Get these from your Supabase project dashboard: https://supabase.com/dashboard/project/{your-project}/settings/api -# Note: PUBLIC_ prefix is required for client-side access in Astro - -# PUBLIC_SUPABASE_URL=https://your-project-id.supabase.co -# PUBLIC_SUPABASE_KEY=your-supabase-anon-key - -# DEVELOPER TEST MODE: -# To test Supabase integration and see category comparisons, open your browser console and run: -# window.CHAINLINK_DEV_MODE = true -# -# This will: -# 1. Show a warning banner above data feed tables -# 2. Display category comparisons like: 🔴 → 🔵 (original → supabase) -# 3. Enable enhanced console logging -# -# To disable: window.CHAINLINK_DEV_MODE = false diff --git a/src/db/supabase.ts b/src/db/supabase.ts index 7c7edc926ea..2510c5e8fdb 100644 --- a/src/db/supabase.ts +++ b/src/db/supabase.ts @@ -1,7 +1,7 @@ import { createClient } from "@supabase/supabase-js" const supabaseUrl = import.meta.env.PUBLIC_SUPABASE_URL -const supabaseKey = import.meta.env.PUBLIC_SUPABASE_ANON_KEY +const supabaseKey = import.meta.env.PUBLIC_SUPABASE_KEY // Export a function that safely creates the client export function getSupabaseClient() { diff --git a/src/pages/supabase-test.astro b/src/pages/supabase-test.astro deleted file mode 100644 index e221b38c2a5..00000000000 --- a/src/pages/supabase-test.astro +++ /dev/null @@ -1,146 +0,0 @@ ---- -import { supabase } from "../db/supabase" -import { getFeedCategories, testSupabaseConnection } from "../db/feedCategories" - -let connectionStatus = "Testing connection..." -let data: any = null -let error: string | null = null -let feedCategoriesData: any = null - -try { - // Test the connection by trying to fetch from the actual docs_feeds_risk table - // This will fail gracefully if the table doesn't exist or if credentials are wrong - if (!supabase) { - connectionStatus = "Connection failed" - error = "Supabase client is not initialized." - } else { - const { data: testData, error: testError } = await supabase.from("docs_feeds_risk").select("*").limit(5) - - if (testError) { - // If it's a table not found error, that's actually good - it means we connected - if ( - testError.code === "PGRST116" || - testError.message.includes("relation") || - testError.message.includes("does not exist") - ) { - connectionStatus = 'Connection successful! (Table "docs_feeds_risk" does not exist, but connection is working)' - } else { - connectionStatus = "Connection error" - error = testError.message - } - } else { - connectionStatus = "Connection successful!" - data = testData - - // If we successfully got data, also try to get feed categories - try { - feedCategoriesData = await getFeedCategories() - } catch (e) { - console.warn("Could not fetch feed categories:", e) - } - } - } -} catch (e) { - connectionStatus = "Connection failed" - error = e.message -} ---- - - - - - - Supabase Connection Test - - - -

Supabase Connection Test

- -
-

Status: {connectionStatus}

-
- - { - error && ( -
-

Error Details:

-
{error}
-
- ) - } - - { - data && ( -
-

Sample docs_feeds_risk Data (first 5 records):

-
{JSON.stringify(data, null, 2)}
-
- ) - } - - { - feedCategoriesData && ( -
-

Feed Categories Analysis:

-
{JSON.stringify(feedCategoriesData, null, 2)}
-
- ) - } - -
-

Environment Variables Check:

-
    -
  • PUBLIC_SUPABASE_URL: {import.meta.env.PUBLIC_SUPABASE_URL ? "✓ Set" : "✗ Not set"}
  • -
  • PUBLIC_SUPABASE_KEY: {import.meta.env.PUBLIC_SUPABASE_ANON_KEY ? "✓ Set" : "✗ Not set"}
  • -
-
- -
-

Next Steps:

-
    -
  1. If you haven't already, create a .env.local file based on .env.local.template
  2. -
  3. Add your actual Supabase URL and anon key to the .env.local file
  4. -
  5. Make sure your Supabase project has the docs_feeds_risk table
  6. -
  7. Restart the dev server after adding environment variables
  8. -
  9. - If successful, you should see sample data from the docs_feeds_risk table and risk category analysis -
  10. -
- -

Expected table structure for docs_feeds_risk:

-
    -
  • proxy_address - Feed contract address
  • -
  • network - Network identifier
  • -
  • risk_status - Risk category (low, medium, high, etc.)
  • -
-
- - From e7719243b7f80a2dab12ef9bd2bd3fa1c5ed70c5 Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Tue, 26 Aug 2025 02:57:56 -0700 Subject: [PATCH 18/33] supbase fix --- src/db/supabase.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db/supabase.ts b/src/db/supabase.ts index 2510c5e8fdb..7c7edc926ea 100644 --- a/src/db/supabase.ts +++ b/src/db/supabase.ts @@ -1,7 +1,7 @@ import { createClient } from "@supabase/supabase-js" const supabaseUrl = import.meta.env.PUBLIC_SUPABASE_URL -const supabaseKey = import.meta.env.PUBLIC_SUPABASE_KEY +const supabaseKey = import.meta.env.PUBLIC_SUPABASE_ANON_KEY // Export a function that safely creates the client export function getSupabaseClient() { From 098a42426643980d63ff1d586006821390b00365 Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Fri, 29 Aug 2025 06:13:10 -0700 Subject: [PATCH 19/33] restored deprecating tag --- src/features/feeds/components/FeedList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/feeds/components/FeedList.tsx b/src/features/feeds/components/FeedList.tsx index 56f8c19e3e5..1be1cd5b2fd 100644 --- a/src/features/feeds/components/FeedList.tsx +++ b/src/features/feeds/components/FeedList.tsx @@ -144,7 +144,7 @@ export const FeedList = ({ { key: "high", name: "High Market Risk" }, { key: "custom", name: "Custom" }, { key: "new", name: "New Token" }, - ]) + { key: "deprecating", name: "Deprecating" }, ]) // Load dynamic categories from Supabase on component mount useEffect(() => { From fb78897a7916609cea5aebed6916eade67f90047 Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Fri, 29 Aug 2025 06:21:13 -0700 Subject: [PATCH 20/33] fix merge --- src/features/feeds/components/Tables.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/features/feeds/components/Tables.tsx b/src/features/feeds/components/Tables.tsx index 795b3a2a5ac..4fd9625e0da 100644 --- a/src/features/feeds/components/Tables.tsx +++ b/src/features/feeds/components/Tables.tsx @@ -345,7 +345,7 @@ const SmartDataTHead = ({ showExtraDetails }: { showExtraDetails: boolean }) => ) const SmartDataTr = ({ network, metadata, showExtraDetails, batchedCategoryData }) => { - // MVR badge only when explicitly flagged and has decoding schema + // Check if this is an MVR feed const hasDecoding = Array.isArray(metadata.docs?.decoding) && metadata.docs.decoding.length > 0 const finalIsMVRFeed = metadata.docs?.isMVR === true && hasDecoding @@ -978,9 +978,12 @@ export const MainnetTable = ({ const isValidStreamsFeed = metadata.contractType === "verifier" && (metadata.docs.feedType === "Crypto" || metadata.docs.feedType === "Crypto-DEX") - return showOnlyDEXFeeds ? isValidStreamsFeed && metadata.docs.feedType === "Crypto-DEX" : isValidStreamsFeed - } + if (showOnlyDEXFeeds) { + return isValidStreamsFeed && metadata.docs.feedType === "Crypto-DEX" + } + + return isValidStreamsFeed } if (dataFeedType === "streamsRwa") { return metadata.contractType === "verifier" && metadata.docs.feedType === "Equities" } From 3a1fbdf9de09fb4e11d0b69bfc85774272489f06 Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Fri, 29 Aug 2025 06:23:35 -0700 Subject: [PATCH 21/33] lint fix --- src/features/feeds/components/FeedList.tsx | 3 ++- src/features/feeds/components/Tables.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/features/feeds/components/FeedList.tsx b/src/features/feeds/components/FeedList.tsx index 1be1cd5b2fd..b4a61ce3642 100644 --- a/src/features/feeds/components/FeedList.tsx +++ b/src/features/feeds/components/FeedList.tsx @@ -144,7 +144,8 @@ export const FeedList = ({ { key: "high", name: "High Market Risk" }, { key: "custom", name: "Custom" }, { key: "new", name: "New Token" }, - { key: "deprecating", name: "Deprecating" }, ]) + { key: "deprecating", name: "Deprecating" }, + ]) // Load dynamic categories from Supabase on component mount useEffect(() => { diff --git a/src/features/feeds/components/Tables.tsx b/src/features/feeds/components/Tables.tsx index 4fd9625e0da..f880aaf6f4f 100644 --- a/src/features/feeds/components/Tables.tsx +++ b/src/features/feeds/components/Tables.tsx @@ -983,7 +983,8 @@ export const MainnetTable = ({ return isValidStreamsFeed && metadata.docs.feedType === "Crypto-DEX" } - return isValidStreamsFeed } + return isValidStreamsFeed + } if (dataFeedType === "streamsRwa") { return metadata.contractType === "verifier" && metadata.docs.feedType === "Equities" } From c2663f6715e70de48d9976ed77d770358aabab0f Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Fri, 29 Aug 2025 06:29:42 -0700 Subject: [PATCH 22/33] restore paginate noop --- src/features/feeds/components/Tables.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/features/feeds/components/Tables.tsx b/src/features/feeds/components/Tables.tsx index f880aaf6f4f..297c7ce1b3e 100644 --- a/src/features/feeds/components/Tables.tsx +++ b/src/features/feeds/components/Tables.tsx @@ -1155,7 +1155,9 @@ export const TestnetTable = ({ lastAddr = 1000, addrPerPage = 8, currentPage = 1, - paginate = () => {}, + paginate = (_page: number) => { + /* Default no-op function */ + }, searchValue = "", showOnlyMVRFeeds, showOnlyDEXFeeds, From 56d28a8298a44916b7dde3ccc4034a4d0bc1f005 Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Fri, 29 Aug 2025 10:43:02 -0700 Subject: [PATCH 23/33] nit --- src/db/feedCategories.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db/feedCategories.ts b/src/db/feedCategories.ts index e002a3cd929..608ff561487 100644 --- a/src/db/feedCategories.ts +++ b/src/db/feedCategories.ts @@ -230,7 +230,7 @@ export async function getFeedRiskTiersBatch( } /** - * Server-safe helper: uses Supabase on the server; uses fallback on the client. + * Server-safe helper: uses Supabase on the server; fallback on the client. */ export async function getFeedRiskTierWithFallback( contractAddress: string, From 8435d49a0c0c49de2fb7f65dffed53335edce061 Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Fri, 29 Aug 2025 11:01:41 -0700 Subject: [PATCH 24/33] reverted unnecessary change --- src/features/feeds/components/Tables.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/features/feeds/components/Tables.tsx b/src/features/feeds/components/Tables.tsx index 297c7ce1b3e..2ce7c9f1478 100644 --- a/src/features/feeds/components/Tables.tsx +++ b/src/features/feeds/components/Tables.tsx @@ -347,8 +347,11 @@ const SmartDataTHead = ({ showExtraDetails }: { showExtraDetails: boolean }) => const SmartDataTr = ({ network, metadata, showExtraDetails, batchedCategoryData }) => { // Check if this is an MVR feed const hasDecoding = Array.isArray(metadata.docs?.decoding) && metadata.docs.decoding.length > 0 - const finalIsMVRFeed = metadata.docs?.isMVR === true && hasDecoding + const isMVRFlagSet = metadata.docs?.isMVR === true + // Only show MVR badge if explicitly flagged as MVR + const finalIsMVRFeed = isMVRFlagSet && hasDecoding + // Resolve final category from batch (fallback to metadata) const contractAddress = metadata.contractAddress || metadata.proxyAddress const networkIdentifier = network?.networkType || "unknown" From 95890b724a5f6c95a65f40ebf8a1c0da0f4edcc0 Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Fri, 29 Aug 2025 11:04:35 -0700 Subject: [PATCH 25/33] lint fix --- src/features/feeds/components/Tables.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/feeds/components/Tables.tsx b/src/features/feeds/components/Tables.tsx index 2ce7c9f1478..b64517ea053 100644 --- a/src/features/feeds/components/Tables.tsx +++ b/src/features/feeds/components/Tables.tsx @@ -351,7 +351,7 @@ const SmartDataTr = ({ network, metadata, showExtraDetails, batchedCategoryData // Only show MVR badge if explicitly flagged as MVR const finalIsMVRFeed = isMVRFlagSet && hasDecoding - + // Resolve final category from batch (fallback to metadata) const contractAddress = metadata.contractAddress || metadata.proxyAddress const networkIdentifier = network?.networkType || "unknown" From f63dd68044a9626273f3fb37a089d42b5c0ca4ec Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Fri, 29 Aug 2025 11:46:37 -0700 Subject: [PATCH 26/33] remove logging --- src/features/feeds/components/FeedList.tsx | 4 +--- src/features/feeds/components/useBatchedFeedCategories.ts | 8 -------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/features/feeds/components/FeedList.tsx b/src/features/feeds/components/FeedList.tsx index b4a61ce3642..27a89c844d5 100644 --- a/src/features/feeds/components/FeedList.tsx +++ b/src/features/feeds/components/FeedList.tsx @@ -153,9 +153,7 @@ export const FeedList = ({ try { const categories = await getFeedCategories() setDataFeedCategory(categories) - } catch (error) { - console.warn("Failed to load categories from Supabase:", error) - } + } catch (error) {} } loadCategories() diff --git a/src/features/feeds/components/useBatchedFeedCategories.ts b/src/features/feeds/components/useBatchedFeedCategories.ts index e70a09da214..42eb8b93be0 100644 --- a/src/features/feeds/components/useBatchedFeedCategories.ts +++ b/src/features/feeds/components/useBatchedFeedCategories.ts @@ -73,20 +73,12 @@ export function useBatchedFeedCategories(network: ChainNetwork | null): BatchedF // Batched DB lookup (returns Map) const batchResults = await getFeedRiskTiersBatch(feedRequests) - // Debug logging for each feed - feedRequests.forEach(({ contractAddress, network, fallbackCategory }) => { - const key = `${contractAddress}-${network}` - const result = batchResults.get(key) - console.log(`[FeedCategoryDebug] key: ${key}, fallback: ${fallbackCategory}, final: ${result?.final}`) - }) - setState({ data: batchResults, isLoading: false, error: null, }) } catch (error) { - console.error("Failed to load batched feed categories:", error) setState((prev) => ({ ...prev, isLoading: false, From ea6ff37390f8398af0a0feb026c0deb72e9f274a Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Fri, 29 Aug 2025 11:53:45 -0700 Subject: [PATCH 27/33] astro version restore, remove typecheck test --- .github/workflows/test.yml | 3 --- package-lock.json | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 30055ad161d..b62c6065e67 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -184,9 +184,6 @@ jobs: typecheck: needs: [setup] runs-on: ubuntu-latest - env: - SUPABASE_URL: ${{ secrets.SUPABASE_URL }} - SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} steps: - name: Checkout Repo uses: actions/checkout@v5 diff --git a/package-lock.json b/package-lock.json index 385529a5f07..8474f8dcf41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "@nanostores/react": "^0.8.4", "@openzeppelin/contracts": "^4.9.6", "@supabase/supabase-js": "^2.53.0", - "astro": "^5.12.8", + "astro": "^5.13.3", "bignumber.js": "^9.3.1", "clipboard": "^2.0.11", "dotenv": "^16.6.1", From ee9ea195db8a84cbc2dfbd0b702ae6365512bb5c Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Fri, 29 Aug 2025 11:59:38 -0700 Subject: [PATCH 28/33] update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3d3ed0c54eb..2531c129fb7 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "@nanostores/react": "^0.8.4", "@openzeppelin/contracts": "^4.9.6", "@supabase/supabase-js": "^2.53.0", - "astro": "^5.12.8", + "astro": "^5.13.3", "bignumber.js": "^9.3.1", "clipboard": "^2.0.11", "dotenv": "^16.6.1", From 120f0cd8231f794249ad594218fd2684c8b7b587 Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Fri, 29 Aug 2025 12:00:56 -0700 Subject: [PATCH 29/33] remove more logging --- src/db/supabase.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/db/supabase.ts b/src/db/supabase.ts index 7c7edc926ea..5d62fb69d0a 100644 --- a/src/db/supabase.ts +++ b/src/db/supabase.ts @@ -6,7 +6,6 @@ const supabaseKey = import.meta.env.PUBLIC_SUPABASE_ANON_KEY // Export a function that safely creates the client export function getSupabaseClient() { if (!supabaseUrl || !supabaseKey) { - console.warn("Supabase environment variables not found. Supabase features will be disabled.") return null } return createClient(supabaseUrl, supabaseKey) From b83ba82f74b48c082e61cdd2d91beb0bbc3d3a06 Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Fri, 29 Aug 2025 12:06:22 -0700 Subject: [PATCH 30/33] cut unused testing functions --- src/db/feedCategories.ts | 93 ---------------------------------------- 1 file changed, 93 deletions(-) diff --git a/src/db/feedCategories.ts b/src/db/feedCategories.ts index 608ff561487..3acb74af431 100644 --- a/src/db/feedCategories.ts +++ b/src/db/feedCategories.ts @@ -10,12 +10,6 @@ type FeedRiskRow = { risk_status: string | null } -type FeedRiskData = { - proxy_address: string - network: string - risk_status: string -} - export type FeedTierResult = { final: string | null } /* =========================== @@ -90,59 +84,12 @@ const chooseTier = (dbTier: string | null | undefined, fallback?: string): strin const defaultCategoryList = () => Object.values(FEED_CATEGORY_CONFIG).map(({ key, name }) => ({ key, name })) -/** Optional convenience for consumers */ -export function getCategoryMeta(category?: string | null) { - const key = normalizeKey(category) - return key ? FEED_CATEGORY_CONFIG[key] : undefined -} - /* =========================== Public API =========================== */ export const getDefaultCategories = defaultCategoryList -/** Fetch rows (optionally filtered by network). Returns [] on any error. */ -export async function getFeedRiskData(network?: string): Promise { - if (!supabase) return [] - - try { - let query = supabase.from(TABLE).select("*") - if (network) query = query.eq("network", network) - - const { data, error } = await query.limit(1000) - if (error || !data) return [] - - // Narrow to non-null risk_status to match FeedRiskData type - return (data as FeedRiskRow[]).filter((r): r is FeedRiskData => !!r.risk_status) - } catch { - return [] - } -} - -/** Single lookup with fallback-first behavior. */ -export async function getFeedRiskTier( - contractAddress: string, - network: string, - fallbackCategory?: string -): Promise { - try { - if (!supabase) return chooseTier(null, fallbackCategory) - - const { data, error } = await supabase - .from(TABLE) - .select("risk_status") - .eq("proxy_address", contractAddress) - .eq("network", network) - .limit(1) - - if (error || !data?.length) return chooseTier(null, fallbackCategory) - return chooseTier(data[0]?.risk_status, fallbackCategory) - } catch { - return chooseTier(null, fallbackCategory) - } -} - /** Merge static categories with those dynamically present in the table. */ export async function getFeedCategories() { try { @@ -228,43 +175,3 @@ export async function getFeedRiskTiersBatch( return out } } - -/** - * Server-safe helper: uses Supabase on the server; fallback on the client. - */ -export async function getFeedRiskTierWithFallback( - contractAddress: string, - network: string, - fallbackCategory?: string -): Promise { - try { - if (typeof window === "undefined") { - const riskTier = await getFeedRiskTier(contractAddress, network, fallbackCategory) - return riskTier ?? fallbackCategory - } - return fallbackCategory - } catch { - return fallbackCategory - } -} - -/** Lightweight connectivity check. */ -export async function testSupabaseConnection() { - try { - if (!supabase) { - return { success: false, data: null, error: "Supabase client not available" } - } - const { data, error } = await supabase.from(TABLE).select("*").limit(1) - return { - success: !error || (error as any)?.code === "PGRST116", - data, - error: (error as any)?.message, - } - } catch (e) { - return { - success: false, - data: null, - error: e instanceof Error ? e.message : "Unknown error", - } - } -} From b2303b37b570fb6c89494811bcdb71fb8da1e130 Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Tue, 2 Sep 2025 05:10:57 -0700 Subject: [PATCH 31/33] update categorziation logic to include hidden field --- src/features/feeds/components/Tables.tsx | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/features/feeds/components/Tables.tsx b/src/features/feeds/components/Tables.tsx index b64517ea053..4501261d058 100644 --- a/src/features/feeds/components/Tables.tsx +++ b/src/features/feeds/components/Tables.tsx @@ -971,6 +971,19 @@ export const MainnetTable = ({ const filteredMetadata = network.metadata .sort((a, b) => (a.name < b.name ? -1 : 1)) .filter((metadata) => { + // --- + // Categorization logic: + // 1. Try to get the risk category for this feed from Supabase (batchedCategoryData). + // - Uses contractAddress and networkIdentifier as lookup keys. + // - If found, use the DB value; if not, fall back to the default from metadata. + // 2. If the risk category is 'hidden', exclude this feed from the docs. + // --- + const contractAddress = metadata.contractAddress || metadata.proxyAddress + const networkIdentifier = network?.networkType || "unknown" + const batchCategory = contractAddress && batchedCategoryData?.size + ? (getFeedCategoryFromBatch(batchedCategoryData, contractAddress, networkIdentifier, metadata.feedCategory)?.final ?? metadata.feedCategory) + : metadata.feedCategory + if (batchCategory === "hidden") return false; if (showOnlySVR && !metadata.secondaryProxyAddress) { return false } @@ -1195,6 +1208,19 @@ export const TestnetTable = ({ const filteredMetadata = network.metadata .sort((a, b) => (a.name < b.name ? -1 : 1)) .filter((metadata) => { + // --- + // Categorization logic: + // 1. Try to get the risk category for this feed from Supabase (batchedCategoryData). + // - Uses contractAddress and networkIdentifier as lookup keys. + // - If found, use the DB value; if not, fall back to the default from metadata. + // 2. If the risk category is 'hidden', exclude this feed from the docs. + // --- + const contractAddress = metadata.contractAddress || metadata.proxyAddress + const networkIdentifier = network?.networkType || "unknown" + const batchCategory = contractAddress && batchedCategoryData?.size + ? (getFeedCategoryFromBatch(batchedCategoryData, contractAddress, networkIdentifier, metadata.feedCategory)?.final ?? metadata.feedCategory) + : metadata.feedCategory + if (batchCategory === "hidden") return false; if (isStreams) { if (dataFeedType === "streamsCrypto") { const isValidStreamsFeed = From ef8c6311cb76c78f096f0c46cb7697a85536a277 Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Tue, 2 Sep 2025 05:19:22 -0700 Subject: [PATCH 32/33] lint fix --- src/features/feeds/components/Tables.tsx | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/features/feeds/components/Tables.tsx b/src/features/feeds/components/Tables.tsx index 4501261d058..7d536917e12 100644 --- a/src/features/feeds/components/Tables.tsx +++ b/src/features/feeds/components/Tables.tsx @@ -980,10 +980,12 @@ export const MainnetTable = ({ // --- const contractAddress = metadata.contractAddress || metadata.proxyAddress const networkIdentifier = network?.networkType || "unknown" - const batchCategory = contractAddress && batchedCategoryData?.size - ? (getFeedCategoryFromBatch(batchedCategoryData, contractAddress, networkIdentifier, metadata.feedCategory)?.final ?? metadata.feedCategory) - : metadata.feedCategory - if (batchCategory === "hidden") return false; + const batchCategory = + contractAddress && batchedCategoryData?.size + ? (getFeedCategoryFromBatch(batchedCategoryData, contractAddress, networkIdentifier, metadata.feedCategory) + ?.final ?? metadata.feedCategory) + : metadata.feedCategory + if (batchCategory === "hidden") return false if (showOnlySVR && !metadata.secondaryProxyAddress) { return false } @@ -1217,10 +1219,12 @@ export const TestnetTable = ({ // --- const contractAddress = metadata.contractAddress || metadata.proxyAddress const networkIdentifier = network?.networkType || "unknown" - const batchCategory = contractAddress && batchedCategoryData?.size - ? (getFeedCategoryFromBatch(batchedCategoryData, contractAddress, networkIdentifier, metadata.feedCategory)?.final ?? metadata.feedCategory) - : metadata.feedCategory - if (batchCategory === "hidden") return false; + const batchCategory = + contractAddress && batchedCategoryData?.size + ? (getFeedCategoryFromBatch(batchedCategoryData, contractAddress, networkIdentifier, metadata.feedCategory) + ?.final ?? metadata.feedCategory) + : metadata.feedCategory + if (batchCategory === "hidden") return false if (isStreams) { if (dataFeedType === "streamsCrypto") { const isValidStreamsFeed = From f94d1d09c64da452fd928311ac689e0dc683900b Mon Sep 17 00:00:00 2001 From: Devin DiStefano Date: Tue, 2 Sep 2025 05:24:09 -0700 Subject: [PATCH 33/33] updated sorting --- src/features/feeds/components/Tables.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/feeds/components/Tables.tsx b/src/features/feeds/components/Tables.tsx index 7d536917e12..85e264e4c1e 100644 --- a/src/features/feeds/components/Tables.tsx +++ b/src/features/feeds/components/Tables.tsx @@ -969,7 +969,7 @@ export const MainnetTable = ({ const isDeprecating = ecosystem === "deprecating" const filteredMetadata = network.metadata - .sort((a, b) => (a.name < b.name ? -1 : 1)) + .sort((a, b) => (a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1)) .filter((metadata) => { // --- // Categorization logic: @@ -1208,7 +1208,7 @@ export const TestnetTable = ({ const isDefault = !isSmartData && !isRates && !isStreams && !isUSGovernmentMacroeconomicData const filteredMetadata = network.metadata - .sort((a, b) => (a.name < b.name ? -1 : 1)) + .sort((a, b) => (a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1)) .filter((metadata) => { // --- // Categorization logic: