From 090f59407f12b8788c39b8a406444aa16cfb7e7e Mon Sep 17 00:00:00 2001 From: aelmanaa Date: Tue, 24 Jun 2025 15:59:11 +0200 Subject: [PATCH 1/3] grey paused tokens --- src/components/CCIP/Drawer/LaneDrawer.tsx | 25 ++++++++++-- src/components/CCIP/Drawer/TokenDrawer.tsx | 21 +++++++++- src/components/CCIP/Tables/Table.css | 45 ++++++++++++++++++++++ src/config/data/ccip/utils.ts | 31 +++++++++++++++ 4 files changed, 117 insertions(+), 5 deletions(-) diff --git a/src/components/CCIP/Drawer/LaneDrawer.tsx b/src/components/CCIP/Drawer/LaneDrawer.tsx index d27ffa938a2..7247762e75f 100644 --- a/src/components/CCIP/Drawer/LaneDrawer.tsx +++ b/src/components/CCIP/Drawer/LaneDrawer.tsx @@ -2,7 +2,7 @@ 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 { displayCapacity, determineTokenMechanism } from "~/config/data/ccip/utils.ts" +import { displayCapacity, determineTokenMechanism, isTokenPaused } 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" @@ -136,11 +136,22 @@ function LaneDrawer({ }) if (!Object.keys(data).length) return null const logo = getTokenIconUrl(token) + + // Check if token is paused + const tokenPaused = isTokenPaused( + data[sourceNetwork.key].decimals, + lane.supportedTokens?.[token]?.rateLimiterConfig?.[ + inOutbound === LaneFilter.Inbound ? "in" : "out" + ] + ) + return ( - + -
+
{`${token} {token} + {tokenPaused && ( + + ⏸️ + + )}
diff --git a/src/components/CCIP/Drawer/TokenDrawer.tsx b/src/components/CCIP/Drawer/TokenDrawer.tsx index 9be122ad2de..3b61b353763 100644 --- a/src/components/CCIP/Drawer/TokenDrawer.tsx +++ b/src/components/CCIP/Drawer/TokenDrawer.tsx @@ -14,6 +14,7 @@ import { getTokenData, LaneConfig, } from "~/config/data/ccip/index.ts" +import { isTokenPaused } from "~/config/data/ccip/utils.ts" import { useState } from "react" import { ChainType, ExplorerInfo, SupportedChain } from "~/config/index.ts" import LaneDrawer from "../Drawer/LaneDrawer.tsx" @@ -214,11 +215,19 @@ function TokenDrawer({ .map(({ networkDetails, laneData, destinationChain, destinationPoolType }) => { if (!laneData || !networkDetails) return null + // Check if token is paused on this lane + const tokenPaused = isTokenPaused( + network.tokenDecimals, + destinationLanes[destinationChain].rateLimiterConfig?.[ + inOutbound === LaneFilter.Inbound ? "in" : "out" + ] + ) + return ( - +
{ drawerContentStore.set(() => ( @@ -239,6 +248,14 @@ function TokenDrawer({ > {networkDetails?.name} {networkDetails?.name} + {tokenPaused && ( + + ⏸️ + + )}
diff --git a/src/components/CCIP/Tables/Table.css b/src/components/CCIP/Tables/Table.css index e0c6734178d..74922752847 100644 --- a/src/components/CCIP/Tables/Table.css +++ b/src/components/CCIP/Tables/Table.css @@ -253,3 +253,48 @@ justify-content: center; } } + +/* Paused token styles */ +.ccip-table__row--paused { + opacity: 0.6; + background-color: var(--gray-50); +} + +.ccip-table__row--paused:hover { + opacity: 0.8; + background-color: var(--gray-100); +} + +.ccip-table__network-name--paused { + color: var(--gray-400) !important; + filter: grayscale(0.7); +} + +.ccip-table__network-name--paused img { + filter: grayscale(0.8) opacity(0.7); +} + +.ccip-table__paused-badge { + margin-left: var(--space-2x); + font-size: 12px; + opacity: 0.8; + display: inline-flex; + align-items: center; + cursor: help; +} + +/* Ensure paused tokens still allow interaction for tooltips */ +.ccip-table__row--paused .ccip-table__network-name { + pointer-events: auto; +} + +/* Additional styling for capacity and rate cells in paused state */ +.ccip-table__row--paused td { + color: var(--gray-400); +} + +.ccip-table__row--paused a { + color: var(--gray-400); + text-decoration: none; + pointer-events: none; +} diff --git a/src/config/data/ccip/utils.ts b/src/config/data/ccip/utils.ts index ca3214c54a6..6b68077dceb 100644 --- a/src/config/data/ccip/utils.ts +++ b/src/config/data/ccip/utils.ts @@ -189,3 +189,34 @@ export const displayRate = (capacity: string, rate: string, symbol: string, deci maxThroughput: `Refills from 0 to ${commify(cleanedCapacity)} ${symbol} in ${displayTime}`, } } + +// ============================== +// UTILITY FUNCTIONS FOR TOKEN STATUS +// ============================== + +/** + * Determines if a token is paused based on its rate limiter configuration + * A token is considered paused if its capacity is <= 1 + * + */ +export const isTokenPaused = (decimals = 18, rateLimiterConfig?: RateLimiterConfig): boolean => { + if (!rateLimiterConfig?.isEnabled) { + return false // N/A tokens are not considered paused + } + + const capacity = rateLimiterConfig?.capacity || "0" + + try { + // Convert to BigInt for precise comparison + const capacityBigInt = BigInt(capacity) + // Calculate threshold: 1 token in smallest units = 10^decimals + const oneTokenInSmallestUnits = BigInt(10) ** BigInt(decimals) + + // Direct BigInt comparison - no floating point risks + return capacityBigInt <= oneTokenInSmallestUnits + } catch (error) { + // If capacity is not a valid number, treat as paused for safety + console.warn(`Invalid capacity value for rate limiter: ${capacity}`, error) + return true + } +} From ea71534a0ddcd068f835dc88d3ad5a599966dbe5 Mon Sep 17 00:00:00 2001 From: aelmanaa Date: Thu, 26 Jun 2025 11:36:03 +0200 Subject: [PATCH 2/3] update text --- src/components/CCIP/Drawer/LaneDrawer.tsx | 5 +---- src/components/CCIP/Drawer/TokenDrawer.tsx | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/components/CCIP/Drawer/LaneDrawer.tsx b/src/components/CCIP/Drawer/LaneDrawer.tsx index 7247762e75f..2b96d00fe71 100644 --- a/src/components/CCIP/Drawer/LaneDrawer.tsx +++ b/src/components/CCIP/Drawer/LaneDrawer.tsx @@ -163,10 +163,7 @@ function LaneDrawer({ /> {token} {tokenPaused && ( - + ⏸️ )} diff --git a/src/components/CCIP/Drawer/TokenDrawer.tsx b/src/components/CCIP/Drawer/TokenDrawer.tsx index 3b61b353763..daae211ce54 100644 --- a/src/components/CCIP/Drawer/TokenDrawer.tsx +++ b/src/components/CCIP/Drawer/TokenDrawer.tsx @@ -249,10 +249,7 @@ function TokenDrawer({ {networkDetails?.name} {networkDetails?.name} {tokenPaused && ( - + ⏸️ )} From 766ea445b35d5b719f532f62065ac153cb2bc3d3 Mon Sep 17 00:00:00 2001 From: aelmanaa Date: Tue, 1 Jul 2025 12:18:07 +0200 Subject: [PATCH 3/3] nit --- .../CCIP/Tables/TokenChainsTable.tsx | 132 ++++++++++-------- src/config/data/ccip/utils.ts | 37 +++++ 2 files changed, 110 insertions(+), 59 deletions(-) diff --git a/src/components/CCIP/Tables/TokenChainsTable.tsx b/src/components/CCIP/Tables/TokenChainsTable.tsx index 7af24d79688..ae3015c321d 100644 --- a/src/components/CCIP/Tables/TokenChainsTable.tsx +++ b/src/components/CCIP/Tables/TokenChainsTable.tsx @@ -2,6 +2,7 @@ import Address from "~/components/AddressReact.tsx" import "./Table.css" import { drawerContentStore } from "../Drawer/drawerStore.ts" import { Environment, SupportedTokenConfig, tokenPoolDisplay, PoolType } from "~/config/data/ccip/index.ts" +import { areAllLanesPaused } from "~/config/data/ccip/utils.ts" import { ChainType, ExplorerInfo } from "~/config/types.ts" import TableSearchInput from "./TableSearchInput.tsx" import { useState } from "react" @@ -64,66 +65,79 @@ function TokenChainsTable({ networks, token, lanes, environment }: TableProps) { {networks ?.filter((network) => network.name.toLowerCase().includes(search.toLowerCase())) - .map((network, index) => ( - - -
{ - drawerContentStore.set(() => ( - { + // Check if all lanes for this token on this network are paused + const allLanesPaused = areAllLanesPaused(network.tokenDecimals, lanes[network.key] || {}) + + return ( + + +
{ + drawerContentStore.set(() => ( + + )) + }} + > + + {network.name} { + currentTarget.onerror = null // prevents looping + currentTarget.src = fallbackTokenIconUrl + }} + /> + {network.tokenId} { + currentTarget.onerror = null // prevents looping + currentTarget.src = fallbackTokenIconUrl + }} /> - )) - }} - > - - {network.name} { - currentTarget.onerror = null // prevents looping - currentTarget.src = fallbackTokenIconUrl - }} - /> - {network.tokenId} { - currentTarget.onerror = null // prevents looping - currentTarget.src = fallbackTokenIconUrl - }} - /> - - {network.name} -
- - {network.tokenName} - {network.tokenSymbol} - {network.tokenDecimals} - -
- - {tokenPoolDisplay(network.tokenPoolType)} - -
- - - ))} + + {network.name} + {allLanesPaused && ( + + ⏸️ + + )} +
+ + {network.tokenName} + {network.tokenSymbol} + {network.tokenDecimals} + +
+ + {tokenPoolDisplay(network.tokenPoolType)} + +
+ + + ) + })}
diff --git a/src/config/data/ccip/utils.ts b/src/config/data/ccip/utils.ts index 6b68077dceb..5a4783a37b6 100644 --- a/src/config/data/ccip/utils.ts +++ b/src/config/data/ccip/utils.ts @@ -220,3 +220,40 @@ export const isTokenPaused = (decimals = 18, rateLimiterConfig?: RateLimiterConf return true } } + +/** + * Determines if all outbound lanes for a token from a specific network are paused + * Used to grey out network rows in the token view when all destination lanes are paused + * + * @example + * // Example: LBTC (8 decimals) on Ink with only one destination lane that has capacity "2" + * const destinationLanes = { + * "ethereum-mainnet-ink-1": { + * rateLimiterConfig: { + * out: { + * capacity: "2", + * isEnabled: true, + * rate: "1" + * } + * } + * } + * } + * areAllLanesPaused(8, destinationLanes) // returns true (2 ≤ 10^8) + */ +export const areAllLanesPaused = ( + decimals = 18, + destinationLanes: { [destinationChain: string]: { rateLimiterConfig?: { out?: RateLimiterConfig } } } +): boolean => { + const laneKeys = Object.keys(destinationLanes) + + // If no lanes exist, don't consider it paused + if (laneKeys.length === 0) { + return false + } + + // Check if ALL outbound lanes are paused + return laneKeys.every((destinationChain) => { + const outboundConfig = destinationLanes[destinationChain]?.rateLimiterConfig?.out + return isTokenPaused(decimals, outboundConfig) + }) +}