@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,15 +1,20 @@
1
1
  import type { EVMChain } from '@lifi/sdk'
2
- import { Skeleton, Tooltip } from '@mui/material'
2
+ import { Skeleton, type Theme, Tooltip, useMediaQuery } from '@mui/material'
3
3
  import { memo, useCallback, useEffect, useMemo } from 'react'
4
+ import { useTranslation } from 'react-i18next'
4
5
  import { useNavigate } from 'react-router-dom'
6
+ import { useChainOrderStore } from '../../stores/chains/ChainOrderStore.js'
5
7
  import {
6
8
  maxChainsToOrder,
7
9
  maxChainsToShow,
10
+ maxGridItemsToShow,
8
11
  } from '../../stores/chains/createChainOrderStore.js'
9
12
  import type { FormTypeProps } from '../../stores/form/types.js'
10
13
  import { FormKeyHelper } from '../../stores/form/types.js'
14
+ import { useFieldActions } from '../../stores/form/useFieldActions.js'
11
15
  import { useFieldValues } from '../../stores/form/useFieldValues.js'
12
16
  import { navigationRoutes } from '../../utils/navigationRoutes.js'
17
+ import { AllChainsAvatar } from '../Chains/AllChainsAvatar.js'
13
18
  import {
14
19
  ChainAvatar,
15
20
  ChainCard,
@@ -20,16 +25,27 @@ import {
20
25
  import { useChainSelect } from './useChainSelect.js'
21
26
 
22
27
  export const ChainSelect = memo(({ formType }: FormTypeProps) => {
28
+ const { t } = useTranslation()
23
29
  const navigate = useNavigate()
30
+ const { setFieldValue } = useFieldActions()
31
+ const isMobile = useMediaQuery((theme: Theme) =>
32
+ theme.breakpoints.down(theme.breakpoints.values.xs)
33
+ )
34
+
24
35
  const {
25
36
  chainOrder,
26
37
  chains,
27
- getChains,
28
38
  isLoading,
39
+ getSelectedChains,
29
40
  setChainOrder,
30
41
  setCurrentChain,
31
42
  } = useChainSelect(formType)
32
43
 
44
+ const { isAllNetworks, setIsAllNetworks } = useChainOrderStore((state) => ({
45
+ isAllNetworks: state.isAllNetworks,
46
+ setIsAllNetworks: state.setIsAllNetworks,
47
+ }))
48
+
33
49
  const [chainId] = useFieldValues(FormKeyHelper.getChainKey(formType))
34
50
 
35
51
  useEffect(() => {
@@ -42,60 +58,116 @@ export const ChainSelect = memo(({ formType }: FormTypeProps) => {
42
58
  }
43
59
  }, [chainId, chainOrder, formType, setChainOrder])
44
60
 
61
+ const onChainSelect = useCallback(
62
+ (selectedChainId: number) => {
63
+ setIsAllNetworks(false)
64
+ setCurrentChain(selectedChainId)
65
+ },
66
+ [setIsAllNetworks, setCurrentChain]
67
+ )
68
+
45
69
  const showAllChains = useCallback(() => {
46
70
  navigate(navigationRoutes[`${formType}Chain`])
47
71
  }, [navigate, formType])
48
72
 
49
- const { chainsToHide, chainsToShow, tilesCount } = useMemo(() => {
50
- // We check if we can accommodate all the chains on the grid
51
- // If there are more than 10 chains we show the number of hidden chains as the last one tile
52
- const chainsToHide =
53
- chains?.length === maxChainsToShow
54
- ? 0
55
- : (chains?.length ?? 0) - maxChainsToOrder
73
+ const selectAllNetworks = useCallback(() => {
74
+ setIsAllNetworks(true)
75
+ setFieldValue('tokenSearchFilter', '')
76
+ }, [setIsAllNetworks, setFieldValue])
56
77
 
57
- // When there is less than 10 chains we don't care about the order
58
- const chainsToShow = chainsToHide > 0 ? getChains() : chains
78
+ const chainsToHide =
79
+ chains?.length === maxChainsToShow
80
+ ? 0
81
+ : (chains?.length ?? 0) - maxChainsToOrder
59
82
 
60
- // Number of tiles to show in the grid
61
- const tilesCount = (chainsToShow?.length ?? 0) + (chainsToHide > 0 ? 1 : 0)
83
+ const chainsToShow = useMemo(
84
+ () => (chainsToHide > 0 ? getSelectedChains() : chains) ?? [],
85
+ [chainsToHide, getSelectedChains, chains]
86
+ )
87
+
88
+ const showAllNetworks = chainsToShow.length > 1
89
+
90
+ const tilesCount =
91
+ chainsToShow.length + (showAllNetworks ? 1 : 0) + (chainsToHide > 0 ? 1 : 0)
62
92
 
63
- return { chainsToHide, chainsToShow, tilesCount }
64
- }, [chains, getChains])
93
+ if (isLoading) {
94
+ return (
95
+ <ChainContainer itemCount={tilesCount}>
96
+ {Array.from({ length: maxGridItemsToShow }).map((_, index) => (
97
+ <Skeleton
98
+ key={index}
99
+ variant="rectangular"
100
+ width={56}
101
+ height={isMobile ? 36 : 56}
102
+ sx={{ borderRadius: 1 }}
103
+ />
104
+ ))}
105
+ </ChainContainer>
106
+ )
107
+ }
65
108
 
66
109
  return (
67
110
  <ChainContainer itemCount={tilesCount}>
68
- {isLoading
69
- ? Array.from({ length: maxChainsToOrder }).map((_, index) => (
70
- <Skeleton
71
- key={index}
72
- variant="rectangular"
73
- width={56}
74
- height={56}
75
- sx={{ borderRadius: 1 }}
111
+ {showAllNetworks && (
112
+ <Tooltip title={t('main.allNetworks')} enterNextDelay={100}>
113
+ <ChainCard
114
+ component="button"
115
+ onClick={selectAllNetworks}
116
+ type={isAllNetworks ? 'selected' : 'default'}
117
+ selectionColor="secondary"
118
+ >
119
+ <AllChainsAvatar
120
+ chains={chains ?? []}
121
+ size={isMobile ? 'small' : 'medium'}
76
122
  />
77
- ))
78
- : chainsToShow?.map((chain: EVMChain) => (
79
- <Tooltip key={chain.id} title={chain.name} enterNextDelay={100}>
80
- <ChainCard
81
- component="button"
82
- onClick={() => setCurrentChain(chain.id)}
83
- type={chainId === chain.id ? 'selected' : 'default'}
84
- selectionColor="secondary"
85
- >
86
- <ChainAvatar src={chain.logoURI} alt={chain.key}>
87
- {chain.name[0]}
88
- </ChainAvatar>
89
- </ChainCard>
90
- </Tooltip>
91
- ))}
92
- {chainsToHide > 0 ? (
123
+ </ChainCard>
124
+ </Tooltip>
125
+ )}
126
+ {chainsToShow.map((chain: EVMChain) => (
127
+ <ChainItem
128
+ key={chain.id}
129
+ chain={chain}
130
+ isSelected={chainId === chain.id}
131
+ isAllNetworks={isAllNetworks}
132
+ onSelect={onChainSelect}
133
+ />
134
+ ))}
135
+ {chainsToHide > 0 && (
93
136
  <ChainCard component="button" onClick={showAllChains}>
94
137
  <MoreChainsBox>
95
138
  <MoreChainsText>+{chainsToHide}</MoreChainsText>
96
139
  </MoreChainsBox>
97
140
  </ChainCard>
98
- ) : null}
141
+ )}
99
142
  </ChainContainer>
100
143
  )
101
144
  })
145
+
146
+ const ChainItem = memo(
147
+ ({
148
+ chain,
149
+ isSelected,
150
+ isAllNetworks,
151
+ onSelect,
152
+ }: {
153
+ chain: EVMChain
154
+ isSelected: boolean
155
+ isAllNetworks: boolean
156
+ onSelect: (id: number) => void
157
+ }) => {
158
+ return (
159
+ <Tooltip title={chain.name} enterNextDelay={100}>
160
+ <ChainCard
161
+ component="button"
162
+ onClick={() => onSelect(chain.id)}
163
+ type={!isAllNetworks && isSelected ? 'selected' : 'default'}
164
+ selectionColor="secondary"
165
+ >
166
+ <ChainAvatar src={chain.logoURI} alt={chain.key}>
167
+ {chain.name[0]}
168
+ </ChainAvatar>
169
+ </ChainCard>
170
+ </Tooltip>
171
+ )
172
+ }
173
+ )
@@ -31,7 +31,7 @@ export const useChainSelect = (formType: FormType) => {
31
31
  const swapOnly = useSwapOnly()
32
32
  const { tryResetToAddress } = useToAddressReset()
33
33
 
34
- const getChains = () => {
34
+ const getSelectedChains = useCallback(() => {
35
35
  if (!chains) {
36
36
  return []
37
37
  }
@@ -39,7 +39,7 @@ export const useChainSelect = (formType: FormType) => {
39
39
  .map((chainId) => chains.find((chain) => chain.id === chainId))
40
40
  .filter(Boolean) as EVMChain[]
41
41
  return selectedChains
42
- }
42
+ }, [chains, chainOrder])
43
43
 
44
44
  const setCurrentChain = useCallback(
45
45
  (chainId: number) => {
@@ -82,7 +82,7 @@ export const useChainSelect = (formType: FormType) => {
82
82
  return {
83
83
  chainOrder,
84
84
  chains,
85
- getChains,
85
+ getSelectedChains,
86
86
  isLoading,
87
87
  setChainOrder,
88
88
  setCurrentChain,
@@ -0,0 +1,113 @@
1
+ import { ChainType, type EVMChain, type ExtendedChain } from '@lifi/sdk'
2
+ import { Avatar, Box } from '@mui/material'
3
+ import { memo, useMemo } from 'react'
4
+
5
+ interface AllChainsAvatarProps {
6
+ chains: ExtendedChain[] | EVMChain[]
7
+ size: 'small' | 'medium'
8
+ }
9
+
10
+ const chainTypeIcons = [
11
+ {
12
+ name: 'Ethereum',
13
+ chainType: ChainType.EVM,
14
+ icon: 'https://lifinance.github.io/types/src/assets/icons/chains/ethereum.svg',
15
+ },
16
+ {
17
+ name: 'Solana',
18
+ chainType: ChainType.SVM,
19
+ icon: 'https://lifinance.github.io/types/src/assets/icons/chains/solana.svg',
20
+ },
21
+ {
22
+ name: 'Bitcoin',
23
+ chainType: ChainType.UTXO,
24
+ icon: 'https://lifinance.github.io/types/src/assets/icons/chains/bitcoin.svg',
25
+ },
26
+ {
27
+ name: 'Sui',
28
+ chainType: ChainType.MVM,
29
+ icon: 'https://lifinance.github.io/types/src/assets/icons/chains/sui.svg',
30
+ },
31
+ ]
32
+
33
+ const maxChainAvatarsCount = chainTypeIcons.length
34
+
35
+ export const AllChainsAvatar = memo(
36
+ ({ chains, size }: AllChainsAvatarProps) => {
37
+ const icons = useMemo(() => {
38
+ // Get existing ecosystem icons
39
+ const existingChainTypeIcons = chainTypeIcons.filter((predefinedChain) =>
40
+ chains.some((chain) => chain.chainType === predefinedChain.chainType)
41
+ )
42
+ if (existingChainTypeIcons.length === maxChainAvatarsCount) {
43
+ return existingChainTypeIcons
44
+ }
45
+
46
+ const remainingSlots =
47
+ maxChainAvatarsCount - existingChainTypeIcons.length
48
+
49
+ const chainsWithLogos = chains.filter((chain) => {
50
+ // Filter out chain icons matching ecosystem icons
51
+ const hasPredefinedIcon = existingChainTypeIcons.some(
52
+ (icon) => icon.name === chain.name
53
+ )
54
+ const hasLogoURI = Boolean(chain.logoURI)
55
+ return !hasPredefinedIcon && hasLogoURI
56
+ })
57
+
58
+ const additionalIcons = chainsWithLogos
59
+ .slice(0, remainingSlots)
60
+ .map((chain) => ({
61
+ name: chain.name,
62
+ chainType: chain.chainType,
63
+ icon: chain.logoURI!,
64
+ }))
65
+
66
+ return [...existingChainTypeIcons, ...additionalIcons]
67
+ }, [chains])
68
+
69
+ const wrapperSize = size === 'small' ? '32px' : '40px'
70
+ const avatarSize = size === 'small' ? '12px' : '16px'
71
+ const avatarsCount = Math.min(icons.length, maxChainAvatarsCount)
72
+ const gridRows = avatarsCount > 2 ? 2 : 1
73
+ const gridColumns = avatarsCount > 1 ? 2 : 1
74
+
75
+ return (
76
+ <Box
77
+ sx={{
78
+ width: wrapperSize,
79
+ height: wrapperSize,
80
+ display: 'grid',
81
+ gridTemplateRows: `repeat(${gridRows}, 1fr)`,
82
+ gridTemplateColumns: `repeat(${gridColumns}, 1fr)`,
83
+ placeItems: 'center',
84
+ }}
85
+ >
86
+ {icons.slice(0, avatarsCount).map((chain, idx) => {
87
+ const isThirdAvatarInThreeAvatarLayout =
88
+ avatarsCount === 3 && idx === 2
89
+
90
+ return (
91
+ <Avatar
92
+ key={`${chain.chainType}-${idx}`}
93
+ src={chain.icon}
94
+ alt={chain.name}
95
+ sx={{
96
+ width: avatarSize,
97
+ height: avatarSize,
98
+ margin: 'auto',
99
+ ...(isThirdAvatarInThreeAvatarLayout && {
100
+ gridColumn: '1 / span 2',
101
+ gridRow: '2',
102
+ justifySelf: 'center',
103
+ }),
104
+ }}
105
+ >
106
+ {chain.chainType[0]}
107
+ </Avatar>
108
+ )
109
+ })}
110
+ </Box>
111
+ )
112
+ }
113
+ )
@@ -17,12 +17,14 @@ interface ChainListProps {
17
17
  onSelect: (chain: ExtendedChain) => void
18
18
  selectedChainId?: number
19
19
  isLoading: boolean
20
+ hasSearchQuery: boolean
20
21
  inExpansion: boolean
21
22
  }
22
23
 
23
24
  export const ChainList = ({
24
25
  parentRef,
25
26
  chains,
27
+ hasSearchQuery,
26
28
  onSelect,
27
29
  selectedChainId,
28
30
  isLoading,
@@ -78,6 +80,7 @@ export const ChainList = ({
78
80
  <VirtualizedChainList
79
81
  scrollElementRef={parentRef}
80
82
  chains={chains}
83
+ hasSearchQuery={hasSearchQuery}
81
84
  onSelect={onSelect}
82
85
  selectedChainId={selectedChainId}
83
86
  itemsSize={itemsSize}
@@ -33,7 +33,7 @@ export const ChainSearchInput = ({
33
33
  <SearchInput
34
34
  inputRef={inputRef}
35
35
  onChange={onChange}
36
- placeholder={t('main.searchChain')}
36
+ placeholder={t('main.searchNetwork')}
37
37
  size="small"
38
38
  onClear={handleClear}
39
39
  />
@@ -45,7 +45,7 @@ export const ChainSearchInput = ({
45
45
  <StickySearchInput
46
46
  inputRef={inputRef}
47
47
  onChange={onChange}
48
- placeholder={t('main.searchChain')}
48
+ placeholder={t('main.searchNetwork')}
49
49
  onClear={handleClear}
50
50
  />
51
51
  )
@@ -104,6 +104,7 @@ export const SelectChainContent = memo(function SelectChainContent({
104
104
  isLoading={isLoading}
105
105
  onSelect={onSelect ?? onSelectChainFallback}
106
106
  selectedChainId={selectedChainId}
107
+ hasSearchQuery={!!inputRef.current?.value}
107
108
  inExpansion={inExpansion}
108
109
  />
109
110
  </Box>
@@ -2,8 +2,17 @@ import type { ExtendedChain } from '@lifi/sdk'
2
2
  import { useVirtualizer } from '@tanstack/react-virtual'
3
3
  import type { RefObject } from 'react'
4
4
  import { useCallback, useEffect, useMemo, useRef } from 'react'
5
+ import { useTranslation } from 'react-i18next'
5
6
  import { useChainOrderStore } from '../../stores/chains/ChainOrderStore.js'
6
- import { List } from './ChainList.style.js'
7
+ import { useFieldActions } from '../../stores/form/useFieldActions.js'
8
+ import { AllChainsAvatar } from './AllChainsAvatar.js'
9
+ import {
10
+ List,
11
+ ListItem,
12
+ ListItemAvatar,
13
+ ListItemButton,
14
+ ListItemText,
15
+ } from './ChainList.style.js'
7
16
  import { ChainListItem } from './ChainListItem.js'
8
17
 
9
18
  interface VirtualizedChainListProps {
@@ -12,23 +21,30 @@ interface VirtualizedChainListProps {
12
21
  onSelect: (chain: ExtendedChain) => void
13
22
  selectedChainId?: number
14
23
  itemsSize: 'small' | 'medium'
24
+ hasSearchQuery: boolean
15
25
  withPinnedChains: boolean
16
26
  }
17
27
 
18
28
  export const VirtualizedChainList = ({
19
29
  chains,
30
+ hasSearchQuery,
20
31
  onSelect,
21
32
  selectedChainId,
22
33
  itemsSize,
23
34
  scrollElementRef,
24
35
  withPinnedChains,
25
36
  }: VirtualizedChainListProps) => {
37
+ const { t } = useTranslation()
38
+ const { setFieldValue } = useFieldActions()
26
39
  const selectedChainIdRef = useRef(selectedChainId) // Store the initial selected chain ID to scroll to it once chains are loaded
27
40
  const hasScrolledRef = useRef(false)
28
- const [pinnedChains, setPinnedChain] = useChainOrderStore((state) => [
29
- state.pinnedChains,
30
- state.setPinnedChain,
31
- ])
41
+ const [pinnedChains, setPinnedChain, isAllNetworks, setIsAllNetworks] =
42
+ useChainOrderStore((state) => [
43
+ state.pinnedChains,
44
+ state.setPinnedChain,
45
+ state.isAllNetworks,
46
+ state.setIsAllNetworks,
47
+ ])
32
48
  const onPin = useCallback(
33
49
  (chainId: number) => {
34
50
  setPinnedChain(chainId)
@@ -49,16 +65,30 @@ export const VirtualizedChainList = ({
49
65
  return [...pinned, ...rest]
50
66
  }, [chains, pinnedChains])
51
67
 
68
+ const showAllNetworks = sortedChains.length > 1 && !hasSearchQuery
69
+
52
70
  const getItemKey = useCallback(
53
71
  (index: number) => {
54
- return `${sortedChains[index].id}-${index}`
72
+ if (showAllNetworks && index === 0) {
73
+ return 'all-chains'
74
+ }
75
+ const chainIndex = index - (showAllNetworks ? 1 : 0)
76
+ return `${sortedChains[chainIndex].id}-${index}`
77
+ },
78
+ [sortedChains, showAllNetworks]
79
+ )
80
+
81
+ const onChainSelect = useCallback(
82
+ (chain: ExtendedChain) => {
83
+ setIsAllNetworks(false)
84
+ onSelect(chain)
55
85
  },
56
- [sortedChains]
86
+ [onSelect, setIsAllNetworks]
57
87
  )
58
88
 
59
89
  const { getVirtualItems, getTotalSize, measure, range, getOffsetForIndex } =
60
90
  useVirtualizer({
61
- count: sortedChains.length,
91
+ count: sortedChains.length + (showAllNetworks ? 1 : 0), // +1 for the all networks item
62
92
  overscan: 3,
63
93
  paddingEnd: 0,
64
94
  getScrollElement: () => scrollElementRef.current,
@@ -96,6 +126,12 @@ export const VirtualizedChainList = ({
96
126
  )
97
127
 
98
128
  useEffect(() => {
129
+ // Mark as scrolled if "All Networks" is initially selected
130
+ if (isAllNetworks) {
131
+ hasScrolledRef.current = true
132
+ return
133
+ }
134
+
99
135
  // Only scroll if sortedChains is not empty and we haven't scrolled yet
100
136
  if (!hasScrolledRef.current && sortedChains.length > 0 && range) {
101
137
  const selectedChainIndex = sortedChains.findIndex(
@@ -113,7 +149,12 @@ export const VirtualizedChainList = ({
113
149
  }
114
150
  hasScrolledRef.current = true // Mark as scrolled (when needed)
115
151
  }
116
- }, [sortedChains, scrollToIndex, range])
152
+ }, [sortedChains, scrollToIndex, range, isAllNetworks])
153
+
154
+ const selectAllNetworks = useCallback(() => {
155
+ setIsAllNetworks(true)
156
+ setFieldValue('tokenSearchFilter', '')
157
+ }, [setIsAllNetworks, setFieldValue])
117
158
 
118
159
  return (
119
160
  <List
@@ -122,13 +163,40 @@ export const VirtualizedChainList = ({
122
163
  disablePadding
123
164
  >
124
165
  {getVirtualItems().map((item) => {
125
- const chain = sortedChains[item.index]
166
+ if (showAllNetworks && item.index === 0) {
167
+ return (
168
+ <ListItem
169
+ key={item.key}
170
+ style={{
171
+ height: `${itemsSize}px`,
172
+ transform: `translateY(${item.start}px)`,
173
+ padding: 0,
174
+ }}
175
+ >
176
+ <ListItemButton
177
+ onClick={selectAllNetworks}
178
+ selected={isAllNetworks}
179
+ size={itemsSize}
180
+ >
181
+ <ListItemAvatar size={itemsSize}>
182
+ <AllChainsAvatar chains={chains} size={itemsSize} />
183
+ </ListItemAvatar>
184
+ <ListItemText
185
+ primary={t('main.allNetworks')}
186
+ size={itemsSize}
187
+ />
188
+ </ListItemButton>
189
+ </ListItem>
190
+ )
191
+ }
192
+
193
+ const chain = sortedChains[item.index - (showAllNetworks ? 1 : 0)]
126
194
  return (
127
195
  <ChainListItem
128
196
  key={item.key}
129
197
  chain={chain}
130
- onSelect={onSelect}
131
- selected={chain.id === selectedChainId}
198
+ onSelect={onChainSelect}
199
+ selected={!isAllNetworks && chain.id === selectedChainId}
132
200
  itemsSize={itemsSize}
133
201
  size={item.size}
134
202
  start={item.start}
@@ -64,7 +64,7 @@ export const RouteCardEssentials: React.FC<RouteCardEssentialsProps> = ({
64
64
  lineHeight: 1,
65
65
  }}
66
66
  >
67
- {hasRelayerSupport
67
+ {hasRelayerSupport || !combinedFeesUSD
68
68
  ? t('main.fees.free')
69
69
  : t('format.currency', {
70
70
  value: combinedFeesUSD,
@@ -4,27 +4,22 @@ import type { BottomSheetBase } from '../BottomSheet/types.js'
4
4
  import { TokenDetailsSheetContent } from './TokenDetailsSheetContent.js'
5
5
  import type { TokenDetailsSheetBase } from './types.js'
6
6
 
7
- interface TokenDetailsSheetProps {
8
- chainId: number | undefined
9
- }
10
-
11
- export const TokenDetailsSheet = forwardRef<
12
- TokenDetailsSheetBase,
13
- TokenDetailsSheetProps
14
- >(({ chainId }, ref) => {
7
+ export const TokenDetailsSheet = forwardRef<TokenDetailsSheetBase>((_, ref) => {
15
8
  const bottomSheetRef = useRef<BottomSheetBase>(null)
16
9
  const [tokenAddress, setTokenAddress] = useState<string | undefined>(
17
10
  undefined
18
11
  )
12
+ const [chainId, setChainId] = useState<number | undefined>(undefined)
19
13
  const [withoutContractAddress, setWithoutContractAddress] = useState(false)
20
14
 
21
15
  useImperativeHandle(
22
16
  ref,
23
17
  () => ({
24
18
  isOpen: () => bottomSheetRef.current?.isOpen(),
25
- open: (address: string, noContractAddress: boolean) => {
19
+ open: (address: string, noContractAddress: boolean, chainId: number) => {
26
20
  setTokenAddress(address)
27
21
  setWithoutContractAddress(noContractAddress)
22
+ setChainId(chainId)
28
23
  bottomSheetRef.current?.open()
29
24
  },
30
25
  close: () => {
@@ -6,7 +6,7 @@ import { forwardRef, type PropsWithChildren, useMemo } from 'react'
6
6
  import { useTranslation } from 'react-i18next'
7
7
  import { useAvailableChains } from '../../hooks/useAvailableChains.js'
8
8
  import { useExplorer } from '../../hooks/useExplorer.js'
9
- import { useTokenSearch } from '../../hooks/useTokenSearch.js'
9
+ import { useToken } from '../../hooks/useToken.js'
10
10
  import { shortenAddress } from '../../utils/wallet.js'
11
11
  import { TokenAvatar } from '../Avatar/TokenAvatar.js'
12
12
  import { CardIconButton } from '../Card/CardIconButton.js'
@@ -34,11 +34,7 @@ export const TokenDetailsSheetContent = forwardRef<
34
34
  const { getAddressLink } = useExplorer()
35
35
  const { getChainById } = useAvailableChains()
36
36
 
37
- const { token, isLoading } = useTokenSearch(
38
- chainId,
39
- tokenAddress,
40
- !!tokenAddress
41
- )
37
+ const { token, isLoading } = useToken(chainId, tokenAddress, true)
42
38
  const chain = useMemo(() => getChainById(chainId), [chainId, getChainById])
43
39
 
44
40
  const copyContractAddress = async (e: React.MouseEvent) => {