From f8c075e07a7dc0cd27146275fc73548e44be8958 Mon Sep 17 00:00:00 2001 From: Mark Shenouda Date: Tue, 16 Dec 2025 09:34:11 +0200 Subject: [PATCH 01/16] update: Token details page --- .../CCIP/Tables/TokenChainsTable.tsx | 81 ++++++++- src/lib/ccip/services/realtime-data.ts | 166 ++++++++++++++++++ 2 files changed, 239 insertions(+), 8 deletions(-) create mode 100644 src/lib/ccip/services/realtime-data.ts diff --git a/src/components/CCIP/Tables/TokenChainsTable.tsx b/src/components/CCIP/Tables/TokenChainsTable.tsx index 5cc4c24778b..796d74318b7 100644 --- a/src/components/CCIP/Tables/TokenChainsTable.tsx +++ b/src/components/CCIP/Tables/TokenChainsTable.tsx @@ -5,9 +5,12 @@ import { Environment, SupportedTokenConfig, tokenPoolDisplay, PoolType } from "~ import { areAllLanesPaused } from "~/config/data/ccip/utils.ts" import { ChainType, ExplorerInfo } from "~/config/types.ts" import TableSearchInput from "./TableSearchInput.tsx" -import { useState } from "react" +import { useState, useEffect } from "react" import { getExplorerAddressUrl, fallbackTokenIconUrl } from "~/features/utils/index.ts" import TokenDrawer from "../Drawer/TokenDrawer.tsx" +import { Tooltip } from "~/features/common/Tooltip/Tooltip.tsx" +import { RealtimeDataService } from "~/lib/ccip/services/realtime-data.ts" +import type { TokenFinalityData } from "~/lib/ccip/types/index.ts" interface TableProps { networks: { @@ -42,6 +45,36 @@ interface TableProps { function TokenChainsTable({ networks, token, lanes, environment }: TableProps) { const [search, setSearch] = useState("") + const [finalityData, setFinalityData] = useState>({}) + const [loading, setLoading] = useState(true) + + console.log("[TokenChainsTable] Render - loading:", loading, "finalityData keys:", Object.keys(finalityData)) + + useEffect(() => { + const fetchFinalityData = async () => { + try { + console.log("[TokenChainsTable] Starting fetch for token:", token.id, "env:", environment) + const realtimeService = new RealtimeDataService() + const result = await realtimeService.getTokenFinality(token.id, environment, "internal_id") + console.log("[TokenChainsTable] Received result:", result) + + if (result && result.data) { + console.log("[TokenChainsTable] Setting finality data:", result.data) + setFinalityData(result.data) + } else { + console.warn("[TokenChainsTable] No data received") + } + } catch (error) { + console.error("Failed to fetch token finality data:", error) + } finally { + console.log("[TokenChainsTable] Setting loading to false") + setLoading(false) + } + } + + fetchFinalityData() + }, [token.id, environment]) + return ( <>
@@ -145,15 +178,47 @@ function TokenChainsTable({ networks, token, lanes, environment }: TableProps) { {network.tokenPoolVersion} - {/* TODO: Fetch from API - GET /api/ccip/v1/tokens/{tokenCanonicalSymbol}/finality?environment={environment} - Custom finality is derived from minBlockConfirmation > 0 - Display: "Yes" | "No" | "N/A" (with tooltip for unavailable) */} - - + {(() => { + console.log( + `[TokenChainsTable] Checking finality for network: ${network.name}, key: "${network.key}", hasData:`, + !!finalityData[network.key], + "finality:", + finalityData[network.key] + ) + return null + })()} + {loading ? ( + "-" + ) : finalityData[network.key] ? ( + finalityData[network.key].hasCustomFinality === null ? ( + + ) : finalityData[network.key].hasCustomFinality ? ( + "Yes" + ) : ( + "No" + ) + ) : ( + + )} - {/* TODO: Fetch from API - GET /api/ccip/v1/tokens/{tokenCanonicalSymbol}/finality?environment={environment} - Display minBlockConfirmation value or "-" if custom finality is disabled/unavailable */} - - + {loading + ? "-" + : finalityData[network.key] + ? finalityData[network.key].minBlockConfirmation === null + ? "-" + : finalityData[network.key].minBlockConfirmation + : "-"} ) diff --git a/src/lib/ccip/services/realtime-data.ts b/src/lib/ccip/services/realtime-data.ts new file mode 100644 index 00000000000..b1f23e4f277 --- /dev/null +++ b/src/lib/ccip/services/realtime-data.ts @@ -0,0 +1,166 @@ +import { Environment } from "~/lib/ccip/types/index.ts" +import type { + TokenRateLimits, + RateLimiterEntry, + RateLimiterConfig, + TokenFinalityData, + OutputKeyType, +} from "~/lib/ccip/types/index.ts" + +export const prerender = false + +/** + * Base URL for CCIP realtime API + * For client-side calls, use relative URLs to hit the local API endpoints + */ +const getApiBaseUrl = () => { + // In browser context, use relative URLs + if (typeof window !== "undefined") { + return "" + } + // In server context, use environment variable or default + return process.env.CCIP_REALTIME_API_BASE_URL || "https://api.ccip.chainlink.com" +} + +/** + * Response structure for lane supported tokens endpoint + */ +export interface LaneSupportedTokensResponse { + metadata: { + environment: Environment + timestamp: string + requestId: string + sourceChain: string + destinationChain: string + tokenCount: number + } + supportedTokens: Record +} + +/** + * Response structure for token finality endpoint + */ +export interface TokenFinalityResponse { + metadata: { + environment: Environment + timestamp: string + requestId: string + tokenSymbol: string + chainCount: number + } + data: Record +} + +/** + * Service class for handling CCIP realtime data operations + * Provides functionality to fetch live data from the CCIP API + */ +export class RealtimeDataService { + private readonly requestId: string + + /** + * Creates a new instance of RealtimeDataService + */ + constructor() { + // Generate UUID - handle both browser and server environments + if (typeof crypto !== "undefined" && crypto.randomUUID) { + this.requestId = crypto.randomUUID() + } else { + this.requestId = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}` + } + } + + /** + * Fetches supported tokens with rate limits for a specific lane + * + * @param sourceInternalId - Source chain internal ID + * @param destinationInternalId - Destination chain internal ID + * @param environment - Network environment (mainnet/testnet) + * @returns Supported tokens with rate limits + */ + async getLaneSupportedTokens( + sourceInternalId: string, + destinationInternalId: string, + environment: Environment + ): Promise { + try { + const baseUrl = getApiBaseUrl() + const url = `${baseUrl}/api/ccip/v1/lanes/by-internal-id/${sourceInternalId}/${destinationInternalId}/supported-tokens?environment=${environment}` + + const response = await fetch(url) + + if (!response.ok) { + return null + } + + const data = await response.json() + return data + } catch (error) { + console.error("Error fetching lane supported tokens:", error) + return null + } + } + + /** + * Fetches token finality details across all chains + * + * @param tokenCanonicalSymbol - Token canonical symbol (e.g., "BETS", "LINK") + * @param environment - Network environment (mainnet/testnet) + * @param outputKey - Format to use for displaying chain keys (optional) + * @returns Token finality data for all chains + */ + async getTokenFinality( + tokenCanonicalSymbol: string, + environment: Environment, + outputKey?: OutputKeyType + ): Promise { + try { + const baseUrl = getApiBaseUrl() + let url = `${baseUrl}/api/ccip/v1/tokens/${tokenCanonicalSymbol}/finality?environment=${environment}` + + if (outputKey) { + url += `&output_key=${outputKey}` + } + + const response = await fetch(url) + + if (!response.ok) { + console.error("Failed to fetch token finality:", response.status) + return null + } + + const data = await response.json() + return data + } catch (error) { + console.error("Error fetching token finality:", error) + return null + } + } + + /** + * Checks if rate limiter data is unavailable (null) + * + * @param entry - Rate limiter entry to check + * @returns True if unavailable (null) + */ + isRateLimiterUnavailable(entry: RateLimiterEntry): entry is null { + return entry === null + } + + /** + * Checks if rate limiter is enabled + * + * @param config - Rate limiter configuration + * @returns True if enabled + */ + isRateLimiterEnabled(config: RateLimiterConfig): boolean { + return config.isEnabled + } + + /** + * Gets the request ID for this service instance + */ + getRequestId(): string { + return this.requestId + } +} From b2b16e8ffc6a17c246f6d70bfea194341fca4da3 Mon Sep 17 00:00:00 2001 From: Mark Shenouda Date: Tue, 16 Dec 2025 10:21:43 +0200 Subject: [PATCH 02/16] update: Lane latest data --- src/components/CCIP/Drawer/LaneDrawer.tsx | 118 ++++++++++++++++++--- src/components/CCIP/Drawer/TokenDrawer.tsx | 100 ++++++++++++++--- src/lib/ccip/services/realtime-data.ts | 91 +++++++++++++++- 3 files changed, 280 insertions(+), 29 deletions(-) diff --git a/src/components/CCIP/Drawer/LaneDrawer.tsx b/src/components/CCIP/Drawer/LaneDrawer.tsx index 126f230033e..5d6bf658b14 100644 --- a/src/components/CCIP/Drawer/LaneDrawer.tsx +++ b/src/components/CCIP/Drawer/LaneDrawer.tsx @@ -3,12 +3,14 @@ import "../Tables/Table.css" import { Environment, LaneConfig, LaneFilter, Version } from "~/config/data/ccip/types.ts" import { getNetwork, getTokenData } from "~/config/data/ccip/data.ts" import { determineTokenMechanism } from "~/config/data/ccip/utils.ts" -import { useState } from "react" +import { useState, useEffect } from "react" import LaneDetailsHero from "../ChainHero/LaneDetailsHero.tsx" import { getExplorerAddressUrl, getTokenIconUrl, fallbackTokenIconUrl } from "~/features/utils/index.ts" import TableSearchInput from "../Tables/TableSearchInput.tsx" import { Tooltip } from "~/features/common/Tooltip/Tooltip.tsx" import { ChainType, ExplorerInfo } from "@config/types.ts" +import { RealtimeDataService } from "~/lib/ccip/services/realtime-data.ts" +import type { TokenRateLimits } from "~/lib/ccip/types/index.ts" function LaneDrawer({ lane, @@ -26,6 +28,9 @@ function LaneDrawer({ inOutbound: LaneFilter }) { const [search, setSearch] = useState("") + const [rateLimits, setRateLimits] = useState>({}) + const [isLoadingRateLimits, setIsLoadingRateLimits] = useState(true) + const destinationNetworkDetails = getNetwork({ filter: environment, chain: destinationNetwork.key, @@ -36,6 +41,27 @@ function LaneDrawer({ chain: sourceNetwork.key, }) + // Fetch rate limits data + useEffect(() => { + const fetchRateLimits = async () => { + setIsLoadingRateLimits(true) + const realtimeService = new RealtimeDataService() + + // Determine source and destination based on inOutbound filter + const source = inOutbound === LaneFilter.Outbound ? sourceNetwork.key : destinationNetwork.key + const destination = inOutbound === LaneFilter.Outbound ? destinationNetwork.key : sourceNetwork.key + + const response = await realtimeService.getLaneSupportedTokens(source, destination, environment) + + if (response?.data) { + setRateLimits(response.data) + } + setIsLoadingRateLimits(false) + } + + fetchRateLimits() + }, [sourceNetwork.key, destinationNetwork.key, environment, inOutbound]) + return ( <>

Lane Details

@@ -184,10 +210,29 @@ function LaneDrawer({ if (!Object.keys(data).length) return null const logo = getTokenIconUrl(token) - // TODO: Fetch rate limits from API for both inbound and outbound - // Token pause detection requires rate limiter data from API - // A token is paused when rate limit capacity is 0 - const tokenPaused = false + // Get rate limit data for this token + const tokenRateLimits = rateLimits[token] + const realtimeService = new RealtimeDataService() + + // Determine direction based on inOutbound filter + const direction = inOutbound === LaneFilter.Outbound ? "out" : "in" + + // Get standard and FTF rate limits + const allLimits = tokenRateLimits + ? realtimeService.getAllRateLimitsForDirection(tokenRateLimits, direction) + : { standard: null, ftf: null } + + // Token is paused if standard rate limit capacity is 0 + const tokenPaused = allLimits.standard?.capacity === "0" + + // Format rate limit values + const formatRateLimit = (value: string | null) => { + if (!value || value === "0") return "0" + // Convert from wei to tokens (divide by 1e18) + const numValue = BigInt(value) + const formatted = Number(numValue) / 1e18 + return formatted.toLocaleString(undefined, { maximumFractionDigits: 2 }) + } return ( @@ -235,23 +280,64 @@ function LaneDrawer({ - {/* TODO: Fetch rate limits from API for both inbound and outbound - GET /api/ccip/v1/lanes/by-internal-id/{source}/{destination}/supported-tokens?environment={environment} - Response will contain both standard and custom rate limits per token */} - Disabled + {isLoadingRateLimits + ? "Loading..." + : allLimits.standard + ? allLimits.standard.isEnabled + ? formatRateLimit(allLimits.standard.capacity) + : "Disabled" + : ( + + Unavailable + + + )} - {/* TODO: Fetch rate limits from API for both inbound and outbound - Display refill rate from standard.in/out or custom.in/out based on inOutbound filter */} - Disabled + {isLoadingRateLimits + ? "Loading..." + : allLimits.standard + ? allLimits.standard.isEnabled + ? formatRateLimit(allLimits.standard.rate) + : "Disabled" + : "N/A"} - {/* Placeholder for FTF Rate limit capacity - data not yet available */} - TBC + {isLoadingRateLimits + ? "Loading..." + : allLimits.ftf + ? allLimits.ftf.isEnabled + ? formatRateLimit(allLimits.ftf.capacity) + : "Disabled" + : ( + + Unavailable + + + )} - {/* Placeholder for FTF Rate limit refill rate - data not yet available */} - TBC + {isLoadingRateLimits + ? "Loading..." + : allLimits.ftf + ? allLimits.ftf.isEnabled + ? formatRateLimit(allLimits.ftf.rate) + : "Disabled" + : "N/A"} ) diff --git a/src/components/CCIP/Drawer/TokenDrawer.tsx b/src/components/CCIP/Drawer/TokenDrawer.tsx index e65ff4f65f9..522decb4b3c 100644 --- a/src/components/CCIP/Drawer/TokenDrawer.tsx +++ b/src/components/CCIP/Drawer/TokenDrawer.tsx @@ -13,12 +13,14 @@ import { getTokenData, LaneConfig, } from "~/config/data/ccip/index.ts" -import { useState } from "react" +import { useState, useEffect } from "react" import { ChainType, ExplorerInfo, SupportedChain } from "~/config/index.ts" import LaneDrawer from "../Drawer/LaneDrawer.tsx" import TableSearchInput from "../Tables/TableSearchInput.tsx" import Tabs from "../Tables/Tabs.tsx" import { Tooltip } from "~/features/common/Tooltip/Tooltip.tsx" +import { RealtimeDataService } from "~/lib/ccip/services/realtime-data.ts" +import type { TokenRateLimits } from "~/lib/ccip/types/index.ts" function TokenDrawer({ token, @@ -54,6 +56,7 @@ function TokenDrawer({ }) { const [search, setSearch] = useState("") const [inOutbound, setInOutbound] = useState(LaneFilter.Outbound) + const [rateLimits, setRateLimits] = useState>>({}) type LaneRow = { networkDetails: { @@ -65,6 +68,29 @@ function TokenDrawer({ destinationPoolType: PoolType } + // Fetch rate limits for all lanes + useEffect(() => { + const fetchAllRateLimits = async () => { + const realtimeService = new RealtimeDataService() + const newRateLimits: Record> = {} + + for (const destinationChain of Object.keys(destinationLanes)) { + const source = inOutbound === LaneFilter.Outbound ? network.key : destinationChain + const destination = inOutbound === LaneFilter.Outbound ? destinationChain : network.key + const laneKey = `${source}-${destination}` + + const response = await realtimeService.getLaneSupportedTokens(source, destination, environment) + if (response?.data) { + newRateLimits[laneKey] = response.data + } + } + + setRateLimits(newRateLimits) + } + + fetchAllRateLimits() + }, [network.key, destinationLanes, environment, inOutbound]) + const laneRows: LaneRow[] = Object.keys(destinationLanes) .map((destinationChain) => { const networkDetails = getNetwork({ @@ -186,6 +212,8 @@ function TokenDrawer({ }} /> + FTF Rate limit capacity + FTF Rate limit refill rate Mechanism { if (!laneData || !networkDetails) return null - // TODO: Fetch rate limits from API for both inbound and outbound - // Token pause detection requires rate limiter data from API - // A token is paused when rate limit capacity is 0 - const tokenPaused = false + // Get rate limit data for this lane + const source = inOutbound === LaneFilter.Outbound ? network.key : destinationChain + const destination = inOutbound === LaneFilter.Outbound ? destinationChain : network.key + const laneKey = `${source}-${destination}` + const laneRateLimits = rateLimits[laneKey] + const tokenRateLimits = laneRateLimits?.[token.id] + + const realtimeService = new RealtimeDataService() + const direction = inOutbound === LaneFilter.Outbound ? "out" : "in" + + // Get standard and FTF rate limits + const allLimits = tokenRateLimits + ? realtimeService.getAllRateLimitsForDirection(tokenRateLimits, direction) + : { standard: null, ftf: null } + + // Token is paused if standard rate limit capacity is 0 + const tokenPaused = allLimits.standard?.capacity === "0" + + // Format rate limit values + const formatRateLimit = (value: string | null) => { + if (!value || value === "0") return "0" + const numValue = BigInt(value) + const formatted = Number(numValue) / 1e18 + return formatted.toLocaleString(undefined, { maximumFractionDigits: 2 }) + } + + const isLoading = !laneRateLimits return ( @@ -257,15 +308,40 @@ function TokenDrawer({ - {/* TODO: Fetch rate limits from API for both inbound and outbound - GET /api/ccip/v1/lanes/by-internal-id/{source}/{destination}/supported-tokens?environment={environment} - Response will contain both standard and custom rate limits per token */} - Disabled + {isLoading + ? "Loading..." + : allLimits.standard + ? allLimits.standard.isEnabled + ? formatRateLimit(allLimits.standard.capacity) + : "Disabled" + : "N/A"} + + + {isLoading + ? "Loading..." + : allLimits.standard + ? allLimits.standard.isEnabled + ? formatRateLimit(allLimits.standard.rate) + : "Disabled" + : "N/A"} + + + {isLoading + ? "Loading..." + : allLimits.ftf + ? allLimits.ftf.isEnabled + ? formatRateLimit(allLimits.ftf.capacity) + : "Disabled" + : "N/A"} - {/* TODO: Fetch rate limits from API for both inbound and outbound - Display refill rate from standard.in/out or custom.in/out based on inOutbound filter */} - Disabled + {isLoading + ? "Loading..." + : allLimits.ftf + ? allLimits.ftf.isEnabled + ? formatRateLimit(allLimits.ftf.rate) + : "Disabled" + : "N/A"} {inOutbound === LaneFilter.Outbound diff --git a/src/lib/ccip/services/realtime-data.ts b/src/lib/ccip/services/realtime-data.ts index b1f23e4f277..1e3ca72f0b4 100644 --- a/src/lib/ccip/services/realtime-data.ts +++ b/src/lib/ccip/services/realtime-data.ts @@ -34,7 +34,7 @@ export interface LaneSupportedTokensResponse { destinationChain: string tokenCount: number } - supportedTokens: Record + data: Record } /** @@ -163,4 +163,93 @@ export class RealtimeDataService { getRequestId(): string { return this.requestId } + + /** + * Extracts FTF (custom) rate limit data for a specific token and direction + * + * @param tokenRateLimits - Token rate limits containing standard and custom entries + * @param direction - Direction ("in" for inbound, "out" for outbound) + * @returns FTF rate limiter config or null if unavailable + */ + getFTFRateLimit(tokenRateLimits: TokenRateLimits, direction: "in" | "out"): RateLimiterConfig | null { + if (!tokenRateLimits.custom || this.isRateLimiterUnavailable(tokenRateLimits.custom)) { + return null + } + + const customEntry = tokenRateLimits.custom + return customEntry[direction] || null + } + + /** + * Gets FTF capacity for a specific token and direction + * + * @param tokenRateLimits - Token rate limits containing standard and custom entries + * @param direction - Direction ("in" for inbound, "out" for outbound) + * @returns FTF capacity value or null if unavailable + */ + getFTFCapacity(tokenRateLimits: TokenRateLimits, direction: "in" | "out"): string | null { + const ftfLimit = this.getFTFRateLimit(tokenRateLimits, direction) + return ftfLimit?.capacity || null + } + + /** + * Gets FTF refill rate for a specific token and direction + * + * @param tokenRateLimits - Token rate limits containing standard and custom entries + * @param direction - Direction ("in" for inbound, "out" for outbound) + * @returns FTF refill rate value or null if unavailable + */ + getFTFRefillRate(tokenRateLimits: TokenRateLimits, direction: "in" | "out"): string | null { + const ftfLimit = this.getFTFRateLimit(tokenRateLimits, direction) + return ftfLimit?.rate || null + } + + /** + * Checks if FTF rate limiting is enabled for a specific token and direction + * + * @param tokenRateLimits - Token rate limits containing standard and custom entries + * @param direction - Direction ("in" for inbound, "out" for outbound) + * @returns True if FTF is enabled, false otherwise + */ + isFTFEnabled(tokenRateLimits: TokenRateLimits, direction: "in" | "out"): boolean { + const ftfLimit = this.getFTFRateLimit(tokenRateLimits, direction) + return ftfLimit?.isEnabled || false + } + + /** + * Gets both standard and FTF rate limits for a specific token and direction + * + * @param tokenRateLimits - Token rate limits containing standard and custom entries + * @param direction - Direction ("in" for inbound, "out" for outbound) + * @returns Object containing both standard and FTF rate limits + */ + getAllRateLimitsForDirection( + tokenRateLimits: TokenRateLimits, + direction: "in" | "out" + ): { + standard: RateLimiterConfig | null + ftf: RateLimiterConfig | null + } { + const standardLimit = + tokenRateLimits.standard && !this.isRateLimiterUnavailable(tokenRateLimits.standard) + ? tokenRateLimits.standard[direction] || null + : null + + const ftfLimit = this.getFTFRateLimit(tokenRateLimits, direction) + + return { + standard: standardLimit, + ftf: ftfLimit, + } + } + + /** + * Checks if a token has FTF rate limiting available + * + * @param tokenRateLimits - Token rate limits to check + * @returns True if FTF data is available (not null/unavailable) + */ + hasFTFRateLimits(tokenRateLimits: TokenRateLimits): boolean { + return tokenRateLimits.custom !== null && !this.isRateLimiterUnavailable(tokenRateLimits.custom) + } } From cd2e4d5ae24164d1de6abeffb1277e53aabeec09 Mon Sep 17 00:00:00 2001 From: Mark Shenouda Date: Tue, 16 Dec 2025 10:33:17 +0200 Subject: [PATCH 03/16] fix: lint --- src/components/CCIP/Drawer/LaneDrawer.tsx | 80 ++++++++++++----------- 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/src/components/CCIP/Drawer/LaneDrawer.tsx b/src/components/CCIP/Drawer/LaneDrawer.tsx index 5d6bf658b14..8081a8a28c0 100644 --- a/src/components/CCIP/Drawer/LaneDrawer.tsx +++ b/src/components/CCIP/Drawer/LaneDrawer.tsx @@ -280,25 +280,27 @@ function LaneDrawer({ - {isLoadingRateLimits - ? "Loading..." - : allLimits.standard - ? allLimits.standard.isEnabled - ? formatRateLimit(allLimits.standard.capacity) - : "Disabled" - : ( - - Unavailable - - - )} + {isLoadingRateLimits ? ( + "Loading..." + ) : allLimits.standard ? ( + allLimits.standard.isEnabled ? ( + formatRateLimit(allLimits.standard.capacity) + ) : ( + "Disabled" + ) + ) : ( + + Unavailable + + + )} {isLoadingRateLimits @@ -310,25 +312,27 @@ function LaneDrawer({ : "N/A"} - {isLoadingRateLimits - ? "Loading..." - : allLimits.ftf - ? allLimits.ftf.isEnabled - ? formatRateLimit(allLimits.ftf.capacity) - : "Disabled" - : ( - - Unavailable - - - )} + {isLoadingRateLimits ? ( + "Loading..." + ) : allLimits.ftf ? ( + allLimits.ftf.isEnabled ? ( + formatRateLimit(allLimits.ftf.capacity) + ) : ( + "Disabled" + ) + ) : ( + + Unavailable + + + )} {isLoadingRateLimits From 5045f2d6a5cf594f094a4017cac226bc798f8151 Mon Sep 17 00:00:00 2001 From: Mark Shenouda Date: Tue, 16 Dec 2025 10:34:49 +0200 Subject: [PATCH 04/16] remove: unused console.log --- src/components/CCIP/Tables/TokenChainsTable.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/components/CCIP/Tables/TokenChainsTable.tsx b/src/components/CCIP/Tables/TokenChainsTable.tsx index 796d74318b7..a62751cf134 100644 --- a/src/components/CCIP/Tables/TokenChainsTable.tsx +++ b/src/components/CCIP/Tables/TokenChainsTable.tsx @@ -48,18 +48,13 @@ function TokenChainsTable({ networks, token, lanes, environment }: TableProps) { const [finalityData, setFinalityData] = useState>({}) const [loading, setLoading] = useState(true) - console.log("[TokenChainsTable] Render - loading:", loading, "finalityData keys:", Object.keys(finalityData)) - useEffect(() => { const fetchFinalityData = async () => { try { - console.log("[TokenChainsTable] Starting fetch for token:", token.id, "env:", environment) const realtimeService = new RealtimeDataService() const result = await realtimeService.getTokenFinality(token.id, environment, "internal_id") - console.log("[TokenChainsTable] Received result:", result) if (result && result.data) { - console.log("[TokenChainsTable] Setting finality data:", result.data) setFinalityData(result.data) } else { console.warn("[TokenChainsTable] No data received") @@ -67,7 +62,6 @@ function TokenChainsTable({ networks, token, lanes, environment }: TableProps) { } catch (error) { console.error("Failed to fetch token finality data:", error) } finally { - console.log("[TokenChainsTable] Setting loading to false") setLoading(false) } } From dcf2fa018e235c8b16c81456fcdad47752eb4b46 Mon Sep 17 00:00:00 2001 From: Mark Shenouda Date: Tue, 16 Dec 2025 10:35:52 +0200 Subject: [PATCH 05/16] remove: unused console.log --- src/components/CCIP/Tables/TokenChainsTable.tsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/components/CCIP/Tables/TokenChainsTable.tsx b/src/components/CCIP/Tables/TokenChainsTable.tsx index a62751cf134..0bcacd435e1 100644 --- a/src/components/CCIP/Tables/TokenChainsTable.tsx +++ b/src/components/CCIP/Tables/TokenChainsTable.tsx @@ -172,15 +172,6 @@ function TokenChainsTable({ networks, token, lanes, environment }: TableProps) { {network.tokenPoolVersion} - {(() => { - console.log( - `[TokenChainsTable] Checking finality for network: ${network.name}, key: "${network.key}", hasData:`, - !!finalityData[network.key], - "finality:", - finalityData[network.key] - ) - return null - })()} {loading ? ( "-" ) : finalityData[network.key] ? ( From 69a5b9dbcd80d652cf12dc3b1942d0ed23fe453a Mon Sep 17 00:00:00 2001 From: Mark Shenouda Date: Tue, 16 Dec 2025 10:59:13 +0200 Subject: [PATCH 06/16] update: token drawer to include verifiers --- src/components/CCIP/Drawer/LaneDrawer.tsx | 2 +- src/components/CCIP/Drawer/TokenDrawer.tsx | 444 ++++++++++++--------- 2 files changed, 261 insertions(+), 185 deletions(-) diff --git a/src/components/CCIP/Drawer/LaneDrawer.tsx b/src/components/CCIP/Drawer/LaneDrawer.tsx index 8081a8a28c0..26b36657451 100644 --- a/src/components/CCIP/Drawer/LaneDrawer.tsx +++ b/src/components/CCIP/Drawer/LaneDrawer.tsx @@ -262,7 +262,7 @@ function LaneDrawer({
diff --git a/src/components/CCIP/Drawer/TokenDrawer.tsx b/src/components/CCIP/Drawer/TokenDrawer.tsx index 522decb4b3c..47cc5a63590 100644 --- a/src/components/CCIP/Drawer/TokenDrawer.tsx +++ b/src/components/CCIP/Drawer/TokenDrawer.tsx @@ -12,9 +12,13 @@ import { PoolType, getTokenData, LaneConfig, + getVerifiersByNetwork, + getVerifierTypeDisplay, } from "~/config/data/ccip/index.ts" import { useState, useEffect } from "react" import { ChainType, ExplorerInfo, SupportedChain } from "~/config/index.ts" +import { getExplorerAddressUrl } from "~/features/utils/index.ts" +import Address from "~/components/AddressReact.tsx" import LaneDrawer from "../Drawer/LaneDrawer.tsx" import TableSearchInput from "../Tables/TableSearchInput.tsx" import Tabs from "../Tables/Tabs.tsx" @@ -55,9 +59,16 @@ function TokenDrawer({ environment: Environment }) { const [search, setSearch] = useState("") - const [inOutbound, setInOutbound] = useState(LaneFilter.Outbound) + const [activeTab, setActiveTab] = useState<"outbound" | "inbound" | "verifiers">("outbound") const [rateLimits, setRateLimits] = useState>>({}) + // Get verifiers for the current network + const verifiers = getVerifiersByNetwork({ + networkId: network.key, + environment, + version: Version.V1_2_0, + }) + type LaneRow = { networkDetails: { name: string @@ -75,8 +86,8 @@ function TokenDrawer({ const newRateLimits: Record> = {} for (const destinationChain of Object.keys(destinationLanes)) { - const source = inOutbound === LaneFilter.Outbound ? network.key : destinationChain - const destination = inOutbound === LaneFilter.Outbound ? destinationChain : network.key + const source = activeTab === "outbound" ? network.key : destinationChain + const destination = activeTab === "outbound" ? destinationChain : network.key const laneKey = `${source}-${destination}` const response = await realtimeService.getLaneSupportedTokens(source, destination, environment) @@ -89,7 +100,7 @@ function TokenDrawer({ } fetchAllRateLimits() - }, [network.key, destinationLanes, environment, inOutbound]) + }, [network.key, destinationLanes, environment, activeTab]) const laneRows: LaneRow[] = Object.keys(destinationLanes) .map((destinationChain) => { @@ -164,191 +175,253 @@ function TokenDrawer({ tabs={[ { name: "Outbound lanes", - key: LaneFilter.Outbound, + key: "outbound", }, { name: "Inbound lanes", - key: LaneFilter.Inbound, + key: "inbound", + }, + { + name: "Verifiers", + key: "verifiers", }, ]} - onChange={(key) => setInOutbound(key as LaneFilter)} + onChange={(key) => setActiveTab(key as "outbound" | "inbound" | "verifiers")} />
-
- {" "} - - - - - - - - - - {/* */} - - - - {laneRows - ?.filter( - ({ networkDetails }) => - networkDetails && networkDetails.name.toLowerCase().includes(search.toLowerCase()) - ) - .map(({ networkDetails, laneData, destinationChain, destinationPoolType }) => { - if (!laneData || !networkDetails) return null + {activeTab === "verifiers" ? ( +
+
{inOutbound === LaneFilter.Inbound ? "Source" : "Destination"} network - Rate limit capacity - - - Rate limit refill rate - - FTF Rate limit capacityFTF Rate limit refill rate - Mechanism - - Status
+ + + + + + + + + + {verifiers.length === 0 ? ( + + + + ) : ( + verifiers + .filter((verifier) => { + if (!search) return true + const searchLower = search.toLowerCase() + return ( + verifier.name.toLowerCase().includes(searchLower) || + verifier.address.toLowerCase().includes(searchLower) || + verifier.type.toLowerCase().includes(searchLower) + ) + }) + .map((verifier) => ( + + + + + + + )) + )} + +
VerifierVerifier addressVerifier typeThreshold amount
+ No verifiers configured +
+
+ {`${verifier.name} + {verifier.name} +
+
+
+
{getVerifierTypeDisplay(verifier.type)}N/A
+
+ ) : ( +
+ {" "} + + + + + + + + + + {/* */} + + + + {laneRows + ?.filter( + ({ networkDetails }) => + networkDetails && networkDetails.name.toLowerCase().includes(search.toLowerCase()) + ) + .map(({ networkDetails, laneData, destinationChain, destinationPoolType }) => { + if (!laneData || !networkDetails) return null - // Get rate limit data for this lane - const source = inOutbound === LaneFilter.Outbound ? network.key : destinationChain - const destination = inOutbound === LaneFilter.Outbound ? destinationChain : network.key - const laneKey = `${source}-${destination}` - const laneRateLimits = rateLimits[laneKey] - const tokenRateLimits = laneRateLimits?.[token.id] + // Get rate limit data for this lane + const source = activeTab === "outbound" ? network.key : destinationChain + const destination = activeTab === "outbound" ? destinationChain : network.key + const laneKey = `${source}-${destination}` + const laneRateLimits = rateLimits[laneKey] + const tokenRateLimits = laneRateLimits?.[token.id] - const realtimeService = new RealtimeDataService() - const direction = inOutbound === LaneFilter.Outbound ? "out" : "in" + const realtimeService = new RealtimeDataService() + const direction = activeTab === "outbound" ? "out" : "in" - // Get standard and FTF rate limits - const allLimits = tokenRateLimits - ? realtimeService.getAllRateLimitsForDirection(tokenRateLimits, direction) - : { standard: null, ftf: null } + // Get standard and FTF rate limits + const allLimits = tokenRateLimits + ? realtimeService.getAllRateLimitsForDirection(tokenRateLimits, direction) + : { standard: null, ftf: null } - // Token is paused if standard rate limit capacity is 0 - const tokenPaused = allLimits.standard?.capacity === "0" + // Token is paused if standard rate limit capacity is 0 + const tokenPaused = allLimits.standard?.capacity === "0" - // Format rate limit values - const formatRateLimit = (value: string | null) => { - if (!value || value === "0") return "0" - const numValue = BigInt(value) - const formatted = Number(numValue) / 1e18 - return formatted.toLocaleString(undefined, { maximumFractionDigits: 2 }) - } + // Format rate limit values + const formatRateLimit = (value: string | null) => { + if (!value || value === "0") return "0" + const numValue = BigInt(value) + const formatted = Number(numValue) / 1e18 + return formatted.toLocaleString(undefined, { maximumFractionDigits: 2 }) + } - const isLoading = !laneRateLimits + const isLoading = !laneRateLimits - return ( - - - - - - - - {/* + + + + + + + {/* */} - - ) - })} - -
{activeTab === "inbound" ? "Source" : "Destination"} network + Rate limit capacity + + + Rate limit refill rate + + FTF Rate limit capacityFTF Rate limit refill rate + Mechanism + + Status
- - - {isLoading - ? "Loading..." - : allLimits.standard - ? allLimits.standard.isEnabled - ? formatRateLimit(allLimits.standard.capacity) - : "Disabled" - : "N/A"} - - {isLoading - ? "Loading..." - : allLimits.standard - ? allLimits.standard.isEnabled - ? formatRateLimit(allLimits.standard.rate) - : "Disabled" - : "N/A"} - - {isLoading - ? "Loading..." - : allLimits.ftf - ? allLimits.ftf.isEnabled - ? formatRateLimit(allLimits.ftf.capacity) - : "Disabled" - : "N/A"} - - {isLoading - ? "Loading..." - : allLimits.ftf - ? allLimits.ftf.isEnabled - ? formatRateLimit(allLimits.ftf.rate) - : "Disabled" - : "N/A"} - - {inOutbound === LaneFilter.Outbound - ? determineTokenMechanism(network.tokenPoolType, destinationPoolType) - : determineTokenMechanism(destinationPoolType, network.tokenPoolType)} - + return ( +
+ + + {isLoading + ? "Loading..." + : allLimits.standard + ? allLimits.standard.isEnabled + ? formatRateLimit(allLimits.standard.capacity) + : "Disabled" + : "N/A"} + + {isLoading + ? "Loading..." + : allLimits.standard + ? allLimits.standard.isEnabled + ? formatRateLimit(allLimits.standard.rate) + : "Disabled" + : "N/A"} + + {isLoading + ? "Loading..." + : allLimits.ftf + ? allLimits.ftf.isEnabled + ? formatRateLimit(allLimits.ftf.capacity) + : "Disabled" + : "N/A"} + + {isLoading + ? "Loading..." + : allLimits.ftf + ? allLimits.ftf.isEnabled + ? formatRateLimit(allLimits.ftf.rate) + : "Disabled" + : "N/A"} + + {activeTab === "outbound" + ? determineTokenMechanism(network.tokenPoolType, destinationPoolType) + : determineTokenMechanism(destinationPoolType, network.tokenPoolType)} +
-
+ + ) + })} + + + + )} -
- {laneRows?.filter( - ({ networkDetails }) => networkDetails && networkDetails.name.toLowerCase().includes(search.toLowerCase()) - ).length === 0 && <>No lanes found} -
+ {activeTab !== "verifiers" && ( +
+ {laneRows?.filter( + ({ networkDetails }) => networkDetails && networkDetails.name.toLowerCase().includes(search.toLowerCase()) + ).length === 0 && <>No lanes found} +
+ )} ) From cfb7a9882aa87393d68eb85681d1fa85839dd04b Mon Sep 17 00:00:00 2001 From: Mark Shenouda Date: Wed, 17 Dec 2025 19:51:41 +0200 Subject: [PATCH 07/16] refactor: extract realtime data logic into reusable hooks and components - Create custom hooks for data fetching: * useTokenRateLimits - single lane rate limits * useMultiLaneRateLimits - multiple lanes in parallel * useTokenFinality - token finality data - Add RateLimitCell component to replace complex nested ternaries - Create singleton realtimeDataService instance to avoid multiple instantiations - Add rate-limit-formatter utilities for consistent formatting - Update LaneDrawer, TokenDrawer, and TokenChainsTable to use new hooks - Add proper cleanup in useEffect hooks to prevent memory leaks Benefits: - Reduced code duplication (~133 lines removed) - Improved readability with vs 7-level nested ternaries - Better separation of concerns - More testable code - Proper cleanup handling --- src/components/CCIP/Drawer/LaneDrawer.tsx | 115 ++++-------------- src/components/CCIP/Drawer/TokenDrawer.tsx | 89 ++++---------- src/components/CCIP/RateLimitCell.tsx | 46 +++++++ .../CCIP/Tables/TokenChainsTable.tsx | 29 +---- src/hooks/useMultiLaneRateLimits.ts | 78 ++++++++++++ src/hooks/useTokenFinality.ts | 66 ++++++++++ src/hooks/useTokenRateLimits.ts | 65 ++++++++++ .../ccip/services/realtime-data-instance.ts | 7 ++ src/lib/ccip/utils/rate-limit-formatter.ts | 72 +++++++++++ 9 files changed, 385 insertions(+), 182 deletions(-) create mode 100644 src/components/CCIP/RateLimitCell.tsx create mode 100644 src/hooks/useMultiLaneRateLimits.ts create mode 100644 src/hooks/useTokenFinality.ts create mode 100644 src/hooks/useTokenRateLimits.ts create mode 100644 src/lib/ccip/services/realtime-data-instance.ts create mode 100644 src/lib/ccip/utils/rate-limit-formatter.ts diff --git a/src/components/CCIP/Drawer/LaneDrawer.tsx b/src/components/CCIP/Drawer/LaneDrawer.tsx index 26b36657451..e7b907b6c4a 100644 --- a/src/components/CCIP/Drawer/LaneDrawer.tsx +++ b/src/components/CCIP/Drawer/LaneDrawer.tsx @@ -3,14 +3,15 @@ import "../Tables/Table.css" import { Environment, LaneConfig, LaneFilter, Version } from "~/config/data/ccip/types.ts" import { getNetwork, getTokenData } from "~/config/data/ccip/data.ts" import { determineTokenMechanism } from "~/config/data/ccip/utils.ts" -import { useState, useEffect } from "react" +import { useState } from "react" import LaneDetailsHero from "../ChainHero/LaneDetailsHero.tsx" import { getExplorerAddressUrl, getTokenIconUrl, fallbackTokenIconUrl } from "~/features/utils/index.ts" import TableSearchInput from "../Tables/TableSearchInput.tsx" import { Tooltip } from "~/features/common/Tooltip/Tooltip.tsx" import { ChainType, ExplorerInfo } from "@config/types.ts" -import { RealtimeDataService } from "~/lib/ccip/services/realtime-data.ts" -import type { TokenRateLimits } from "~/lib/ccip/types/index.ts" +import { useTokenRateLimits } from "~/hooks/useTokenRateLimits.ts" +import { RateLimitCell } from "~/components/CCIP/RateLimitCell.tsx" +import { realtimeDataService } from "~/lib/ccip/services/realtime-data-instance.ts" function LaneDrawer({ lane, @@ -28,8 +29,6 @@ function LaneDrawer({ inOutbound: LaneFilter }) { const [search, setSearch] = useState("") - const [rateLimits, setRateLimits] = useState>({}) - const [isLoadingRateLimits, setIsLoadingRateLimits] = useState(true) const destinationNetworkDetails = getNetwork({ filter: environment, @@ -41,26 +40,12 @@ function LaneDrawer({ chain: sourceNetwork.key, }) - // Fetch rate limits data - useEffect(() => { - const fetchRateLimits = async () => { - setIsLoadingRateLimits(true) - const realtimeService = new RealtimeDataService() + // Determine source and destination based on inOutbound filter + const source = inOutbound === LaneFilter.Outbound ? sourceNetwork.key : destinationNetwork.key + const destination = inOutbound === LaneFilter.Outbound ? destinationNetwork.key : sourceNetwork.key - // Determine source and destination based on inOutbound filter - const source = inOutbound === LaneFilter.Outbound ? sourceNetwork.key : destinationNetwork.key - const destination = inOutbound === LaneFilter.Outbound ? destinationNetwork.key : sourceNetwork.key - - const response = await realtimeService.getLaneSupportedTokens(source, destination, environment) - - if (response?.data) { - setRateLimits(response.data) - } - setIsLoadingRateLimits(false) - } - - fetchRateLimits() - }, [sourceNetwork.key, destinationNetwork.key, environment, inOutbound]) + // Fetch rate limits data using custom hook + const { rateLimits, isLoading: isLoadingRateLimits } = useTokenRateLimits(source, destination, environment) return ( <> @@ -212,28 +197,18 @@ function LaneDrawer({ // Get rate limit data for this token const tokenRateLimits = rateLimits[token] - const realtimeService = new RealtimeDataService() // Determine direction based on inOutbound filter const direction = inOutbound === LaneFilter.Outbound ? "out" : "in" // Get standard and FTF rate limits const allLimits = tokenRateLimits - ? realtimeService.getAllRateLimitsForDirection(tokenRateLimits, direction) + ? realtimeDataService.getAllRateLimitsForDirection(tokenRateLimits, direction) : { standard: null, ftf: null } // Token is paused if standard rate limit capacity is 0 const tokenPaused = allLimits.standard?.capacity === "0" - // Format rate limit values - const formatRateLimit = (value: string | null) => { - if (!value || value === "0") return "0" - // Convert from wei to tokens (divide by 1e18) - const numValue = BigInt(value) - const formatted = Number(numValue) / 1e18 - return formatted.toLocaleString(undefined, { maximumFractionDigits: 2 }) - } - return ( @@ -280,68 +255,26 @@ function LaneDrawer({ - {isLoadingRateLimits ? ( - "Loading..." - ) : allLimits.standard ? ( - allLimits.standard.isEnabled ? ( - formatRateLimit(allLimits.standard.capacity) - ) : ( - "Disabled" - ) - ) : ( - - Unavailable - - - )} + - {isLoadingRateLimits - ? "Loading..." - : allLimits.standard - ? allLimits.standard.isEnabled - ? formatRateLimit(allLimits.standard.rate) - : "Disabled" - : "N/A"} + - {isLoadingRateLimits ? ( - "Loading..." - ) : allLimits.ftf ? ( - allLimits.ftf.isEnabled ? ( - formatRateLimit(allLimits.ftf.capacity) - ) : ( - "Disabled" - ) - ) : ( - - Unavailable - - - )} + - {isLoadingRateLimits - ? "Loading..." - : allLimits.ftf - ? allLimits.ftf.isEnabled - ? formatRateLimit(allLimits.ftf.rate) - : "Disabled" - : "N/A"} + ) diff --git a/src/components/CCIP/Drawer/TokenDrawer.tsx b/src/components/CCIP/Drawer/TokenDrawer.tsx index 47cc5a63590..213384d8c45 100644 --- a/src/components/CCIP/Drawer/TokenDrawer.tsx +++ b/src/components/CCIP/Drawer/TokenDrawer.tsx @@ -15,7 +15,7 @@ import { getVerifiersByNetwork, getVerifierTypeDisplay, } from "~/config/data/ccip/index.ts" -import { useState, useEffect } from "react" +import { useState, useMemo } from "react" import { ChainType, ExplorerInfo, SupportedChain } from "~/config/index.ts" import { getExplorerAddressUrl } from "~/features/utils/index.ts" import Address from "~/components/AddressReact.tsx" @@ -23,8 +23,9 @@ import LaneDrawer from "../Drawer/LaneDrawer.tsx" import TableSearchInput from "../Tables/TableSearchInput.tsx" import Tabs from "../Tables/Tabs.tsx" import { Tooltip } from "~/features/common/Tooltip/Tooltip.tsx" -import { RealtimeDataService } from "~/lib/ccip/services/realtime-data.ts" -import type { TokenRateLimits } from "~/lib/ccip/types/index.ts" +import { useMultiLaneRateLimits } from "~/hooks/useMultiLaneRateLimits.ts" +import { RateLimitCell } from "~/components/CCIP/RateLimitCell.tsx" +import { realtimeDataService } from "~/lib/ccip/services/realtime-data-instance.ts" function TokenDrawer({ token, @@ -60,7 +61,6 @@ function TokenDrawer({ }) { const [search, setSearch] = useState("") const [activeTab, setActiveTab] = useState<"outbound" | "inbound" | "verifiers">("outbound") - const [rateLimits, setRateLimits] = useState>>({}) // Get verifiers for the current network const verifiers = getVerifiersByNetwork({ @@ -79,28 +79,16 @@ function TokenDrawer({ destinationPoolType: PoolType } - // Fetch rate limits for all lanes - useEffect(() => { - const fetchAllRateLimits = async () => { - const realtimeService = new RealtimeDataService() - const newRateLimits: Record> = {} + // Build lane configurations for fetching rate limits + const laneConfigs = useMemo(() => { + return Object.keys(destinationLanes).map((destinationChain) => ({ + source: activeTab === "outbound" ? network.key : destinationChain, + destination: activeTab === "outbound" ? destinationChain : network.key, + })) + }, [destinationLanes, network.key, activeTab]) - for (const destinationChain of Object.keys(destinationLanes)) { - const source = activeTab === "outbound" ? network.key : destinationChain - const destination = activeTab === "outbound" ? destinationChain : network.key - const laneKey = `${source}-${destination}` - - const response = await realtimeService.getLaneSupportedTokens(source, destination, environment) - if (response?.data) { - newRateLimits[laneKey] = response.data - } - } - - setRateLimits(newRateLimits) - } - - fetchAllRateLimits() - }, [network.key, destinationLanes, environment, activeTab]) + // Fetch rate limits for all lanes using custom hook + const { rateLimitsMap, isLoading: isLoadingRateLimits } = useMultiLaneRateLimits(laneConfigs, environment) const laneRows: LaneRow[] = Object.keys(destinationLanes) .map((destinationChain) => { @@ -318,30 +306,19 @@ function TokenDrawer({ const source = activeTab === "outbound" ? network.key : destinationChain const destination = activeTab === "outbound" ? destinationChain : network.key const laneKey = `${source}-${destination}` - const laneRateLimits = rateLimits[laneKey] + const laneRateLimits = rateLimitsMap[laneKey] const tokenRateLimits = laneRateLimits?.[token.id] - const realtimeService = new RealtimeDataService() const direction = activeTab === "outbound" ? "out" : "in" // Get standard and FTF rate limits const allLimits = tokenRateLimits - ? realtimeService.getAllRateLimitsForDirection(tokenRateLimits, direction) + ? realtimeDataService.getAllRateLimitsForDirection(tokenRateLimits, direction) : { standard: null, ftf: null } // Token is paused if standard rate limit capacity is 0 const tokenPaused = allLimits.standard?.capacity === "0" - // Format rate limit values - const formatRateLimit = (value: string | null) => { - if (!value || value === "0") return "0" - const numValue = BigInt(value) - const formatted = Number(numValue) / 1e18 - return formatted.toLocaleString(undefined, { maximumFractionDigits: 2 }) - } - - const isLoading = !laneRateLimits - return ( @@ -381,40 +358,20 @@ function TokenDrawer({ - {isLoading - ? "Loading..." - : allLimits.standard - ? allLimits.standard.isEnabled - ? formatRateLimit(allLimits.standard.capacity) - : "Disabled" - : "N/A"} + - {isLoading - ? "Loading..." - : allLimits.standard - ? allLimits.standard.isEnabled - ? formatRateLimit(allLimits.standard.rate) - : "Disabled" - : "N/A"} + - {isLoading - ? "Loading..." - : allLimits.ftf - ? allLimits.ftf.isEnabled - ? formatRateLimit(allLimits.ftf.capacity) - : "Disabled" - : "N/A"} + - {isLoading - ? "Loading..." - : allLimits.ftf - ? allLimits.ftf.isEnabled - ? formatRateLimit(allLimits.ftf.rate) - : "Disabled" - : "N/A"} + {activeTab === "outbound" diff --git a/src/components/CCIP/RateLimitCell.tsx b/src/components/CCIP/RateLimitCell.tsx new file mode 100644 index 00000000000..61a4a8c7aa1 --- /dev/null +++ b/src/components/CCIP/RateLimitCell.tsx @@ -0,0 +1,46 @@ +import { Tooltip } from "~/features/common/Tooltip/Tooltip.tsx" +import type { RateLimiterConfig } from "~/lib/ccip/types/index.ts" +import { formatRateLimit } from "~/lib/ccip/utils/rate-limit-formatter.ts" + +interface RateLimitCellProps { + isLoading: boolean + rateLimit: RateLimiterConfig | null | undefined + type: "capacity" | "rate" + showUnavailableTooltip?: boolean +} + +/** + * Component for displaying rate limit values in table cells + * Handles loading, disabled, unavailable, and value states + */ +export function RateLimitCell({ isLoading, rateLimit, type, showUnavailableTooltip = false }: RateLimitCellProps) { + if (isLoading) { + return <>Loading... + } + + if (!rateLimit) { + if (showUnavailableTooltip) { + return ( + + Unavailable + + + ) + } + return <>N/A + } + + if (!rateLimit.isEnabled) { + return <>Disabled + } + + const value = type === "capacity" ? rateLimit.capacity : rateLimit.rate + return <>{formatRateLimit(value)} +} diff --git a/src/components/CCIP/Tables/TokenChainsTable.tsx b/src/components/CCIP/Tables/TokenChainsTable.tsx index 0bcacd435e1..7614b0b32d4 100644 --- a/src/components/CCIP/Tables/TokenChainsTable.tsx +++ b/src/components/CCIP/Tables/TokenChainsTable.tsx @@ -5,12 +5,11 @@ import { Environment, SupportedTokenConfig, tokenPoolDisplay, PoolType } from "~ import { areAllLanesPaused } from "~/config/data/ccip/utils.ts" import { ChainType, ExplorerInfo } from "~/config/types.ts" import TableSearchInput from "./TableSearchInput.tsx" -import { useState, useEffect } from "react" +import { useState } from "react" import { getExplorerAddressUrl, fallbackTokenIconUrl } from "~/features/utils/index.ts" import TokenDrawer from "../Drawer/TokenDrawer.tsx" import { Tooltip } from "~/features/common/Tooltip/Tooltip.tsx" -import { RealtimeDataService } from "~/lib/ccip/services/realtime-data.ts" -import type { TokenFinalityData } from "~/lib/ccip/types/index.ts" +import { useTokenFinality } from "~/hooks/useTokenFinality.ts" interface TableProps { networks: { @@ -45,29 +44,9 @@ interface TableProps { function TokenChainsTable({ networks, token, lanes, environment }: TableProps) { const [search, setSearch] = useState("") - const [finalityData, setFinalityData] = useState>({}) - const [loading, setLoading] = useState(true) - useEffect(() => { - const fetchFinalityData = async () => { - try { - const realtimeService = new RealtimeDataService() - const result = await realtimeService.getTokenFinality(token.id, environment, "internal_id") - - if (result && result.data) { - setFinalityData(result.data) - } else { - console.warn("[TokenChainsTable] No data received") - } - } catch (error) { - console.error("Failed to fetch token finality data:", error) - } finally { - setLoading(false) - } - } - - fetchFinalityData() - }, [token.id, environment]) + // Fetch finality data using custom hook + const { finalityData, isLoading: loading } = useTokenFinality(token.id, environment, "internal_id") return ( <> diff --git a/src/hooks/useMultiLaneRateLimits.ts b/src/hooks/useMultiLaneRateLimits.ts new file mode 100644 index 00000000000..2f0274e810f --- /dev/null +++ b/src/hooks/useMultiLaneRateLimits.ts @@ -0,0 +1,78 @@ +import { useState, useEffect } from "react" +import type { TokenRateLimits, Environment } from "~/lib/ccip/types/index.ts" +import { realtimeDataService } from "~/lib/ccip/services/realtime-data-instance.ts" + +interface LaneConfig { + source: string + destination: string +} + +interface UseMultiLaneRateLimitsResult { + rateLimitsMap: Record> + isLoading: boolean + error: Error | null +} + +/** + * Custom hook to fetch rate limits for multiple lanes + * Useful for components that need to display rate limits across multiple lanes + * @param lanes - Array of lane configurations with source and destination + * @param environment - Network environment (mainnet/testnet) + * @returns Map of rate limits keyed by lane (source-destination), loading state, and error state + */ +export function useMultiLaneRateLimits(lanes: LaneConfig[], environment: Environment): UseMultiLaneRateLimitsResult { + const [rateLimitsMap, setRateLimitsMap] = useState>>({}) + const [isLoading, setIsLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + let isMounted = true + + const fetchAllRateLimits = async () => { + setIsLoading(true) + setError(null) + + try { + const newRateLimits: Record> = {} + + // Fetch all lanes in parallel + const promises = lanes.map(async ({ source, destination }) => { + const laneKey = `${source}-${destination}` + const response = await realtimeDataService.getLaneSupportedTokens(source, destination, environment) + + if (response?.data) { + newRateLimits[laneKey] = response.data + } + }) + + await Promise.all(promises) + + if (isMounted) { + setRateLimitsMap(newRateLimits) + } + } catch (err) { + if (isMounted) { + console.error("Error fetching multi-lane rate limits:", err) + setError(err instanceof Error ? err : new Error("Failed to fetch rate limits")) + setRateLimitsMap({}) + } + } finally { + if (isMounted) { + setIsLoading(false) + } + } + } + + if (lanes.length > 0) { + fetchAllRateLimits() + } else { + setIsLoading(false) + } + + return () => { + isMounted = false + } + }, [lanes, environment]) + + return { rateLimitsMap, isLoading, error } +} diff --git a/src/hooks/useTokenFinality.ts b/src/hooks/useTokenFinality.ts new file mode 100644 index 00000000000..5036a3848f0 --- /dev/null +++ b/src/hooks/useTokenFinality.ts @@ -0,0 +1,66 @@ +import { useState, useEffect } from "react" +import type { TokenFinalityData, Environment, OutputKeyType } from "~/lib/ccip/types/index.ts" +import { realtimeDataService } from "~/lib/ccip/services/realtime-data-instance.ts" + +interface UseTokenFinalityResult { + finalityData: Record + isLoading: boolean + error: Error | null +} + +/** + * Custom hook to fetch token finality data across all chains + * @param tokenCanonicalSymbol - Token canonical symbol (e.g., "BETS", "LINK") + * @param environment - Network environment (mainnet/testnet) + * @param outputKey - Format to use for displaying chain keys (optional) + * @returns Finality data for all chains, loading state, and error state + */ +export function useTokenFinality( + tokenCanonicalSymbol: string, + environment: Environment, + outputKey?: OutputKeyType +): UseTokenFinalityResult { + const [finalityData, setFinalityData] = useState>({}) + const [isLoading, setIsLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + let isMounted = true + + const fetchFinalityData = async () => { + setIsLoading(true) + setError(null) + + try { + const result = await realtimeDataService.getTokenFinality(tokenCanonicalSymbol, environment, outputKey) + + if (isMounted) { + if (result?.data) { + setFinalityData(result.data) + } else { + console.warn("[useTokenFinality] No data received") + setFinalityData({}) + } + } + } catch (err) { + if (isMounted) { + console.error("Failed to fetch token finality data:", err) + setError(err instanceof Error ? err : new Error("Failed to fetch token finality")) + setFinalityData({}) + } + } finally { + if (isMounted) { + setIsLoading(false) + } + } + } + + fetchFinalityData() + + return () => { + isMounted = false + } + }, [tokenCanonicalSymbol, environment, outputKey]) + + return { finalityData, isLoading, error } +} diff --git a/src/hooks/useTokenRateLimits.ts b/src/hooks/useTokenRateLimits.ts new file mode 100644 index 00000000000..cbfd8ce04df --- /dev/null +++ b/src/hooks/useTokenRateLimits.ts @@ -0,0 +1,65 @@ +import { useState, useEffect } from "react" +import type { TokenRateLimits, Environment } from "~/lib/ccip/types/index.ts" +import { realtimeDataService } from "~/lib/ccip/services/realtime-data-instance.ts" + +interface UseTokenRateLimitsResult { + rateLimits: Record + isLoading: boolean + error: Error | null +} + +/** + * Custom hook to fetch token rate limits for a specific lane + * @param source - Source chain internal ID + * @param destination - Destination chain internal ID + * @param environment - Network environment (mainnet/testnet) + * @returns Rate limits data, loading state, and error state + */ +export function useTokenRateLimits( + source: string, + destination: string, + environment: Environment +): UseTokenRateLimitsResult { + const [rateLimits, setRateLimits] = useState>({}) + const [isLoading, setIsLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + let isMounted = true + + const fetchRateLimits = async () => { + setIsLoading(true) + setError(null) + + try { + const response = await realtimeDataService.getLaneSupportedTokens(source, destination, environment) + + if (isMounted) { + if (response?.data) { + setRateLimits(response.data) + } else { + setRateLimits({}) + } + } + } catch (err) { + if (isMounted) { + console.error("Error fetching rate limits:", err) + setError(err instanceof Error ? err : new Error("Failed to fetch rate limits")) + setRateLimits({}) + } + } finally { + if (isMounted) { + setIsLoading(false) + } + } + } + + fetchRateLimits() + + return () => { + isMounted = false + } + }, [source, destination, environment]) + + return { rateLimits, isLoading, error } +} diff --git a/src/lib/ccip/services/realtime-data-instance.ts b/src/lib/ccip/services/realtime-data-instance.ts new file mode 100644 index 00000000000..b1ac8df61bd --- /dev/null +++ b/src/lib/ccip/services/realtime-data-instance.ts @@ -0,0 +1,7 @@ +import { RealtimeDataService } from "./realtime-data.ts" + +/** + * Singleton instance of RealtimeDataService + * Use this shared instance across all components to avoid creating multiple instances + */ +export const realtimeDataService = new RealtimeDataService() diff --git a/src/lib/ccip/utils/rate-limit-formatter.ts b/src/lib/ccip/utils/rate-limit-formatter.ts new file mode 100644 index 00000000000..d7431f39f31 --- /dev/null +++ b/src/lib/ccip/utils/rate-limit-formatter.ts @@ -0,0 +1,72 @@ +import type { RateLimiterConfig } from "~/lib/ccip/types/index.ts" + +/** + * Formats a rate limit value from wei to tokens + * @param value - Rate limit value in wei (as string) + * @returns Formatted string with proper number formatting + */ +export function formatRateLimit(value: string | null | undefined): string { + if (!value || value === "0") return "0" + + try { + // Convert from wei to tokens (divide by 1e18) + const numValue = BigInt(value) + const formatted = Number(numValue) / 1e18 + return formatted.toLocaleString(undefined, { maximumFractionDigits: 2 }) + } catch (error) { + console.error("Error formatting rate limit:", error) + return "0" + } +} + +/** + * Checks if a token is paused based on rate limit configuration + * A token is considered paused if the capacity is "0" + * @param rateLimit - Rate limiter configuration + * @returns True if token is paused + */ +export function isTokenPaused(rateLimit: RateLimiterConfig | null | undefined): boolean { + return rateLimit?.capacity === "0" +} + +/** + * Gets display value for a rate limit + * @param rateLimit - Rate limiter configuration + * @param isLoading - Whether data is still loading + * @returns Display string for the rate limit + */ +export function getRateLimitDisplay(rateLimit: RateLimiterConfig | null | undefined, isLoading: boolean): string { + if (isLoading) return "Loading..." + if (!rateLimit) return "N/A" + if (!rateLimit.isEnabled) return "Disabled" + return formatRateLimit(rateLimit.capacity) +} + +/** + * Gets display value for a rate limit capacity + * @param rateLimit - Rate limiter configuration + * @param isLoading - Whether data is still loading + * @returns Display string for capacity + */ +export function getRateLimitCapacityDisplay( + rateLimit: RateLimiterConfig | null | undefined, + isLoading: boolean +): string { + if (isLoading) return "Loading..." + if (!rateLimit) return "Unavailable" + if (!rateLimit.isEnabled) return "Disabled" + return formatRateLimit(rateLimit.capacity) +} + +/** + * Gets display value for a rate limit refill rate + * @param rateLimit - Rate limiter configuration + * @param isLoading - Whether data is still loading + * @returns Display string for refill rate + */ +export function getRateLimitRateDisplay(rateLimit: RateLimiterConfig | null | undefined, isLoading: boolean): string { + if (isLoading) return "Loading..." + if (!rateLimit) return "N/A" + if (!rateLimit.isEnabled) return "Disabled" + return formatRateLimit(rateLimit.rate) +} From 6cc53abf23153ee779cbd1e40c5822402e314f50 Mon Sep 17 00:00:00 2001 From: Mark Shenouda Date: Fri, 19 Dec 2025 09:27:26 +0200 Subject: [PATCH 08/16] fix: use enums for tabs --- src/components/CCIP/Drawer/LaneDrawer.tsx | 4 +-- src/components/CCIP/Drawer/TokenDrawer.tsx | 40 ++++++++++++---------- src/lib/ccip/services/realtime-data.ts | 8 +++-- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/components/CCIP/Drawer/LaneDrawer.tsx b/src/components/CCIP/Drawer/LaneDrawer.tsx index e7b907b6c4a..f58e5b81860 100644 --- a/src/components/CCIP/Drawer/LaneDrawer.tsx +++ b/src/components/CCIP/Drawer/LaneDrawer.tsx @@ -202,9 +202,7 @@ function LaneDrawer({ const direction = inOutbound === LaneFilter.Outbound ? "out" : "in" // Get standard and FTF rate limits - const allLimits = tokenRateLimits - ? realtimeDataService.getAllRateLimitsForDirection(tokenRateLimits, direction) - : { standard: null, ftf: null } + const allLimits = realtimeDataService.getAllRateLimitsForDirection(tokenRateLimits, direction) // Token is paused if standard rate limit capacity is 0 const tokenPaused = allLimits.standard?.capacity === "0" diff --git a/src/components/CCIP/Drawer/TokenDrawer.tsx b/src/components/CCIP/Drawer/TokenDrawer.tsx index 213384d8c45..4a759d87d52 100644 --- a/src/components/CCIP/Drawer/TokenDrawer.tsx +++ b/src/components/CCIP/Drawer/TokenDrawer.tsx @@ -27,6 +27,12 @@ import { useMultiLaneRateLimits } from "~/hooks/useMultiLaneRateLimits.ts" import { RateLimitCell } from "~/components/CCIP/RateLimitCell.tsx" import { realtimeDataService } from "~/lib/ccip/services/realtime-data-instance.ts" +enum TokenTab { + Outbound = "outbound", + Inbound = "inbound", + Verifiers = "verifiers", +} + function TokenDrawer({ token, network, @@ -60,7 +66,7 @@ function TokenDrawer({ environment: Environment }) { const [search, setSearch] = useState("") - const [activeTab, setActiveTab] = useState<"outbound" | "inbound" | "verifiers">("outbound") + const [activeTab, setActiveTab] = useState(TokenTab.Outbound) // Get verifiers for the current network const verifiers = getVerifiersByNetwork({ @@ -82,8 +88,8 @@ function TokenDrawer({ // Build lane configurations for fetching rate limits const laneConfigs = useMemo(() => { return Object.keys(destinationLanes).map((destinationChain) => ({ - source: activeTab === "outbound" ? network.key : destinationChain, - destination: activeTab === "outbound" ? destinationChain : network.key, + source: activeTab === TokenTab.Outbound ? network.key : destinationChain, + destination: activeTab === TokenTab.Outbound ? destinationChain : network.key, })) }, [destinationLanes, network.key, activeTab]) @@ -163,23 +169,23 @@ function TokenDrawer({ tabs={[ { name: "Outbound lanes", - key: "outbound", + key: TokenTab.Outbound, }, { name: "Inbound lanes", - key: "inbound", + key: TokenTab.Inbound, }, { name: "Verifiers", - key: "verifiers", + key: TokenTab.Verifiers, }, ]} - onChange={(key) => setActiveTab(key as "outbound" | "inbound" | "verifiers")} + onChange={(key) => setActiveTab(key as TokenTab)} /> - {activeTab === "verifiers" ? ( + {activeTab === TokenTab.Verifiers ? (
@@ -242,7 +248,7 @@ function TokenDrawer({
- + @@ -397,7 +401,7 @@ function TokenDrawer({ )} - {activeTab !== "verifiers" && ( + {activeTab !== TokenTab.Verifiers && (
{laneRows?.filter( ({ networkDetails }) => networkDetails && networkDetails.name.toLowerCase().includes(search.toLowerCase()) diff --git a/src/lib/ccip/services/realtime-data.ts b/src/lib/ccip/services/realtime-data.ts index 1e3ca72f0b4..a0b7a84b470 100644 --- a/src/lib/ccip/services/realtime-data.ts +++ b/src/lib/ccip/services/realtime-data.ts @@ -219,17 +219,21 @@ export class RealtimeDataService { /** * Gets both standard and FTF rate limits for a specific token and direction * - * @param tokenRateLimits - Token rate limits containing standard and custom entries + * @param tokenRateLimits - Token rate limits containing standard and custom entries (can be null/undefined) * @param direction - Direction ("in" for inbound, "out" for outbound) * @returns Object containing both standard and FTF rate limits */ getAllRateLimitsForDirection( - tokenRateLimits: TokenRateLimits, + tokenRateLimits: TokenRateLimits | null | undefined, direction: "in" | "out" ): { standard: RateLimiterConfig | null ftf: RateLimiterConfig | null } { + if (!tokenRateLimits) { + return { standard: null, ftf: null } + } + const standardLimit = tokenRateLimits.standard && !this.isRateLimiterUnavailable(tokenRateLimits.standard) ? tokenRateLimits.standard[direction] || null From e86773d912a362578301dd1e7ff5d356289ec838 Mon Sep 17 00:00:00 2001 From: Mark Shenouda Date: Fri, 19 Dec 2025 09:32:15 +0200 Subject: [PATCH 09/16] refactor: cleanup Lane's table logic --- src/components/CCIP/Drawer/LaneDrawer.tsx | 191 ++++++++++------------ src/hooks/useLaneTokens.ts | 64 ++++++++ 2 files changed, 151 insertions(+), 104 deletions(-) create mode 100644 src/hooks/useLaneTokens.ts diff --git a/src/components/CCIP/Drawer/LaneDrawer.tsx b/src/components/CCIP/Drawer/LaneDrawer.tsx index f58e5b81860..918383baf86 100644 --- a/src/components/CCIP/Drawer/LaneDrawer.tsx +++ b/src/components/CCIP/Drawer/LaneDrawer.tsx @@ -1,17 +1,17 @@ import Address from "~/components/AddressReact.tsx" import "../Tables/Table.css" -import { Environment, LaneConfig, LaneFilter, Version } from "~/config/data/ccip/types.ts" -import { getNetwork, getTokenData } from "~/config/data/ccip/data.ts" +import { Environment, LaneConfig, LaneFilter } from "~/config/data/ccip/types.ts" +import { getNetwork } from "~/config/data/ccip/data.ts" import { determineTokenMechanism } from "~/config/data/ccip/utils.ts" import { useState } from "react" import LaneDetailsHero from "../ChainHero/LaneDetailsHero.tsx" -import { getExplorerAddressUrl, getTokenIconUrl, fallbackTokenIconUrl } from "~/features/utils/index.ts" +import { getExplorerAddressUrl, fallbackTokenIconUrl } from "~/features/utils/index.ts" import TableSearchInput from "../Tables/TableSearchInput.tsx" import { Tooltip } from "~/features/common/Tooltip/Tooltip.tsx" import { ChainType, ExplorerInfo } from "@config/types.ts" import { useTokenRateLimits } from "~/hooks/useTokenRateLimits.ts" import { RateLimitCell } from "~/components/CCIP/RateLimitCell.tsx" -import { realtimeDataService } from "~/lib/ccip/services/realtime-data-instance.ts" +import { useLaneTokens } from "~/hooks/useLaneTokens.ts" function LaneDrawer({ lane, @@ -47,6 +47,15 @@ function LaneDrawer({ // Fetch rate limits data using custom hook const { rateLimits, isLoading: isLoadingRateLimits } = useTokenRateLimits(source, destination, environment) + // Process tokens with hook + const { tokens: processedTokens, count: tokenCount } = useLaneTokens({ + tokens: lane.supportedTokens, + environment, + rateLimitsData: rateLimits, + inOutbound, + searchQuery: search, + }) + return ( <>

Lane Details

@@ -73,7 +82,7 @@ function LaneDrawer({
- Tokens ({lane?.supportedTokens ? lane.supportedTokens.length : 0}) + Tokens ({tokenCount})
@@ -183,109 +192,83 @@ function LaneDrawer({
- {lane.supportedTokens && - lane.supportedTokens - .filter((token) => token.toLowerCase().includes(search.toLowerCase())) - .map((token, index) => { - const data = getTokenData({ - environment, - version: Version.V1_2_0, - tokenId: token || "", - }) - if (!Object.keys(data).length) return null - const logo = getTokenIconUrl(token) - - // Get rate limit data for this token - const tokenRateLimits = rateLimits[token] - - // Determine direction based on inOutbound filter - const direction = inOutbound === LaneFilter.Outbound ? "out" : "in" - - // Get standard and FTF rate limits - const allLimits = realtimeDataService.getAllRateLimitsForDirection(tokenRateLimits, direction) - - // Token is paused if standard rate limit capacity is 0 - const tokenPaused = allLimits.standard?.capacity === "0" - - return ( - - - - - + {processedTokens.map((token, index) => ( + + + + + - - - - - - ) - })} + + + + + + ))}
{activeTab === "inbound" ? "Source" : "Destination"} network{activeTab === TokenTab.Inbound ? "Source" : "Destination"} network Rate limit capacity )) @@ -374,7 +378,7 @@ function TokenDrawer({ - {activeTab === "outbound" + {activeTab === TokenTab.Outbound ? determineTokenMechanism(network.tokenPoolType, destinationPoolType) : determineTokenMechanism(destinationPoolType, network.tokenPoolType)}
- -
- {`${token} { - currentTarget.onerror = null // prevents looping - currentTarget.src = fallbackTokenIconUrl - }} - /> - {token} - {tokenPaused && ( - - ⏸️ - - )} -
-
-
-
-
{data[sourceNetwork.key].decimals} - {inOutbound === LaneFilter.Outbound - ? determineTokenMechanism( - data[sourceNetwork.key].pool.type, - data[destinationNetwork.key].pool.type - ) - : determineTokenMechanism( - data[destinationNetwork.key].pool.type, - data[sourceNetwork.key].pool.type - )} -
+ +
+ {`${token.id} { + currentTarget.onerror = null // prevents looping + currentTarget.src = fallbackTokenIconUrl + }} + /> + {token.id} + {token.isPaused && ( + + ⏸️ + + )} +
+
+
+
+
{token.data[sourceNetwork.key].decimals} + {inOutbound === LaneFilter.Outbound + ? determineTokenMechanism( + token.data[sourceNetwork.key].pool.type, + token.data[destinationNetwork.key].pool.type + ) + : determineTokenMechanism( + token.data[destinationNetwork.key].pool.type, + token.data[sourceNetwork.key].pool.type + )} + - - - - - - - -
+ + + + + + + +
-
- {lane.supportedTokens && - lane.supportedTokens.filter((token) => token.toLowerCase().includes(search.toLowerCase())).length === 0 && ( - <>No tokens found - )} -
+
{processedTokens.length === 0 && <>No tokens found}
) diff --git a/src/hooks/useLaneTokens.ts b/src/hooks/useLaneTokens.ts new file mode 100644 index 00000000000..dfa004659e8 --- /dev/null +++ b/src/hooks/useLaneTokens.ts @@ -0,0 +1,64 @@ +import { useMemo } from "react" +import { Environment, LaneFilter, Version } from "~/config/data/ccip/types" +import { getTokenData } from "~/config/data/ccip/data" +import { getTokenIconUrl } from "~/features/utils" +import { realtimeDataService } from "~/lib/ccip/services/realtime-data-instance" + +export interface ProcessedToken { + id: string + data: ReturnType + logo: string + rateLimits: { + standard?: { capacity: string; rate: string; isEnabled: boolean } + ftf?: { capacity: string; rate: string; isEnabled: boolean } + } + isPaused: boolean +} + +interface UseLaneTokensParams { + tokens: string[] | undefined + environment: Environment + rateLimitsData: Record + inOutbound: LaneFilter + searchQuery: string +} + +export function useLaneTokens({ tokens, environment, rateLimitsData, inOutbound, searchQuery }: UseLaneTokensParams) { + const processedTokens = useMemo(() => { + if (!tokens) return [] + + const direction = inOutbound === LaneFilter.Outbound ? "out" : "in" + + return tokens + .filter((token) => token.toLowerCase().includes(searchQuery.toLowerCase())) + .map((token) => { + const data = getTokenData({ + environment, + version: Version.V1_2_0, + tokenId: token || "", + }) + + // Skip tokens with no data + if (!Object.keys(data).length) return null + + const logo = getTokenIconUrl(token) + const tokenRateLimits = rateLimitsData[token] + const allLimits = realtimeDataService.getAllRateLimitsForDirection(tokenRateLimits, direction) + const isPaused = allLimits.standard?.capacity === "0" + + return { + id: token, + data, + logo, + rateLimits: allLimits, + isPaused, + } + }) + .filter((token): token is ProcessedToken => token !== null) + }, [tokens, environment, rateLimitsData, inOutbound, searchQuery]) + + return { + tokens: processedTokens, + count: tokens?.length ?? 0, + } +} From 679c456f32ef899dca4e9d48b8a93a2c25050bbd Mon Sep 17 00:00:00 2001 From: Mark Shenouda Date: Fri, 19 Dec 2025 09:46:38 +0200 Subject: [PATCH 10/16] refactor: token drawer filtering logic --- src/components/CCIP/Drawer/TokenDrawer.tsx | 24 ++++++++++++---------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/components/CCIP/Drawer/TokenDrawer.tsx b/src/components/CCIP/Drawer/TokenDrawer.tsx index 4a759d87d52..b8aa3dd16c2 100644 --- a/src/components/CCIP/Drawer/TokenDrawer.tsx +++ b/src/components/CCIP/Drawer/TokenDrawer.tsx @@ -75,6 +75,18 @@ function TokenDrawer({ version: Version.V1_2_0, }) + // Filter verifiers based on search + const filteredVerifiers = useMemo(() => { + if (!search) return verifiers + const searchLower = search.toLowerCase() + return verifiers.filter( + (verifier) => + verifier.name.toLowerCase().includes(searchLower) || + verifier.address.toLowerCase().includes(searchLower) || + verifier.type.toLowerCase().includes(searchLower) + ) + }, [verifiers, search]) + type LaneRow = { networkDetails: { name: string @@ -204,17 +216,7 @@ function TokenDrawer({ ) : ( - verifiers - .filter((verifier) => { - if (!search) return true - const searchLower = search.toLowerCase() - return ( - verifier.name.toLowerCase().includes(searchLower) || - verifier.address.toLowerCase().includes(searchLower) || - verifier.type.toLowerCase().includes(searchLower) - ) - }) - .map((verifier) => ( + filteredVerifiers.map((verifier) => (
From b14dec037bf7b0ada4417a81814b406962f5817e Mon Sep 17 00:00:00 2001 From: Mark Shenouda Date: Fri, 19 Dec 2025 09:53:36 +0200 Subject: [PATCH 11/16] fix: token grid in chain page design --- src/components/CCIP/Cards/TokenCard.css | 54 ++++++++++++++++++++ src/components/CCIP/Cards/TokenCard.tsx | 33 +++++++++++- src/components/CCIP/Chain/Chain.astro | 8 ++- src/components/CCIP/Chain/ChainTokenGrid.tsx | 1 + 4 files changed, 93 insertions(+), 3 deletions(-) diff --git a/src/components/CCIP/Cards/TokenCard.css b/src/components/CCIP/Cards/TokenCard.css index d8e2b7d6e2d..72966985150 100644 --- a/src/components/CCIP/Cards/TokenCard.css +++ b/src/components/CCIP/Cards/TokenCard.css @@ -51,3 +51,57 @@ height: 124px; } } + +/* Square variant styles */ +.token-card__square-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + background: var(--white); + border: 1px solid var(--gray-200); + border-radius: var(--space-1x); + text-align: center; +} + +.token-card__square-container:hover { + background-color: var(--gray-50); +} + +.token-card__square-logo { + display: flex; + align-items: center; + justify-content: center; + margin-bottom: var(--space-4x); +} + +.token-card__square-logo object, +.token-card__square-logo object img { + width: var(--space-16x); + height: var(--space-16x); + border-radius: 50%; +} + +.token-card__square-content { + display: flex; + flex-direction: column; + align-items: center; +} + +.token-card__square-content h3 { + font-size: var(--space-4x); + font-weight: var(--font-weight-medium); + line-height: var(--space-6x); + color: var(--gray-950); + margin-bottom: var(--space-1x); + text-align: center; +} + +.token-card__square-content p { + margin-bottom: 0; + font-size: var(--space-3x); + line-height: var(--space-5x); + color: var(--gray-500); + text-align: center; +} diff --git a/src/components/CCIP/Cards/TokenCard.tsx b/src/components/CCIP/Cards/TokenCard.tsx index 7810da7f4f7..1b9ca0b2de8 100644 --- a/src/components/CCIP/Cards/TokenCard.tsx +++ b/src/components/CCIP/Cards/TokenCard.tsx @@ -9,9 +9,10 @@ interface TokenCardProps { link?: string onClick?: () => void totalNetworks?: number + variant?: "default" | "square" } -const TokenCard = memo(function TokenCard({ id, logo, link, onClick, totalNetworks }: TokenCardProps) { +const TokenCard = memo(function TokenCard({ id, logo, link, onClick, totalNetworks, variant = "default" }: TokenCardProps) { const logoElement = ( {`${id} @@ -21,6 +22,36 @@ const TokenCard = memo(function TokenCard({ id, logo, link, onClick, totalNetwor const subtitle = totalNetworks !== undefined ? `${totalNetworks} ${totalNetworks === 1 ? "network" : "networks"}` : undefined + if (variant === "square") { + const content = ( + <> +
{logoElement}
+
+

{id}

+ {subtitle &&

{subtitle}

} +
+ + ) + + if (link) { + return ( + +
{content}
+
+ ) + } + + if (onClick) { + return ( + + ) + } + + return
{content}
+ } + return ( div { + min-width: 0; } .networks__grid { diff --git a/src/components/CCIP/Chain/ChainTokenGrid.tsx b/src/components/CCIP/Chain/ChainTokenGrid.tsx index fd267bcfd56..1459faf37f4 100644 --- a/src/components/CCIP/Chain/ChainTokenGrid.tsx +++ b/src/components/CCIP/Chain/ChainTokenGrid.tsx @@ -36,6 +36,7 @@ function ChainTokenGrid({ tokens, network, environment }: ChainTokenGridProps) { id={token.id} logo={token.logo} key={token.id} + variant="square" onClick={() => { const selectedNetwork = Object.keys(data) .map((key) => { From e3b63a322dc48163acbc1a75f71edda58b12493c Mon Sep 17 00:00:00 2001 From: Mark Shenouda Date: Fri, 19 Dec 2025 10:05:16 +0200 Subject: [PATCH 12/16] fix: the token card desgin --- src/components/CCIP/Cards/TokenCard.css | 1 + src/components/CCIP/Chain/ChainTokenGrid.css | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CCIP/Cards/TokenCard.css b/src/components/CCIP/Cards/TokenCard.css index 72966985150..ae035e97a19 100644 --- a/src/components/CCIP/Cards/TokenCard.css +++ b/src/components/CCIP/Cards/TokenCard.css @@ -58,6 +58,7 @@ flex-direction: column; align-items: center; justify-content: center; + padding: var(--space-6x); width: 100%; background: var(--white); border: 1px solid var(--gray-200); diff --git a/src/components/CCIP/Chain/ChainTokenGrid.css b/src/components/CCIP/Chain/ChainTokenGrid.css index 9f61a1e8472..aa0bcdbcc86 100644 --- a/src/components/CCIP/Chain/ChainTokenGrid.css +++ b/src/components/CCIP/Chain/ChainTokenGrid.css @@ -6,7 +6,6 @@ @media (min-width: 992px) { .tokens__grid { - min-height: 420px; grid-template-columns: 1fr 1fr 1fr 1fr; gap: var(--space-4x); } From d16d244dc0a3e5b3dff6e339a2c632759a976997 Mon Sep 17 00:00:00 2001 From: Mark Shenouda Date: Fri, 19 Dec 2025 10:06:08 +0200 Subject: [PATCH 13/16] fix: lint --- src/components/CCIP/Cards/TokenCard.tsx | 16 +++++++- src/components/CCIP/Drawer/LaneDrawer.tsx | 6 +-- src/components/CCIP/Drawer/TokenDrawer.tsx | 48 +++++++++++----------- 3 files changed, 40 insertions(+), 30 deletions(-) diff --git a/src/components/CCIP/Cards/TokenCard.tsx b/src/components/CCIP/Cards/TokenCard.tsx index 1b9ca0b2de8..51372209112 100644 --- a/src/components/CCIP/Cards/TokenCard.tsx +++ b/src/components/CCIP/Cards/TokenCard.tsx @@ -12,7 +12,14 @@ interface TokenCardProps { variant?: "default" | "square" } -const TokenCard = memo(function TokenCard({ id, logo, link, onClick, totalNetworks, variant = "default" }: TokenCardProps) { +const TokenCard = memo(function TokenCard({ + id, + logo, + link, + onClick, + totalNetworks, + variant = "default", +}: TokenCardProps) { const logoElement = ( {`${id} @@ -43,7 +50,12 @@ const TokenCard = memo(function TokenCard({ id, logo, link, onClick, totalNetwor if (onClick) { return ( - ) diff --git a/src/components/CCIP/Drawer/LaneDrawer.tsx b/src/components/CCIP/Drawer/LaneDrawer.tsx index 918383baf86..693277167cf 100644 --- a/src/components/CCIP/Drawer/LaneDrawer.tsx +++ b/src/components/CCIP/Drawer/LaneDrawer.tsx @@ -246,11 +246,7 @@ function LaneDrawer({ /> - + ) : ( filteredVerifiers.map((verifier) => ( - - -
- {`${verifier.name} - {verifier.name} -
- - -
+ +
+ {`${verifier.name} - - {getVerifierTypeDisplay(verifier.type)} - N/A - - )) + {verifier.name} +
+ + +
+ + {getVerifierTypeDisplay(verifier.type)} + N/A + + )) )} @@ -343,7 +343,9 @@ function TokenDrawer({ logo: networkDetails?.logo || "", key: destinationChain, }} - inOutbound={activeTab === TokenTab.Outbound ? LaneFilter.Outbound : LaneFilter.Inbound} + inOutbound={ + activeTab === TokenTab.Outbound ? LaneFilter.Outbound : LaneFilter.Inbound + } explorer={network.explorer} /> )) From 950bfe01d20b00597a3ec38d9b0062eb300f429d Mon Sep 17 00:00:00 2001 From: "Mark S. Shenouda" <49324987+markshenouda@users.noreply.github.com> Date: Fri, 19 Dec 2025 10:16:19 +0200 Subject: [PATCH 14/16] feat: add verifiers to the search (#184) * feat: add verifiers to the search * refactor: simplify verifiers mapping in Chain component * refactor: simplify verifiers mapping in CCIP components * refactor: optimize token mapping for verifiers in CCIP components --- src/components/CCIP/Chain/Chain.astro | 7 +++ src/components/CCIP/ChainHero/ChainHero.tsx | 27 ++++++++- src/components/CCIP/Hero/Hero.tsx | 11 +++- .../CCIP/Landing/ccip-landing.astro | 9 ++- src/components/CCIP/Search/Search.tsx | 60 ++++++++++++++++--- src/components/CCIP/Token/Token.astro | 7 +++ src/components/CCIP/Verifiers/Verifiers.astro | 13 ++-- src/workers/data-worker.ts | 20 ++++++- 8 files changed, 134 insertions(+), 20 deletions(-) diff --git a/src/components/CCIP/Chain/Chain.astro b/src/components/CCIP/Chain/Chain.astro index 08dfcfcf9a7..c236891c8b8 100644 --- a/src/components/CCIP/Chain/Chain.astro +++ b/src/components/CCIP/Chain/Chain.astro @@ -6,6 +6,7 @@ import { Network, getAllNetworkLanes, getAllNetworks, + getAllUniqueVerifiers, getSearchLanes, getTokensOfChain, Version, @@ -49,6 +50,11 @@ const lanes = await getAllNetworkLanes({ const searchLanes = getSearchLanes({ environment }) +const allVerifiers = getAllUniqueVerifiers({ + environment, + version: Version.V1_2_0, +}) + // Generate dynamic metadata for this specific chain const environmentText = environment === Environment.Mainnet ? "Mainnet" : "Testnet" const logoPath = network.logo || "" @@ -107,6 +113,7 @@ const chainStructuredData = generateChainStructuredData( network={network} environment={environment} lanes={searchLanes} + verifiers={allVerifiers} client:load />
diff --git a/src/components/CCIP/ChainHero/ChainHero.tsx b/src/components/CCIP/ChainHero/ChainHero.tsx index 1b0550380ee..bc4b18ab7c6 100644 --- a/src/components/CCIP/ChainHero/ChainHero.tsx +++ b/src/components/CCIP/ChainHero/ChainHero.tsx @@ -46,6 +46,13 @@ interface ChainHeroProps { } lane: LaneConfig }[] + verifiers?: { + id: string + name: string + type: string + logo: string + totalNetworks: number + }[] network?: Network token?: { id: string @@ -60,7 +67,16 @@ interface ChainHeroProps { }> } -function ChainHero({ chains, tokens, network, token, environment, lanes, breadcrumbItems }: ChainHeroProps) { +function ChainHero({ + chains, + tokens, + network, + token, + environment, + lanes, + verifiers = [], + breadcrumbItems, +}: ChainHeroProps) { // Get chain-specific tooltip configuration const chainTooltipConfig = network?.chain ? getChainTooltip(network.chain) : null @@ -119,7 +135,14 @@ function ChainHero({ chains, tokens, network, token, environment, lanes, breadcr } />
- +
diff --git a/src/components/CCIP/Hero/Hero.tsx b/src/components/CCIP/Hero/Hero.tsx index 5f479f22828..2886a4c2a95 100644 --- a/src/components/CCIP/Hero/Hero.tsx +++ b/src/components/CCIP/Hero/Hero.tsx @@ -33,17 +33,24 @@ interface HeroProps { } lane: LaneConfig }[] + verifiers?: { + id: string + name: string + type: string + logo: string + totalNetworks: number + }[] environment: Environment } -function Hero({ chains, tokens, environment, lanes }: HeroProps) { +function Hero({ chains, tokens, environment, lanes, verifiers = [] }: HeroProps) { return (
CCIP Directory - +
) diff --git a/src/components/CCIP/Landing/ccip-landing.astro b/src/components/CCIP/Landing/ccip-landing.astro index 4e101df4cbe..f65de34c258 100644 --- a/src/components/CCIP/Landing/ccip-landing.astro +++ b/src/components/CCIP/Landing/ccip-landing.astro @@ -72,7 +72,14 @@ const directoryStructuredData = generateDirectoryStructuredData(environment, net }} suppressDefaultStructuredData={true} > - +
diff --git a/src/components/CCIP/Search/Search.tsx b/src/components/CCIP/Search/Search.tsx index b53ab4cdbfc..7d908596af7 100644 --- a/src/components/CCIP/Search/Search.tsx +++ b/src/components/CCIP/Search/Search.tsx @@ -38,11 +38,18 @@ interface SearchProps { } lane: LaneConfig }[] + verifiers?: { + id: string + name: string + type: string + logo: string + totalNetworks: number + }[] small?: boolean environment: Environment } -function Search({ chains, tokens, small, environment, lanes }: SearchProps) { +function Search({ chains, tokens, small, environment, lanes, verifiers = [] }: SearchProps) { const [search, setSearch] = useState("") const [debouncedSearch, setDebouncedSearch] = useState("") const [openSearchMenu, setOpenSearchMenu] = useState(false) @@ -50,6 +57,7 @@ function Search({ chains, tokens, small, environment, lanes }: SearchProps) { const [networksResults, setNetworksResults] = useState([]) const [tokensResults, setTokensResults] = useState([]) const [lanesResults, setLanesResults] = useState([]) + const [verifiersResults, setVerifiersResults] = useState([]) const searchRef = useRef(null) const workerRef = useRef(null) const workerReadyRef = useRef(false) @@ -59,10 +67,11 @@ function Search({ chains, tokens, small, environment, lanes }: SearchProps) { if (workerReadyRef.current || typeof window === "undefined") return workerRef.current = new Worker(new URL("~/workers/data-worker.ts", import.meta.url), { type: "module" }) workerRef.current.onmessage = (event: MessageEvent) => { - const { networks, tokens: workerTokens, lanes: workerLanes } = event.data + const { networks, tokens: workerTokens, lanes: workerLanes, verifiers: workerVerifiers } = event.data setNetworksResults(networks || []) setTokensResults(workerTokens || []) setLanesResults(workerLanes || []) + setVerifiersResults(workerVerifiers || []) } workerReadyRef.current = true } @@ -90,6 +99,7 @@ function Search({ chains, tokens, small, environment, lanes }: SearchProps) { setNetworksResults([]) setTokensResults([]) setLanesResults([]) + setVerifiersResults([]) return } @@ -102,11 +112,12 @@ function Search({ chains, tokens, small, environment, lanes }: SearchProps) { chains, tokens, lanes, + verifiers, }, } workerRef.current.postMessage(message) } - }, [debouncedSearch, chains, tokens, lanes]) + }, [debouncedSearch, chains, tokens, lanes, verifiers]) // Handle menu visibility useEffect(() => { @@ -146,7 +157,7 @@ function Search({ chains, tokens, small, environment, lanes }: SearchProps) { Search icon setSearch(e.target.value)} onFocus={() => { @@ -154,7 +165,7 @@ function Search({ chains, tokens, small, environment, lanes }: SearchProps) { ensureWorker() }} onBlur={() => setIsActive(false)} - aria-label="Search networks, tokens, and lanes" + aria-label="Search networks, tokens, lanes, and verifiers" aria-describedby={openSearchMenu ? "search-results" : undefined} /> {openSearchMenu && ( @@ -167,9 +178,12 @@ function Search({ chains, tokens, small, environment, lanes }: SearchProps) { aria-live="polite" aria-label="Search results" > - {networksResults.length === 0 && tokensResults.length === 0 && ( - No results found - )} + {networksResults.length === 0 && + tokensResults.length === 0 && + lanesResults.length === 0 && + verifiersResults.length === 0 && ( + No results found + )} {networksResults.length > 0 && ( <> Networks @@ -284,6 +298,36 @@ function Search({ chains, tokens, small, environment, lanes }: SearchProps) { )} + + {verifiersResults.length > 0 && ( + <> + Verifiers + + + )}
)}
diff --git a/src/components/CCIP/Token/Token.astro b/src/components/CCIP/Token/Token.astro index 7d82846121e..874fbdc18f2 100644 --- a/src/components/CCIP/Token/Token.astro +++ b/src/components/CCIP/Token/Token.astro @@ -5,6 +5,7 @@ import { getAllNetworks, getAllSupportedTokens, getAllTokenLanes, + getAllUniqueVerifiers, getChainsOfToken, getSearchLanes, getTokenData, @@ -76,6 +77,11 @@ const tokenLanes = getAllTokenLanes({ const searchLanes = getSearchLanes({ environment }) +const allVerifiers = getAllUniqueVerifiers({ + environment, + version: Version.V1_2_0, +}) + // Generate dynamic metadata for this specific token const environmentText = environment === Environment.Mainnet ? "Mainnet" : "Testnet" const tokenMetadata = { @@ -117,6 +123,7 @@ const tokenStructuredData = generateTokenStructuredData(token, environment, chai tokens={allTokens} client:load lanes={searchLanes} + verifiers={allVerifiers} token={{ id: token, name: data[firstSupportedChain]?.name || "", diff --git a/src/components/CCIP/Verifiers/Verifiers.astro b/src/components/CCIP/Verifiers/Verifiers.astro index 2672f983332..79cdc2dc916 100644 --- a/src/components/CCIP/Verifiers/Verifiers.astro +++ b/src/components/CCIP/Verifiers/Verifiers.astro @@ -35,6 +35,12 @@ const uniqueVerifiers = getAllUniqueVerifiers({ const searchLanes = getSearchLanes({ environment }) +const allTokens = uniqueVerifiers.map((verifier) => ({ + id: verifier.id, + totalNetworks: verifier.totalNetworks, + logo: verifier.logo, +})) + // Generate dynamic metadata for verifiers page const environmentText = environment === Environment.Mainnet ? "Mainnet" : "Testnet" const verifiersMetadata = { @@ -70,12 +76,9 @@ const canonicalForJsonLd = `${DOCS_BASE_URL}${currentPath}` > ({ - id: verifier.id, - totalNetworks: verifier.totalNetworks, - logo: verifier.logo, - }))} + tokens={allTokens} lanes={searchLanes} + verifiers={uniqueVerifiers} environment={environment} breadcrumbItems={[ { diff --git a/src/workers/data-worker.ts b/src/workers/data-worker.ts index ac75689d4ba..db3cf94b45f 100644 --- a/src/workers/data-worker.ts +++ b/src/workers/data-worker.ts @@ -33,6 +33,13 @@ interface SearchData { } lane: LaneConfig }> + verifiers: Array<{ + id: string + name: string + type: string + logo: string + totalNetworks: number + }> } interface WorkerMessage { @@ -44,6 +51,7 @@ interface WorkerResponse { networks: SearchData["chains"] tokens: SearchData["tokens"] lanes: SearchData["lanes"] + verifiers: SearchData["verifiers"] } self.onmessage = (event: MessageEvent) => { @@ -51,7 +59,7 @@ self.onmessage = (event: MessageEvent) => { const { search, data } = event.data if (!search || !data) { - self.postMessage({ networks: [], tokens: [], lanes: [] } as WorkerResponse) + self.postMessage({ networks: [], tokens: [], lanes: [], verifiers: [] } as WorkerResponse) return } @@ -74,7 +82,15 @@ self.onmessage = (event: MessageEvent) => { return matchesNetwork && hasTokens }) - self.postMessage({ networks, tokens, lanes } as WorkerResponse) + // Filter verifiers + const verifiers = data.verifiers.filter( + (verifier) => + verifier.name.toLowerCase().includes(searchLower) || + verifier.id.toLowerCase().includes(searchLower) || + verifier.type.toLowerCase().includes(searchLower) + ) + + self.postMessage({ networks, tokens, lanes, verifiers } as WorkerResponse) } // Export types for use in main thread From 696682330736f34bdf87076274d9e8ca6a75f143 Mon Sep 17 00:00:00 2001 From: Mark Shenouda Date: Fri, 19 Dec 2025 10:22:03 +0200 Subject: [PATCH 15/16] feat: enhance verifiers data handling with token support --- src/components/CCIP/Verifiers/Verifiers.astro | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/components/CCIP/Verifiers/Verifiers.astro b/src/components/CCIP/Verifiers/Verifiers.astro index 79cdc2dc916..dd3dc730de4 100644 --- a/src/components/CCIP/Verifiers/Verifiers.astro +++ b/src/components/CCIP/Verifiers/Verifiers.astro @@ -1,11 +1,20 @@ --- import CcipDirectoryLayout from "~/layouts/CcipDirectoryLayout.astro" import { getEntry, render } from "astro:content" -import { getAllNetworks, getAllVerifiers, getSearchLanes, Version, Environment } from "~/config/data/ccip" +import { + getAllNetworks, + getAllVerifiers, + getSearchLanes, + Version, + Environment, + getAllSupportedTokens, + getChainsOfToken, +} from "~/config/data/ccip" import Table from "~/components/CCIP/Tables/VerifiersTable" import { getAllUniqueVerifiers } from "~/config/data/ccip/data.ts" import { DOCS_BASE_URL } from "~/utils/structuredData" import ChainHero from "~/components/CCIP/ChainHero/ChainHero" +import { getTokenIconUrl } from "~/features/utils" import "./Verifiers.css" interface Props { @@ -35,11 +44,19 @@ const uniqueVerifiers = getAllUniqueVerifiers({ const searchLanes = getSearchLanes({ environment }) -const allTokens = uniqueVerifiers.map((verifier) => ({ - id: verifier.id, - totalNetworks: verifier.totalNetworks, - logo: verifier.logo, -})) +const supportedTokens = getAllSupportedTokens({ + environment, + version: Version.V1_2_0, +}) +const tokens = Object.keys(supportedTokens).sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" })) +const allTokens = tokens.map((token) => { + const logo = getTokenIconUrl(token) || "" + return { + id: token, + logo, + totalNetworks: getChainsOfToken({ token, filter: environment }).length, + } +}) // Generate dynamic metadata for verifiers page const environmentText = environment === Environment.Mainnet ? "Mainnet" : "Testnet" From e0425bcd1b74c00decd14b06fd10c3ceb7421962 Mon Sep 17 00:00:00 2001 From: Mark Shenouda Date: Fri, 19 Dec 2025 10:34:29 +0200 Subject: [PATCH 16/16] fix: typecheck --- src/hooks/useLaneTokens.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/hooks/useLaneTokens.ts b/src/hooks/useLaneTokens.ts index dfa004659e8..12fcdc3e808 100644 --- a/src/hooks/useLaneTokens.ts +++ b/src/hooks/useLaneTokens.ts @@ -1,16 +1,16 @@ import { useMemo } from "react" -import { Environment, LaneFilter, Version } from "~/config/data/ccip/types" -import { getTokenData } from "~/config/data/ccip/data" -import { getTokenIconUrl } from "~/features/utils" -import { realtimeDataService } from "~/lib/ccip/services/realtime-data-instance" +import { Environment, LaneFilter, Version } from "~/config/data/ccip/types.ts" +import { getTokenData } from "~/config/data/ccip/data.ts" +import { getTokenIconUrl } from "~/features/utils/index.ts" +import { realtimeDataService } from "~/lib/ccip/services/realtime-data-instance.ts" export interface ProcessedToken { id: string data: ReturnType logo: string rateLimits: { - standard?: { capacity: string; rate: string; isEnabled: boolean } - ftf?: { capacity: string; rate: string; isEnabled: boolean } + standard: { capacity: string; rate: string; isEnabled: boolean } | null + ftf: { capacity: string; rate: string; isEnabled: boolean } | null } isPaused: boolean }