@lifi/widget 4.7.1 → 4.9.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 (214) hide show
  1. package/dist/esm/components/AmountInput/AmountInput.style.d.ts.map +1 -1
  2. package/dist/esm/components/AmountInputCard/AmountInputCardPair.js +5 -2
  3. package/dist/esm/components/AmountInputCard/AmountInputCardPair.js.map +1 -1
  4. package/dist/esm/components/Card/Card.d.ts.map +1 -1
  5. package/dist/esm/components/Card/CardIconButton.d.ts.map +1 -1
  6. package/dist/esm/components/ContractComponent/ItemPrice.d.ts.map +1 -1
  7. package/dist/esm/components/ListItem/ListItemButton.d.ts.map +1 -1
  8. package/dist/esm/components/ListItemButton.d.ts.map +1 -1
  9. package/dist/esm/components/SelectChainAndToken.js +5 -2
  10. package/dist/esm/components/SelectChainAndToken.js.map +1 -1
  11. package/dist/esm/components/SendToWallet/SendToWalletButton.js +5 -4
  12. package/dist/esm/components/SendToWallet/SendToWalletButton.js.map +1 -1
  13. package/dist/esm/components/Token/Token.d.ts.map +1 -1
  14. package/dist/esm/components/TokenList/types.d.ts.map +1 -1
  15. package/dist/esm/components/TokenList/useTokenSelect.d.ts.map +1 -1
  16. package/dist/esm/components/TokenList/useTokenSelect.js +10 -0
  17. package/dist/esm/components/TokenList/useTokenSelect.js.map +1 -1
  18. package/dist/esm/config/constants.js.map +1 -1
  19. package/dist/esm/config/version.d.ts +1 -1
  20. package/dist/esm/config/version.js +1 -1
  21. package/dist/esm/config/version.js.map +1 -1
  22. package/dist/esm/hooks/useAccountsBalancesData.js +5 -2
  23. package/dist/esm/hooks/useAccountsBalancesData.js.map +1 -1
  24. package/dist/esm/hooks/useAddressValidation.d.ts +3 -2
  25. package/dist/esm/hooks/useAddressValidation.d.ts.map +1 -1
  26. package/dist/esm/hooks/useAddressValidation.js +48 -21
  27. package/dist/esm/hooks/useAddressValidation.js.map +1 -1
  28. package/dist/esm/hooks/useAvailableChains.d.ts.map +1 -1
  29. package/dist/esm/hooks/useAvailableChains.js +21 -38
  30. package/dist/esm/hooks/useAvailableChains.js.map +1 -1
  31. package/dist/esm/hooks/useFromTokenSufficiency.js +3 -3
  32. package/dist/esm/hooks/useFromTokenSufficiency.js.map +1 -1
  33. package/dist/esm/hooks/useGasRecommendation.js +3 -3
  34. package/dist/esm/hooks/useGasRecommendation.js.map +1 -1
  35. package/dist/esm/hooks/useGasRefuel.js +4 -1
  36. package/dist/esm/hooks/useGasRefuel.js.map +1 -1
  37. package/dist/esm/hooks/useGasSufficiency.js +3 -3
  38. package/dist/esm/hooks/useGasSufficiency.js.map +1 -1
  39. package/dist/esm/hooks/useIsBatchingSupported.js +2 -2
  40. package/dist/esm/hooks/useIsBatchingSupported.js.map +1 -1
  41. package/dist/esm/hooks/useRouteExecution.js +6 -6
  42. package/dist/esm/hooks/useRouteExecution.js.map +1 -1
  43. package/dist/esm/hooks/useRoutes.d.ts.map +1 -1
  44. package/dist/esm/hooks/useRoutes.js +18 -13
  45. package/dist/esm/hooks/useRoutes.js.map +1 -1
  46. package/dist/esm/hooks/useToAddressAutoPopulate.js +6 -8
  47. package/dist/esm/hooks/useToAddressAutoPopulate.js.map +1 -1
  48. package/dist/esm/hooks/useToAddressRequirements.d.ts +3 -2
  49. package/dist/esm/hooks/useToAddressRequirements.d.ts.map +1 -1
  50. package/dist/esm/hooks/useToAddressRequirements.js +8 -7
  51. package/dist/esm/hooks/useToAddressRequirements.js.map +1 -1
  52. package/dist/esm/hooks/useToAddressReset.js +6 -3
  53. package/dist/esm/hooks/useToAddressReset.js.map +1 -1
  54. package/dist/esm/hooks/useTokenBalance.js +6 -5
  55. package/dist/esm/hooks/useTokenBalance.js.map +1 -1
  56. package/dist/esm/hooks/useTokenBalancesQueries.js +3 -3
  57. package/dist/esm/hooks/useTokenBalancesQueries.js.map +1 -1
  58. package/dist/esm/hooks/useTokenSearch.js +3 -3
  59. package/dist/esm/hooks/useTokenSearch.js.map +1 -1
  60. package/dist/esm/hooks/useTokens.js +15 -15
  61. package/dist/esm/hooks/useTokens.js.map +1 -1
  62. package/dist/esm/hooks/useTools.d.ts.map +1 -1
  63. package/dist/esm/hooks/useTools.js +26 -27
  64. package/dist/esm/hooks/useTools.js.map +1 -1
  65. package/dist/esm/hooks/useTransactionDetails.js +2 -2
  66. package/dist/esm/hooks/useTransactionDetails.js.map +1 -1
  67. package/dist/esm/hooks/useTransactionHistory.js +3 -3
  68. package/dist/esm/hooks/useTransactionHistory.js.map +1 -1
  69. package/dist/esm/i18n/bn.json +2 -1
  70. package/dist/esm/i18n/de.json +2 -1
  71. package/dist/esm/i18n/en.json +2 -1
  72. package/dist/esm/i18n/es.json +2 -1
  73. package/dist/esm/i18n/fr.json +2 -1
  74. package/dist/esm/i18n/hi.json +2 -1
  75. package/dist/esm/i18n/id.json +2 -1
  76. package/dist/esm/i18n/it.json +2 -1
  77. package/dist/esm/i18n/ja.json +2 -1
  78. package/dist/esm/i18n/ko.json +2 -1
  79. package/dist/esm/i18n/pl.json +2 -1
  80. package/dist/esm/i18n/pt.json +2 -1
  81. package/dist/esm/i18n/th.json +2 -1
  82. package/dist/esm/i18n/tr.json +2 -1
  83. package/dist/esm/i18n/uk.json +2 -1
  84. package/dist/esm/i18n/vi.json +2 -1
  85. package/dist/esm/i18n/zh.json +2 -1
  86. package/dist/esm/index.d.ts +5 -1
  87. package/dist/esm/index.js +3 -1
  88. package/dist/esm/pages/RoutesPage/RoutesPage.style.d.ts.map +1 -1
  89. package/dist/esm/pages/SendToWallet/BookmarkAddressSheet.js +7 -4
  90. package/dist/esm/pages/SendToWallet/BookmarkAddressSheet.js.map +1 -1
  91. package/dist/esm/pages/SendToWallet/BookmarksPage.js +35 -31
  92. package/dist/esm/pages/SendToWallet/BookmarksPage.js.map +1 -1
  93. package/dist/esm/pages/SendToWallet/ConnectedWalletsPage.js +4 -3
  94. package/dist/esm/pages/SendToWallet/ConnectedWalletsPage.js.map +1 -1
  95. package/dist/esm/pages/SendToWallet/RecentWalletsPage.js +43 -39
  96. package/dist/esm/pages/SendToWallet/RecentWalletsPage.js.map +1 -1
  97. package/dist/esm/pages/SendToWallet/SendToConfiguredWalletPage.js +32 -26
  98. package/dist/esm/pages/SendToWallet/SendToConfiguredWalletPage.js.map +1 -1
  99. package/dist/esm/pages/SendToWallet/SendToWalletPage.js +5 -10
  100. package/dist/esm/pages/SendToWallet/SendToWalletPage.js.map +1 -1
  101. package/dist/esm/providers/I18nProvider/types.d.ts.map +1 -1
  102. package/dist/esm/providers/WalletProvider/WalletProvider.d.ts.map +1 -1
  103. package/dist/esm/providers/WalletProvider/WalletProvider.js +14 -2
  104. package/dist/esm/providers/WalletProvider/WalletProvider.js.map +1 -1
  105. package/dist/esm/providers/WidgetProvider/WidgetProvider.d.ts.map +1 -1
  106. package/dist/esm/providers/WidgetProvider/WidgetProvider.js +15 -11
  107. package/dist/esm/providers/WidgetProvider/WidgetProvider.js.map +1 -1
  108. package/dist/esm/providers/WidgetProvider/types.d.ts +2 -0
  109. package/dist/esm/providers/WidgetProvider/types.d.ts.map +1 -1
  110. package/dist/esm/queries/getChains.d.ts +38 -0
  111. package/dist/esm/queries/getChains.d.ts.map +1 -0
  112. package/dist/esm/queries/getChains.js +51 -0
  113. package/dist/esm/queries/getChains.js.map +1 -0
  114. package/dist/esm/queries/getTools.d.ts +26 -0
  115. package/dist/esm/queries/getTools.d.ts.map +1 -0
  116. package/dist/esm/queries/getTools.js +30 -0
  117. package/dist/esm/queries/getTools.js.map +1 -0
  118. package/dist/esm/queries/index.d.ts +4 -0
  119. package/dist/esm/queries/index.js +3 -0
  120. package/dist/esm/queries/types.d.ts +25 -0
  121. package/dist/esm/queries/types.d.ts.map +1 -0
  122. package/dist/esm/shared.d.ts +2 -1
  123. package/dist/esm/shared.js +2 -1
  124. package/dist/esm/stores/StoreProvider.d.ts.map +1 -1
  125. package/dist/esm/stores/StoreProvider.js +16 -17
  126. package/dist/esm/stores/StoreProvider.js.map +1 -1
  127. package/dist/esm/stores/bookmarks/types.d.ts +3 -0
  128. package/dist/esm/stores/bookmarks/types.d.ts.map +1 -1
  129. package/dist/esm/stores/chains/ChainOrderStore.d.ts.map +1 -1
  130. package/dist/esm/stores/chains/ChainOrderStore.js +4 -1
  131. package/dist/esm/stores/chains/ChainOrderStore.js.map +1 -1
  132. package/dist/esm/stores/form/FormStore.js +3 -2
  133. package/dist/esm/stores/form/FormStore.js.map +1 -1
  134. package/dist/esm/stores/form/URLSearchParamsBuilder.js +7 -3
  135. package/dist/esm/stores/form/URLSearchParamsBuilder.js.map +1 -1
  136. package/dist/esm/stores/form/getDefaultValuesFromQueryString.js +4 -1
  137. package/dist/esm/stores/form/getDefaultValuesFromQueryString.js.map +1 -1
  138. package/dist/esm/stores/header/useHeaderStore.d.ts +1 -1
  139. package/dist/esm/stores/header/useHeaderStore.d.ts.map +1 -1
  140. package/dist/esm/stores/header/useHeaderStore.js.map +1 -1
  141. package/dist/esm/themes/createTheme.d.ts.map +1 -1
  142. package/dist/esm/types/events.d.ts.map +1 -1
  143. package/dist/esm/types/token.d.ts.map +1 -1
  144. package/dist/esm/types/widget.d.ts +30 -0
  145. package/dist/esm/types/widget.d.ts.map +1 -1
  146. package/dist/esm/types/widget.js.map +1 -1
  147. package/dist/esm/utils/chainType.js +32 -1
  148. package/dist/esm/utils/chainType.js.map +1 -1
  149. package/dist/esm/utils/customReceiver.js +6 -1
  150. package/dist/esm/utils/customReceiver.js.map +1 -1
  151. package/dist/esm/utils/queries.js.map +1 -1
  152. package/dist/esm/utils/scopeKeys.d.ts +6 -0
  153. package/dist/esm/utils/scopeKeys.d.ts.map +1 -0
  154. package/dist/esm/utils/scopeKeys.js +8 -0
  155. package/dist/esm/utils/scopeKeys.js.map +1 -0
  156. package/package.json +15 -7
  157. package/src/components/AmountInputCard/AmountInputCardPair.tsx +6 -2
  158. package/src/components/SelectChainAndToken.tsx +5 -1
  159. package/src/components/SendToWallet/SendToWalletButton.tsx +13 -8
  160. package/src/components/TokenList/useTokenSelect.ts +15 -0
  161. package/src/config/constants.ts +3 -0
  162. package/src/config/version.ts +1 -1
  163. package/src/hooks/useAccountsBalancesData.ts +6 -2
  164. package/src/hooks/useAddressValidation.ts +87 -32
  165. package/src/hooks/useAvailableChains.test.tsx +170 -0
  166. package/src/hooks/useAvailableChains.ts +29 -47
  167. package/src/hooks/useFromTokenSufficiency.ts +2 -2
  168. package/src/hooks/useGasRecommendation.ts +2 -2
  169. package/src/hooks/useGasRefuel.ts +11 -1
  170. package/src/hooks/useGasSufficiency.ts +2 -2
  171. package/src/hooks/useIsBatchingSupported.ts +2 -2
  172. package/src/hooks/useRouteExecution.ts +5 -5
  173. package/src/hooks/useRoutes.ts +28 -23
  174. package/src/hooks/useToAddressAutoPopulate.ts +13 -17
  175. package/src/hooks/useToAddressRequirements.ts +19 -4
  176. package/src/hooks/useToAddressReset.ts +13 -4
  177. package/src/hooks/useTokenBalance.ts +10 -5
  178. package/src/hooks/useTokenBalancesQueries.ts +3 -3
  179. package/src/hooks/useTokenSearch.ts +7 -3
  180. package/src/hooks/useTokens.ts +13 -14
  181. package/src/hooks/useTools.sharedEntry.test.tsx +155 -0
  182. package/src/hooks/useTools.ts +42 -37
  183. package/src/hooks/useTransactionDetails.ts +3 -3
  184. package/src/hooks/useTransactionHistory.ts +2 -2
  185. package/src/index.ts +2 -1
  186. package/src/pages/SendToWallet/BookmarkAddressSheet.tsx +5 -1
  187. package/src/pages/SendToWallet/BookmarksPage.tsx +42 -52
  188. package/src/pages/SendToWallet/ConnectedWalletsPage.tsx +4 -14
  189. package/src/pages/SendToWallet/RecentWalletsPage.tsx +59 -66
  190. package/src/pages/SendToWallet/SendToConfiguredWalletPage.tsx +45 -50
  191. package/src/pages/SendToWallet/SendToWalletPage.tsx +4 -16
  192. package/src/providers/WalletProvider/WalletProvider.tsx +28 -3
  193. package/src/providers/WidgetProvider/WidgetProvider.test.tsx +73 -0
  194. package/src/providers/WidgetProvider/WidgetProvider.tsx +28 -14
  195. package/src/providers/WidgetProvider/types.ts +2 -0
  196. package/src/queries/getChains.ts +93 -0
  197. package/src/queries/getTools.ts +62 -0
  198. package/src/queries/index.ts +30 -0
  199. package/src/queries/sharedCache.test.tsx +223 -0
  200. package/src/queries/types.ts +38 -0
  201. package/src/shared.ts +1 -0
  202. package/src/stores/StoreProvider.test.tsx +107 -0
  203. package/src/stores/StoreProvider.tsx +8 -6
  204. package/src/stores/bookmarks/types.ts +3 -0
  205. package/src/stores/chains/ChainOrderStore.tsx +4 -1
  206. package/src/stores/form/FormStore.tsx +2 -0
  207. package/src/stores/form/URLSearchParamsBuilder.tsx +4 -2
  208. package/src/stores/form/getDefaultValuesFromQueryString.ts +19 -0
  209. package/src/stores/header/useHeaderStore.tsx +3 -4
  210. package/src/types/widget.ts +30 -0
  211. package/src/utils/chainType.ts +73 -1
  212. package/src/utils/customReceiver.ts +36 -0
  213. package/src/utils/queries.ts +1 -0
  214. package/src/utils/scopeKeys.ts +18 -0
