|
| 1 | +import { supabase } from "./supabase.js" |
| 2 | + |
| 3 | +/* =========================== |
| 4 | + Types |
| 5 | + =========================== */ |
| 6 | + |
| 7 | +type FeedRiskRow = { |
| 8 | + proxy_address: string |
| 9 | + network: string |
| 10 | + risk_status: string | null |
| 11 | +} |
| 12 | + |
| 13 | +export type FeedTierResult = { final: string | null } |
| 14 | + |
| 15 | +/* =========================== |
| 16 | + Category Config |
| 17 | + =========================== */ |
| 18 | + |
| 19 | +export const FEED_CATEGORY_CONFIG = { |
| 20 | + low: { |
| 21 | + key: "low", |
| 22 | + name: "Low Market Risk", |
| 23 | + icon: "🟢", |
| 24 | + title: "Low Market Risk - Feeds that deliver a market price for liquid assets with robust market structure.", |
| 25 | + link: "/data-feeds/selecting-data-feeds#-low-market-risk-feeds", |
| 26 | + }, |
| 27 | + medium: { |
| 28 | + key: "medium", |
| 29 | + name: "Medium Market Risk", |
| 30 | + icon: "🟡", |
| 31 | + title: |
| 32 | + "Medium Market Risk - Feeds that deliver a market price for assets that show signs of liquidity-related risk or other market structure-related risk.", |
| 33 | + link: "/data-feeds/selecting-data-feeds#-medium-market-risk-feeds", |
| 34 | + }, |
| 35 | + high: { |
| 36 | + key: "high", |
| 37 | + name: "High Market Risk", |
| 38 | + icon: "🔴", |
| 39 | + title: |
| 40 | + "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.", |
| 41 | + link: "/data-feeds/selecting-data-feeds#-high-market-risk-feeds", |
| 42 | + }, |
| 43 | + new: { |
| 44 | + key: "new", |
| 45 | + name: "New Token", |
| 46 | + icon: "🟠", |
| 47 | + title: |
| 48 | + "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.", |
| 49 | + link: "/data-feeds/selecting-data-feeds#-new-token-feeds", |
| 50 | + }, |
| 51 | + custom: { |
| 52 | + key: "custom", |
| 53 | + name: "Custom", |
| 54 | + icon: "🔵", |
| 55 | + title: |
| 56 | + "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.", |
| 57 | + link: "/data-feeds/selecting-data-feeds#-custom-feeds", |
| 58 | + }, |
| 59 | + deprecating: { |
| 60 | + key: "deprecating", |
| 61 | + name: "Deprecating", |
| 62 | + icon: "⭕", |
| 63 | + title: |
| 64 | + "Deprecating - These feeds are scheduled for deprecation. See the [Deprecation](/data-feeds/deprecating-feeds) page to learn more.", |
| 65 | + link: "/data-feeds/deprecating-feeds", |
| 66 | + }, |
| 67 | +} as const |
| 68 | + |
| 69 | +export type CategoryKey = keyof typeof FEED_CATEGORY_CONFIG |
| 70 | + |
| 71 | +/* =========================== |
| 72 | + Small helpers |
| 73 | + =========================== */ |
| 74 | + |
| 75 | +const TABLE = "docs_feeds_risk" |
| 76 | + |
| 77 | +const normalizeKey = (v?: string | null): CategoryKey | undefined => { |
| 78 | + if (!v) return undefined |
| 79 | + const key = v.toLowerCase() as CategoryKey |
| 80 | + return key in FEED_CATEGORY_CONFIG ? key : undefined |
| 81 | +} |
| 82 | + |
| 83 | +const chooseTier = (dbTier: string | null | undefined, fallback?: string): string | null => dbTier ?? fallback ?? null |
| 84 | + |
| 85 | +const defaultCategoryList = () => Object.values(FEED_CATEGORY_CONFIG).map(({ key, name }) => ({ key, name })) |
| 86 | + |
| 87 | +/* =========================== |
| 88 | + Public API |
| 89 | + =========================== */ |
| 90 | + |
| 91 | +export const getDefaultCategories = defaultCategoryList |
| 92 | + |
| 93 | +/** Merge static categories with those dynamically present in the table. */ |
| 94 | +export async function getFeedCategories() { |
| 95 | + try { |
| 96 | + if (!supabase) return defaultCategoryList() |
| 97 | + |
| 98 | + const { data, error } = await supabase |
| 99 | + .from(TABLE) |
| 100 | + .select("risk_status") |
| 101 | + .not("risk_status", "is", null) |
| 102 | + .neq("risk_status", "hidden") |
| 103 | + |
| 104 | + if (error || !data) return defaultCategoryList() |
| 105 | + |
| 106 | + const dynamic = Array.from( |
| 107 | + new Set(data.map((d) => normalizeKey(d.risk_status)).filter(Boolean) as CategoryKey[]) |
| 108 | + ).map((key) => ({ key, name: FEED_CATEGORY_CONFIG[key].name })) |
| 109 | + |
| 110 | + // Dedup by key while keeping all defaults first |
| 111 | + const byKey = new Map<string, { key: string; name: string }>() |
| 112 | + defaultCategoryList().forEach((c) => byKey.set(c.key, c)) |
| 113 | + dynamic.forEach((c) => byKey.set(c.key, c)) |
| 114 | + |
| 115 | + return Array.from(byKey.values()) |
| 116 | + } catch { |
| 117 | + return defaultCategoryList() |
| 118 | + } |
| 119 | +} |
| 120 | + |
| 121 | +/** |
| 122 | + * Batch lookup: returns a Map of `${address}-${network}` → { final }. |
| 123 | + * Uses DB value when present; otherwise uses per-item fallback. |
| 124 | + */ |
| 125 | +export async function getFeedRiskTiersBatch( |
| 126 | + feedRequests: Array<{ |
| 127 | + contractAddress: string |
| 128 | + network: string |
| 129 | + fallbackCategory?: string |
| 130 | + }> |
| 131 | +): Promise<Map<string, FeedTierResult>> { |
| 132 | + const out = new Map<string, FeedTierResult>() |
| 133 | + const keyFor = (addr: string, net: string) => `${addr}-${net}` |
| 134 | + |
| 135 | + if (!supabase) { |
| 136 | + feedRequests.forEach(({ contractAddress, network, fallbackCategory }) => |
| 137 | + out.set(keyFor(contractAddress, network), { final: chooseTier(null, fallbackCategory) }) |
| 138 | + ) |
| 139 | + return out |
| 140 | + } |
| 141 | + |
| 142 | + const networks = Array.from(new Set(feedRequests.map((r) => r.network))) |
| 143 | + const addresses = Array.from(new Set(feedRequests.map((r) => r.contractAddress))) |
| 144 | + |
| 145 | + try { |
| 146 | + const { data, error } = await supabase |
| 147 | + .from(TABLE) |
| 148 | + .select("proxy_address, network, risk_status") |
| 149 | + .in("proxy_address", addresses) |
| 150 | + .in("network", networks) |
| 151 | + .limit(1000) |
| 152 | + |
| 153 | + if (error) { |
| 154 | + feedRequests.forEach(({ contractAddress, network, fallbackCategory }) => |
| 155 | + out.set(keyFor(contractAddress, network), { final: chooseTier(null, fallbackCategory) }) |
| 156 | + ) |
| 157 | + return out |
| 158 | + } |
| 159 | + |
| 160 | + const lookup = new Map<string, string | null>() |
| 161 | + ;(data as FeedRiskRow[] | null)?.forEach((row) => |
| 162 | + lookup.set(keyFor(row.proxy_address, row.network), row.risk_status ?? null) |
| 163 | + ) |
| 164 | + |
| 165 | + feedRequests.forEach(({ contractAddress, network, fallbackCategory }) => { |
| 166 | + const key = keyFor(contractAddress, network) |
| 167 | + out.set(key, { final: chooseTier(lookup.get(key), fallbackCategory) }) |
| 168 | + }) |
| 169 | + |
| 170 | + return out |
| 171 | + } catch { |
| 172 | + feedRequests.forEach(({ contractAddress, network, fallbackCategory }) => |
| 173 | + out.set(keyFor(contractAddress, network), { final: chooseTier(null, fallbackCategory) }) |
| 174 | + ) |
| 175 | + return out |
| 176 | + } |
| 177 | +} |
0 commit comments