@lifi/widget 3.29.0 → 3.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/esm/components/AmountInput/AmountInputEndAdornment.d.ts +1 -1
  3. package/dist/esm/components/AmountInput/AmountInputEndAdornment.js +3 -2
  4. package/dist/esm/components/AmountInput/AmountInputEndAdornment.js.map +1 -1
  5. package/dist/esm/components/AmountInput/PriceFormHelperText.d.ts +1 -1
  6. package/dist/esm/components/AmountInput/PriceFormHelperText.js +3 -2
  7. package/dist/esm/components/AmountInput/PriceFormHelperText.js.map +1 -1
  8. package/dist/esm/components/BottomSheet/BottomSheet.js +2 -2
  9. package/dist/esm/components/BottomSheet/BottomSheet.js.map +1 -1
  10. package/dist/esm/components/BottomSheet/types.d.ts +1 -0
  11. package/dist/esm/components/ChainSelect/ChainSelect.js +35 -18
  12. package/dist/esm/components/ChainSelect/ChainSelect.js.map +1 -1
  13. package/dist/esm/components/ChainSelect/useChainSelect.d.ts +1 -1
  14. package/dist/esm/components/ChainSelect/useChainSelect.js +3 -3
  15. package/dist/esm/components/ChainSelect/useChainSelect.js.map +1 -1
  16. package/dist/esm/components/Chains/AllChainsAvatar.d.ts +7 -0
  17. package/dist/esm/components/Chains/AllChainsAvatar.js +77 -0
  18. package/dist/esm/components/Chains/AllChainsAvatar.js.map +1 -0
  19. package/dist/esm/components/Chains/ChainList.d.ts +2 -1
  20. package/dist/esm/components/Chains/ChainList.js +2 -2
  21. package/dist/esm/components/Chains/ChainList.js.map +1 -1
  22. package/dist/esm/components/Chains/ChainSearchInput.js +2 -2
  23. package/dist/esm/components/Chains/ChainSearchInput.js.map +1 -1
  24. package/dist/esm/components/Chains/SelectChainContent.js +1 -1
  25. package/dist/esm/components/Chains/SelectChainContent.js.map +1 -1
  26. package/dist/esm/components/Chains/VirtualizedChainList.d.ts +2 -1
  27. package/dist/esm/components/Chains/VirtualizedChainList.js +42 -10
  28. package/dist/esm/components/Chains/VirtualizedChainList.js.map +1 -1
  29. package/dist/esm/components/RouteCard/RouteCardEssentials.js +1 -1
  30. package/dist/esm/components/RouteCard/RouteCardEssentials.js.map +1 -1
  31. package/dist/esm/components/TokenList/TokenDetailsSheet.d.ts +1 -5
  32. package/dist/esm/components/TokenList/TokenDetailsSheet.js +5 -3
  33. package/dist/esm/components/TokenList/TokenDetailsSheet.js.map +1 -1
  34. package/dist/esm/components/TokenList/TokenDetailsSheetContent.js +2 -2
  35. package/dist/esm/components/TokenList/TokenDetailsSheetContent.js.map +1 -1
  36. package/dist/esm/components/TokenList/TokenList.js +11 -53
  37. package/dist/esm/components/TokenList/TokenList.js.map +1 -1
  38. package/dist/esm/components/TokenList/TokenListItem.d.ts +1 -1
  39. package/dist/esm/components/TokenList/TokenListItem.js +29 -25
  40. package/dist/esm/components/TokenList/TokenListItem.js.map +1 -1
  41. package/dist/esm/components/TokenList/VirtualizedTokenList.js +56 -37
  42. package/dist/esm/components/TokenList/VirtualizedTokenList.js.map +1 -1
  43. package/dist/esm/components/TokenList/types.d.ts +6 -10
  44. package/dist/esm/components/TokenList/useTokenSelect.js +3 -4
  45. package/dist/esm/components/TokenList/useTokenSelect.js.map +1 -1
  46. package/dist/esm/components/TransactionDetails.js +2 -2
  47. package/dist/esm/components/TransactionDetails.js.map +1 -1
  48. package/dist/esm/config/version.d.ts +1 -1
  49. package/dist/esm/config/version.js +1 -1
  50. package/dist/esm/hooks/useAccountsBalancesData.d.ts +6 -0
  51. package/dist/esm/hooks/useAccountsBalancesData.js +64 -0
  52. package/dist/esm/hooks/useAccountsBalancesData.js.map +1 -0
  53. package/dist/esm/hooks/useFilteredByTokenBalances.d.ts +8 -0
  54. package/dist/esm/hooks/useFilteredByTokenBalances.js +69 -0
  55. package/dist/esm/hooks/useFilteredByTokenBalances.js.map +1 -0
  56. package/dist/esm/hooks/useListHeight.d.ts +0 -1
  57. package/dist/esm/hooks/useListHeight.js +0 -1
  58. package/dist/esm/hooks/useListHeight.js.map +1 -1
  59. package/dist/esm/hooks/useToken.d.ts +2 -2
  60. package/dist/esm/hooks/useToken.js +13 -11
  61. package/dist/esm/hooks/useToken.js.map +1 -1
  62. package/dist/esm/hooks/useTokenAddressBalance.d.ts +2 -3
  63. package/dist/esm/hooks/useTokenAddressBalance.js +10 -14
  64. package/dist/esm/hooks/useTokenAddressBalance.js.map +1 -1
  65. package/dist/esm/hooks/useTokenBalances.d.ts +6 -9
  66. package/dist/esm/hooks/useTokenBalances.js +57 -72
  67. package/dist/esm/hooks/useTokenBalances.js.map +1 -1
  68. package/dist/esm/hooks/useTokenBalancesQueries.d.ts +11 -0
  69. package/dist/esm/hooks/useTokenBalancesQueries.js +74 -0
  70. package/dist/esm/hooks/useTokenBalancesQueries.js.map +1 -0
  71. package/dist/esm/hooks/useTokens.d.ts +6 -6
  72. package/dist/esm/hooks/useTokens.js +78 -70
  73. package/dist/esm/hooks/useTokens.js.map +1 -1
  74. package/dist/esm/i18n/bn.json +3 -3
  75. package/dist/esm/i18n/de.json +3 -3
  76. package/dist/esm/i18n/en.json +3 -3
  77. package/dist/esm/i18n/es.json +3 -3
  78. package/dist/esm/i18n/fr.json +3 -3
  79. package/dist/esm/i18n/hi.json +3 -3
  80. package/dist/esm/i18n/id.json +3 -3
  81. package/dist/esm/i18n/it.json +3 -3
  82. package/dist/esm/i18n/ja.json +3 -3
  83. package/dist/esm/i18n/ko.json +3 -3
  84. package/dist/esm/i18n/pl.json +3 -3
  85. package/dist/esm/i18n/pt.json +3 -3
  86. package/dist/esm/i18n/th.json +3 -3
  87. package/dist/esm/i18n/tr.json +3 -3
  88. package/dist/esm/i18n/uk.json +3 -3
  89. package/dist/esm/i18n/vi.json +3 -3
  90. package/dist/esm/i18n/zh.json +3 -3
  91. package/dist/esm/pages/SelectTokenPage/SearchTokenInput.js +6 -1
  92. package/dist/esm/pages/SelectTokenPage/SearchTokenInput.js.map +1 -1
  93. package/dist/esm/pages/SelectTokenPage/SelectTokenPage.js +2 -4
  94. package/dist/esm/pages/SelectTokenPage/SelectTokenPage.js.map +1 -1
  95. package/dist/esm/pages/TransactionPage/TokenValueBottomSheet.js +1 -1
  96. package/dist/esm/pages/TransactionPage/TokenValueBottomSheet.js.map +1 -1
  97. package/dist/esm/stores/chains/createChainOrderStore.d.ts +3 -2
  98. package/dist/esm/stores/chains/createChainOrderStore.js +13 -8
  99. package/dist/esm/stores/chains/createChainOrderStore.js.map +1 -1
  100. package/dist/esm/stores/chains/types.d.ts +2 -0
  101. package/dist/esm/stores/chains/useChainOrder.js +5 -1
  102. package/dist/esm/stores/chains/useChainOrder.js.map +1 -1
  103. package/dist/esm/types/token.d.ts +7 -2
  104. package/dist/esm/utils/chainType.d.ts +1 -0
  105. package/dist/esm/utils/chainType.js +2 -0
  106. package/dist/esm/utils/chainType.js.map +1 -1
  107. package/dist/esm/utils/token.d.ts +8 -0
  108. package/dist/esm/utils/token.js +29 -0
  109. package/dist/esm/utils/token.js.map +1 -0
  110. package/dist/esm/utils/tokenList.d.ts +13 -0
  111. package/dist/esm/utils/tokenList.js +106 -0
  112. package/dist/esm/utils/tokenList.js.map +1 -0
  113. package/package.json +8 -8
  114. package/package.json.tmp +7 -7
  115. package/src/components/AmountInput/AmountInputEndAdornment.tsx +3 -2
  116. package/src/components/AmountInput/PriceFormHelperText.tsx +3 -2
  117. package/src/components/BottomSheet/BottomSheet.tsx +2 -2
  118. package/src/components/BottomSheet/types.ts +1 -0
  119. package/src/components/ChainSelect/ChainSelect.tsx +112 -40
  120. package/src/components/ChainSelect/useChainSelect.ts +3 -3
  121. package/src/components/Chains/AllChainsAvatar.tsx +113 -0
  122. package/src/components/Chains/ChainList.tsx +3 -0
  123. package/src/components/Chains/ChainSearchInput.tsx +2 -2
  124. package/src/components/Chains/SelectChainContent.tsx +1 -0
  125. package/src/components/Chains/VirtualizedChainList.tsx +80 -12
  126. package/src/components/RouteCard/RouteCardEssentials.tsx +1 -1
  127. package/src/components/TokenList/TokenDetailsSheet.tsx +5 -10
  128. package/src/components/TokenList/TokenDetailsSheetContent.tsx +2 -6
  129. package/src/components/TokenList/TokenList.tsx +57 -129
  130. package/src/components/TokenList/TokenListItem.tsx +191 -166
  131. package/src/components/TokenList/VirtualizedTokenList.tsx +88 -48
  132. package/src/components/TokenList/types.ts +14 -10
  133. package/src/components/TokenList/useTokenSelect.ts +3 -4
  134. package/src/components/TransactionDetails.tsx +2 -2
  135. package/src/config/version.ts +1 -1
  136. package/src/hooks/useAccountsBalancesData.ts +101 -0
  137. package/src/hooks/useFilteredByTokenBalances.ts +101 -0
  138. package/src/hooks/useListHeight.ts +0 -1
  139. package/src/hooks/useToken.ts +26 -14
  140. package/src/hooks/useTokenAddressBalance.ts +14 -20
  141. package/src/hooks/useTokenBalances.ts +81 -80
  142. package/src/hooks/useTokenBalancesQueries.ts +94 -0
  143. package/src/hooks/useTokens.ts +118 -90
  144. package/src/i18n/bn.json +3 -3
  145. package/src/i18n/de.json +3 -3
  146. package/src/i18n/en.json +3 -3
  147. package/src/i18n/es.json +3 -3
  148. package/src/i18n/fr.json +3 -3
  149. package/src/i18n/hi.json +3 -3
  150. package/src/i18n/id.json +3 -3
  151. package/src/i18n/it.json +3 -3
  152. package/src/i18n/ja.json +3 -3
  153. package/src/i18n/ko.json +3 -3
  154. package/src/i18n/pl.json +3 -3
  155. package/src/i18n/pt.json +3 -3
  156. package/src/i18n/th.json +3 -3
  157. package/src/i18n/tr.json +3 -3
  158. package/src/i18n/uk.json +3 -3
  159. package/src/i18n/vi.json +3 -3
  160. package/src/i18n/zh.json +3 -3
  161. package/src/pages/SelectTokenPage/SearchTokenInput.tsx +5 -0
  162. package/src/pages/SelectTokenPage/SelectTokenPage.tsx +7 -13
  163. package/src/pages/TransactionPage/TokenValueBottomSheet.tsx +1 -1
  164. package/src/stores/chains/createChainOrderStore.ts +17 -8
  165. package/src/stores/chains/types.ts +2 -0
  166. package/src/stores/chains/useChainOrder.ts +5 -1
  167. package/src/types/token.ts +11 -2
  168. package/src/utils/chainType.ts +2 -0
  169. package/src/utils/token.ts +65 -0
  170. package/src/utils/tokenList.ts +172 -0
  171. package/dist/esm/components/TokenList/utils.d.ts +0 -2
  172. package/dist/esm/components/TokenList/utils.js +0 -35
  173. package/dist/esm/components/TokenList/utils.js.map +0 -1
  174. package/src/components/TokenList/utils.ts +0 -42
