@lifi/widget 4.8.0 → 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 (115) hide show
  1. package/dist/esm/components/AmountInputCard/AmountInputCardPair.js +5 -2
  2. package/dist/esm/components/AmountInputCard/AmountInputCardPair.js.map +1 -1
  3. package/dist/esm/components/SelectChainAndToken.js +5 -2
  4. package/dist/esm/components/SelectChainAndToken.js.map +1 -1
  5. package/dist/esm/components/SendToWallet/SendToWalletButton.js +5 -4
  6. package/dist/esm/components/SendToWallet/SendToWalletButton.js.map +1 -1
  7. package/dist/esm/components/TokenList/useTokenSelect.d.ts.map +1 -1
  8. package/dist/esm/components/TokenList/useTokenSelect.js +10 -0
  9. package/dist/esm/components/TokenList/useTokenSelect.js.map +1 -1
  10. package/dist/esm/config/constants.js.map +1 -1
  11. package/dist/esm/config/version.d.ts +1 -1
  12. package/dist/esm/config/version.js +1 -1
  13. package/dist/esm/config/version.js.map +1 -1
  14. package/dist/esm/hooks/useAccountsBalancesData.js +5 -2
  15. package/dist/esm/hooks/useAccountsBalancesData.js.map +1 -1
  16. package/dist/esm/hooks/useAddressValidation.d.ts +3 -2
  17. package/dist/esm/hooks/useAddressValidation.d.ts.map +1 -1
  18. package/dist/esm/hooks/useAddressValidation.js +48 -21
  19. package/dist/esm/hooks/useAddressValidation.js.map +1 -1
  20. package/dist/esm/hooks/useGasRefuel.js +4 -1
  21. package/dist/esm/hooks/useGasRefuel.js.map +1 -1
  22. package/dist/esm/hooks/useRoutes.d.ts.map +1 -1
  23. package/dist/esm/hooks/useRoutes.js +11 -6
  24. package/dist/esm/hooks/useRoutes.js.map +1 -1
  25. package/dist/esm/hooks/useToAddressAutoPopulate.js +6 -8
  26. package/dist/esm/hooks/useToAddressAutoPopulate.js.map +1 -1
  27. package/dist/esm/hooks/useToAddressRequirements.d.ts +3 -2
  28. package/dist/esm/hooks/useToAddressRequirements.d.ts.map +1 -1
  29. package/dist/esm/hooks/useToAddressRequirements.js +8 -7
  30. package/dist/esm/hooks/useToAddressRequirements.js.map +1 -1
  31. package/dist/esm/hooks/useToAddressReset.js +6 -3
  32. package/dist/esm/hooks/useToAddressReset.js.map +1 -1
  33. package/dist/esm/hooks/useTokenBalance.js +2 -1
  34. package/dist/esm/hooks/useTokenBalance.js.map +1 -1
  35. package/dist/esm/hooks/useTokens.js +1 -1
  36. package/dist/esm/i18n/bn.json +2 -1
  37. package/dist/esm/i18n/de.json +2 -1
  38. package/dist/esm/i18n/en.json +2 -1
  39. package/dist/esm/i18n/es.json +2 -1
  40. package/dist/esm/i18n/fr.json +2 -1
  41. package/dist/esm/i18n/hi.json +2 -1
  42. package/dist/esm/i18n/id.json +2 -1
  43. package/dist/esm/i18n/it.json +2 -1
  44. package/dist/esm/i18n/ja.json +2 -1
  45. package/dist/esm/i18n/ko.json +2 -1
  46. package/dist/esm/i18n/pl.json +2 -1
  47. package/dist/esm/i18n/pt.json +2 -1
  48. package/dist/esm/i18n/th.json +2 -1
  49. package/dist/esm/i18n/tr.json +2 -1
  50. package/dist/esm/i18n/uk.json +2 -1
  51. package/dist/esm/i18n/vi.json +2 -1
  52. package/dist/esm/i18n/zh.json +2 -1
  53. package/dist/esm/pages/SendToWallet/BookmarkAddressSheet.js +7 -4
  54. package/dist/esm/pages/SendToWallet/BookmarkAddressSheet.js.map +1 -1
  55. package/dist/esm/pages/SendToWallet/BookmarksPage.js +35 -31
  56. package/dist/esm/pages/SendToWallet/BookmarksPage.js.map +1 -1
  57. package/dist/esm/pages/SendToWallet/ConnectedWalletsPage.js +4 -3
  58. package/dist/esm/pages/SendToWallet/ConnectedWalletsPage.js.map +1 -1
  59. package/dist/esm/pages/SendToWallet/RecentWalletsPage.js +43 -39
  60. package/dist/esm/pages/SendToWallet/RecentWalletsPage.js.map +1 -1
  61. package/dist/esm/pages/SendToWallet/SendToConfiguredWalletPage.js +32 -26
  62. package/dist/esm/pages/SendToWallet/SendToConfiguredWalletPage.js.map +1 -1
  63. package/dist/esm/pages/SendToWallet/SendToWalletPage.js +5 -10
  64. package/dist/esm/pages/SendToWallet/SendToWalletPage.js.map +1 -1
  65. package/dist/esm/providers/WalletProvider/WalletProvider.d.ts.map +1 -1
  66. package/dist/esm/providers/WalletProvider/WalletProvider.js +14 -2
  67. package/dist/esm/providers/WalletProvider/WalletProvider.js.map +1 -1
  68. package/dist/esm/providers/WidgetProvider/WidgetProvider.d.ts.map +1 -1
  69. package/dist/esm/providers/WidgetProvider/WidgetProvider.js +10 -14
  70. package/dist/esm/providers/WidgetProvider/WidgetProvider.js.map +1 -1
  71. package/dist/esm/stores/bookmarks/types.d.ts +3 -0
  72. package/dist/esm/stores/bookmarks/types.d.ts.map +1 -1
  73. package/dist/esm/stores/chains/ChainOrderStore.d.ts.map +1 -1
  74. package/dist/esm/stores/chains/ChainOrderStore.js +4 -1
  75. package/dist/esm/stores/chains/ChainOrderStore.js.map +1 -1
  76. package/dist/esm/stores/form/FormStore.js +3 -2
  77. package/dist/esm/stores/form/FormStore.js.map +1 -1
  78. package/dist/esm/stores/form/URLSearchParamsBuilder.js +7 -3
  79. package/dist/esm/stores/form/URLSearchParamsBuilder.js.map +1 -1
  80. package/dist/esm/stores/form/getDefaultValuesFromQueryString.js +4 -1
  81. package/dist/esm/stores/form/getDefaultValuesFromQueryString.js.map +1 -1
  82. package/dist/esm/utils/chainType.js +32 -1
  83. package/dist/esm/utils/chainType.js.map +1 -1
  84. package/dist/esm/utils/customReceiver.js +6 -1
  85. package/dist/esm/utils/customReceiver.js.map +1 -1
  86. package/package.json +7 -7
  87. package/src/components/AmountInputCard/AmountInputCardPair.tsx +6 -2
  88. package/src/components/SelectChainAndToken.tsx +5 -1
  89. package/src/components/SendToWallet/SendToWalletButton.tsx +13 -8
  90. package/src/components/TokenList/useTokenSelect.ts +15 -0
  91. package/src/config/constants.ts +3 -0
  92. package/src/config/version.ts +1 -1
  93. package/src/hooks/useAccountsBalancesData.ts +6 -2
  94. package/src/hooks/useAddressValidation.ts +87 -32
  95. package/src/hooks/useGasRefuel.ts +11 -1
  96. package/src/hooks/useRoutes.ts +23 -18
  97. package/src/hooks/useToAddressAutoPopulate.ts +13 -17
  98. package/src/hooks/useToAddressRequirements.ts +19 -4
  99. package/src/hooks/useToAddressReset.ts +13 -4
  100. package/src/hooks/useTokenBalance.ts +6 -1
  101. package/src/pages/SendToWallet/BookmarkAddressSheet.tsx +5 -1
  102. package/src/pages/SendToWallet/BookmarksPage.tsx +42 -52
  103. package/src/pages/SendToWallet/ConnectedWalletsPage.tsx +4 -14
  104. package/src/pages/SendToWallet/RecentWalletsPage.tsx +59 -66
  105. package/src/pages/SendToWallet/SendToConfiguredWalletPage.tsx +45 -50
  106. package/src/pages/SendToWallet/SendToWalletPage.tsx +4 -16
  107. package/src/providers/WalletProvider/WalletProvider.tsx +28 -3
  108. package/src/providers/WidgetProvider/WidgetProvider.tsx +20 -19
  109. package/src/stores/bookmarks/types.ts +3 -0
  110. package/src/stores/chains/ChainOrderStore.tsx +4 -1
  111. package/src/stores/form/FormStore.tsx +2 -0
  112. package/src/stores/form/URLSearchParamsBuilder.tsx +4 -2
  113. package/src/stores/form/getDefaultValuesFromQueryString.ts +19 -0
  114. package/src/utils/chainType.ts +73 -1
  115. package/src/utils/customReceiver.ts +36 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifi/widget",
