diff --git a/src/features/ccip/components/supported-networks/LaneConfig.astro b/src/features/ccip/components/supported-networks/LaneConfig.astro index 8d8edb51f20..db060c337c4 100644 --- a/src/features/ccip/components/supported-networks/LaneConfig.astro +++ b/src/features/ccip/components/supported-networks/LaneConfig.astro @@ -4,7 +4,7 @@ import { Environment, Version, PoolType, - LaneConfig, + type LaneConfig, determineTokenMechanism, TokenMechanism, } from "@config/data/ccip/" @@ -14,6 +14,7 @@ import BigNumber from "bignumber.js" import { utils } from "ethers" import { getExplorer, getExplorerAddressUrl } from "@features/utils" import { Tooltip } from "@features/common/Tooltip" +import TokenSearch from "./TokenSearch" type ConfigProps = { laneConfig: LaneConfig @@ -242,89 +243,7 @@ if (supportedTokens) {

Transferable tokens

-
- - - - - - - - - - - - - {tokensWithExtraInfo.map((tokenWithExtraInfo) => { - return ( - - - - - - - - - ) - })} - -
SymbolToken AddressDecimals - - - - - -
{tokenWithExtraInfo.token} -
-
{tokenWithExtraInfo.decimals}{tokenWithExtraInfo.poolMechanism} - {tokenWithExtraInfo.rateLimiterConfig?.isEnabled - ? display(tokenWithExtraInfo.rateLimiterConfig.capacity, tokenWithExtraInfo.decimals) + - " " + - tokenWithExtraInfo.token - : "N/A"} - - {tokenWithExtraInfo.rateLimiterConfig?.isEnabled - ? (() => { - const { rateSecond, maxThroughput } = displayRate( - tokenWithExtraInfo.rateLimiterConfig.capacity, - tokenWithExtraInfo.rateLimiterConfig.rate, - tokenWithExtraInfo.token, - tokenWithExtraInfo.decimals - ) - - return ( - - ) - })() - : "N/A"} -
-
+ ) : ( diff --git a/src/features/ccip/components/supported-networks/TokenSearch.tsx b/src/features/ccip/components/supported-networks/TokenSearch.tsx new file mode 100644 index 00000000000..8a152facde3 --- /dev/null +++ b/src/features/ccip/components/supported-networks/TokenSearch.tsx @@ -0,0 +1,215 @@ +/** @jsxImportSource preact */ +import { useState } from "preact/hooks" +import { h, FunctionComponent } from "preact" +import BigNumber from "bignumber.js" +import { utils } from "ethers" +import { SupportedChain } from "@config" +import { getExplorer, getExplorerAddressUrl } from "@features/utils" +import Address from "@components/Address" +import { SimplePreactTooltip } from "@features/common/Tooltip" + +interface TokenExtraInfo { + token: string + address: string + rateLimiterConfig: { + capacity: string + isEnabled: boolean + rate: string + } + decimals: number + poolMechanism?: string +} + +interface TokenSearchProps { + tokens: TokenExtraInfo[] + sourceChain: SupportedChain +} + +const normalizeNumber = (bigNum: BigNumber, decimals = 18) => { + const divisor = new BigNumber(10).pow(decimals) + const normalized = bigNum.dividedBy(divisor) + + return normalized.toNumber() +} + +const display = (bigNum: string, decimals = 18) => { + const numberWithoutDecimals = normalizeNumber(new BigNumber(bigNum), decimals).toString() + return utils.commify(numberWithoutDecimals) +} + +const formatTime = (seconds: number) => { + const minute = 60 + const hour = 3600 // 60*60 + + if (seconds < minute) { + return `${seconds} second${seconds > 1 ? "s" : ""}` + } else if (seconds < hour && hour - seconds > 300) { + // if the difference less than 5 minutes(300 seconds), round to hours + const minutes = Math.round(seconds / minute) + return `${minutes} minute${minutes > 1 ? "s" : ""}` + } else { + let hours = Math.floor(seconds / hour) + const remainingSeconds = seconds % hour + + // Determine the nearest 5-minute interval + let minutes = Math.round(remainingSeconds / minute / 5) * 5 + + // Round up to the next hour if minutes are 60 + if (minutes === 60) { + hours += 1 + minutes = 0 + } + + return `${hours}${ + minutes > 0 + ? ` hour${hours > 1 ? "s" : ""} and ${minutes} minute${minutes > 1 ? "s" : ""}` + : ` hour${hours > 1 ? "s" : ""}` + }` + } +} + +const displayRate = (capacity: string, rate: string, symbol: string, decimals = 18) => { + const capacityNormalized = normalizeNumber(new BigNumber(capacity), decimals) // normalize capacity + const rateNormalized = normalizeNumber(new BigNumber(rate), decimals) // normalize capacity + + const totalRefillTime = capacityNormalized / rateNormalized // in seconds + const displayTime = `${formatTime(totalRefillTime)}` + + return { + rateSecond: `${utils.commify(rateNormalized)} ${symbol}/second`, + maxThroughput: `Refills from 0 to ${utils.commify(capacityNormalized)} ${symbol} in ${displayTime}`, + } +} + +const TokenSearch: FunctionComponent = ({ tokens, sourceChain }) => { + const [searchTerm, setSearchTerm] = useState("") + const [filteredTokens, setFilteredTokens] = useState(tokens) + + const explorerUrl = getExplorer(sourceChain) + + if (!explorerUrl) throw Error(`Explorer url not found for ${sourceChain}`) + + const handleInput = (event: h.JSX.TargetedEvent) => { + const newSearchTerm = event.currentTarget.value.toLowerCase() + setSearchTerm(newSearchTerm) + const newFilteredTokens = tokens.filter((token) => token.token.toLowerCase().includes(newSearchTerm)) + setFilteredTokens(newFilteredTokens) + } + + return ( + <> + + +
+ + + + + + + + + + + + + {filteredTokens.length > 0 ? ( + filteredTokens.map((token) => ( + + + + + + + + + )) + ) : ( + + + + )} + +
SymbolToken AddressDecimals +
+ +
+
+
+ +
+
+
+ +
+
{token.token} +
+
{token.decimals}{token.poolMechanism} + {token.rateLimiterConfig?.isEnabled + ? display(token.rateLimiterConfig.capacity, token.decimals) + " " + token.token + : "N/A"} + + {token.rateLimiterConfig?.isEnabled + ? (() => { + const { rateSecond, maxThroughput } = displayRate( + token.rateLimiterConfig.capacity, + token.rateLimiterConfig.rate, + token.token, + token.decimals + ) + return ( +
+ +
+ ) + })() + : "N/A"} +
+ No token found +
+
+ + ) +} + +export default TokenSearch diff --git a/src/features/common/Tooltip/SimplePreactTooltip.tsx b/src/features/common/Tooltip/SimplePreactTooltip.tsx new file mode 100644 index 00000000000..1c7c5993d07 --- /dev/null +++ b/src/features/common/Tooltip/SimplePreactTooltip.tsx @@ -0,0 +1,55 @@ +/** @jsxImportSource preact */ +import { useState } from "preact/hooks" + +export const SimplePreactTooltip = ({ + label, + tip, + imgURL = "https://smartcontract.imgix.net/icons/info.svg?auto=compress%2Cformat", + labelStyle = {}, + tooltipStyle = {}, +}) => { + const [isVisible, setIsVisible] = useState(false) + + const containerStyle = { + display: "flex", + alignItems: "center", + justifyContent: "center", + cursor: "help", + ...labelStyle, + } + + const iconStyle = { + width: "0.8em", + height: "0.8em", + marginLeft: "2px", + marginRight: "4px", + } + + const defaultTooltipStyle = { + position: "absolute", + backgroundColor: "white", + color: "var(--color-text-secondary)", + padding: "8px 12px", + borderRadius: "4px", + right: "20%", + whiteSpace: "normal", + display: isVisible ? "block" : "none", + fontSize: "12px", + fontWeight: "normal", + maxWidth: "200px", + textAlign: "center", + boxShadow: "0 2px 4px rgba(0,0,0,0.2)", + zIndex: "1000", + ...tooltipStyle, + } + + return ( +
+
setIsVisible(true)} onMouseLeave={() => setIsVisible(false)}> + {label} + Info +
+ {isVisible &&
{tip}
} +
+ ) +} diff --git a/src/features/common/Tooltip/index.ts b/src/features/common/Tooltip/index.ts index f3f90275287..9fb535cc98c 100644 --- a/src/features/common/Tooltip/index.ts +++ b/src/features/common/Tooltip/index.ts @@ -1 +1,2 @@ export * from "./Tooltip" +export * from "./SimplePreactTooltip"