@@ -1,96 +1,97 @@
1
- import { getTokenBalances } from '@lifi/sdk'
2
- import { useAccount } from '@lifi/wallet-management'
3
- import { useQuery } from '@tanstack/react-query'
4
- import { formatUnits } from 'viem'
1
+ import { useMemo } from 'react'
5
2
  import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'
6
- import type { TokenAmount } from '../types/token.js'
7
- import { getQueryKey } from '../utils/queries.js'
3
+ import type { FormType } from '../stores/form/types.js'
4
+ import { isSearchMatch, processTokenBalances } from '../utils/tokenList.js'
5
+ import { useAccountsBalancesData } from './useAccountsBalancesData.js'
6
+ import { useTokenBalancesQueries } from './useTokenBalancesQueries.js'
8
7
  import { useTokens } from './useTokens.js'
9
8
 
10
- const defaultRefetchInterval = 32_000
9
+ export const useTokenBalances = (
10
+ selectedChainId?: number,
11
+ formType?: FormType,
12
+ isAllNetworks?: boolean,
13
+ search?: string
14
+ ) => {
15
+ const {
16
+ allTokens,
17
+ isLoading: isTokensLoading,
18
+ isSearchLoading,
19
+ } = useTokens(formType, search)
11
20
 
12
- export const useTokenBalances = (selectedChainId?: number) => {
13
- const { tokens, featuredTokens, popularTokens, chain, isLoading } =
14
- useTokens(selectedChainId)
15
- const { account } = useAccount({ chainType: chain?.chainType })
16
- const { keyPrefix } = useWidgetConfig()
21
+ const { data: accountsWithAllTokens, isLoading: isAccountsLoading } =
22
+ useAccountsBalancesData(selectedChainId, formType, isAllNetworks, allTokens)
17
23
 
18
24
  const isBalanceLoadingEnabled =
19
- Boolean(account.address) &&
20
- Boolean(tokens?.length) &&
21
- Boolean(selectedChainId)
25
+ Boolean(accountsWithAllTokens) && !isAccountsLoading
22
26
 
23
- const {
24
- data: tokensWithBalance,
25
- isLoading: isBalanceLoading,
26
- refetch,
27
- } = useQuery({
28
- queryKey: [
29
- getQueryKey('token-balances', keyPrefix),
30
- account.address,
31
- selectedChainId,
32
- tokens?.length,
33
- ],
34
- queryFn: async ({ queryKey: [, accountAddress] }) => {
35
- const tokensWithBalance: TokenAmount[] = await getTokenBalances(
36
- accountAddress as string,
37
- tokens!
38
- )
27
+ const { data: allTokensWithBalances, isLoading: isBalanceQueriesLoading } =
28
+ useTokenBalancesQueries(accountsWithAllTokens, isBalanceLoadingEnabled)
39
29
 
40
- if (!tokensWithBalance?.length) {
41
- return tokens as TokenAmount[]
42
- }
30
+ const { tokens: configTokens } = useWidgetConfig()
43
31
 
44
- const sortFn = (a: TokenAmount, b: TokenAmount) =>
45
- Number.parseFloat(formatUnits(b.amount ?? 0n, b.decimals)) *
46
- Number.parseFloat(b.priceUSD ?? '0') -
47
- Number.parseFloat(formatUnits(a.amount ?? 0n, a.decimals)) *
48
- Number.parseFloat(a.priceUSD ?? '0')
32
+ const isBalanceLoading =
33
+ (isBalanceQueriesLoading || isAccountsLoading) &&
34
+ !allTokensWithBalances?.length
49
35
 
50
- const featuredTokens: TokenAmount[] = []
51
- const tokensWithAmount: TokenAmount[] = []
52
- const popularTokens: TokenAmount[] = []
53
- const allTokens: TokenAmount[] = []
36
+ const displayedTokensList = useMemo(() => {
37
+ const tokensByChain = isAllNetworks
38
+ ? Object.values(allTokens ?? {}).flat()
39
+ : selectedChainId
40
+ ? allTokens?.[selectedChainId]
41
+ : undefined
42
+ return tokensByChain?.filter((t) => isSearchMatch(t, search)) ?? []
43
+ }, [allTokens, isAllNetworks, selectedChainId, search])
54
44
 
55
- tokensWithBalance.forEach((token) => {
56
- if (token.amount) {
57
- token.featured = false
58
- token.popular = false
59
- }
60
- if (token.featured) {
61
- featuredTokens.push(token)
62
- } else if (token.amount) {
63
- tokensWithAmount.push(token)
64
- } else if (token.popular) {
65
- popularTokens.push(token)
66
- } else {
67
- allTokens.push(token)
68
- }
69
- })
45
+ const displayedTokensWithBalances = useMemo(() => {
46
+ const balancesByChain = isAllNetworks
47
+ ? allTokensWithBalances
48
+ : selectedChainId
49
+ ? allTokensWithBalances?.filter((t) => t.chainId === selectedChainId)
50
+ : undefined
51
+ const displayedTokensSet = new Set(
52
+ displayedTokensList?.map(
53
+ (t) => `${t.chainId}-${t.address.toLowerCase()}`
54
+ ) || []
55
+ )
56
+ return balancesByChain?.filter((token) => {
57
+ const tokenKey = `${token.chainId}-${token.address.toLowerCase()}`
58
+ // Check if token is in displayed list and has amount
59
+ const isInDisplayedList = displayedTokensSet.has(tokenKey) && token.amount
60
+ // Check if it matches search (for cached appended tokens)
61
+ const matchesSearch = isSearchMatch(token, search)
62
+ return isInDisplayedList || matchesSearch
63
+ })
64
+ }, [
65
+ allTokensWithBalances,
66
+ displayedTokensList,
67
+ search,
68
+ selectedChainId,
69
+ isAllNetworks,
70
+ ])
70
71
 
71
- tokensWithAmount.sort(sortFn)
72
-
73
- const result = [
74
- ...featuredTokens,
75
- ...tokensWithAmount,
76
- ...popularTokens,
77
- ...allTokens,
78
- ]
79
- return result
80
- },
81
- enabled: isBalanceLoadingEnabled,
82
- refetchInterval: defaultRefetchInterval,
83
- staleTime: defaultRefetchInterval,
84
- })
72
+ const { processedTokens, withCategories } = useMemo(() => {
73
+ return processTokenBalances(
74
+ isBalanceLoading,
75
+ isAllNetworks ?? false,
76
+ configTokens,
77
+ selectedChainId,
78
+ displayedTokensList,
79
+ displayedTokensWithBalances
80
+ )
81
+ }, [
82
+ isBalanceLoading,
83
+ isAllNetworks,
84
+ configTokens,
85
+ selectedChainId,
86
+ displayedTokensList,
87
+ displayedTokensWithBalances,
88
+ ])
85
89
 
86
90
  return {
87
- tokens,
88
- tokensWithBalance,
89
- featuredTokens,
90
- popularTokens,
91
- chain,
92
- isLoading,
93
- isBalanceLoading: isBalanceLoading && isBalanceLoadingEnabled,
94
- refetch,
91
+ tokens: processedTokens ?? [],
92
+ withCategories,
93
+ isTokensLoading,
94
+ isSearchLoading,
95
+ isBalanceLoading,
95
96
  }
96
97
  }
@@ -0,0 +1,94 @@
1
+ import { getTokenBalances, type TokenExtended } from '@lifi/sdk'
2
+ import { useQueries } from '@tanstack/react-query'
3
+ import { useMemo, useRef } from 'react'
4
+ import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'
5
+ import type { TokenAmount, TokenAmountExtended } from '../types/token.js'
6
+ import { getQueryKey } from '../utils/queries.js'
7
+
8
+ const defaultRefetchInterval = 32_000
9
+
10
+ export const useTokenBalancesQueries = (
11
+ accountsWithTokens?: Record<string, Record<number, TokenExtended[]>>,
12
+ isBalanceLoadingEnabled?: boolean
13
+ ) => {
14
+ const { keyPrefix } = useWidgetConfig()
15
+ const firstLoadStartRef = useRef<number | null>(null)
16
+
17
+ const queryConfig = useMemo(() => {
18
+ if (!accountsWithTokens) {
19
+ return []
20
+ }
21
+ return Object.entries(accountsWithTokens).flatMap(
22
+ ([accountAddress, chainTokens]) =>
23
+ Object.entries(chainTokens).map(([chainIdStr, tokens]) => {
24
+ const chainId = Number(chainIdStr)
25
+ return {
26
+ queryKey: [
27
+ getQueryKey('token-balances', keyPrefix),
28
+ accountAddress,
29
+ chainId,
30
+ tokens.length,
31
+ ],
32
+ queryFn: async (): Promise<TokenAmountExtended[]> => {
33
+ if (!accountAddress || !tokens) {
34
+ return []
35
+ }
36
+ return await getTokenBalances(accountAddress, tokens)
37
+ },
38
+ enabled: isBalanceLoadingEnabled,
39
+ refetchInterval: defaultRefetchInterval,
40
+ staleTime: defaultRefetchInterval,
41
+ keepPreviousData: true,
42
+ }
43
+ })
44
+ )
45
+ }, [accountsWithTokens, isBalanceLoadingEnabled, keyPrefix])
46
+
47
+ const result = useQueries({
48
+ queries: queryConfig,
49
+ combine: (results) => {
50
+ const now = Date.now()
51
+
52
+ const hasLoadingQueries = results.some((result) => result.isLoading)
53
+ if (hasLoadingQueries && firstLoadStartRef.current === null) {
54
+ firstLoadStartRef.current = now
55
+ }
56
+
57
+ const allComplete = results.every(
58
+ (result) => result.isSuccess || result.isError
59
+ )
60
+
61
+ // Reset the start time when all queries complete
62
+ if (allComplete) {
63
+ firstLoadStartRef.current = null
64
+ }
65
+
66
+ // Calculate time since first load started
67
+ const timeSinceStart = firstLoadStartRef.current
68
+ ? now - firstLoadStartRef.current
69
+ : 0
70
+
71
+ // Return results if all complete OR if 500ms have passed since first query started
72
+ const shouldReturnResults = allComplete || timeSinceStart >= 500
73
+
74
+ if (shouldReturnResults) {
75
+ const data: TokenAmount[] = results
76
+ .flatMap((result) => result.data || [])
77
+ .filter((token) => token.amount)
78
+ return {
79
+ data,
80
+ isLoading: !allComplete,
81
+ isError: results.some((result) => result.isError),
82
+ }
83
+ }
84
+
85
+ return {
86
+ data: undefined,
87
+ isLoading: true,
88
+ isError: false,
89
+ }
90
+ },
91
+ })
92
+
93
+ return result
94
+ }
@@ -1,111 +1,139 @@
1
- import { ChainType, getTokens } from '@lifi/sdk'
2
- import { useQuery } from '@tanstack/react-query'
1
+ import { ChainType, getTokens, type TokensExtendedResponse } from '@lifi/sdk'
2
+ import { useQuery, useQueryClient } from '@tanstack/react-query'
3
3
  import { useMemo } from 'react'
4
4
  import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'
5
- import type { TokenAmount } from '../types/token.js'
5
+ import type { FormType } from '../stores/form/types.js'
6
+ import { isItemAllowed } from '../utils/item.js'
6
7
  import { getQueryKey } from '../utils/queries.js'
7
- import { useChains } from './useChains.js'
8
+ import { filterAllowedTokens } from '../utils/token.js'
9
+
10
+ export const useTokens = (formType?: FormType, search?: string) => {
11
+ const {
12
+ tokens: configTokens,
13
+ chains: chainsConfig,
14
+ keyPrefix,
15
+ } = useWidgetConfig()
16
+
17
+ const { isLoading: isSearchLoading } = useBackgroundTokenSearch(search)
8
18
 
9
- export const useTokens = (selectedChainId?: number) => {
10
- const { tokens: configTokens, keyPrefix } = useWidgetConfig()
11
19
  const { data, isLoading } = useQuery({
12
20
  queryKey: [getQueryKey('tokens', keyPrefix)],
13
- queryFn: () =>
14
- getTokens({
15
- chainTypes: [
16
- ChainType.EVM,
17
- ChainType.SVM,
18
- ChainType.UTXO,
19
- ChainType.MVM,
20
- ],
21
- orderBy: 'volumeUSD24H',
22
- limit: 1000,
23
- }),
21
+ queryFn: async ({ signal }) => {
22
+ const chainTypes = [
23
+ ChainType.EVM,
24
+ ChainType.SVM,
25
+ ChainType.UTXO,
26
+ ChainType.MVM,
27
+ ].filter((chainType) => isItemAllowed(chainType, chainsConfig?.types))
28
+ const tokensResponse: TokensExtendedResponse = await getTokens(
29
+ {
30
+ chainTypes,
31
+ orderBy: 'volumeUSD24H',
32
+ extended: true,
33
+ limit: 1000,
34
+ },
35
+ { signal }
36
+ )
37
+ return tokensResponse
38
+ },
24
39
  refetchInterval: 300_000,
25
40
  staleTime: 300_000,
26
41
  })
27
- const {
28
- chains,
29
- isLoading: isSupportedChainsLoading,
30
- getChainById,
31
- } = useChains()
32
-
33
- const filteredData = useMemo(() => {
34
- if (isSupportedChainsLoading || !data) {
35
- return
36
- }
37
- const chain = getChainById(selectedChainId, chains)
38
- const chainAllowed = selectedChainId && chain
39
- if (!chainAllowed) {
40
- return
41
- }
42
- let filteredTokens = data.tokens?.[selectedChainId] || []
43
- const includedTokens = configTokens?.include?.filter(
44
- (token) => token.chainId === selectedChainId
45
- )
46
- if (includedTokens?.length) {
47
- filteredTokens = [...includedTokens, ...filteredTokens]
48
- }
49
42
 
50
- const filteredTokensMap = new Map(
51
- filteredTokens.map((token) => [token.address, token])
43
+ const allTokens = useMemo(() => {
44
+ return filterAllowedTokens(
45
+ data?.tokens,
46
+ configTokens,
47
+ chainsConfig,
48
+ formType
52
49
  )
50
+ }, [data?.tokens, configTokens, chainsConfig, formType])
51
+
52
+ return {
53
+ allTokens,
54
+ isLoading,
55
+ isSearchLoading,
56
+ }
57
+ }
58
+
59
+ // This hook is used to search for tokens in the background.
60
+ // It updates the main tokens cache with the search results,
61
+ // if any of the tokens are not already in the cache.
62
+ const useBackgroundTokenSearch = (search?: string) => {
63
+ const { chains: chainsConfig, keyPrefix } = useWidgetConfig()
64
+ const queryClient = useQueryClient()
53
65
 
54
- const [popularTokens, featuredTokens] = (
55
- ['popular', 'featured'] as ('popular' | 'featured')[]
56
- ).map((tokenType) => {
57
- const typedConfigTokens = configTokens?.[tokenType]?.filter(
58
- (token) => token.chainId === selectedChainId
66
+ const { isLoading: isSearchLoading } = useQuery({
67
+ queryKey: [getQueryKey('tokens-search', keyPrefix), search],
68
+ queryFn: async ({ queryKey: [, searchQuery], signal }) => {
69
+ const chainTypes = [
70
+ ChainType.EVM,
71
+ ChainType.SVM,
72
+ ChainType.UTXO,
73
+ ChainType.MVM,
74
+ ].filter((chainType) => isItemAllowed(chainType, chainsConfig?.types))
75
+ const tokensResponse: TokensExtendedResponse = await getTokens(
76
+ {
77
+ chainTypes,
78
+ orderBy: 'volumeUSD24H',
79
+ extended: true,
80
+ search: searchQuery,
81
+ limit: 1000,
82
+ },
83
+ { signal }
59
84
  )
60
85
 
61
- const populatedConfigTokens = typedConfigTokens?.map((token) => {
62
- // Mark token as popular
63
- ;(token as TokenAmount)[tokenType] = true
64
- // Check if this token exists in the filteredTokensMap and add priceUSD if it does
65
- const matchingFilteredToken = filteredTokensMap.get(token.address)
66
- if (matchingFilteredToken?.priceUSD) {
67
- ;(token as TokenAmount).priceUSD = matchingFilteredToken.priceUSD
68
- }
69
- if (!token.logoURI && matchingFilteredToken) {
70
- ;(token as TokenAmount).logoURI = matchingFilteredToken.logoURI
71
- }
72
- return token
73
- }) as TokenAmount[]
74
-
75
- if (populatedConfigTokens?.length) {
76
- const configTokenAddresses = new Set(
77
- populatedConfigTokens?.map((token) => token.address)
78
- )
79
- filteredTokens = filteredTokens.filter(
80
- (token) => !configTokenAddresses.has(token.address)
86
+ // Merge search results into main tokens cache
87
+ if (searchQuery) {
88
+ queryClient.setQueriesData<TokensExtendedResponse>(
89
+ { queryKey: [getQueryKey('tokens', keyPrefix)] },
90
+ (data) => {
91
+ if (!data) {
92
+ return data
93
+ }
94
+
95
+ const clonedData = { ...data, tokens: { ...data.tokens } }
96
+
97
+ Object.entries(tokensResponse.tokens).forEach(
98
+ ([chainId, searchTokens]) => {
99
+ const chainIdNum = Number(chainId)
100
+ const existingTokens = clonedData.tokens[chainIdNum] || []
101
+
102
+ const existingTokenAddresses = new Set(
103
+ existingTokens.map((token) => token.address.toLowerCase())
104
+ )
105
+
106
+ // Find tokens in search results that don't exist in the main list
107
+ const newTokens = searchTokens.filter(
108
+ (searchToken) =>
109
+ !existingTokenAddresses.has(
110
+ searchToken.address.toLowerCase()
111
+ )
112
+ )
113
+
114
+ // Add new tokens to the main list
115
+ if (newTokens.length > 0) {
116
+ clonedData.tokens[chainIdNum] = [
117
+ ...existingTokens,
118
+ ...newTokens,
119
+ ]
120
+ }
121
+ }
122
+ )
123
+
124
+ return clonedData
125
+ }
81
126
  )
82
- populatedConfigTokens.push(...filteredTokens)
83
- filteredTokens = populatedConfigTokens
84
127
  }
85
128
 
86
- return populatedConfigTokens
87
- })
88
-
89
- return {
90
- tokens: filteredTokens,
91
- featuredTokens,
92
- popularTokens,
93
- chain,
94
- }
95
- }, [
96
- chains,
97
- configTokens,
98
- data,
99
- getChainById,
100
- isSupportedChainsLoading,
101
- selectedChainId,
102
- ])
129
+ return tokensResponse
130
+ },
131
+ enabled: !!search,
132
+ refetchInterval: 300_000,
133
+ staleTime: 300_000,
134
+ })
103
135
 
104
136
  return {
105
- tokens: filteredData?.tokens,
106
- featuredTokens: filteredData?.featuredTokens,
107
- popularTokens: filteredData?.popularTokens,
108
- chain: filteredData?.chain,
109
- isLoading,
137
+ isLoading: isSearchLoading,
110
138
  }
111
139
  }
package/src/i18n/bn.json CHANGED
@@ -212,6 +212,7 @@
212
212
  "slippage": ""
213
213
  },
214
214
  "main": {
215
+ "allNetworks": "",
215
216
  "allTokens": "",
216
217
  "bridgeStepDetails": "",
217
218
  "checkoutStepDetails": "",
@@ -310,10 +311,9 @@
310
311
  "tokenOnChainAmount": "",
311
312
  "tokenSearch": "",
312
313
  "valueLoss": "মান ক্ষতি",
313
- "searchChain": "",
314
- "searchChains": "",
315
314
  "searchBridges": "",
316
- "searchExchanges": ""
315
+ "searchExchanges": "",
316
+ "searchNetwork": ""
317
317
  },
318
318
  "settings": {
319
319
  "appearance": "",
package/src/i18n/de.json CHANGED
@@ -212,6 +212,7 @@
212
212
  "slippage": ""
213
213
  },
214
214
  "main": {
215
+ "allNetworks": "",
215
216
  "allTokens": "",
216
217
  "bridgeStepDetails": "",
217
218
  "checkoutStepDetails": "",
@@ -310,10 +311,9 @@
310
311
  "tokenOnChainAmount": "",
311
312
  "tokenSearch": "",
312
313
  "valueLoss": "Wertverlust",
313
- "searchChain": "",
314
- "searchChains": "",
315
314
  "searchBridges": "",
316
- "searchExchanges": ""
315
+ "searchExchanges": "",
316
+ "searchNetwork": ""
317
317
  },
318
318
  "settings": {
319
319
  "appearance": "",
package/src/i18n/en.json CHANGED
@@ -212,6 +212,7 @@
212
212
  "slippage": "The maximum percentage difference between the expected price, and the actual price at which a transfer is executed. This value can be changed in settings."
213
213
  },
214
214
  "main": {
215
+ "allNetworks": "All networks",
215
216
  "allTokens": "All tokens",
216
217
  "bridgeStepDetails": "Bridge from {{from}} to {{to}} via {{tool}}",
217
218
  "checkoutStepDetails": "Purchase via {{tool}}",
@@ -310,10 +311,9 @@
310
311
  "tokenOnChainAmount": "{{amount, numberExt}} {{tokenSymbol}} on {{chainName}}",
311
312
  "tokenSearch": "Search by token or address",
312
313
  "valueLoss": "Value loss",
313
- "searchChain": "Search chain",
314
- "searchChains": "Search by chain name",
315
314
  "searchBridges": "Search by bridge name",
316
- "searchExchanges": "Search by exchange name"
315
+ "searchExchanges": "Search by exchange name",
316
+ "searchNetwork": "Search network"
317
317
  },
318
318
  "settings": {
319
319
  "appearance": "Appearance",
package/src/i18n/es.json CHANGED
@@ -212,6 +212,7 @@
212
212
  "slippage": "El porcentaje máximo de diferencia entre el precio esperado, y el precio real al que se ejecuta una transferencia. Este valor puede cambiarse en ajustes."
213
213
  },
214
214
  "main": {
215
+ "allNetworks": "",
215
216
  "allTokens": "Todos los tokens",
216
217
  "bridgeStepDetails": "Puente de {{from}} a {{to}} vía {{tool}}",
217
218
  "checkoutStepDetails": "Comprar a través de {{tool}}",
@@ -310,10 +311,9 @@
310
311
  "tokenOnChainAmount": "",
311
312
  "tokenSearch": "",
312
313
  "valueLoss": "Valor perdido",
313
- "searchChain": "",
314
- "searchChains": "Buscar por nombre de cadena",
315
314
  "searchBridges": "Buscar por nombre de puente",
316
- "searchExchanges": "Buscar por nombre de intercambio"
315
+ "searchExchanges": "Buscar por nombre de intercambio",
316
+ "searchNetwork": ""
317
317
  },
318
318
  "settings": {
319
319
  "appearance": "",
package/src/i18n/fr.json CHANGED
@@ -212,6 +212,7 @@
212
212
  "slippage": ""
213
213
  },
214
214
  "main": {
215
+ "allNetworks": "",
215
216
  "allTokens": "Tous les tokens",
216
217
  "bridgeStepDetails": "Transférer de {{from}} à {{to}} via {{tool}}",
217
218
  "checkoutStepDetails": "Achat via {{tool}}",
@@ -310,10 +311,9 @@
310
311
  "tokenOnChainAmount": "",
311
312
  "tokenSearch": "",
312
313
  "valueLoss": "Perte de valeur",
313
- "searchChain": "",
314
- "searchChains": "",
315
314
  "searchBridges": "",
316
- "searchExchanges": ""
315
+ "searchExchanges": "",
316
+ "searchNetwork": ""
317
317
  },
318
318
  "settings": {
319
319
  "appearance": "",
package/src/i18n/hi.json CHANGED
@@ -212,6 +212,7 @@
212
212
  "slippage": ""
213
213
  },
214
214
  "main": {
215
+ "allNetworks": "",
215
216
  "allTokens": "",
216
217
  "bridgeStepDetails": "",
217
218
  "checkoutStepDetails": "",
@@ -310,10 +311,9 @@
310
311
  "tokenOnChainAmount": "",
311
312
  "tokenSearch": "",
312
313
  "valueLoss": "",
313
- "searchChain": "",
314
- "searchChains": "",
315
314
  "searchBridges": "",
316
- "searchExchanges": ""
315
+ "searchExchanges": "",
316
+ "searchNetwork": ""
317
317
  },
318
318
  "settings": {
319
319
  "appearance": "",
package/src/i18n/id.json CHANGED
@@ -212,6 +212,7 @@
212
212
  "slippage": "Perbedaan persentase maksimum antara harga yang diharapkan dan harga sebenarnya saat transfer dilakukan. Nilai ini dapat diubah dalam pengaturan."
213
213
  },
214
214
  "main": {
215
+ "allNetworks": "",
215
216
  "allTokens": "Semua Token",
216
217
  "bridgeStepDetails": "Bridge dari {{from}} to {{to}} via {{tool}}",
217
218
  "checkoutStepDetails": "Dapatkan melalui {{tool}}",
@@ -310,10 +311,9 @@
310
311
  "tokenOnChainAmount": "{{amount, numberExt}} {{tokenSymbol}} di {{chainName}}",
311
312
  "tokenSearch": "",
312
313
  "valueLoss": "Kehilangan nilai",
313
- "searchChain": "",
314
- "searchChains": "Cari berdasarkan nama rantai",
315
314
  "searchBridges": "Cari berdasarkan nama bridge",
316
- "searchExchanges": "Cari berdasarkan nama exchange"
315
+ "searchExchanges": "Cari berdasarkan nama exchange",
316
+ "searchNetwork": ""
317
317
  },
318
318
  "settings": {
319
319
  "appearance": "",
package/src/i18n/it.json CHANGED
@@ -212,6 +212,7 @@
212
212
  "slippage": ""
213
213
  },
214
214
  "main": {
215
+ "allNetworks": "",
215
216
  "allTokens": "",
216
217
  "bridgeStepDetails": "",
217
218
  "checkoutStepDetails": "",
@@ -310,10 +311,9 @@
310
311
  "tokenOnChainAmount": "",
311
312
  "tokenSearch": "",
312
313
  "valueLoss": "Perdita di valore",
313
- "searchChain": "",
314
- "searchChains": "",
315
314
  "searchBridges": "",
316
- "searchExchanges": ""
315
+ "searchExchanges": "",
316
+ "searchNetwork": ""
317
317
  },
318
318
  "settings": {
319
319
  "appearance": "",