@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
@@ -50,7 +50,7 @@ export const SendToWalletButton: React.FC<
50
50
  const { selectedBookmark } = useBookmarks()
51
51
  const { setSelectedBookmark } = useBookmarkActions()
52
52
  const { accounts } = useAccount()
53
- const { getChainTypeFromAddress } = useChainTypeFromAddress()
53
+ const { getChainFromAddress } = useChainTypeFromAddress()
54
54
  const { requiredToAddress } = useToAddressRequirements()
55
55
  const disabledToAddress = disabledUI?.toAddress
56
56
  const hiddenToAddress = hiddenUI?.toAddress
@@ -65,11 +65,16 @@ export const SendToWalletButton: React.FC<
65
65
  (account) => account.address === toAddressFieldValue
66
66
  )
67
67
 
68
+ // A bookmark from the config or the URL may carry no chainId.
69
+ const detectedChain =
70
+ !matchingConnectedAccount &&
71
+ selectedBookmark?.chainId === undefined &&
72
+ toAddressFieldValue
73
+ ? getChainFromAddress(toAddressFieldValue)
74
+ : undefined
75
+
68
76
  const chainType = !matchingConnectedAccount
69
- ? selectedBookmark?.chainType ||
70
- (toAddressFieldValue
71
- ? getChainTypeFromAddress(toAddressFieldValue)
72
- : undefined)
77
+ ? selectedBookmark?.chainType || detectedChain?.chainType
73
78
  : undefined
74
79
 
75
80
  const chainId =
@@ -77,9 +82,9 @@ export const SendToWalletButton: React.FC<
77
82
  ? toChainId
78
83
  : matchingConnectedAccount
79
84
  ? matchingConnectedAccount.chainId
80
- : chainType
81
- ? defaultChainIdsByType[chainType]
82
- : undefined
85
+ : (selectedBookmark?.chainId ??
86
+ detectedChain?.chainId ??
87
+ (chainType ? defaultChainIdsByType[chainType] : undefined))
83
88
 
84
89
  const isConnectedAccount =
85
90
  selectedBookmark?.isConnectedAccount &&
@@ -1,5 +1,7 @@
1
1
  import { useCallback } from 'react'
2
+ import { useAvailableChains } from '../../hooks/useAvailableChains.js'
2
3
  import { useToAddressAutoPopulate } from '../../hooks/useToAddressAutoPopulate.js'
4
+ import { useToAddressReset } from '../../hooks/useToAddressReset.js'
3
5
  import { useWidgetEvents } from '../../hooks/useWidgetEvents.js'
4
6
  import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'
5
7
  import { useChainOrderStore } from '../../stores/chains/ChainOrderStore.js'