@@ -43,7 +43,7 @@ export const useRouteExecution = ({
43
43
  const queryClient = useQueryClient()
44
44
  const { account } = useAccount()
45
45
  const resumedAfterMount = useRef(false)
46
- const { keyPrefix } = useWidgetConfig()
46
+ const { storageScopeKey } = useWidgetConfig()
47
47
  const sdkClient = useSDKClient()
48
48
  const emitter = useWidgetEvents()
49
49
  const routeExecutionStoreContext = useRouteExecutionStoreContext()
@@ -84,16 +84,16 @@ export const useRouteExecution = ({
84
84
  if (executionCompleted || executionFailed) {
85
85
  const invalidateKeys = [
86
86
  [
87
- getQueryKey('token-balances', keyPrefix),
87
+ getQueryKey('token-balances', storageScopeKey),
88
88
  clonedUpdatedRoute.fromAddress,
89
89
  clonedUpdatedRoute.fromChainId,
90
90
  ],
91
91
  [
92
- getQueryKey('token-balances', keyPrefix),
92
+ getQueryKey('token-balances', storageScopeKey),
93
93
  clonedUpdatedRoute.toAddress,
94
94
  clonedUpdatedRoute.toChainId,
95
95
  ],
96
- [getQueryKey('transaction-history', keyPrefix)],
96
+ [getQueryKey('transaction-history', storageScopeKey)],
97
97
  ]
98
98
  for (const key of invalidateKeys) {
99
99
  queryClient.invalidateQueries(
@@ -133,7 +133,7 @@ export const useRouteExecution = ({
133
133
  throw new Error('Execution route not found.')
134
134
  }
135
135
  queryClient.removeQueries({
136
- queryKey: [getQueryKey('routes', keyPrefix)],
136
+ queryKey: [getQueryKey('routes', storageScopeKey)],
137
137
  exact: false,
138
138
  })
139
139
  return executeRoute(sdkClient, routeExecution.route, {
@@ -9,10 +9,7 @@ import {
9
9
  parseUnits,
10
10
  } from '@lifi/sdk'
11
11
  import { useAccount } from '@lifi/wallet-management'
12
- import {
13
- useChainTypeFromAddress,
14
- useEthereumContext,
15
- } from '@lifi/widget-provider'
12
+ import { useAddressForChain, useEthereumContext } from '@lifi/widget-provider'
16
13
  import {
17
14
  keepPreviousData,
18
15
  useQuery,
@@ -29,7 +26,11 @@ import { defaultSlippage } from '../stores/settings/createSettingsStore.js'
29
26
  import { useSettings } from '../stores/settings/useSettings.js'
30
27
  import { WidgetEvent } from '../types/events.js'
31
28
  import type { TokensByChain } from '../types/token.js'
32
- import { isCustomReceiverBlocked } from '../utils/customReceiver.js'
29
+ import {
30
+ canQuoteWithToAddress,
31
+ isCustomReceiverBlocked,
32
+ isSignerInvalidReceiver,
33
+ } from '../utils/customReceiver.js'
33
34
  import { getQueryKey } from '../utils/queries.js'
34
35
  import { updateTokenInCache } from '../utils/token.js'
35
36
  import { useChain } from './useChain.js'
@@ -82,7 +83,7 @@ export const useRoutes = ({
82
83
  exchanges,
83
84
  feeConfig,
84
85
  useRelayerRoutes,
85
- keyPrefix,
86
+ storageScopeKey,
86
87
  } = useWidgetConfig()
87
88
  const sdkClient = useSDKClient()
88
89
  const setExecutableRoute = useSetExecutableRoute()
@@ -138,7 +139,7 @@ export const useRoutes = ({
138
139
  const { chain: toChain } = useChain(toChainId)
139
140
  const { enabled: enabledRefuel, fromAmount: gasRecommendationFromAmount } =
140
141
  useGasRefuel()
141
- const { getChainTypeFromAddress } = useChainTypeFromAddress()
142
+ const { isAddressForChain } = useAddressForChain()
142
143
  const { isGaslessStep, disableMessageSigning } = useEthereumContext()
143
144
  const { account } = useAccount({ chainType: fromChain?.chainType })
144
145
  const { isBatchingSupported, isBatchingSupportedLoading } =
@@ -165,18 +166,22 @@ export const useRoutes = ({
165
166
  ? Boolean(account.address)
166
167
  : mode !== 'custom' || customType === 'deposit'
167
168
 
168
- const effectiveFromAddress = account.address ?? quoteFromAddress
169
+ // Quote as if disconnected while the signer would be taken as the receiver.
170
+ const effectiveFromAddress = isSignerInvalidReceiver({
171
+ signerAddress: account.address,
172
+ toAddress,
173
+ fromChain,
174
+ toChain,
175
+ isAddressForChain,
176
+ })
177
+ ? quoteFromAddress
178
+ : (account.address ?? quoteFromAddress)
169
179
 
170
- // When we bridge between ecosystems we need to be sure toAddress is set and has the same chainType as toChain
171
- // If toAddress is set, it must have the same chainType as toChain
172
- const hasToAddressAndChainTypeSatisfied: boolean =
173
- !!toChain &&
174
- !!toAddress &&
175
- getChainTypeFromAddress(toAddress) === toChain.chainType
176
- // We need to check for toAddress only if it is set
177
- const isToAddressSatisfied = toAddress
178
- ? hasToAddressAndChainTypeSatisfied
179
- : true
180
+ const isToAddressSatisfied = canQuoteWithToAddress(
181
+ toAddress,
182
+ toChain,
183
+ isAddressForChain
184
+ )
180
185
 
181
186
  // Not `effectiveFromAddress`: a quote placeholder must never match a receiver.
182
187
  const customReceiverBlocked = isCustomReceiverBlocked({
@@ -214,7 +219,7 @@ export const useRoutes = ({
214
219
  const queryKey = useMemo(
215
220
  () =>
216
221
  [
217
- getQueryKey('routes', keyPrefix),
222
+ getQueryKey('routes', storageScopeKey),
218
223
  effectiveFromAddress,
219
224
  fromChain?.id as number,
220
225
  fromToken?.address as string,
@@ -244,7 +249,7 @@ export const useRoutes = ({
244
249
  observableRoute?.id,
245
250
  ] as const,
246
251
  [
247
- keyPrefix,
252
+ storageScopeKey,
248
253
  effectiveFromAddress,
249
254
  fromChain?.id,
250
255
  fromToken?.address,
@@ -550,14 +555,14 @@ export const useRoutes = ({
550
555
  ;[fromToken, toToken].forEach((token) => {
551
556
  // Update main tokens cache (verified)
552
557
  queryClient.setQueriesData<TokensByChain>(
553
- { queryKey: [getQueryKey('tokens', keyPrefix)] },
558
+ { queryKey: [getQueryKey('tokens', storageScopeKey)] },
554
559
  (data) => updateTokenInCache(data, token)
555
560
  )
556
561
 
557
562
  // Update search tokens cache (unverified) - matches any search query
558
563
  queryClient.setQueriesData<TokensByChain>(
559
564
  {
560
- queryKey: [getQueryKey('tokens-search', keyPrefix)],
565
+ queryKey: [getQueryKey('tokens-search', storageScopeKey)],
561
566
  exact: false,
562
567
  },
563
568
  (data) => updateTokenInCache(data, token)
@@ -566,7 +571,7 @@ export const useRoutes = ({
566
571
  queryClient.setQueriesData<Token[]>(
567
572
  {
568
573
  queryKey: [
569
- getQueryKey('token-balances', keyPrefix),
574
+ getQueryKey('token-balances', storageScopeKey),
570
575
  fromAddress,
571
576
  token.chainId,
572
577
  ],
@@ -1,5 +1,5 @@
1
1
  import { useAccount } from '@lifi/wallet-management'
2
- import { useChainTypeFromAddress } from '@lifi/widget-provider'
2
+ import { useAddressForChain } from '@lifi/widget-provider'
3
3
  import { useCallback } from 'react'
4
4
  import { useBookmarkActions } from '../stores/bookmarks/useBookmarkActions.js'
5
5
  import type { FormType } from '../stores/form/types.js'
@@ -24,7 +24,7 @@ export const useToAddressAutoPopulate = (): ((
24
24
  const { setSelectedBookmark } = useBookmarkActions()
25
25
  const { getChainById } = useAvailableChains()
26
26
  const { accounts } = useAccount()
27
- const { getChainTypeFromAddress } = useChainTypeFromAddress()
27
+ const { isAddressForChain } = useAddressForChain()
28
28
 
29
29
  return useCallback(
30
30
  ({
@@ -52,22 +52,18 @@ export const useToAddressAutoPopulate = (): ((
52
52
  ) {
53
53
  return
54
54
  }
55
- // Identify the destination chain type based on the bridge direction ('from' or 'to')
56
- const destinationChainType =
57
- formType === 'from'
58
- ? selectedOppositeChain.chainType
59
- : selectedChain.chainType
60
- // If toAddress is already selected, verify that it matches the destination chain type
61
- if (selectedToAddress) {
62
- const selectedToAddressChainType =
63
- getChainTypeFromAddress(selectedToAddress)
64
- if (destinationChainType === selectedToAddressChainType) {
65
- return
66
- }
55
+ const destinationChain =
56
+ formType === 'from' ? selectedOppositeChain : selectedChain
57
+ if (
58
+ selectedToAddress &&
59
+ isAddressForChain(selectedToAddress, destinationChain)
60
+ ) {
61
+ return
67
62
  }
68
- // Find connected account compatible with the destination chain type
69
63
  const destinationAccount = accounts?.find(
70
- (account) => account.chainType === destinationChainType
64
+ (account) =>
65
+ account.address &&
66
+ isAddressForChain(account.address, destinationChain)
71
67
  )
72
68
  // If a compatible destination account is found, set toAddress as if selecting it from the "Send to Wallet" connected wallets page
73
69
  if (destinationAccount?.address) {
@@ -89,7 +85,7 @@ export const useToAddressAutoPopulate = (): ((
89
85
  getChainById,
90
86
  setFieldValue,
91
87
  setSelectedBookmark,
92
- getChainTypeFromAddress,
88
+ isAddressForChain,
93
89
  ]
94
90
  )
95
91
  }
@@ -1,6 +1,6 @@
1
- import type { ChainType, RouteExtended } from '@lifi/sdk'
1
+ import type { RouteExtended } from '@lifi/sdk'
2
2
  import { useAccount } from '@lifi/wallet-management'
3
- import { useEthereumContext } from '@lifi/widget-provider'
3
+ import { useAddressForChain, useEthereumContext } from '@lifi/widget-provider'
4
4
  import { useChain } from '../hooks/useChain.js'
5
5
  import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'
6
6
  import { useFieldValues } from '../stores/form/useFieldValues.js'
@@ -16,7 +16,8 @@ export const useToAddressRequirements = (
16
16
  requiredToAddress: boolean
17
17
  unsupportedToAddress: boolean
18
18
  unsupportedReceiverBlocking: boolean
19
- requiredToChainType: ChainType | undefined
19
+ /** Whether a saved or connected address can receive on the destination chain. */
20
+ isValidReceiver: (address?: string) => boolean
20
21
  accountNotDeployedAtDestination: boolean
21
22
  accountDeployedAtDestination: boolean
22
23
  toAddress: string | undefined
@@ -32,6 +33,7 @@ export const useToAddressRequirements = (
32
33
  'toAddress'
33
34
  )
34
35
  const { isDelegationDesignatorCode } = useEthereumContext()
36
+ const { isAddressForChain } = useAddressForChain()
35
37
 
36
38
  const fromChainId = route?.fromChainId ?? formFromChainId
37
39
  const toChainId = route?.toChainId ?? formToChainId
@@ -87,14 +89,27 @@ export const useToAddressRequirements = (
87
89
  receiverRequired: requiredUI?.toAddress,
88
90
  })
89
91
 
92
+ // A shared chain type is not a shared format: a Bitcoin signer cannot receive on ZEC.
93
+ const isSignerAddressInvalidAtDestination = Boolean(
94
+ fromAddress &&
95
+ fromChain &&
96
+ toChain &&
97
+ isAddressForChain(fromAddress, fromChain) &&
98
+ !isAddressForChain(fromAddress, toChain)
99
+ )
100
+
90
101
  const requiredToAddress = Boolean(
91
102
  (isDifferentChainType ||
92
103
  isCrossChainContractAddress ||
104
+ isSignerAddressInvalidAtDestination ||
93
105
  requiredUI?.toAddress) &&
94
106
  !hiddenUI?.toAddress &&
95
107
  !unsupportedToAddress
96
108
  )
97
109
 
110
+ const isValidReceiver = (address?: string): boolean =>
111
+ !toChain || (!!address && isAddressForChain(address, toChain))
112
+
98
113
  const accountNotDeployedAtDestination = Boolean(
99
114
  isFromContractAddress &&
100
115
  !fromContractCodeHasDelegationIndicator &&
@@ -113,7 +128,7 @@ export const useToAddressRequirements = (
113
128
  requiredToAddress,
114
129
  unsupportedToAddress,
115
130
  unsupportedReceiverBlocking,
116
- requiredToChainType: toChain?.chainType,
131
+ isValidReceiver,
117
132
  accountNotDeployedAtDestination,
118
133
  accountDeployedAtDestination,
119
134
  toAddress,
@@ -1,4 +1,5 @@
1
1
  import type { ExtendedChain } from '@lifi/sdk'
2
+ import { useAddressForChain } from '@lifi/widget-provider'
2
3
  import { useCallback } from 'react'
3
4
  import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'
4
5
  import { useBookmarkActions } from '../stores/bookmarks/useBookmarkActions.js'
@@ -12,16 +13,18 @@ export const useToAddressReset = (): {
12
13
  const { setFieldValue } = useFieldActions()
13
14
  const { selectedBookmark } = useBookmarks()
14
15
  const { setSelectedBookmark } = useBookmarkActions()
16
+ const { isAddressForChain } = useAddressForChain()
15
17
 
16
18
  const tryResetToAddress = useCallback(
17
19
  (toChain: ExtendedChain) => {
18
20
  const requiredToAddress = requiredUI?.toAddress
19
21
 
20
- const bookmarkSatisfiesToChainType =
21
- selectedBookmark?.chainType === toChain?.chainType
22
+ const bookmarkSatisfiesToChain = Boolean(
23
+ selectedBookmark && isAddressForChain(selectedBookmark.address, toChain)
24
+ )
22
25
 
23
26
  const shouldResetToAddress =
24
- !requiredToAddress && !bookmarkSatisfiesToChainType
27
+ !requiredToAddress && !bookmarkSatisfiesToChain
25
28
 
26
29
  // We reset toAddress on each chain change if it's no longer required, ensuring that
27
30
  // switching chain types doesn't leave a stale toAddress from a different ecosystem.
@@ -30,7 +33,13 @@ export const useToAddressReset = (): {
30
33
  setSelectedBookmark()
31
34
  }
32
35
  },
33
- [setFieldValue, setSelectedBookmark, requiredUI, selectedBookmark]
36
+ [
37
+ setFieldValue,
38
+ setSelectedBookmark,
39
+ requiredUI,
40
+ selectedBookmark,
41
+ isAddressForChain,
42
+ ]
34
43
  )
35
44
 
36
45
  return {
@@ -8,6 +8,7 @@ import { useQuery, useQueryClient } from '@tanstack/react-query'
8
8
  import { useCallback, useMemo } from 'react'
9
9
  import { useSDKClient } from '../providers/SDKClientProvider.js'
10
10
  import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'
11
+ import { isDestinationOnlyChain } from '../utils/chainType.js'
11
12
  import { getQueryKey } from '../utils/queries.js'
12
13
 
13
14
  const defaultRefetchInterval = 30_000
@@ -23,18 +24,18 @@ export const useTokenBalance = (
23
24
  getTokenBalancesWithRetry: typeof getTokenBalancesWithRetry
24
25
  } => {
25
26
  const queryClient = useQueryClient()
26
- const { keyPrefix } = useWidgetConfig()
27
+ const { storageScopeKey } = useWidgetConfig()
27
28
  const sdkClient = useSDKClient()
28
29
 
29
30
  const tokenBalanceQueryKey = useMemo(
30
31
  () =>
31
32
  [
32
- getQueryKey('token-balance', keyPrefix),
33
+ getQueryKey('token-balance', storageScopeKey),
33
34
  accountAddress,
34
35
  token?.chainId,
35
36
  token?.address,
36
37
  ] as const,
37
- [token?.address, token?.chainId, accountAddress, keyPrefix]
38
+ [token?.address, token?.chainId, accountAddress, storageScopeKey]
38
39
  )
39
40
 
40
41
  const { data, isLoading, refetch } = useQuery({
@@ -67,7 +68,7 @@ export const useTokenBalance = (
67
68
  queryClient.setQueriesData<TokenAmount[]>(
68
69
  {
69
70
  queryKey: [
70
- getQueryKey('token-balances', keyPrefix),
71
+ getQueryKey('token-balances', storageScopeKey),
71
72
  accountAddress,
72
73
  tokenChainId,
73
74
  ],
@@ -93,7 +94,11 @@ export const useTokenBalance = (
93
94
  } as TokenAmount
94
95
  },
95
96
 
96
- enabled: Boolean(accountAddress && token),
97
+ // No provider reads a balance on a destination-only chain, and a balance
98
+ // without a block number is retried until the backoff gives up.
99
+ enabled:
100
+ Boolean(accountAddress && token) &&
101
+ !isDestinationOnlyChain(token?.chainId),
97
102
  refetchInterval: defaultRefetchInterval,
98
103
  staleTime: defaultRefetchInterval,
99
104
  })
@@ -20,7 +20,7 @@ export const useTokenBalancesQueries = (
20
20
  isLoading: boolean
21
21
  isError: boolean
22
22
  } => {
23
- const { keyPrefix } = useWidgetConfig()
23
+ const { storageScopeKey } = useWidgetConfig()
24
24
  const sdkClient = useSDKClient()
25
25
  const firstLoadStartRef = useRef<number | null>(null)
26
26
 
@@ -34,7 +34,7 @@ export const useTokenBalancesQueries = (
34
34
  const chainId = Number(chainIdStr)
35
35
  return {
36
36
  queryKey: [
37
- getQueryKey('token-balances', keyPrefix),
37
+ getQueryKey('token-balances', storageScopeKey),
38
38
  accountAddress,
39
39
  chainId,
40
40
  tokens.length,
@@ -55,7 +55,7 @@ export const useTokenBalancesQueries = (
55
55
  }
56
56
  })
57
57
  )
58
- }, [accountsWithTokens, isBalanceLoadingEnabled, keyPrefix, sdkClient])
58
+ }, [accountsWithTokens, isBalanceLoadingEnabled, storageScopeKey, sdkClient])
59
59
 
60
60
  const result = useQueries({
61
61
  queries: queryConfig,
@@ -18,11 +18,15 @@ export const useTokenSearch = (
18
18
  isLoading: boolean
19
19
  } => {
20
20
  const queryClient = useQueryClient()
21
- const { tokens: configTokens, keyPrefix } = useWidgetConfig()
21
+ const { tokens: configTokens, storageScopeKey } = useWidgetConfig()
22
22
  const sdkClient = useSDKClient()
23
23
 
24
24
  const { data, isLoading } = useQuery({
25
- queryKey: [getQueryKey('token-search', keyPrefix), chainId, tokenQuery],
25
+ queryKey: [
26
+ getQueryKey('token-search', storageScopeKey),
27
+ chainId,
28
+ tokenQuery,
29
+ ],
26
30
  queryFn: async ({ queryKey: [, chainId, tokenQuery], signal }) => {
27
31
  const token = await getToken(
28
32
  sdkClient,
@@ -52,7 +56,7 @@ export const useTokenSearch = (
52
56
 
53
57
  // Add token to main tokens cache
54
58
  queryClient.setQueriesData<TokensByChain>(
55
- { queryKey: [getQueryKey('tokens', keyPrefix)] },
59
+ { queryKey: [getQueryKey('tokens', storageScopeKey)] },
56
60
  (data) => {
57
61
  if (!data) {
58
62
  return data
@@ -25,6 +25,14 @@ import { useAvailableChains } from './useAvailableChains.js'
25
25
 
26
26
  const refetchInterval = 300_000
27
27
 
28
+ // The main list and the search send the same list parameters.
29
+ const tokenListParams = {
30
+ orderBy: 'volumeUSD24H',
31
+ extended: true,
32
+ limit: 1100,
33
+ minPriceUSD: 0.000001,
34
+ } as const
35
+
28
36
  export const useTokens = (
29
37
  formType?: FormType,
30
38
  search?: string,
@@ -39,7 +47,7 @@ export const useTokens = (
39
47
  const {
40
48
  tokens: configTokens,
41
49
  chains: chainsConfig,
42
- keyPrefix,
50
+ storageScopeKey,
43
51
  } = useWidgetConfig()
44
52
  const sdkClient = useSDKClient()
45
53
  const { getChainTypeFromTokenAddress } = useChainTypeFromAddress()
@@ -55,7 +63,7 @@ export const useTokens = (
55
63
 
56
64
  // Main tokens cache - verified tokens from API
57
65
  const { data: verifiedTokens, isLoading } = useQuery({
58
- queryKey: [getQueryKey('tokens', keyPrefix)],
66
+ queryKey: [getQueryKey('tokens', storageScopeKey)],
59
67
  queryFn: async ({ signal }) => {
60
68
  const chainTypes = [
61
69
  ChainType.EVM,
@@ -68,13 +76,7 @@ export const useTokens = (
68
76
 
69
77
  const tokensResponse: TokensExtendedResponse = await getTokens(
70
78
  sdkClient,
71
- {
72
- chainTypes,
73
- orderBy: 'volumeUSD24H',
74
- extended: true,
75
- limit: 1000,
76
- minPriceUSD: 0.000001,
77
- },
79
+ { ...tokenListParams, chainTypes },
78
80
  { signal }
79
81
  )
80
82
 
@@ -95,7 +97,7 @@ export const useTokens = (
95
97
  // Search tokens cache - unverified tokens from search
96
98
  const { data: searchTokens, isLoading: isSearchLoading } = useQuery({
97
99
  queryKey: [
98
- getQueryKey('tokens-search', keyPrefix),
100
+ getQueryKey('tokens-search', storageScopeKey),
99
101
  trimmedSearch,
100
102
  chainId,
101
103
  isAddressSearch,
@@ -114,12 +116,9 @@ export const useTokens = (
114
116
  const tokensResponse: TokensExtendedResponse = await getTokens(
115
117
  sdkClient,
116
118
  {
119
+ ...tokenListParams,
117
120
  chainTypes,
118
- orderBy: 'volumeUSD24H',
119
- extended: true,
120
121
  search: searchQuery,
121
- limit: 1000,
122
- minPriceUSD: 0.000001,
123
122
  },
124
123
  { signal }
125
124
  )
@@ -0,0 +1,155 @@
1
+ /** @vitest-environment happy-dom */
2
+
3
+ import type * as LifiSdk from '@lifi/sdk'
4
+ import type { SDKClient, ToolsResponse } from '@lifi/sdk'
5
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
6
+ import { act } from 'react'
7
+ import { createRoot, type Root } from 'react-dom/client'
8
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
9
+ import type { WidgetConfig } from '../types/widget.js'
10
+
11
+ /**
12
+ * The tools entry is shared with the host app through
13
+ * `getToolsQueryOptions`, keyed by the client's API base and the widget's
14
+ * `queryScopeKey`. When the host fetched first, the widget must read that entry
15
+ * — no request of its own — and still seed its settings store from the
16
+ * filtered view.
17
+ */
18
+
19
+ const API_URL = 'https://api.example/v1'
20
+
21
+ const mocks = vi.hoisted(() => ({
22
+ getTools: vi.fn(),
23
+ initializeTools: vi.fn(),
24
+ }))
25
+
26
+ vi.mock('@lifi/sdk', async (importOriginal) => ({
27
+ ...(await importOriginal<typeof LifiSdk>()),
28
+ getTools: mocks.getTools,
29
+ }))
30
+
31
+ let widgetConfig: Partial<WidgetConfig> = {}
32
+
33
+ vi.mock('../providers/WidgetProvider/WidgetProvider.js', () => ({
34
+ useWidgetConfig: () => widgetConfig,
35
+ }))
36
+
37
+ const client = { config: { apiUrl: API_URL } } as unknown as SDKClient
38
+ vi.mock('../providers/SDKClientProvider.js', () => ({
39
+ useSDKClient: () => client,
40
+ }))
41
+
42
+ const settingsStore = {
43
+ getState: () => ({ initializeTools: mocks.initializeTools }),
44
+ }
45
+ vi.mock('../stores/settings/SettingsStore.js', () => ({
46
+ useSettingsStoreContext: () => settingsStore,
47
+ }))
48
+
49
+ import { getToolsQueryKey } from '../queries/getTools.js'
50
+ import { useTools } from './useTools.js'
51
+
52
+ const TOOLS = {
53
+ bridges: [{ key: 'across' }, { key: 'stargate' }, { key: 'hop' }],
54
+ exchanges: [{ key: '1inch' }, { key: 'paraswap' }],
55
+ } as unknown as ToolsResponse
56
+
57
+ const Probe = () => {
58
+ useTools()
59
+ return null
60
+ }
61
+
62
+ let root: Root
63
+ let queryClient: QueryClient
64
+
65
+ const render = async () => {
66
+ await act(async () => {
67
+ root.render(
68
+ <QueryClientProvider client={queryClient}>
69
+ <Probe />
70
+ </QueryClientProvider>
71
+ )
72
+ })
73
+ }
74
+
75
+ describe('useTools with a shared tools entry', () => {
76
+ beforeEach(() => {
77
+ vi.clearAllMocks()
78
+ mocks.getTools.mockResolvedValue(TOOLS)
79
+ widgetConfig = {
80
+ integrator: 'test',
81
+ queryScopeKey: 'host',
82
+ bridges: { allow: ['across', 'hop'] },
83
+ exchanges: { deny: ['paraswap'] },
84
+ }
85
+ queryClient = new QueryClient({
86
+ defaultOptions: { queries: { retry: false } },
87
+ })
88
+ const container = document.createElement('div')
89
+ document.body.appendChild(container)
90
+ root = createRoot(container)
91
+ })
92
+
93
+ afterEach(async () => {
94
+ await act(async () => root.unmount())
95
+ queryClient.clear()
96
+ })
97
+
98
+ it('initialises the settings store when the host already fetched the tools', async () => {
99
+ // The host app fetched first and filled the shared entry.
100
+ queryClient.setQueryData(
101
+ getToolsQueryKey({ apiUrl: API_URL, scopeKey: 'host' }),
102
+ TOOLS
103
+ )
104
+
105
+ await render()
106
+
107
+ await vi.waitFor(() => {
108
+ expect(mocks.initializeTools).toHaveBeenCalledWith('Bridges', [
109
+ 'across',
110
+ 'hop',
111
+ ])
112
+ })
113
+ expect(mocks.initializeTools).toHaveBeenCalledWith('Exchanges', ['1inch'])
114
+ // The widget read the host's entry instead of sending its own request.
115
+ expect(mocks.getTools).not.toHaveBeenCalled()
116
+ })
117
+
118
+ it('initialises the settings store when the widget fetched the tools itself', async () => {
119
+ await render()
120
+
121
+ await vi.waitFor(() => {
122
+ expect(mocks.initializeTools).toHaveBeenCalledWith('Bridges', [
123
+ 'across',
124
+ 'hop',
125
+ ])
126
+ })
127
+ expect(mocks.getTools).toHaveBeenCalledTimes(1)
128
+ })
129
+
130
+ it('initialises with the filtered view while the cache keeps the raw list', async () => {
131
+ await render()
132
+
133
+ await vi.waitFor(() => {
134
+ expect(mocks.initializeTools).toHaveBeenCalled()
135
+ })
136
+ const cached = queryClient.getQueryData<ToolsResponse>(
137
+ getToolsQueryKey({ apiUrl: API_URL, scopeKey: 'host' })
138
+ )
139
+ expect(cached?.bridges).toHaveLength(3)
140
+ expect(cached?.exchanges).toHaveLength(2)
141
+ })
142
+
143
+ it('refreshes the tools every three minutes', async () => {
144
+ await render()
145
+ await vi.waitFor(() => {
146
+ expect(mocks.initializeTools).toHaveBeenCalled()
147
+ })
148
+
149
+ const [observer] = queryClient.getQueryCache().find({
150
+ queryKey: getToolsQueryKey({ apiUrl: API_URL, scopeKey: 'host' }),
151
+ })!.observers
152
+ expect(observer.options.refetchInterval).toBe(180_000)
153
+ expect(observer.options.staleTime).toBe(180_000)
154
+ })
155
+ })