diff --git a/packages/widget/src/components/TokenList/PinTokenButton.tsx b/packages/widget/src/components/TokenList/PinTokenButton.tsx
new file mode 100644
index 000000000..e9dab8681
--- /dev/null
+++ b/packages/widget/src/components/TokenList/PinTokenButton.tsx
@@ -0,0 +1,50 @@
+import PushPinIcon from '@mui/icons-material/PushPin'
+import PushPinOutlinedIcon from '@mui/icons-material/PushPinOutlined'
+import { IconButton } from '@mui/material'
+import { usePinnedTokensStore } from '../../stores/pinnedTokens/PinnedTokensStore.js'
+
+interface PinTokenButtonProps {
+ chainId: number
+ tokenAddress: string
+}
+
+export const PinTokenButton = ({
+ chainId,
+ tokenAddress,
+}: PinTokenButtonProps) => {
+ const [pinnedTokens, pinToken, unpinToken] = usePinnedTokensStore((state) => [
+ state.pinnedTokens,
+ state.pinToken,
+ state.unpinToken,
+ ])
+
+ const isPinned =
+ pinnedTokens[chainId]?.includes(tokenAddress.toLowerCase()) ?? false
+
+ const handleClick = (e: React.MouseEvent) => {
+ e.stopPropagation()
+ ;(e.currentTarget as HTMLElement).blur()
+ if (isPinned) {
+ unpinToken(chainId, tokenAddress)
+ } else {
+ pinToken(chainId, tokenAddress)
+ }
+ }
+
+ const PinIcon = isPinned ? PushPinIcon : PushPinOutlinedIcon
+
+ return (
+
+
+
+ )
+}
diff --git a/packages/widget/src/components/TokenList/TokenList.tsx b/packages/widget/src/components/TokenList/TokenList.tsx
index 955bb3705..17f75a012 100644
--- a/packages/widget/src/components/TokenList/TokenList.tsx
+++ b/packages/widget/src/components/TokenList/TokenList.tsx
@@ -41,6 +41,7 @@ export const TokenList: FC = memo(({ formType, headerRef }) => {
const {
tokens,
withCategories,
+ withPinnedTokens,
isTokensLoading,
isBalanceLoading,
isSearchLoading,
@@ -77,6 +78,7 @@ export const TokenList: FC = memo(({ formType, headerRef }) => {
isLoading={isTokensLoading || isSearchLoading}
isBalanceLoading={isBalanceLoading}
showCategories={showCategories}
+ showPinnedTokens={withPinnedTokens}
onClick={handleTokenClick}
selectedTokenAddress={selectedTokenAddress}
isAllNetworks={isAllNetworks}
diff --git a/packages/widget/src/components/TokenList/TokenListItem.tsx b/packages/widget/src/components/TokenList/TokenListItem.tsx
index eb9c76b08..5663a53a7 100644
--- a/packages/widget/src/components/TokenList/TokenListItem.tsx
+++ b/packages/widget/src/components/TokenList/TokenListItem.tsx
@@ -19,6 +19,7 @@ import { formatTokenAmount, formatTokenPrice } from '../../utils/format.js'
import { shortenAddress } from '../../utils/wallet.js'
import { TokenAvatar } from '../Avatar/TokenAvatar.js'
import { ListItemButton } from '../ListItem/ListItemButton.js'
+import { PinTokenButton } from './PinTokenButton.js'
import { IconButton, ListItem } from './TokenList.style.js'
import type {
TokenListItemAvatarProps,
@@ -226,17 +227,17 @@ const TokenListItemButton: React.FC = memo(
appear={false}
mountOnEnter
>
-
+
+
@@ -279,6 +280,8 @@ const TokenListItemButton: React.FC = memo(
= memo(
>
{shortenAddress(token.address)}
-
+
+
+
+
diff --git a/packages/widget/src/components/TokenList/VirtualizedTokenList.tsx b/packages/widget/src/components/TokenList/VirtualizedTokenList.tsx
index 4134d4767..44ba2d457 100644
--- a/packages/widget/src/components/TokenList/VirtualizedTokenList.tsx
+++ b/packages/widget/src/components/TokenList/VirtualizedTokenList.tsx
@@ -23,6 +23,7 @@ export const VirtualizedTokenList: FC = ({
isLoading,
isBalanceLoading,
showCategories,
+ showPinnedTokens,
onClick,
isAllNetworks,
}) => {
@@ -62,29 +63,33 @@ export const VirtualizedTokenList: FC = ({
const estimateSize = useCallback(
(index: number) => {
const currentToken = tokens[index]
-
- // Base size for TokenListItem
+ const previousToken = tokens[index - 1]
let size = tokenItemHeight
- // Early return if categories are not shown
+ // Pinned tokens (always shown, even in all networks mode)
+ if (currentToken.pinned && index === 0) {
+ size += 24
+ }
+ if (previousToken?.pinned && !currentToken.pinned) {
+ size += 32
+ }
+
if (!showCategories) {
return size
}
- const previousToken = tokens[index - 1]
-
- // Adjust size for the first featured token
- if (currentToken.featured && index === 0) {
+ if (currentToken.featured && !currentToken.pinned && index === 0) {
size += 24
}
- // Adjust size based on changes between the current and previous tokens
- const isCategoryChanged =
- (previousToken?.amount && !currentToken.amount) ||
- (previousToken?.featured && !currentToken.featured) ||
- (previousToken?.popular && !currentToken.popular)
-
- if (isCategoryChanged) {
+ // Category transition (excluding pinned tokens)
+ const isNotPinned = !currentToken.pinned && !previousToken?.pinned
+ if (
+ isNotPinned &&
+ ((previousToken?.amount && !currentToken.amount) ||
+ (previousToken?.featured && !currentToken.featured) ||
+ (previousToken?.popular && !currentToken.popular))
+ ) {
size += 32
}
@@ -93,7 +98,6 @@ export const VirtualizedTokenList: FC = ({
[tokens, showCategories]
)
- // Chunk the tokens for infinite loading simulation
const virtualizerConfig = useMemo(
() => ({
count: tokens.length,
@@ -135,47 +139,64 @@ export const VirtualizedTokenList: FC = ({
{getVirtualItems().map((item) => {
const currentToken = tokens[item.index]
const previousToken: TokenAmount | undefined = tokens[item.index - 1]
-
const chain = chainsSet?.get(currentToken.chainId)
- const isFirstFeaturedToken = currentToken.featured && item.index === 0
-
- const isTransitionFromFeaturedTokens =
- previousToken?.featured && !currentToken.featured
+ const isNotPinned = !currentToken.pinned
+ const isFirstPinnedToken = currentToken.pinned && item.index === 0
+ const isTransitionFromPinned = previousToken?.pinned && isNotPinned
+ // Category transitions (excluding pinned)
+ const isTransitionFromFeatured =
+ previousToken?.featured && !currentToken.featured && isNotPinned
const isTransitionFromMyTokens =
- previousToken?.amount && !currentToken.amount
-
- const isTransitionToMyTokens =
- isTransitionFromFeaturedTokens && currentToken.amount
-
- const isTransitionToPopularTokens =
- (isTransitionFromFeaturedTokens || isTransitionFromMyTokens) &&
- currentToken.popular
-
- const shouldShowAllTokensCategory =
- isTransitionFromMyTokens ||
- isTransitionFromFeaturedTokens ||
- (previousToken?.popular && !currentToken.popular)
-
- const startAdornmentLabel =
- !isAllNetworks && showCategories
- ? (() => {
- if (isFirstFeaturedToken) {
- return t('main.featuredTokens')
- }
- if (isTransitionToMyTokens) {
- return t('main.myTokens')
- }
- if (isTransitionToPopularTokens) {
- return t('main.popularTokens')
- }
- if (shouldShowAllTokensCategory) {
- return t('main.allTokens')
- }
- return null
- })()
- : null
+ previousToken?.amount && !currentToken.amount && isNotPinned
+ const isTransitionFromPopular =
+ previousToken?.popular && !currentToken.popular && isNotPinned
+
+ // Determine which category label to show
+ const startAdornmentLabel = (() => {
+ if (showPinnedTokens && isFirstPinnedToken) {
+ return t('main.pinnedTokens')
+ }
+ if (showPinnedTokens && !showCategories && isTransitionFromPinned) {
+ return t('main.allTokens')
+ }
+ if (!showCategories) {
+ return null
+ }
+
+ if (
+ (isTransitionFromPinned && currentToken.featured) ||
+ (currentToken.featured && isNotPinned && item.index === 0)
+ ) {
+ return t('main.featuredTokens')
+ }
+ if (
+ (isTransitionFromFeatured || isTransitionFromPinned) &&
+ currentToken.amount &&
+ isNotPinned
+ ) {
+ return t('main.myTokens')
+ }
+ if (
+ (isTransitionFromFeatured ||
+ isTransitionFromMyTokens ||
+ isTransitionFromPinned) &&
+ currentToken.popular &&
+ isNotPinned
+ ) {
+ return t('main.popularTokens')
+ }
+ if (
+ isTransitionFromMyTokens ||
+ isTransitionFromFeatured ||
+ isTransitionFromPinned ||
+ isTransitionFromPopular
+ ) {
+ return t('main.allTokens')
+ }
+ return null
+ })()
const isSelected =
selectedTokenAddress === currentToken.address &&
@@ -200,7 +221,13 @@ export const VirtualizedTokenList: FC = ({
fontWeight: 600,
lineHeight: '16px',
px: 1.5,
- pt: isFirstFeaturedToken ? 0 : 1,
+ pt:
+ isFirstPinnedToken ||
+ (currentToken.featured &&
+ isNotPinned &&
+ item.index === 0)
+ ? 0
+ : 1,
pb: 1,
}}
>
diff --git a/packages/widget/src/components/TokenList/types.ts b/packages/widget/src/components/TokenList/types.ts
index af9e8500a..4a96f49de 100644
--- a/packages/widget/src/components/TokenList/types.ts
+++ b/packages/widget/src/components/TokenList/types.ts
@@ -15,6 +15,7 @@ export interface VirtualizedTokenListProps {
isBalanceLoading: boolean
chainId?: number
showCategories?: boolean
+ showPinnedTokens?: boolean
onClick(tokenAddress: string, chainId?: number): void
selectedTokenAddress?: string
isAllNetworks: boolean
diff --git a/packages/widget/src/hooks/useTokenBalances.ts b/packages/widget/src/hooks/useTokenBalances.ts
index c63a51af6..2cdaa0cd7 100644
--- a/packages/widget/src/hooks/useTokenBalances.ts
+++ b/packages/widget/src/hooks/useTokenBalances.ts
@@ -1,6 +1,7 @@
import { useMemo } from 'react'
import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'
import type { FormType } from '../stores/form/types.js'
+import { usePinnedTokensStore } from '../stores/pinnedTokens/PinnedTokensStore.js'
import { isSearchMatch, processTokenBalances } from '../utils/tokenList.js'
import { useAccountsBalancesData } from './useAccountsBalancesData.js'
import { useTokenBalancesQueries } from './useTokenBalancesQueries.js'
@@ -29,10 +30,46 @@ export const useTokenBalances = (
const { tokens: configTokens } = useWidgetConfig()
+ const pinnedTokens = usePinnedTokensStore((state) => state.pinnedTokens)
+
const isBalanceLoading =
(isBalanceQueriesLoading || isAccountsLoading) &&
!allTokensWithBalances?.length
+ // Create function to check if token is pinned
+ const isPinnedToken = useMemo(() => {
+ if (isAllNetworks) {
+ // For all networks, check all pinned tokens
+ const allPinned: Array<{ chainId: number; tokenAddress: string }> = []
+ Object.entries(pinnedTokens).forEach(([chainIdStr, addresses]) => {
+ const chainId = Number.parseInt(chainIdStr, 10)
+ addresses.forEach((address) => {
+ allPinned.push({ chainId, tokenAddress: address })
+ })
+ })
+ const pinnedSet = new Set(
+ allPinned.map((p) => `${p.chainId}-${p.tokenAddress.toLowerCase()}`)
+ )
+ return (chainId: number, tokenAddress: string) => {
+ const key = `${chainId}-${tokenAddress.toLowerCase()}`
+ return pinnedSet.has(key)
+ }
+ } else if (selectedChainId) {
+ // For single chain, check only selected chain
+ const chainPinnedTokens = pinnedTokens[selectedChainId] || []
+ const pinnedSet = new Set(
+ chainPinnedTokens.map((addr) => addr.toLowerCase())
+ )
+ return (chainId: number, tokenAddress: string) => {
+ return (
+ chainId === selectedChainId &&
+ pinnedSet.has(tokenAddress.toLowerCase())
+ )
+ }
+ }
+ return undefined
+ }, [isAllNetworks, selectedChainId, pinnedTokens])
+
const displayedTokensList = useMemo(() => {
const tokensByChain = isAllNetworks
? Object.values(allTokens ?? {}).flat()
@@ -69,14 +106,15 @@ export const useTokenBalances = (
isAllNetworks,
])
- const { processedTokens, withCategories } = useMemo(() => {
+ const { processedTokens, withCategories, withPinnedTokens } = useMemo(() => {
return processTokenBalances(
isBalanceLoading,
isAllNetworks || !!search,
configTokens,
selectedChainId,
displayedTokensList,
- displayedTokensWithBalances
+ displayedTokensWithBalances,
+ isPinnedToken
)
}, [
isBalanceLoading,
@@ -86,11 +124,13 @@ export const useTokenBalances = (
displayedTokensList,
displayedTokensWithBalances,
search,
+ isPinnedToken,
])
return {
tokens: processedTokens ?? [],
withCategories,
+ withPinnedTokens,
isTokensLoading,
isSearchLoading,
isBalanceLoading,
diff --git a/packages/widget/src/i18n/en.json b/packages/widget/src/i18n/en.json
index 42bda9d9f..fe9ae63fb 100644
--- a/packages/widget/src/i18n/en.json
+++ b/packages/widget/src/i18n/en.json
@@ -237,6 +237,7 @@
"myTokens": "My tokens",
"onChain": "on {{chainName}}",
"ownedBy": "Owned by",
+ "pinnedTokens": "Pinned tokens",
"popularTokens": "Popular tokens",
"priceImpact": "Price impact",
"process": {
diff --git a/packages/widget/src/stores/StoreProvider.tsx b/packages/widget/src/stores/StoreProvider.tsx
index 2cfd3ac2d..bfd60943f 100644
--- a/packages/widget/src/stores/StoreProvider.tsx
+++ b/packages/widget/src/stores/StoreProvider.tsx
@@ -4,6 +4,7 @@ import { BookmarkStoreProvider } from './bookmarks/BookmarkStore.js'
import { ChainOrderStoreProvider } from './chains/ChainOrderStore.js'
import { FormStoreProvider } from './form/FormStore.js'
import { HeaderStoreProvider } from './header/useHeaderStore.js'
+import { PinnedTokensStoreProvider } from './pinnedTokens/PinnedTokensStore.js'
import { RouteExecutionStoreProvider } from './routes/RouteExecutionStore.js'
import { SplitSubvariantStoreProvider } from './settings/useSplitSubvariantStore.js'
@@ -22,13 +23,15 @@ export const StoreProvider: React.FC> = ({
>
-
-
-
- {children}
-
-
-
+
+
+
+
+ {children}
+
+
+
+
diff --git a/packages/widget/src/stores/pinnedTokens/PinnedTokensStore.tsx b/packages/widget/src/stores/pinnedTokens/PinnedTokensStore.tsx
new file mode 100644
index 000000000..503137bf3
--- /dev/null
+++ b/packages/widget/src/stores/pinnedTokens/PinnedTokensStore.tsx
@@ -0,0 +1,38 @@
+import { createContext, useContext, useRef } from 'react'
+import { useShallow } from 'zustand/shallow'
+import type { PersistStoreProviderProps } from '../types.js'
+import { createPinnedTokensStore } from './createPinnedTokensStore.js'
+import type { PinnedTokensState, PinnedTokensStore } from './types.js'
+
+const PinnedTokensStoreContext = createContext(null)
+
+export const PinnedTokensStoreProvider: React.FC = ({
+ children,
+ ...props
+}) => {
+ const storeRef = useRef(null)
+
+ if (!storeRef.current) {
+ storeRef.current = createPinnedTokensStore(props)
+ }
+
+ return (
+
+ {children}
+
+ )
+}
+
+export function usePinnedTokensStore(
+ selector: (store: PinnedTokensState) => T
+) {
+ const useStore = useContext(PinnedTokensStoreContext)
+
+ if (!useStore) {
+ throw new Error(
+ `You forgot to wrap your component in <${PinnedTokensStoreProvider.name}>.`
+ )
+ }
+
+ return useStore(useShallow(selector))
+}
diff --git a/packages/widget/src/stores/pinnedTokens/createPinnedTokensStore.ts b/packages/widget/src/stores/pinnedTokens/createPinnedTokensStore.ts
new file mode 100644
index 000000000..e1db26926
--- /dev/null
+++ b/packages/widget/src/stores/pinnedTokens/createPinnedTokensStore.ts
@@ -0,0 +1,71 @@
+import { create } from 'zustand'
+import { persist } from 'zustand/middleware'
+import type { PersistStoreProps } from '../types.js'
+import type { PinnedTokensState } from './types.js'
+
+export const createPinnedTokensStore = ({ namePrefix }: PersistStoreProps) =>
+ create()(
+ persist(
+ (set, get) => ({
+ pinnedTokens: {},
+ pinToken: (chainId: number, tokenAddress: string) => {
+ set((state) => {
+ const normalizedAddress = tokenAddress.toLowerCase()
+ const chainTokens = state.pinnedTokens[chainId] || []
+ if (!chainTokens.includes(normalizedAddress)) {
+ return {
+ pinnedTokens: {
+ ...state.pinnedTokens,
+ [chainId]: [...chainTokens, normalizedAddress],
+ },
+ }
+ }
+ return state
+ })
+ },
+ unpinToken: (chainId: number, tokenAddress: string) => {
+ set((state) => {
+ const normalizedAddress = tokenAddress.toLowerCase()
+ const chainTokens = state.pinnedTokens[chainId] || []
+ if (chainTokens.includes(normalizedAddress)) {
+ return {
+ pinnedTokens: {
+ ...state.pinnedTokens,
+ [chainId]: chainTokens.filter(
+ (addr) => addr !== normalizedAddress
+ ),
+ },
+ }
+ }
+ return state
+ })
+ },
+ isPinned: (chainId: number, tokenAddress: string) => {
+ const normalizedAddress = tokenAddress.toLowerCase()
+ const chainTokens = get().pinnedTokens[chainId] || []
+ return chainTokens.includes(normalizedAddress)
+ },
+ getPinnedTokens: (chainId: number) => {
+ return get().pinnedTokens[chainId] || []
+ },
+ getAllPinnedTokens: () => {
+ const allPinned: Array<{ chainId: number; tokenAddress: string }> = []
+ const pinnedTokens = get().pinnedTokens
+ Object.entries(pinnedTokens).forEach(([chainIdStr, addresses]) => {
+ const chainId = Number.parseInt(chainIdStr, 10)
+ addresses.forEach((address) => {
+ allPinned.push({ chainId, tokenAddress: address })
+ })
+ })
+ return allPinned
+ },
+ }),
+ {
+ name: `${namePrefix || 'li.fi'}-pinned-tokens`,
+ version: 0,
+ partialize: (state) => ({
+ pinnedTokens: state.pinnedTokens,
+ }),
+ }
+ )
+ )
diff --git a/packages/widget/src/stores/pinnedTokens/types.ts b/packages/widget/src/stores/pinnedTokens/types.ts
new file mode 100644
index 000000000..256d78f9f
--- /dev/null
+++ b/packages/widget/src/stores/pinnedTokens/types.ts
@@ -0,0 +1,20 @@
+import type { StoreApi } from 'zustand'
+import type { UseBoundStoreWithEqualityFn } from 'zustand/traditional'
+
+export interface PinnedTokensProps {
+ pinnedTokens: Record
+}
+
+export interface PinnedTokensActions {
+ pinToken: (chainId: number, tokenAddress: string) => void
+ unpinToken: (chainId: number, tokenAddress: string) => void
+ isPinned: (chainId: number, tokenAddress: string) => boolean
+ getPinnedTokens: (chainId: number) => string[]
+ getAllPinnedTokens: () => Array<{ chainId: number; tokenAddress: string }>
+}
+
+export type PinnedTokensState = PinnedTokensProps & PinnedTokensActions
+
+export type PinnedTokensStore = UseBoundStoreWithEqualityFn<
+ StoreApi
+>
diff --git a/packages/widget/src/types/token.ts b/packages/widget/src/types/token.ts
index a90d08f2b..a0b15ee19 100644
--- a/packages/widget/src/types/token.ts
+++ b/packages/widget/src/types/token.ts
@@ -6,6 +6,7 @@ import type {
interface TokenFlags {
featured?: boolean
popular?: boolean
+ pinned?: boolean
}
export interface TokenAmount extends SDKTokenAmount, TokenFlags {}
diff --git a/packages/widget/src/utils/tokenList.ts b/packages/widget/src/utils/tokenList.ts
index 116242ea0..eb3af2c9e 100644
--- a/packages/widget/src/utils/tokenList.ts
+++ b/packages/widget/src/utils/tokenList.ts
@@ -21,21 +21,42 @@ export const processTokenBalances = (
configTokens?: WidgetTokens,
selectedChainId?: number,
tokens?: TokenExtended[],
- tokensWithBalances?: TokenAmount[]
+ tokensWithBalances?: TokenAmount[],
+ isPinnedToken?: (chainId: number, tokenAddress: string) => boolean
) => {
if (isBalanceLoading) {
if (noCategories) {
const sortedTokens = [...(tokens ?? [])].sort(sortByVolume)
+ // Separate pinned tokens
+ if (isPinnedToken) {
+ const pinned: TokenAmount[] = []
+ const notPinned: TokenAmount[] = []
+ for (const token of sortedTokens) {
+ if (isPinnedToken(token.chainId, token.address)) {
+ const pinnedToken = { ...token, pinned: true } as TokenAmount
+ pinned.push(pinnedToken)
+ } else {
+ notPinned.push(token)
+ }
+ }
+ return {
+ processedTokens: [...pinned, ...notPinned],
+ withCategories: false,
+ withPinnedTokens: !!pinned.length,
+ }
+ }
return {
processedTokens: sortedTokens,
withCategories: false,
+ withPinnedTokens: false,
}
} else {
return processedTypedTokens(
tokens ?? [],
[],
selectedChainId,
- configTokens
+ configTokens,
+ isPinnedToken
)
}
}
@@ -58,16 +79,55 @@ export const processTokenBalances = (
.sort(sortByVolume) ?? []
if (noCategories) {
+ // Separate pinned tokens
+ if (isPinnedToken) {
+ const pinnedWithBalances: TokenAmount[] = []
+ const notPinnedWithBalances: TokenAmount[] = []
+ const pinnedWithoutBalances: TokenAmount[] = []
+ const notPinnedWithoutBalances: TokenAmount[] = []
+
+ for (const token of sortedTokensWithBalances) {
+ if (isPinnedToken(token.chainId, token.address)) {
+ const pinnedToken = { ...token, pinned: true } as TokenAmount
+ pinnedWithBalances.push(pinnedToken)
+ } else {
+ notPinnedWithBalances.push(token)
+ }
+ }
+
+ for (const token of tokensWithoutBalances) {
+ if (isPinnedToken(token.chainId, token.address)) {
+ const pinnedToken = { ...token, pinned: true } as TokenAmount
+ pinnedWithoutBalances.push(pinnedToken)
+ } else {
+ notPinnedWithoutBalances.push(token)
+ }
+ }
+
+ return {
+ processedTokens: [
+ ...pinnedWithBalances,
+ ...pinnedWithoutBalances,
+ ...notPinnedWithBalances,
+ ...notPinnedWithoutBalances,
+ ],
+ withCategories: false,
+ withPinnedTokens:
+ !!pinnedWithBalances.length || !!pinnedWithoutBalances.length,
+ }
+ }
return {
processedTokens: [...sortedTokensWithBalances, ...tokensWithoutBalances],
withCategories: false,
+ withPinnedTokens: false,
}
} else {
return processedTypedTokens(
tokensWithoutBalances,
sortedTokensWithBalances,
selectedChainId,
- configTokens
+ configTokens,
+ isPinnedToken
)
}
}
@@ -77,12 +137,14 @@ const processedTypedTokens = (
tokens: TokenAmount[],
tokensWithBalances: TokenAmount[],
selectedChainId?: number,
- configTokens?: WidgetTokens
+ configTokens?: WidgetTokens,
+ isPinnedToken?: (chainId: number, tokenAddress: string) => boolean
) => {
const filteredTokensMap = new Map(
tokens.map((token) => [token.address, token])
)
+ const pinnedTokens: TokenAmount[] = []
const featuredTokensFromConfig: TokenAmount[] = []
const popularTokensFromConfig: TokenAmount[] = []
@@ -108,6 +170,16 @@ const processedTypedTokens = (
} else {
featuredTokensFromConfig.push(tokenAmount)
}
+
+ // Additionally add to pinned tokens if it is pinned
+ const isPinned =
+ isPinnedToken && selectedChainId
+ ? isPinnedToken(selectedChainId, token.address)
+ : false
+ if (isPinned) {
+ const pinnedToken = { ...tokenAmount, pinned: true } as TokenAmount
+ pinnedTokens.push(pinnedToken)
+ }
})
})
@@ -124,8 +196,17 @@ const processedTypedTokens = (
const otherTokens: TokenAmount[] = []
+ // Separate pinned tokens and categorize remaining tokens
for (const token of remainingTokens) {
- if (token.featured) {
+ const isPinned =
+ isPinnedToken && selectedChainId
+ ? isPinnedToken(selectedChainId, token.address)
+ : false
+
+ if (isPinned) {
+ const pinnedToken = { ...token, pinned: true } as TokenAmount
+ pinnedTokens.push(pinnedToken)
+ } else if (token.featured) {
featuredTokensFromConfig.push(token)
} else if (token.popular) {
popularTokensFromConfig.push(token)
@@ -134,20 +215,42 @@ const processedTypedTokens = (
}
}
+ // Also check tokens with balances for pinned status
+ const pinnedTokensWithBalances: TokenAmount[] = []
+ const nonPinnedTokensWithBalances: TokenAmount[] = []
+
+ if (isPinnedToken && selectedChainId) {
+ for (const token of tokensWithBalances) {
+ if (isPinnedToken(selectedChainId, token.address)) {
+ const pinnedToken = { ...token, pinned: true } as TokenAmount
+ pinnedTokensWithBalances.push(pinnedToken)
+ } else {
+ nonPinnedTokensWithBalances.push(token)
+ }
+ }
+ } else {
+ nonPinnedTokensWithBalances.push(...tokensWithBalances)
+ }
+
+ const sortedPinnedTokens = [
+ ...pinnedTokens,
+ ...pinnedTokensWithBalances,
+ ].sort(sortByVolume)
const sortedFeaturedTokens = [...featuredTokensFromConfig].sort(sortByVolume)
const sortedPopularTokens = [...popularTokensFromConfig].sort(sortByVolume)
const sortedOtherTokens = [...otherTokens].sort(sortByVolume)
return {
processedTokens: [
+ ...sortedPinnedTokens,
...sortedFeaturedTokens,
- ...tokensWithBalances,
+ ...nonPinnedTokensWithBalances,
...sortedPopularTokens,
...sortedOtherTokens,
],
- withCategories: Boolean(
- featuredTokensFromConfig?.length || popularTokensFromConfig?.length
- ),
+ withCategories:
+ !!featuredTokensFromConfig.length || !!popularTokensFromConfig.length,
+ withPinnedTokens: !!sortedPinnedTokens.length,
}
}