@@ -20,6 +22,8 @@ export const useTokenSelect = (
20
22
  const emitter = useWidgetEvents()
21
23
  const { setFieldValue, getFieldValues } = useFieldActions()
22
24
  const autoPopulateToAddress = useToAddressAutoPopulate()
25
+ const { tryResetToAddress } = useToAddressReset()
26
+ const { getChainById } = useAvailableChains()
23
27
  const [setChain, setIsAllNetworks] = useChainOrderStore((state) => [
24
28
  state.setChain,
25
29
  state.setIsAllNetworks,
@@ -29,6 +33,7 @@ export const useTokenSelect = (
29
33
 
30
34
  return useCallback(
31
35
  (tokenAddress: string, chainId?: number) => {
36
+ const [toChainIdBefore] = getFieldValues('toChain')
32
37
  setFieldValue(tokenKey, tokenAddress, { isDirty: true, isTouched: true })
33
38
  const selectedChainId =
34
39
  chainId ?? getFieldValues(FormKeyHelper.getChainKey(formType))[0]
@@ -93,6 +98,14 @@ export const useTokenSelect = (
93
98
  setChain(selectedChainId, oppositeFormType)
94
99
  }
95
100
 
101
+ // A token from "All networks" moves the destination without the chain select,
102
+ // which resets a receiver the new chain cannot take.
103
+ const [toChainId] = getFieldValues('toChain')
104
+ const toChain = getChainById(toChainId)
105
+ if (toChainId !== toChainIdBefore && toChain) {
106
+ tryResetToAddress(toChain)
107
+ }
108
+
96
109
  // Automatically populate toAddress field if bridging across ecosystems and compatible wallet is connected
97
110
  autoPopulateToAddress({
98
111
  formType,
@@ -118,6 +131,8 @@ export const useTokenSelect = (
118
131
  },
119
132
  [
120
133
  autoPopulateToAddress,
134
+ tryResetToAddress,
135
+ getChainById,
121
136
  disabledUI,
122
137
  emitter,
123
138
  formType,
@@ -1,3 +1,6 @@
1
1
  export const defaultMaxHeight = 686
2
2
 
3
3
  export const internalExplorerUrl = 'https://scan.li.fi'
4
+
5
+ // Fits the longest Zcash unified address in the official test vectors (509 characters).
6
+ export const addressInputMaxLength = 512
@@ -1,2 +1,2 @@
1
1
  export const name = '@lifi/widget'
2
- export const version = '4.7.1'
2
+ export const version = '4.9.0'
@@ -1,5 +1,6 @@
1
1
  import type { TokenExtended } from '@lifi/sdk'
2
2
  import { useAccount } from '@lifi/wallet-management'
3
+ import { useAddressForChain } from '@lifi/widget-provider'
3
4
  import { useMemo } from 'react'
4
5
  import type { FormType } from '../stores/form/types.js'
5
6
  import { useChains } from './useChains.js'
@@ -30,6 +31,7 @@ export const useAccountsBalancesData = (
30
31
  const { accounts: allAccounts, account: currentAccount } = useAccount(
31
32
  isAllNetworks ? undefined : { chainType: currentChain?.chainType }
32
33
  )
34
+ const { isAddressForChain } = useAddressForChain()
33
35
  const accounts = useMemo(() => {
34
36
  return isAllNetworks
35
37
  ? allAccounts
@@ -48,7 +50,9 @@ export const useAccountsBalancesData = (
48
50
  (acc, account) => {
49
51
  if (account.address) {
50
52
  const accountChains = chains?.filter(
51
- (chain) => account.chainType === chain?.chainType
53
+ (chain) =>
54
+ account.chainType === chain?.chainType &&
55
+ isAddressForChain(account.address as string, chain)
52
56
  )
53
57
  if (accountChains) {
54
58
  const chainIdSet = new Set(accountChains.map((chain) => chain.id))
@@ -68,7 +72,7 @@ export const useAccountsBalancesData = (
68
72
  },
69
73
  {} as Record<string, Record<number, TokenExtended[]>>
70
74
  )
71
- }, [accounts, chains, allTokens])
75
+ }, [accounts, chains, allTokens, isAddressForChain])
72
76
 
73
77
  return {
74
78
  data: accountsWithTokens,
@@ -1,6 +1,9 @@
1
1
  import type { Chain, ChainType } from '@lifi/sdk'
2
- import { getNameServiceAddress } from '@lifi/sdk'
3
- import { useChainTypeFromAddress } from '@lifi/widget-provider'
2
+ import { ChainId, getNameServiceAddress } from '@lifi/sdk'
3
+ import {
4
+ useAddressForChain,
5
+ useChainTypeFromAddress,
6
+ } from '@lifi/widget-provider'
4
7
  import { useMutation } from '@tanstack/react-query'
5
8
  import { useTranslation } from 'react-i18next'
6
9
  import { useSDKClient } from '../providers/SDKClientProvider.js'
@@ -12,7 +15,7 @@ export enum AddressType {
12
15
 
13
16
  type ValidationArgs = {
14
17
  value: string
15
- chainType?: ChainType
18
+ /** Strict: the receiver must be valid on this chain. */
16
19
  chain?: Chain
17
20
  }
18
21
 
@@ -20,6 +23,7 @@ type ValidResponse = {
20
23
  address: string
21
24
  addressType: AddressType
22
25
  chainType: ChainType
26
+ chainId?: ChainId
23
27
  isValid: true
24
28
  }
25
29
 
@@ -35,49 +39,100 @@ export const useAddressValidation = (): {
35
39
  isValidating: boolean
36
40
  } => {
37
41
  const { t } = useTranslation()
38
- const { getChainTypeFromAddress } = useChainTypeFromAddress()
42
+ const { getChainFromAddress } = useChainTypeFromAddress()
43
+ const { isAddressForChain } = useAddressForChain()
39
44
  const sdkClient = useSDKClient()
40
45
 
46
+ const validFor = (
47
+ address: string,
48
+ addressType: AddressType,
49
+ chain: Chain
50
+ ): ValidResponse => ({
51
+ address,
52
+ addressType,
53
+ chainType: chain.chainType,
54
+ chainId: getChainFromAddress(address)?.chainId,
55
+ isValid: true,
56
+ })
57
+
58
+ const validateForChain = async (
59
+ value: string,
60
+ chain: Chain
61
+ ): Promise<ValidResponse | InvalidResponse> => {
62
+ if (isAddressForChain(value, chain)) {
63
+ return validFor(value, AddressType.Address, chain)
64
+ }
65
+ // A recognised address is no name to resolve.
66
+ if (getChainFromAddress(value)) {
67
+ return {
68
+ isValid: false,
69
+ error: t('error.title.walletChainTypeInvalid', {
70
+ chainName: chain.name,
71
+ }),
72
+ }
73
+ }
74
+ const resolved = await getNameServiceAddress(
75
+ sdkClient,
76
+ value,
77
+ chain.chainType
78
+ )
79
+ if (resolved && isAddressForChain(resolved, chain)) {
80
+ return validFor(resolved, AddressType.NameService, chain)
81
+ }
82
+ return {
83
+ isValid: false,
84
+ error:
85
+ chain.id === ChainId.ZEC
86
+ ? t('error.title.zcashAddressInvalid')
87
+ : t('error.title.walletAddressInvalid', {
88
+ context: 'chain',
89
+ chainName: chain.name,
90
+ }),
91
+ }
92
+ }
93
+
94
+ const validateWithoutChain = async (
95
+ value: string
96
+ ): Promise<ValidResponse | undefined> => {
97
+ const detected = getChainFromAddress(value)
98
+ if (detected) {
99
+ return {
100
+ address: value,
101
+ addressType: AddressType.Address,
102
+ ...detected,
103
+ isValid: true,
104
+ }
105
+ }
106
+ const address = await getNameServiceAddress(sdkClient, value)
107
+ const resolved = address ? getChainFromAddress(address) : undefined
108
+ if (address && resolved) {
109
+ return {
110
+ address,
111
+ addressType: AddressType.NameService,
112
+ ...resolved,
113
+ isValid: true,
114
+ }
115
+ }
116
+ return undefined
117
+ }
118
+
41
119
  const { mutateAsync: validateAddress, isPending: isValidating } = useMutation(
42
120
  {
43
121
  mutationFn: async ({
44
122
  value,
45
- chainType,
46
123
  chain,
47
124
  }: ValidationArgs): Promise<ValidResponse | InvalidResponse> => {
48
125
  try {
49
126
  if (!value) {
50
127
  throw new Error()
51
128
  }
52
-
53
- const _chainType = getChainTypeFromAddress(value)
54
- if (_chainType) {
55
- return {
56
- address: value,
57
- addressType: AddressType.Address,
58
- chainType: _chainType,
59
- isValid: true,
60
- }
129
+ if (chain) {
130
+ return await validateForChain(value, chain)
61
131
  }
62
-
63
- const address = await getNameServiceAddress(
64
- sdkClient,
65
- value,
66
- chainType
67
- )
68
-
69
- if (address) {
70
- const _chainType = getChainTypeFromAddress(address)
71
- if (_chainType) {
72
- return {
73
- address: address,
74
- addressType: AddressType.NameService,
75
- chainType: _chainType,
76
- isValid: true,
77
- }
78
- }
132
+ const result = await validateWithoutChain(value)
133
+ if (result) {
134
+ return result
79
135
  }
80
-
81
136
  throw new Error()
82
137
  } catch (_) {
83
138
  return {
@@ -0,0 +1,170 @@
1
+ /** @vitest-environment happy-dom */
2
+
3
+ import type * as LifiSdk from '@lifi/sdk'
4
+ import { ChainType, type ExtendedChain, type SDKClient } 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 chains entry is shared with the host app through
13
+ * `getChainsQueryOptions`, keyed by the client's API base, the requested chain
14
+ * types and the widget's `queryScopeKey`. The widget's own SDK client still
15
+ * gets the chains, whoever filled the entry.
16
+ */
17
+
18
+ const API_URL = 'https://api.example/v1'
19
+
20
+ const mocks = vi.hoisted(() => ({
21
+ getChains: vi.fn(),
22
+ setChains: vi.fn(),
23
+ }))
24
+
25
+ vi.mock('@lifi/sdk', async (importOriginal) => ({
26
+ ...(await importOriginal<typeof LifiSdk>()),
27
+ getChains: mocks.getChains,
28
+ }))
29
+
30
+ let widgetConfig: Partial<WidgetConfig> = {}
31
+
32
+ vi.mock('../providers/WidgetProvider/WidgetProvider.js', () => ({
33
+ useWidgetConfig: () => widgetConfig,
34
+ }))
35
+
36
+ const client = {
37
+ config: { apiUrl: API_URL },
38
+ setChains: mocks.setChains,
39
+ } as unknown as SDKClient
40
+ vi.mock('../providers/SDKClientProvider.js', () => ({
41
+ useSDKClient: () => client,
42
+ }))
43
+
44
+ import { getChainsQueryKey } from '../queries/getChains.js'
45
+ import { useAvailableChains } from './useAvailableChains.js'
46
+
47
+ const CHAINS = [{ id: 1, name: 'Ethereum' }] as ExtendedChain[]
48
+
49
+ let root: Root
50
+ let queryClient: QueryClient
51
+ let externalConfig: WidgetConfig | undefined
52
+
53
+ const Probe = () => {
54
+ useAvailableChains(undefined, externalConfig)
55
+ return null
56
+ }
57
+
58
+ const render = async () => {
59
+ await act(async () => {
60
+ root.render(
61
+ <QueryClientProvider client={queryClient}>
62
+ <Probe />
63
+ </QueryClientProvider>
64
+ )
65
+ })
66
+ }
67
+
68
+ const evmKey = getChainsQueryKey({
69
+ apiUrl: API_URL,
70
+ chainTypes: [ChainType.EVM],
71
+ scopeKey: 'host',
72
+ })
73
+
74
+ describe('useAvailableChains', () => {
75
+ beforeEach(() => {
76
+ vi.clearAllMocks()
77
+ mocks.getChains.mockResolvedValue(CHAINS)
78
+ widgetConfig = {
79
+ queryScopeKey: 'host',
80
+ chains: { types: { allow: [ChainType.EVM] } },
81
+ }
82
+ externalConfig = undefined
83
+ queryClient = new QueryClient({
84
+ defaultOptions: { queries: { retry: false } },
85
+ })
86
+ const container = document.createElement('div')
87
+ document.body.appendChild(container)
88
+ root = createRoot(container)
89
+ })
90
+
91
+ afterEach(async () => {
92
+ await act(async () => root.unmount())
93
+ queryClient.clear()
94
+ })
95
+
96
+ it('fetches the allowed chain types under the widget scope and hands them to the client', async () => {
97
+ await render()
98
+
99
+ await vi.waitFor(() => {
100
+ expect(mocks.setChains).toHaveBeenCalledWith(CHAINS)
101
+ })
102
+ expect(mocks.getChains).toHaveBeenCalledWith(client, {
103
+ chainTypes: [ChainType.EVM],
104
+ })
105
+ expect(queryClient.getQueryData(evmKey)).toBe(CHAINS)
106
+ })
107
+
108
+ it('reads an entry the host filled, and still hands the chains to the client', async () => {
109
+ queryClient.setQueryData(evmKey, CHAINS)
110
+
111
+ await render()
112
+
113
+ await vi.waitFor(() => {
114
+ expect(mocks.setChains).toHaveBeenCalledWith(CHAINS)
115
+ })
116
+ expect(mocks.getChains).not.toHaveBeenCalled()
117
+ })
118
+
119
+ it('hands the chains to the client again after a refetch with equal data', async () => {
120
+ // Structural sharing keeps the data reference on an equal refetch. The
121
+ // client's own chain storage expires, so every fetch must refresh it.
122
+ await render()
123
+ await vi.waitFor(() => {
124
+ expect(mocks.setChains).toHaveBeenCalledTimes(1)
125
+ })
126
+
127
+ await act(async () => {
128
+ await queryClient.refetchQueries({ queryKey: evmKey })
129
+ })
130
+
131
+ await vi.waitFor(() => {
132
+ expect(mocks.setChains).toHaveBeenCalledTimes(2)
133
+ })
134
+ })
135
+
136
+ it('refreshes the chains every five minutes', async () => {
137
+ await render()
138
+ await vi.waitFor(() => {
139
+ expect(queryClient.getQueryData(evmKey)).toBe(CHAINS)
140
+ })
141
+
142
+ const [observer] = queryClient
143
+ .getQueryCache()
144
+ .find({ queryKey: evmKey })!.observers
145
+ expect(observer.options.refetchInterval).toBe(300_000)
146
+ expect(observer.options.staleTime).toBe(300_000)
147
+ })
148
+
149
+ it('uses the API base and scope of an external widget config', async () => {
150
+ externalConfig = {
151
+ integrator: 'partner',
152
+ keyPrefix: 'partner',
153
+ sdkConfig: { apiUrl: 'https://partner.example/v1' },
154
+ chains: { types: { allow: [ChainType.SVM] } },
155
+ }
156
+
157
+ await render()
158
+
159
+ const partnerKey = getChainsQueryKey({
160
+ apiUrl: 'https://partner.example/v1',
161
+ chainTypes: [ChainType.SVM],
162
+ scopeKey: 'partner',
163
+ })
164
+ await vi.waitFor(() => {
165
+ expect(queryClient.getQueryData(partnerKey)).toBe(CHAINS)
166
+ })
167
+ // The external config's own client, not the widget's.
168
+ expect(mocks.getChains.mock.calls[0][0]).not.toBe(client)
169
+ })
170
+ })
@@ -1,26 +1,23 @@
1
- import type { ExtendedChain } from '@lifi/sdk'
2
- import { ChainType, createClient, getChains } from '@lifi/sdk'
1
+ import type { ChainType, ExtendedChain } from '@lifi/sdk'
2
+ import { createClient } from '@lifi/sdk'
3
3
  import { useQuery } from '@tanstack/react-query'
4
4
  import { useCallback, useEffect, useMemo } from 'react'
5
5
  import { useSDKClient } from '../providers/SDKClientProvider.js'
6
6
  import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'
7
+ import {
8
+ getChainsQueryOptions,
9
+ supportedChainTypes,
10
+ } from '../queries/getChains.js'
7
11
  import type { WidgetConfig } from '../types/widget.js'
8
12
  import { getConfigItemSets, isItemAllowedForSets } from '../utils/item.js'
9
- import { getQueryKey } from '../utils/queries.js'
13
+ import { resolveQueryScopeKey } from '../utils/scopeKeys.js'
10
14
 
11
15
  type GetChainById = (
12
16
  chainId?: number,
13
17
  chains?: ExtendedChain[]
14
18
  ) => ExtendedChain | undefined
15
19
 
16
- const supportedChainTypes = [
17
- ChainType.EVM,
18
- ChainType.SVM,
19
- ChainType.UTXO,
20
- ChainType.MVM,
21
- ChainType.TVM,
22
- ChainType.STL,
23
- ]
20
+ const refetchInterval = 300_000
24
21
 
25
22
  export const useAvailableChains = (
26
23
  chainTypes?: ChainType[],
@@ -30,7 +27,7 @@ export const useAvailableChains = (
30
27
  getChainById: GetChainById
31
28
  isLoading: boolean
32
29
  } => {
33
- const { chains: internalChains, keyPrefix: internalKeyPrefix } =
30
+ const { chains: internalChains, queryScopeKey: internalQueryScopeKey } =
34
31
  useWidgetConfig()
35
32
  const internalClient = useSDKClient()
36
33
 
@@ -46,46 +43,31 @@ export const useAvailableChains = (
46
43
  }, [widgetConfig])
47
44
 
48
45
  // Overwrite widget config if passed as param
49
- const keyPrefix = widgetConfig?.keyPrefix ?? internalKeyPrefix
46
+ const client = externalClient ?? internalClient
50
47
  const chains = widgetConfig?.chains ?? internalChains
48
+ const queryScopeKey = widgetConfig
49
+ ? resolveQueryScopeKey(widgetConfig)
50
+ : internalQueryScopeKey
51
51
 
52
- const { data, isLoading } = useQuery({
53
- queryKey: [
54
- getQueryKey('chains', keyPrefix),
55
- chains?.types,
56
- chains?.allow,
57
- chains?.deny,
58
- chains?.from,
59
- chains?.to,
60
- !!externalClient,
61
- ] as const,
62
- queryFn: async ({ queryKey: [, chainTypesConfig] }) => {
63
- const chainsConfigSets = getConfigItemSets(
64
- chainTypesConfig,
65
- (chains) => new Set(chains)
66
- )
67
- const chainTypesRequest = supportedChainTypes.filter((chainType) =>
68
- isItemAllowedForSets(chainType, chainsConfigSets)
69
- )
70
- const client = externalClient ?? internalClient
71
- const availableChains = await getChains(client, {
72
- chainTypes: chainTypes || chainTypesRequest,
73
- })
74
- client.setChains(availableChains)
75
- return availableChains
76
- },
77
- refetchInterval: 300_000,
78
- staleTime: 300_000,
79
- })
52
+ const typeSets = getConfigItemSets(chains?.types, (types) => new Set(types))
53
+ const requestedTypes =
54
+ chainTypes ??
55
+ supportedChainTypes.filter((type) => isItemAllowedForSets(type, typeSets))
56
+
57
+ const { data, dataUpdatedAt, isLoading } = useQuery(
58
+ getChainsQueryOptions(client, {
59
+ chainTypes: requestedTypes,
60
+ scopeKey: queryScopeKey,
61
+ query: { refetchInterval, staleTime: refetchInterval },
62
+ })
63
+ )
80
64
 
81
- // Ensure the current internal client always has chains, even when:
82
- // - the query result came from cache (external call populated it first)
83
- // - the client was recreated due to config changes (stale closure in queryFn)
65
+ // A host may fill the entry; key on dataUpdatedAt, as a refetch keeps data.
84
66
  useEffect(() => {
85
- if (data && !externalClient) {
86
- internalClient.setChains(data)
67
+ if (data && dataUpdatedAt) {
68
+ client.setChains(data)
87
69
  }
88
- }, [data, externalClient, internalClient])
70
+ }, [data, dataUpdatedAt, client])
89
71
 
90
72
  const getChainById: GetChainById = useCallback(
91
73
  (chainId?: number, chains: ExtendedChain[] | undefined = data) => {
@@ -22,7 +22,7 @@ export const useFromTokenSufficiency = (
22
22
  'fromToken',
23
23
  'fromAmount'
24
24
  )
25
- const { keyPrefix } = useWidgetConfig()
25
+ const { storageScopeKey } = useWidgetConfig()
26
26
  const sdkClient = useSDKClient()
27
27
 
28
28
  let chainId = fromChainId
@@ -42,7 +42,7 @@ export const useFromTokenSufficiency = (
42
42
 
43
43
  const { data: insufficientFromToken, isLoading } = useQuery({
44
44
  queryKey: [
45
- getQueryKey('from-token-sufficiency-check', keyPrefix),
45
+ getQueryKey('from-token-sufficiency-check', storageScopeKey),
46
46
  account.address,
47
47
  chainId,
48
48
  tokenAddress,
@@ -14,7 +14,7 @@ export const useGasRecommendation = (
14
14
  fromToken?: string
15
15
  ): UseQueryResult<Awaited<ReturnType<typeof getGasRecommendation>> | null> => {
16
16
  const { chains } = useAvailableChains()
17
- const { keyPrefix, hiddenUI } = useWidgetConfig()
17
+ const { storageScopeKey, hiddenUI } = useWidgetConfig()
18
18
  const sdkClient = useSDKClient()
19
19
 
20
20
  const checkRecommendationLiFuel =
@@ -28,7 +28,7 @@ export const useGasRecommendation = (
28
28
 
29
29
  return useQuery({
30
30
  queryKey: [
31
- getQueryKey('gas-recommendation', keyPrefix),
31
+ getQueryKey('gas-recommendation', storageScopeKey),
32
32
  toChainId,
33
33
  fromChain,
34
34
  fromToken,
@@ -1,5 +1,6 @@
1
1
  import type { ExtendedChain } from '@lifi/sdk'
2
2
  import { useAccount } from '@lifi/wallet-management'
3
+ import { useAddressForChain } from '@lifi/widget-provider'
3
4
  import { useMemo } from 'react'
4
5
  import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'
5
6
  import { useFieldValues } from '../stores/form/useFieldValues.js'
@@ -28,8 +29,17 @@ export const useGasRefuel = (): {
28
29
  const fromChain = getChainById(fromChainId)
29
30
 
30
31
  const { account: toAccount } = useAccount({ chainType: toChain?.chainType })
32
+ const { isAddressForChain } = useAddressForChain()
31
33
 
32
- const effectiveToAddress = toAddress || toAccount?.address
34
+ // A shared chain type is not a shared format: a Bitcoin account cannot receive on ZEC.
35
+ const toAccountAddress =
36
+ toAccount?.address &&
37
+ toChain &&
38
+ isAddressForChain(toAccount.address, toChain)
39
+ ? toAccount.address
40
+ : undefined
41
+
42
+ const effectiveToAddress = toAddress || toAccountAddress
33
43
 
34
44
  const { isContractAddress: isToContractAddress } = useIsContractAddress(
35
45
  effectiveToAddress,
@@ -32,7 +32,7 @@ export const useGasSufficiency = (
32
32
  const { account: EVMAccount, accounts } = useAccount({
33
33
  chainType: ChainType.EVM,
34
34
  })
35
- const { keyPrefix, hiddenUI } = useWidgetConfig()
35
+ const { storageScopeKey, hiddenUI } = useWidgetConfig()
36
36
  const sdkClient = useSDKClient()
37
37
 
38
38
  const { relevantAccounts, relevantAccountsQueryKey } = useMemo(() => {
@@ -67,7 +67,7 @@ export const useGasSufficiency = (
67
67
 
68
68
  const { data: insufficientGas, isLoading } = useQuery<GasSufficiency[]>({
69
69
  queryKey: [
70
- getQueryKey('gas-sufficiency-check', keyPrefix),
70
+ getQueryKey('gas-sufficiency-check', storageScopeKey),
71
71
  relevantAccountsQueryKey,
72
72
  route?.id,
73
73
  isContractAddress,
@@ -13,7 +13,7 @@ export function useIsBatchingSupported(
13
13
  isBatchingSupported: boolean | undefined
14
14
  isBatchingSupportedLoading: boolean
15
15
  } {
16
- const { keyPrefix } = useWidgetConfig()
16
+ const { storageScopeKey } = useWidgetConfig()
17
17
  const sdkClient = useSDKClient()
18
18
  const { isBatchingSupported } = useEthereumContext()
19
19
 
@@ -22,7 +22,7 @@ export function useIsBatchingSupported(
22
22
  )
23
23
  const { data, isLoading } = useQuery({
24
24
  queryKey: [
25
- getQueryKey('isBatchingSupported', keyPrefix),
25
+ getQueryKey('isBatchingSupported', storageScopeKey),
26
26
  chain?.id,
27
27
  address,
28
28
  ],