3
- "version": "4.8.0",
3
+ "version": "4.9.0",
4
4
  "description": "LI.FI Widget for cross-chain bridging and swapping. It will drive your multi-chain strategy and attract new users from everywhere.",
5
5
  "type": "module",
6
6
  "main": "./dist/esm/index.js",
@@ -57,19 +57,19 @@
57
57
  "dependencies": {
58
58
  "@emotion/react": "^11.14.0",
59
59
  "@emotion/styled": "^11.14.1",
60
- "@lifi/sdk": "^4.10.0",
61
- "@lifi/wallet-management": "4.3.1",
62
- "@lifi/widget-provider": "4.5.1",
60
+ "@lifi/sdk": "^4.11.0",
61
+ "@lifi/wallet-management": "4.3.2",
62
+ "@lifi/widget-provider": "4.6.0",
63
63
  "@mui/icons-material": "^9.4.0",
64
64
  "@mui/material": "^9.4.0",
65
65
  "@mui/system": "^9.4.0",
66
- "@tanstack/react-router": "^1.170.38",
66
+ "@tanstack/react-router": "^1.170.41",
67
67
  "@tanstack/react-virtual": "^3.14.13",
68
68
  "eventemitter3": "^5.0.4",
69
69
  "i18next": "^26.4.2",
70
70
  "microdiff": "^1.6.0",
71
- "motion": "^13.4.0",
72
- "react-i18next": "^17.0.14",
71
+ "motion": "^14.0.0",
72
+ "react-i18next": "^17.0.16",
73
73
  "react-intersection-observer": "^11.0.1",
74
74
  "react-transition-group": "^4.4.5",
75
75
  "zustand": "^5.0.15"
@@ -2,6 +2,8 @@ import type { BoxProps } from '@mui/material'
2
2
  import type React from 'react'
3
3
  import type { JSX } from 'react'
4
4
  import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'
5
+ import { useFieldValues } from '../../stores/form/useFieldValues.js'
6
+ import { isDestinationAllowedAsSource } from '../../utils/chainType.js'
5
7
  import { LimitPriceCard } from '../LimitPriceCard/LimitPriceCard.js'
6
8
  import { SwapButton } from '../SwapButton/SwapButton.js'
7
9
  import { CardContainer } from './AmountInputCard.style.js'
@@ -11,8 +13,10 @@ import { SendAmountCard } from './SendAmountCard.js'
11
13
  export const AmountInputCardPair: React.FC<BoxProps> = (props): JSX.Element => {
12
14
  // Refuel's destination is fixed to native gas, so reversing from/to is
13
15
  // meaningless — hide the swap button (and the notch mask it sits in).
14
- const { mode } = useWidgetConfig()
15
- const showSwapButton = mode !== 'refuel'
16
+ const { mode, chains } = useWidgetConfig()
17
+ const [toChainId] = useFieldValues('toChain')
18
+ const showSwapButton =
19
+ mode !== 'refuel' && isDestinationAllowedAsSource(toChainId, chains)
16
20
 
17
21
  return (
18
22
  <CardContainer {...props}>
@@ -3,13 +3,17 @@ import { Box } from '@mui/material'
3
3
  import { ReverseTokensButton } from '../components/ReverseTokensButton/ReverseTokensButton.js'
4
4
  import { SelectTokenButton } from '../components/SelectTokenButton/SelectTokenButton.js'
5
5
  import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'
6
+ import { useFieldValues } from '../stores/form/useFieldValues.js'
7
+ import { isDestinationAllowedAsSource } from '../utils/chainType.js'
6
8
  import { ReverseTokensButtonEmpty } from './ReverseTokensButton/ReverseTokensButton.style.js'
7
9
 
8
10
  export const SelectChainAndToken: React.FC<BoxProps> = (props) => {
9
- const { disabledUI, hiddenUI, mode } = useWidgetConfig()
11
+ const { disabledUI, hiddenUI, mode, chains } = useWidgetConfig()
12
+ const [toChainId] = useFieldValues('toChain')
10
13
 
11
14
  const hiddenReverse =
12
15
  mode === 'refuel' ||
16
+ !isDestinationAllowedAsSource(toChainId, chains) ||
13
17
  disabledUI?.fromToken ||
14
18
  disabledUI?.toToken ||
15
19
  hiddenUI?.fromToken ||
@@ -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.8.0'
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 {
@@ -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,
@@ -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'
@@ -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
-
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
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)
179
+
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({
@@ -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
@@ -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
  })