Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 2 additions & 12 deletions src/components/ChainSelector/ChainSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { useState, useRef, useEffect } from "preact/hooks"
import { clsx } from "~/lib/clsx/clsx.ts"
import { Chain } from "~/features/data/chains.ts"
import { chainMatchesFeedTypeTag } from "~/features/feeds/utils/chainFilters.ts"
import styles from "./ChainSelector.module.css"

interface ChainSelectorProps {
Expand Down Expand Up @@ -33,18 +34,7 @@ export function ChainSelector({

// Filter chains based on dataFeedType and search term
const filteredChains = chains.filter((chain) => {
// Filter by dataFeedType first
const matchesDataFeedType = (() => {
if (dataFeedType.includes("streams")) return chain.tags?.includes("streams") ?? false
if (dataFeedType === "smartdata") return chain.tags?.includes("smartData") ?? false
if (dataFeedType === "rates") return chain.tags?.includes("rates") ?? false
if (dataFeedType === "usGovernmentMacroeconomicData")
return chain.tags?.includes("usGovernmentMacroeconomicData") ?? false
if (dataFeedType === "tokenizedEquity") return chain.tags?.includes("tokenizedEquity") ?? false
return chain.tags?.includes("default") ?? false
})()

// Filter by search term
const matchesDataFeedType = chainMatchesFeedTypeTag(chain, dataFeedType as never)
const matchesSearch = !searchTerm || chain.label.toLowerCase().includes(searchTerm.toLowerCase())

return matchesDataFeedType && matchesSearch
Expand Down
4 changes: 4 additions & 0 deletions src/config/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1175,6 +1175,10 @@ export const SIDEBAR: Partial<Record<Sections, SectionEntry[]>> = {
title: "Market Hours",
url: "data-streams/market-hours",
},
{
title: "Selecting Quality Data Streams",
url: "data-streams/selecting-data-streams",
},
{
title: "Deprecating Streams",
url: "data-streams/deprecating-streams",
Expand Down
14 changes: 14 additions & 0 deletions src/content/data-streams/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6293,6 +6293,20 @@ Source: https://docs.chain.link/data-streams/rwa-streams

---

# Selecting Quality Data Streams
Source: https://docs.chain.link/data-streams/selecting-data-streams

When you design your applications, consider the quality of the data that you use. Ultimately you are responsible for identifying and assessing the accuracy, availability, and quality of data that you choose to consume via the Chainlink Network. Note that all streams contain some inherent risk. Read the [Data Streams Best Practices](/data-streams/concepts/best-practices) and [Developer Responsibilities](/data-streams/developer-responsibilities) sections when making design decisions.

For a summary of data sourcing models by asset type, see [Data Sources](/data-streams/data-sources).

<Aside type="note">
For important updates regarding the use of Chainlink Data Streams, subscribe to the [Chainlink
Changelog](https://dev.chain.link/changelog?product=Data+Streams).
</Aside>

---

# SmartData Streams
Source: https://docs.chain.link/data-streams/smartdata-streams

Expand Down
21 changes: 21 additions & 0 deletions src/content/data-streams/selecting-data-streams.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
section: dataStreams
date: Last Modified
title: "Selecting Quality Data Streams"
metadata:
excerpt: "Learn how to assess data streams that you use in your applications."
---

import { Aside } from "@components"
import MarketPricingRiskTiers from "@features/feeds/components/MarketPricingRiskTiers.astro"

When you design your applications, consider the quality of the data that you use. Ultimately you are responsible for identifying and assessing the accuracy, availability, and quality of data that you choose to consume via the Chainlink Network. Note that all streams contain some inherent risk. Read the [Data Streams Best Practices](/data-streams/concepts/best-practices) and [Developer Responsibilities](/data-streams/developer-responsibilities) sections when making design decisions.

For a summary of data sourcing models by asset type, see [Data Sources](/data-streams/data-sources).

<MarketPricingRiskTiers product="streams" />

<Aside type="note">
For important updates regarding the use of Chainlink Data Streams, subscribe to the [Chainlink
Changelog](https://dev.chain.link/changelog?product=Data+Streams).
</Aside>
97 changes: 76 additions & 21 deletions src/db/feedCategories.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import {
getMarketPricingRiskTerms,
tierAnchor,
type MarketPricingRiskProduct,
} from "../features/feeds/content/marketPricingRiskTerms.ts"
import { supabase } from "./supabase.js"

/* ===========================
Expand Down Expand Up @@ -26,63 +31,102 @@ export type FeedTierResult = { final: string | null }
export const FEED_CATEGORY_CONFIG = {
low: {
key: "low",
name: "Low Market Risk",
name: "Low Market Pricing 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",
title:
"Low Market Pricing Risk - Feeds that follow a standardized workflow to report market prices for liquid assets with robust market structure.",
link: "/data-feeds/selecting-data-feeds#-low-market-pricing-risk-feeds",
},
medium: {
key: "medium",
name: "Medium Market Risk",
name: "Medium Market Pricing 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",
"Medium Market Pricing Risk - Feeds that report market prices for asset pairs that may have features making them more challenging to reliably price, or potentially subject them to volatility.",
link: "/data-feeds/selecting-data-feeds#-medium-market-pricing-risk-feeds",
},
high: {
key: "high",
name: "High Market Risk",
name: "High Market Pricing 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",
"High Market Pricing Risk - Feeds for pairs that often exhibit a heightened degree of Medium Market Pricing Risk factors, or separate risks that make the market price subject to uncertainty or volatility.",
link: "/data-feeds/selecting-data-feeds#-high-market-pricing-risk-feeds",
},
veryhigh: {
key: "veryhigh",
name: "Very High Market Risk",
name: "Very High Market Pricing Risk",
icon: "🔴",
title:
"Very High Market Risk - Feeds with significant risk factors that require careful consideration. Users must thoroughly evaluate and understand all associated risks before use.",
link: "/data-feeds/selecting-data-feeds#-very-high-market-risk-feeds",
"Very High Market Pricing Risk - Feeds that price assets with quotes subject to extreme levels of risk, greater than those outlined for High Market Pricing Risk feeds.",
link: "/data-feeds/selecting-data-feeds#-very-high-market-pricing-risk-feeds",
},
new: {
key: "new",
name: "New Token",
name: "New Token Feeds",
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.",
"New Token Feeds - 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.",
link: "/data-feeds/selecting-data-feeds#-new-token-feeds",
},
custom: {
key: "custom",
name: "Custom",
name: "Custom Feeds",
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.",
"Custom Feeds - Feeds built to serve a specific use case and might not be suitable for general use or your use case's risk parameters.",
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",
title: "Deprecating - These feeds are scheduled for deprecation.",
link: "/data-feeds/selecting-data-feeds#-deprecating",
},
} as const

export type CategoryKey = keyof typeof FEED_CATEGORY_CONFIG

const TIER_ANCHOR_KEY: Record<CategoryKey, string> = {
low: "low",
medium: "medium",
high: "high",
veryhigh: "very-high",
new: "new-token",
custom: "custom",
deprecating: "deprecating",
}

const RISK_DOC_BASE_PATH: Record<MarketPricingRiskProduct, string> = {
feeds: "/data-feeds/selecting-data-feeds",
streams: "/data-streams/selecting-data-streams",
}

export function getRiskCategoryLink(key: CategoryKey, product: MarketPricingRiskProduct = "feeds"): string {
const base = RISK_DOC_BASE_PATH[product]

if (key === "deprecating") {
return `${base}#-deprecating`
}

return `${base}${tierAnchor(TIER_ANCHOR_KEY[key], getMarketPricingRiskTerms(product))}`
}

export function getRiskCategoryTitle(key: CategoryKey, product: MarketPricingRiskProduct = "feeds"): string {
const title = FEED_CATEGORY_CONFIG[key].title

if (product === "feeds") {
return title
}

return title
.replace(/\bFeeds\b/g, "Streams")
.replace(/\bFeed\b/g, "Stream")
.replace(/\bfeed\b/g, "stream")
.replace(/\bfeeds\b/g, "streams")
}

/* ===========================
Small helpers
=========================== */
Expand All @@ -103,13 +147,23 @@ const resolveRiskStatus = (
shutdownDate?: string,
fallbackCategory?: string
): string | null => {
if (dbTier != null) return dbTier
// Deprecating feeds always show the deprecating icon, even when a DB risk tier exists.
if (shutdownDate) return "deprecating"
if (dbTier != null) return dbTier
if (fallbackCategory && FALLBACK_ONLY_CATEGORIES.has(fallbackCategory.toLowerCase()))
return fallbackCategory.toLowerCase()
return null
}

/** Client-side helper for resolving the displayed feed category. */
export function resolveFeedCategory(
dbTier: string | null | undefined,
shutdownDate?: string,
fallbackCategory?: string
): string | null {
return resolveRiskStatus(dbTier, shutdownDate, fallbackCategory)
}

const defaultCategoryList = () => Object.values(FEED_CATEGORY_CONFIG).map(({ key, name }) => ({ key, name }))

/* ===========================
Expand Down Expand Up @@ -148,7 +202,8 @@ export async function getFeedCategories() {

/**
* Batch lookup: returns a Map of `${address}-${network}` → { final }.
* Uses DB risk_status when present. If absent, infers "deprecating" from shutdownDate.
* Uses DB risk_status when present, unless the feed has a shutdownDate (deprecating).
* If absent, infers "deprecating" from shutdownDate.
* Returns null when neither is available.
*/
export async function getFeedRiskTiersBatch(feedRequests: FeedRequest[]): Promise<Map<string, FeedTierResult>> {
Expand Down
78 changes: 78 additions & 0 deletions src/db/streamCategories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { resolveFeedCategory, type FeedTierResult } from "./feedCategories.js"
import { supabase } from "./supabase.js"

const TABLE = "prod_streams_risk_docs"
const ADDRESS_BATCH_SIZE = 100

type StreamRiskRow = {
stream_proxy_address: string
risk_status: string | null
}

export type StreamRiskRequest = {
streamProxyAddress: string
shutdownDate?: string
}

const normalizeAddress = (value: string) => value.toLowerCase()

async function queryStreamRiskByAddresses(addresses: string[]): Promise<Map<string, string | null>> {
const lookup = new Map<string, string | null>()

if (!supabase || addresses.length === 0) return lookup

for (let i = 0; i < addresses.length; i += ADDRESS_BATCH_SIZE) {
const chunk = addresses.slice(i, i + ADDRESS_BATCH_SIZE)

try {
const { data, error } = await supabase
.from(TABLE)
.select("stream_proxy_address, risk_status")
.in("stream_proxy_address", chunk)
.limit(1000)

if (error) continue
;(data as StreamRiskRow[] | null)?.forEach((row) => {
lookup.set(normalizeAddress(row.stream_proxy_address), row.risk_status ?? null)
})
} catch {
continue
}
}

return lookup
}

/**
* Batch lookup: returns Map of normalized stream_proxy_address → { final }.
* All stream risk docs are keyed by address; network is not used for matching.
*/
export async function getStreamRiskTiersBatch(requests: StreamRiskRequest[]): Promise<Map<string, FeedTierResult>> {
const out = new Map<string, FeedTierResult>()

if (requests.length === 0) return out

const finish = (lookup: Map<string, string | null>) => {
requests.forEach(({ streamProxyAddress, shutdownDate }) => {
const normalizedAddress = normalizeAddress(streamProxyAddress)
const dbTier = lookup.get(normalizedAddress) ?? null
out.set(normalizedAddress, { final: resolveFeedCategory(dbTier, shutdownDate) })
})
}

if (!supabase) {
finish(new Map())
return out
}

const addresses = Array.from(new Set(requests.map((r) => normalizeAddress(r.streamProxyAddress))))

try {
const lookup = await queryStreamRiskByAddresses(addresses)
finish(lookup)
return out
} catch {
finish(new Map())
return out
}
}
5 changes: 3 additions & 2 deletions src/features/data/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,13 +486,15 @@ export const CHAINS: Chain[] = [
networkType: "mainnet",
rddUrl: "https://reference-data-directory.vercel.app/feeds-monad-mainnet.json",
queryString: "monad-mainnet",
tags: ["smartData"],
},
{
name: "Monad Testnet",
explorerUrl: "https://testnet.monadvision.com/address/%s",
networkType: "testnet",
rddUrl: "https://reference-data-directory.vercel.app/feeds-monad-testnet.json",
queryString: "monad-testnet",
tags: ["smartData"],
},
],
},
Expand Down Expand Up @@ -679,7 +681,7 @@ export const CHAINS: Chain[] = [
title: "Solana Data Feeds",
img: "/assets/chains/solana.svg",
networkStatusUrl: "https://status.solana.com/",
tags: ["default", "smartData"],
tags: ["default"],
supportedFeatures: ["feeds"],
networks: [
{
Expand All @@ -688,7 +690,6 @@ export const CHAINS: Chain[] = [
networkType: "mainnet",
rddUrl: "https://reference-data-directory.vercel.app/feeds-solana-mainnet.json",
queryString: "solana-mainnet",
tags: ["smartData"],
},
{
name: "Solana Devnet",
Expand Down
Loading
Loading