@lifi/widget 3.29.1 → 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 (169) hide show
  1. package/CHANGELOG.md +12 -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/ChainSelect/ChainSelect.js +35 -18
  9. package/dist/esm/components/ChainSelect/ChainSelect.js.map +1 -1
  10. package/dist/esm/components/ChainSelect/useChainSelect.d.ts +1 -1
  11. package/dist/esm/components/ChainSelect/useChainSelect.js +3 -3
  12. package/dist/esm/components/ChainSelect/useChainSelect.js.map +1 -1
  13. package/dist/esm/components/Chains/AllChainsAvatar.d.ts +7 -0
  14. package/dist/esm/components/Chains/AllChainsAvatar.js +77 -0
  15. package/dist/esm/components/Chains/AllChainsAvatar.js.map +1 -0
  16. package/dist/esm/components/Chains/ChainList.d.ts +2 -1
  17. package/dist/esm/components/Chains/ChainList.js +2 -2
  18. package/dist/esm/components/Chains/ChainList.js.map +1 -1
  19. package/dist/esm/components/Chains/ChainSearchInput.js +2 -2
  20. package/dist/esm/components/Chains/ChainSearchInput.js.map +1 -1
  21. package/dist/esm/components/Chains/SelectChainContent.js +1 -1
  22. package/dist/esm/components/Chains/SelectChainContent.js.map +1 -1
  23. package/dist/esm/components/Chains/VirtualizedChainList.d.ts +2 -1
  24. package/dist/esm/components/Chains/VirtualizedChainList.js +42 -10
  25. package/dist/esm/components/Chains/VirtualizedChainList.js.map +1 -1
  26. package/dist/esm/components/RouteCard/RouteCardEssentials.js +1 -1
  27. package/dist/esm/components/RouteCard/RouteCardEssentials.js.map +1 -1
  28. package/dist/esm/components/TokenList/TokenDetailsSheet.d.ts +1 -5
  29. package/dist/esm/components/TokenList/TokenDetailsSheet.js +4 -2
  30. package/dist/esm/components/TokenList/TokenDetailsSheet.js.map +1 -1
  31. package/dist/esm/components/TokenList/TokenDetailsSheetContent.js +2 -2
  32. package/dist/esm/components/TokenList/TokenDetailsSheetContent.js.map +1 -1
  33. package/dist/esm/components/TokenList/TokenList.js +11 -53
  34. package/dist/esm/components/TokenList/TokenList.js.map +1 -1
  35. package/dist/esm/components/TokenList/TokenListItem.d.ts +1 -1
  36. package/dist/esm/components/TokenList/TokenListItem.js +29 -25
  37. package/dist/esm/components/TokenList/TokenListItem.js.map +1 -1
  38. package/dist/esm/components/TokenList/VirtualizedTokenList.js +56 -37
  39. package/dist/esm/components/TokenList/VirtualizedTokenList.js.map +1 -1
  40. package/dist/esm/components/TokenList/types.d.ts +6 -10
  41. package/dist/esm/components/TokenList/useTokenSelect.js +3 -4
  42. package/dist/esm/components/TokenList/useTokenSelect.js.map +1 -1
  43. package/dist/esm/components/TransactionDetails.js +2 -2
  44. package/dist/esm/components/TransactionDetails.js.map +1 -1
  45. package/dist/esm/config/version.d.ts +1 -1
  46. package/dist/esm/config/version.js +1 -1
  47. package/dist/esm/hooks/useAccountsBalancesData.d.ts +6 -0
  48. package/dist/esm/hooks/useAccountsBalancesData.js +64 -0
  49. package/dist/esm/hooks/useAccountsBalancesData.js.map +1 -0
  50. package/dist/esm/hooks/useFilteredByTokenBalances.d.ts +8 -0
  51. package/dist/esm/hooks/useFilteredByTokenBalances.js +69 -0
  52. package/dist/esm/hooks/useFilteredByTokenBalances.js.map +1 -0
  53. package/dist/esm/hooks/useListHeight.d.ts +0 -1
  54. package/dist/esm/hooks/useListHeight.js +0 -1
  55. package/dist/esm/hooks/useListHeight.js.map +1 -1
  56. package/dist/esm/hooks/useToken.d.ts +2 -2
  57. package/dist/esm/hooks/useToken.js +13 -11
  58. package/dist/esm/hooks/useToken.js.map +1 -1
  59. package/dist/esm/hooks/useTokenAddressBalance.d.ts +2 -3
  60. package/dist/esm/hooks/useTokenAddressBalance.js +10 -14
  61. package/dist/esm/hooks/useTokenAddressBalance.js.map +1 -1
  62. package/dist/esm/hooks/useTokenBalances.d.ts +6 -9
  63. package/dist/esm/hooks/useTokenBalances.js +57 -72
  64. package/dist/esm/hooks/useTokenBalances.js.map +1 -1
  65. package/dist/esm/hooks/useTokenBalancesQueries.d.ts +11 -0
  66. package/dist/esm/hooks/useTokenBalancesQueries.js +74 -0
  67. package/dist/esm/hooks/useTokenBalancesQueries.js.map +1 -0
  68. package/dist/esm/hooks/useTokens.d.ts +6 -6
  69. package/dist/esm/hooks/useTokens.js +78 -70
  70. package/dist/esm/hooks/useTokens.js.map +1 -1
  71. package/dist/esm/i18n/bn.json +3 -3
  72. package/dist/esm/i18n/de.json +3 -3
  73. package/dist/esm/i18n/en.json +3 -3
  74. package/dist/esm/i18n/es.json +3 -3
  75. package/dist/esm/i18n/fr.json +3 -3
  76. package/dist/esm/i18n/hi.json +3 -3
  77. package/dist/esm/i18n/id.json +3 -3
  78. package/dist/esm/i18n/it.json +3 -3
  79. package/dist/esm/i18n/ja.json +3 -3
  80. package/dist/esm/i18n/ko.json +3 -3
  81. package/dist/esm/i18n/pl.json +3 -3
  82. package/dist/esm/i18n/pt.json +3 -3
  83. package/dist/esm/i18n/th.json +3 -3
  84. package/dist/esm/i18n/tr.json +3 -3
  85. package/dist/esm/i18n/uk.json +3 -3
  86. package/dist/esm/i18n/vi.json +3 -3
  87. package/dist/esm/i18n/zh.json +3 -3
  88. package/dist/esm/pages/SelectTokenPage/SearchTokenInput.js +6 -1
  89. package/dist/esm/pages/SelectTokenPage/SearchTokenInput.js.map +1 -1
  90. package/dist/esm/pages/SelectTokenPage/SelectTokenPage.js +2 -4
  91. package/dist/esm/pages/SelectTokenPage/SelectTokenPage.js.map +1 -1
  92. package/dist/esm/pages/TransactionPage/TokenValueBottomSheet.js +1 -1
  93. package/dist/esm/pages/TransactionPage/TokenValueBottomSheet.js.map +1 -1
  94. package/dist/esm/stores/chains/createChainOrderStore.d.ts +3 -2
  95. package/dist/esm/stores/chains/createChainOrderStore.js +13 -8
  96. package/dist/esm/stores/chains/createChainOrderStore.js.map +1 -1
  97. package/dist/esm/stores/chains/types.d.ts +2 -0
  98. package/dist/esm/stores/chains/useChainOrder.js +5 -1
  99. package/dist/esm/stores/chains/useChainOrder.js.map +1 -1
  100. package/dist/esm/types/token.d.ts +7 -2
  101. package/dist/esm/utils/chainType.d.ts +1 -0
  102. package/dist/esm/utils/chainType.js +2 -0
  103. package/dist/esm/utils/chainType.js.map +1 -1
  104. package/dist/esm/utils/token.d.ts +8 -0
  105. package/dist/esm/utils/token.js +29 -0
  106. package/dist/esm/utils/token.js.map +1 -0
  107. package/dist/esm/utils/tokenList.d.ts +13 -0
  108. package/dist/esm/utils/tokenList.js +106 -0
  109. package/dist/esm/utils/tokenList.js.map +1 -0
  110. package/package.json +7 -7
  111. package/package.json.tmp +6 -6
  112. package/src/components/AmountInput/AmountInputEndAdornment.tsx +3 -2
  113. package/src/components/AmountInput/PriceFormHelperText.tsx +3 -2
  114. package/src/components/ChainSelect/ChainSelect.tsx +112 -40
  115. package/src/components/ChainSelect/useChainSelect.ts +3 -3
  116. package/src/components/Chains/AllChainsAvatar.tsx +113 -0
  117. package/src/components/Chains/ChainList.tsx +3 -0
  118. package/src/components/Chains/ChainSearchInput.tsx +2 -2
  119. package/src/components/Chains/SelectChainContent.tsx +1 -0
  120. package/src/components/Chains/VirtualizedChainList.tsx +80 -12
  121. package/src/components/RouteCard/RouteCardEssentials.tsx +1 -1
  122. package/src/components/TokenList/TokenDetailsSheet.tsx +4 -9
  123. package/src/components/TokenList/TokenDetailsSheetContent.tsx +2 -6
  124. package/src/components/TokenList/TokenList.tsx +57 -129
  125. package/src/components/TokenList/TokenListItem.tsx +191 -166
  126. package/src/components/TokenList/VirtualizedTokenList.tsx +88 -48
  127. package/src/components/TokenList/types.ts +14 -10
  128. package/src/components/TokenList/useTokenSelect.ts +3 -4
  129. package/src/components/TransactionDetails.tsx +2 -2
  130. package/src/config/version.ts +1 -1
  131. package/src/hooks/useAccountsBalancesData.ts +101 -0
  132. package/src/hooks/useFilteredByTokenBalances.ts +101 -0
  133. package/src/hooks/useListHeight.ts +0 -1
  134. package/src/hooks/useToken.ts +26 -14
  135. package/src/hooks/useTokenAddressBalance.ts +14 -20
  136. package/src/hooks/useTokenBalances.ts +81 -80
  137. package/src/hooks/useTokenBalancesQueries.ts +94 -0
  138. package/src/hooks/useTokens.ts +118 -90
  139. package/src/i18n/bn.json +3 -3
  140. package/src/i18n/de.json +3 -3
  141. package/src/i18n/en.json +3 -3
  142. package/src/i18n/es.json +3 -3
  143. package/src/i18n/fr.json +3 -3
  144. package/src/i18n/hi.json +3 -3
  145. package/src/i18n/id.json +3 -3
  146. package/src/i18n/it.json +3 -3
  147. package/src/i18n/ja.json +3 -3
  148. package/src/i18n/ko.json +3 -3
  149. package/src/i18n/pl.json +3 -3
  150. package/src/i18n/pt.json +3 -3
  151. package/src/i18n/th.json +3 -3
  152. package/src/i18n/tr.json +3 -3
  153. package/src/i18n/uk.json +3 -3
  154. package/src/i18n/vi.json +3 -3
  155. package/src/i18n/zh.json +3 -3
  156. package/src/pages/SelectTokenPage/SearchTokenInput.tsx +5 -0
  157. package/src/pages/SelectTokenPage/SelectTokenPage.tsx +7 -13
  158. package/src/pages/TransactionPage/TokenValueBottomSheet.tsx +1 -1
  159. package/src/stores/chains/createChainOrderStore.ts +17 -8
  160. package/src/stores/chains/types.ts +2 -0
  161. package/src/stores/chains/useChainOrder.ts +5 -1
  162. package/src/types/token.ts +11 -2
  163. package/src/utils/chainType.ts +2 -0
  164. package/src/utils/token.ts +65 -0
  165. package/src/utils/tokenList.ts +172 -0
  166. package/dist/esm/components/TokenList/utils.d.ts +0 -2
  167. package/dist/esm/components/TokenList/utils.js +0 -35
  168. package/dist/esm/components/TokenList/utils.js.map +0 -1
  169. package/src/components/TokenList/utils.ts +0 -42
@@ -1,8 +1,9 @@
1
1
  import { Typography } from '@mui/material'
2
2
  import { useVirtualizer } from '@tanstack/react-virtual'
3
3
  import type { FC } from 'react'
4
- import { useCallback, useEffect, useRef } from 'react'
4
+ import { useCallback, useEffect, useMemo, useRef } from 'react'
5
5
  import { useTranslation } from 'react-i18next'
6
+ import { useAvailableChains } from '../../hooks/useAvailableChains.js'
6
7
  import type { TokenAmount } from '../../types/token.js'
7
8
  import { TokenDetailsSheet } from './TokenDetailsSheet.js'
8
9
  import { List } from './TokenList.style.js'
@@ -12,50 +13,64 @@ import type {
12
13
  VirtualizedTokenListProps,
13
14
  } from './types.js'
14
15
 
16
+ const tokenItemHeight = 64 // 60 + 4px margin-bottom
17
+
15
18
  export const VirtualizedTokenList: FC<VirtualizedTokenListProps> = ({
16
- account,
17
19
  tokens,
18
20
  scrollElementRef,
19
21
  chainId,
20
- chain,
21
22
  selectedTokenAddress,
22
23
  isLoading,
23
24
  isBalanceLoading,
24
25
  showCategories,
25
26
  onClick,
27
+ isAllNetworks,
26
28
  }) => {
27
29
  const { t } = useTranslation()
28
30
 
31
+ const { chains } = useAvailableChains()
32
+
33
+ // Create Set for O(1) chain lookup instead of O(n) find
34
+ const chainsSet = useMemo(() => {
35
+ if (!chains) {
36
+ return undefined
37
+ }
38
+ return new Map(chains.map((chain) => [chain.id, chain]))
39
+ }, [chains])
40
+
29
41
  const tokenDetailsSheetRef = useRef<TokenDetailsSheetBase>(null)
30
42
 
31
43
  const onShowTokenDetails = useCallback(
32
- (tokenAddress: string, noContractAddress: boolean) => {
33
- tokenDetailsSheetRef.current?.open(tokenAddress, noContractAddress)
44
+ (tokenAddress: string, noContractAddress: boolean, chainId: number) => {
45
+ tokenDetailsSheetRef.current?.open(
46
+ tokenAddress,
47
+ noContractAddress,
48
+ chainId
49
+ )
34
50
  },
35
51
  []
36
52
  )
37
53
 
38
54
  const getItemKey = useCallback(
39
55
  (index: number) => {
40
- return `${tokens[index].address}-${index}`
56
+ const token = tokens[index]
57
+ return `${token.chainId}-${token.address}-${index}`
41
58
  },
42
59
  [tokens]
43
60
  )
44
61
 
45
- const { getVirtualItems, getTotalSize, scrollToIndex } = useVirtualizer({
46
- count: tokens.length,
47
- overscan: 5,
48
- paddingEnd: 12,
49
- getScrollElement: () => scrollElementRef.current,
50
- estimateSize: (index) => {
62
+ const estimateSize = useCallback(
63
+ (index: number) => {
64
+ const currentToken = tokens[index]
65
+
51
66
  // Base size for TokenListItem
52
- let size = 64
67
+ let size = tokenItemHeight
68
+
53
69
  // Early return if categories are not shown
54
70
  if (!showCategories) {
55
71
  return size
56
72
  }
57
73
 
58
- const currentToken = tokens[index]
59
74
  const previousToken = tokens[index - 1]
60
75
 
61
76
  // Adjust size for the first featured token
@@ -75,8 +90,30 @@ export const VirtualizedTokenList: FC<VirtualizedTokenListProps> = ({
75
90
 
76
91
  return size
77
92
  },
78
- getItemKey,
79
- })
93
+ [tokens, showCategories]
94
+ )
95
+
96
+ // Chunk the tokens for infinite loading simulation
97
+ const virtualizerConfig = useMemo(
98
+ () => ({
99
+ count: tokens.length,
100
+ overscan: 5,
101
+ getScrollElement: () => scrollElementRef.current,
102
+ estimateSize,
103
+ getItemKey,
104
+ }),
105
+ [tokens.length, estimateSize, getItemKey, scrollElementRef]
106
+ )
107
+
108
+ const { getVirtualItems, getTotalSize, scrollToIndex, measure } =
109
+ useVirtualizer(virtualizerConfig)
110
+
111
+ // Address the issue of disappearing tokens on rerender
112
+ useEffect(() => {
113
+ if (scrollElementRef.current) {
114
+ measure()
115
+ }
116
+ }, [measure, scrollElementRef.current])
80
117
 
81
118
  // biome-ignore lint/correctness/useExhaustiveDependencies: run only when chainId changes
82
119
  useEffect(() => {
@@ -86,17 +123,7 @@ export const VirtualizedTokenList: FC<VirtualizedTokenListProps> = ({
86
123
  }
87
124
  // Close the token details sheet when switching the chains
88
125
  tokenDetailsSheetRef.current?.close()
89
- }, [scrollToIndex, chainId, getVirtualItems])
90
-
91
- if (isLoading) {
92
- return (
93
- <List disablePadding>
94
- {Array.from({ length: 3 }).map((_, index) => (
95
- <TokenListItemSkeleton key={index} />
96
- ))}
97
- </List>
98
- )
99
- }
126
+ }, [scrollToIndex, isAllNetworks, chainId, getVirtualItems])
100
127
 
101
128
  return (
102
129
  <>
@@ -109,6 +136,8 @@ export const VirtualizedTokenList: FC<VirtualizedTokenListProps> = ({
109
136
  const currentToken = tokens[item.index]
110
137
  const previousToken: TokenAmount | undefined = tokens[item.index - 1]
111
138
 
139
+ const chain = chainsSet?.get(currentToken.chainId)
140
+
112
141
  const isFirstFeaturedToken = currentToken.featured && item.index === 0
113
142
 
114
143
  const isTransitionFromFeaturedTokens =
@@ -129,23 +158,28 @@ export const VirtualizedTokenList: FC<VirtualizedTokenListProps> = ({
129
158
  isTransitionFromFeaturedTokens ||
130
159
  (previousToken?.popular && !currentToken.popular)
131
160
 
132
- const startAdornmentLabel = showCategories
133
- ? (() => {
134
- if (isFirstFeaturedToken) {
135
- return t('main.featuredTokens')
136
- }
137
- if (isTransitionToMyTokens) {
138
- return t('main.myTokens')
139
- }
140
- if (isTransitionToPopularTokens) {
141
- return t('main.popularTokens')
142
- }
143
- if (shouldShowAllTokensCategory) {
144
- return t('main.allTokens')
145
- }
146
- return null
147
- })()
148
- : null
161
+ const startAdornmentLabel =
162
+ !isAllNetworks && showCategories
163
+ ? (() => {
164
+ if (isFirstFeaturedToken) {
165
+ return t('main.featuredTokens')
166
+ }
167
+ if (isTransitionToMyTokens) {
168
+ return t('main.myTokens')
169
+ }
170
+ if (isTransitionToPopularTokens) {
171
+ return t('main.popularTokens')
172
+ }
173
+ if (shouldShowAllTokensCategory) {
174
+ return t('main.allTokens')
175
+ }
176
+ return null
177
+ })()
178
+ : null
179
+
180
+ const isSelected =
181
+ selectedTokenAddress === currentToken.address &&
182
+ chainId === currentToken.chainId
149
183
 
150
184
  return (
151
185
  <TokenListItem
@@ -154,11 +188,10 @@ export const VirtualizedTokenList: FC<VirtualizedTokenListProps> = ({
154
188
  size={item.size}
155
189
  start={item.start}
156
190
  token={currentToken}
157
- chain={chain}
158
- selected={currentToken.address === selectedTokenAddress}
191
+ chain={isAllNetworks ? chain : undefined}
192
+ selected={isSelected}
159
193
  onShowTokenDetails={onShowTokenDetails}
160
194
  isBalanceLoading={isBalanceLoading}
161
- accountAddress={account.address}
162
195
  startAdornment={
163
196
  startAdornmentLabel ? (
164
197
  <Typography
@@ -179,7 +212,14 @@ export const VirtualizedTokenList: FC<VirtualizedTokenListProps> = ({
179
212
  )
180
213
  })}
181
214
  </List>
182
- <TokenDetailsSheet ref={tokenDetailsSheetRef} chainId={chainId} />
215
+ <TokenDetailsSheet ref={tokenDetailsSheetRef} />
216
+ {isLoading && (
217
+ <List disablePadding sx={{ cursor: 'default' }}>
218
+ {Array.from({ length: 3 }).map((_, index) => (
219
+ <TokenListItemSkeleton key={index} />
220
+ ))}
221
+ </List>
222
+ )}
183
223
  </>
184
224
  )
185
225
  }
@@ -1,6 +1,5 @@
1
1
  import type { ExtendedChain } from '@lifi/sdk'
2
- import type { Account } from '@lifi/wallet-management'
3
- import type { MouseEventHandler, RefObject } from 'react'
2
+ import type { RefObject } from 'react'
4
3
  import type { FormType } from '../../stores/form/types.js'
5
4
  import type { TokenAmount } from '../../types/token.js'
6
5
 
@@ -12,16 +11,15 @@ export interface TokenListProps {
12
11
  }
13
12
 
14
13
  export interface VirtualizedTokenListProps {
15
- account: Account
16
14
  tokens: TokenAmount[]
17
15
  scrollElementRef: RefObject<HTMLElement | null>
18
16
  isLoading: boolean
19
17
  isBalanceLoading: boolean
20
18
  chainId?: number
21
- chain?: ExtendedChain
22
19
  showCategories?: boolean
23
20
  onClick(tokenAddress: string, chainId?: number): void
24
21
  selectedTokenAddress?: string
22
+ isAllNetworks: boolean
25
23
  }
26
24
 
27
25
  export interface TokenListItemBaseProps {
@@ -31,9 +29,12 @@ export interface TokenListItemBaseProps {
31
29
  }
32
30
 
33
31
  export interface TokenListItemProps extends TokenListItemBaseProps {
34
- accountAddress?: string
35
32
  token: TokenAmount
36
- onShowTokenDetails: (tokenAddress: string, noContractAddress: boolean) => void
33
+ onShowTokenDetails: (
34
+ tokenAddress: string,
35
+ noContractAddress: boolean,
36
+ chainId: number
37
+ ) => void
37
38
  chain?: ExtendedChain
38
39
  isBalanceLoading?: boolean
39
40
  startAdornment?: React.ReactNode
@@ -42,9 +43,12 @@ export interface TokenListItemProps extends TokenListItemBaseProps {
42
43
  }
43
44
 
44
45
  export interface TokenListItemButtonProps {
45
- onShowTokenDetails: (tokenAddress: string, noContractAddress: boolean) => void
46
- onClick?: MouseEventHandler<HTMLDivElement>
47
- accountAddress?: string
46
+ onShowTokenDetails: (
47
+ tokenAddress: string,
48
+ noContractAddress: boolean,
49
+ chainId: number
50
+ ) => void
51
+ onClick?(tokenAddress: string, chainId?: number): void
48
52
  token: TokenAmount
49
53
  chain?: ExtendedChain
50
54
  isBalanceLoading?: boolean
@@ -57,6 +61,6 @@ export interface TokenListItemAvatarProps {
57
61
 
58
62
  export interface TokenDetailsSheetBase {
59
63
  isOpen(): void
60
- open(address: string, noContractAddress: boolean): void
64
+ open(address: string, noContractAddress: boolean, chainId: number): void
61
65
  close(): void
62
66
  }
@@ -2,7 +2,7 @@ import { useCallback } from 'react'
2
2
  import { useToAddressAutoPopulate } from '../../hooks/useToAddressAutoPopulate.js'
3
3
  import { useWidgetEvents } from '../../hooks/useWidgetEvents.js'
4
4
  import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'
5
- import { useChainOrderStoreContext } from '../../stores/chains/ChainOrderStore.js'
5
+ import { useChainOrderStore } from '../../stores/chains/ChainOrderStore.js'
6
6
  import type { FormType } from '../../stores/form/types.js'
7
7
  import { FormKeyHelper } from '../../stores/form/types.js'
8
8
  import { useFieldActions } from '../../stores/form/useFieldActions.js'
@@ -21,7 +21,7 @@ export const useTokenSelect = (formType: FormType, onClick?: () => void) => {
21
21
  const emitter = useWidgetEvents()
22
22
  const { setFieldValue, getFieldValues } = useFieldActions()
23
23
  const autoPopulateToAddress = useToAddressAutoPopulate()
24
- const chainOrderStore = useChainOrderStoreContext()
24
+ const setChain = useChainOrderStore((state) => state.setChain)
25
25
 
26
26
  const tokenKey = FormKeyHelper.getTokenKey(formType)
27
27
 
@@ -71,7 +71,6 @@ export const useTokenSelect = (formType: FormType, onClick?: () => void) => {
71
71
  }
72
72
 
73
73
  // If no opposite token is selected, synchronize the opposite chain to match the currently selected chain
74
- const { setChain } = chainOrderStore.getState()
75
74
  if (!selectedOppositeTokenAddress && selectedChainId) {
76
75
  setFieldValue(
77
76
  FormKeyHelper.getChainKey(oppositeFormType),
@@ -109,12 +108,12 @@ export const useTokenSelect = (formType: FormType, onClick?: () => void) => {
109
108
  },
110
109
  [
111
110
  autoPopulateToAddress,
112
- chainOrderStore,
113
111
  disabledUI,
114
112
  emitter,
115
113
  formType,
116
114
  getFieldValues,
117
115
  onClick,
116
+ setChain,
118
117
  setFieldValue,
119
118
  subvariant,
120
119
  splitSubvariant,
@@ -125,7 +125,7 @@ export const TransactionDetails: React.FC<TransactionDetailsProps> = ({
125
125
  lineHeight: 1.429,
126
126
  }}
127
127
  >
128
- {hasRelayerSupport
128
+ {hasRelayerSupport || !combinedFeesUSD
129
129
  ? t('main.fees.free')
130
130
  : t('format.currency', { value: combinedFeesUSD })}
131
131
  </Typography>
@@ -163,7 +163,7 @@ export const TransactionDetails: React.FC<TransactionDetailsProps> = ({
163
163
  variant="body2"
164
164
  sx={{ fontWeight: 600, cursor: 'help' }}
165
165
  >
166
- {hasRelayerSupport
166
+ {hasRelayerSupport || !gasCostUSD
167
167
  ? t('main.fees.free')
168
168
  : t('format.currency', {
169
169
  value: gasCostUSD,
@@ -1,2 +1,2 @@
1
1
  export const name = '@lifi/widget'
2
- export const version = '3.29.1'
2
+ export const version = '3.30.0'
@@ -0,0 +1,101 @@
1
+ import type { ChainType, TokenExtended } from '@lifi/sdk'
2
+ import { useAccount } from '@lifi/wallet-management'
3
+ import { useMemo } from 'react'
4
+ import type { FormType } from '../stores/form/types.js'
5
+ import { useChains } from './useChains.js'
6
+ import { useFilteredTokensByBalance } from './useFilteredByTokenBalances.js'
7
+
8
+ export const useAccountsBalancesData = (
9
+ selectedChainId?: number,
10
+ formType?: FormType,
11
+ isAllNetworks?: boolean,
12
+ allTokens?: Record<number, TokenExtended[]>
13
+ ) => {
14
+ const { data: accountsWithTokens, isLoading: isAccountsLoading } =
15
+ useAccountsData(selectedChainId, formType, isAllNetworks, allTokens)
16
+
17
+ // Filter out EVM tokens that do not have balances
18
+ const { data: filteredTokens, isLoading: isCachedBalancesLoading } =
19
+ useFilteredTokensByBalance(accountsWithTokens)
20
+
21
+ return {
22
+ data: filteredTokens,
23
+ isLoading: isAccountsLoading || isCachedBalancesLoading,
24
+ }
25
+ }
26
+
27
+ const useAccountsData = (
28
+ selectedChainId?: number,
29
+ formType?: FormType,
30
+ isAllNetworks?: boolean,
31
+ allTokens?: Record<number, TokenExtended[]>
32
+ ) => {
33
+ const {
34
+ chains: allChains,
35
+ isLoading: isChainsLoading,
36
+ getChainById,
37
+ } = useChains(formType)
38
+ const currentChain = useMemo(() => {
39
+ return selectedChainId
40
+ ? getChainById(selectedChainId, allChains)
41
+ : undefined
42
+ }, [selectedChainId, allChains, getChainById])
43
+ const chains = useMemo(() => {
44
+ return isAllNetworks ? allChains : currentChain ? [currentChain] : undefined
45
+ }, [allChains, isAllNetworks, currentChain])
46
+
47
+ const { accounts: allAccounts, account: currentAccount } = useAccount(
48
+ isAllNetworks ? undefined : { chainType: currentChain?.chainType }
49
+ )
50
+ const accounts = useMemo(() => {
51
+ return isAllNetworks
52
+ ? allAccounts
53
+ : currentAccount
54
+ ? [currentAccount]
55
+ : undefined
56
+ }, [allAccounts, currentAccount, isAllNetworks])
57
+
58
+ const accountsWithTokens = useMemo(() => {
59
+ if (!chains || !allTokens || !accounts?.length) {
60
+ return undefined
61
+ }
62
+ return accounts
63
+ ?.filter((account) => account.address)
64
+ .reduce(
65
+ (acc, account) => {
66
+ if (account.address) {
67
+ const accountChains = chains?.filter(
68
+ (chain) => account.chainType === chain?.chainType
69
+ )
70
+ if (accountChains) {
71
+ const chainIdSet = new Set(accountChains.map((chain) => chain.id))
72
+ const filteredTokens = Object.entries(allTokens).reduce(
73
+ (tokenAcc, [chainIdStr, tokens]) => {
74
+ const chainId = Number(chainIdStr)
75
+ if (chainIdSet.has(chainId)) {
76
+ tokenAcc[chainId] = tokens
77
+ }
78
+ return tokenAcc
79
+ },
80
+ {} as { [chainId: number]: TokenExtended[] }
81
+ )
82
+ acc[account.address] = {
83
+ chainType: account.chainType,
84
+ tokens: filteredTokens,
85
+ }
86
+ }
87
+ }
88
+ return acc
89
+ },
90
+ {} as Record<
91
+ string,
92
+ { chainType: ChainType; tokens: Record<number, TokenExtended[]> }
93
+ >
94
+ )
95
+ }, [accounts, chains, allTokens])
96
+
97
+ return {
98
+ data: accountsWithTokens,
99
+ isLoading: isChainsLoading,
100
+ }
101
+ }
@@ -0,0 +1,101 @@
1
+ import {
2
+ ChainType,
3
+ getWalletBalances,
4
+ type TokenExtended,
5
+ type WalletTokenExtended,
6
+ } from '@lifi/sdk'
7
+ import { useQuery } from '@tanstack/react-query'
8
+ import { useMemo } from 'react'
9
+ import { isSupportedToken } from '../utils/tokenList.js'
10
+
11
+ export const useFilteredTokensByBalance = (
12
+ accountsWithTokens?: Record<
13
+ string,
14
+ { chainType: ChainType; tokens: Record<number, TokenExtended[]> }
15
+ >
16
+ ) => {
17
+ const evmAddress = useMemo(() => {
18
+ const evmAccount = Object.entries(accountsWithTokens ?? {}).find(
19
+ ([_, { chainType }]) => chainType === ChainType.EVM
20
+ )
21
+ return evmAccount?.[0]
22
+ }, [accountsWithTokens])
23
+
24
+ const { data: existingBalances, isLoading } = useQuery({
25
+ queryKey: ['existing-evm-balances', evmAddress],
26
+ queryFn: () => getWalletBalances(evmAddress ?? ''),
27
+ enabled: !!evmAddress,
28
+ refetchInterval: 30_000, // 30 seconds
29
+ staleTime: 30_000, // 30 seconds
30
+ })
31
+
32
+ const accountsWithFilteredTokens = useMemo(() => {
33
+ if (!accountsWithTokens) {
34
+ return undefined
35
+ }
36
+
37
+ // Early return if no existing balances - return all tokens
38
+ const result: Record<string, Record<number, TokenExtended[]>> = {}
39
+ if (!existingBalances) {
40
+ for (const [address, { tokens }] of Object.entries(accountsWithTokens)) {
41
+ result[address] = tokens
42
+ }
43
+ return result
44
+ }
45
+
46
+ for (const [address, { tokens }] of Object.entries(accountsWithTokens)) {
47
+ result[address] = {}
48
+
49
+ for (const [chainIdStr, chainTokens] of Object.entries(tokens)) {
50
+ const chainId = Number(chainIdStr)
51
+ // Get balances for this specific chain
52
+ const balances = existingBalances?.[chainId]
53
+ // If no balances, RPC all tokens of the chain
54
+ if (!balances?.length) {
55
+ if (chainTokens.length) {
56
+ result[address][chainId] = chainTokens
57
+ }
58
+ continue
59
+ }
60
+
61
+ // Optimize token matching with Set for O(1) lookup
62
+ const balanceSet = new Set(
63
+ balances.map((balance: WalletTokenExtended) =>
64
+ balance.address.toLowerCase()
65
+ )
66
+ )
67
+
68
+ // Get tokens that are in chainTokens and have balances
69
+ const filteredTokens = chainTokens.filter((token) => {
70
+ const tokenKey = token.address.toLowerCase()
71
+ return balanceSet.has(tokenKey)
72
+ })
73
+
74
+ // Get tokens that are in balances but not in chainTokens
75
+ const chainTokenSet = new Set(
76
+ chainTokens.map((token) => token.address.toLowerCase())
77
+ )
78
+ const additionalTokens = balances.filter(
79
+ (balance: WalletTokenExtended) => {
80
+ const balanceKey = balance.address.toLowerCase()
81
+ return !chainTokenSet.has(balanceKey) && isSupportedToken(balance)
82
+ }
83
+ ) as TokenExtended[]
84
+
85
+ // Combine both sets of tokens - convert WalletTokenExtended to TokenAmount
86
+ const allTokens = [
87
+ ...filteredTokens,
88
+ ...additionalTokens,
89
+ ] as TokenExtended[]
90
+
91
+ if (allTokens.length) {
92
+ result[address][chainId] = allTokens
93
+ }
94
+ }
95
+ }
96
+
97
+ return result
98
+ }, [accountsWithTokens, existingBalances])
99
+
100
+ return { data: accountsWithFilteredTokens, isLoading }
101
+ }
@@ -102,7 +102,6 @@ export const useListHeight = ({
102
102
  )
103
103
 
104
104
  return {
105
- minListHeight,
106
105
  listHeight,
107
106
  }
108
107
  }
@@ -1,23 +1,35 @@
1
- import { useMemo } from 'react'
2
1
  import { useTokenSearch } from './useTokenSearch.js'
3
2
  import { useTokens } from './useTokens.js'
4
3
 
5
- export const useToken = (chainId?: number, tokenAddress?: string) => {
6
- const { tokens, isLoading } = useTokens(chainId)
4
+ export const useToken = (
5
+ chainId?: number,
6
+ tokenAddress?: string,
7
+ latest?: boolean
8
+ ) => {
9
+ const { allTokens, isLoading: isTokensLoading } = useTokens()
7
10
 
8
- const token = useMemo(() => {
9
- const token = tokens?.find(
10
- (token) => token.address === tokenAddress && token.chainId === chainId
11
- )
12
- return token
13
- }, [chainId, tokenAddress, tokens])
11
+ const token =
12
+ chainId && tokenAddress
13
+ ? allTokens?.[chainId]?.find((t) => t.address === tokenAddress)
14
+ : undefined
14
15
 
15
- const tokenSearchEnabled = !isLoading && !token
16
- const { token: searchedToken, isLoading: isSearchedTokenLoading } =
17
- useTokenSearch(chainId, tokenAddress, tokenSearchEnabled)
16
+ const tokenSearchEnabled =
17
+ !!chainId && !!tokenAddress && (latest || (!isTokensLoading && !token))
18
+
19
+ const { token: searchedToken, isLoading: isSearchLoading } = useTokenSearch(
20
+ chainId,
21
+ tokenAddress,
22
+ tokenSearchEnabled
23
+ )
24
+
25
+ const resolvedToken = latest
26
+ ? (searchedToken ?? token)
27
+ : (token ?? searchedToken)
18
28
 
19
29
  return {
20
- token: token ?? searchedToken,
21
- isLoading: isLoading || (tokenSearchEnabled && isSearchedTokenLoading),
30
+ token: resolvedToken,
31
+ isLoading:
32
+ !resolvedToken &&
33
+ (isTokensLoading || (tokenSearchEnabled && isSearchLoading)),
22
34
  }
23
35
  }
@@ -1,32 +1,26 @@
1
- import { useMemo } from 'react'
2
- import type { TokenAmount } from '../types/token.js'
3
- import { useTokenBalances } from './useTokenBalances.js'
1
+ import { useAccount } from '@lifi/wallet-management'
2
+ import { useChain } from './useChain.js'
3
+ import { useToken } from './useToken.js'
4
+ import { useTokenBalance } from './useTokenBalance.js'
4
5
 
5
6
  export const useTokenAddressBalance = (
6
7
  chainId?: number,
7
8
  tokenAddress?: string
8
9
  ) => {
9
- const { tokens, tokensWithBalance, chain, isBalanceLoading, refetch } =
10
- useTokenBalances(chainId)
10
+ const { chain, isLoading: isChainLoading } = useChain(chainId)
11
+ const { account } = useAccount({ chainType: chain?.chainType })
12
+ const { token, isLoading: isTokenLoading } = useToken(chainId, tokenAddress)
11
13
 
12
- const token = useMemo(() => {
13
- if (tokenAddress && chainId) {
14
- let token = tokensWithBalance?.find(
15
- (token) => token.address === tokenAddress && token.chainId === chainId
16
- )
17
- if (!token) {
18
- token = tokens?.find(
19
- (token) => token.address === tokenAddress && token.chainId === chainId
20
- )
21
- }
22
- return token as TokenAmount
23
- }
24
- }, [chainId, tokenAddress, tokens, tokensWithBalance])
14
+ const {
15
+ token: tokenBalance,
16
+ isLoading: isBalanceLoading,
17
+ refetch,
18
+ } = useTokenBalance(account?.address, token)
25
19
 
26
20
  return {
27
- token,
21
+ token: tokenBalance,
28
22
  chain,
29
- isLoading: isBalanceLoading,
23
+ isLoading: isBalanceLoading || isChainLoading || isTokenLoading,
30
24
  refetch,
31
25
  }
32
26
  }