@lifi/widget 4.8.0 → 4.10.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 (136) hide show
  1. package/README.md +26 -3
  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/SelectChainAndToken.js +5 -2
  5. package/dist/esm/components/SelectChainAndToken.js.map +1 -1
  6. package/dist/esm/components/SendToWallet/SendToWalletButton.js +5 -4
  7. package/dist/esm/components/SendToWallet/SendToWalletButton.js.map +1 -1
  8. package/dist/esm/components/TokenList/TokenDetailsSheet.js +5 -8
  9. package/dist/esm/components/TokenList/TokenDetailsSheet.js.map +1 -1
  10. package/dist/esm/components/TokenList/TokenDetailsSheetContent.js +30 -2
  11. package/dist/esm/components/TokenList/TokenDetailsSheetContent.js.map +1 -1
  12. package/dist/esm/components/TokenList/TokenListItem.js +36 -47
  13. package/dist/esm/components/TokenList/TokenListItem.js.map +1 -1
  14. package/dist/esm/components/TokenList/VirtualizedTokenList.d.ts.map +1 -1
  15. package/dist/esm/components/TokenList/VirtualizedTokenList.js +2 -2
  16. package/dist/esm/components/TokenList/VirtualizedTokenList.js.map +1 -1
  17. package/dist/esm/components/TokenList/tokenVerificationIcons.js +29 -0
  18. package/dist/esm/components/TokenList/tokenVerificationIcons.js.map +1 -0
  19. package/dist/esm/components/TokenList/useTokenSelect.d.ts.map +1 -1
  20. package/dist/esm/components/TokenList/useTokenSelect.js +10 -0
  21. package/dist/esm/components/TokenList/useTokenSelect.js.map +1 -1
  22. package/dist/esm/config/constants.js.map +1 -1
  23. package/dist/esm/config/version.d.ts +1 -1
  24. package/dist/esm/config/version.js +1 -1
  25. package/dist/esm/config/version.js.map +1 -1
  26. package/dist/esm/hooks/useAccountsBalancesData.js +5 -2
  27. package/dist/esm/hooks/useAccountsBalancesData.js.map +1 -1
  28. package/dist/esm/hooks/useAddressValidation.d.ts +3 -2
  29. package/dist/esm/hooks/useAddressValidation.d.ts.map +1 -1
  30. package/dist/esm/hooks/useAddressValidation.js +48 -21
  31. package/dist/esm/hooks/useAddressValidation.js.map +1 -1
  32. package/dist/esm/hooks/useGasRefuel.js +4 -1
  33. package/dist/esm/hooks/useGasRefuel.js.map +1 -1
  34. package/dist/esm/hooks/useRoutes.d.ts.map +1 -1
  35. package/dist/esm/hooks/useRoutes.js +11 -6
  36. package/dist/esm/hooks/useRoutes.js.map +1 -1
  37. package/dist/esm/hooks/useToAddressAutoPopulate.js +6 -8
  38. package/dist/esm/hooks/useToAddressAutoPopulate.js.map +1 -1
  39. package/dist/esm/hooks/useToAddressRequirements.d.ts +3 -2
  40. package/dist/esm/hooks/useToAddressRequirements.d.ts.map +1 -1
  41. package/dist/esm/hooks/useToAddressRequirements.js +8 -7
  42. package/dist/esm/hooks/useToAddressRequirements.js.map +1 -1
  43. package/dist/esm/hooks/useToAddressReset.js +6 -3
  44. package/dist/esm/hooks/useToAddressReset.js.map +1 -1
  45. package/dist/esm/hooks/useTokenBalance.js +2 -1
  46. package/dist/esm/hooks/useTokenBalance.js.map +1 -1
  47. package/dist/esm/hooks/useTokens.js +1 -1
  48. package/dist/esm/i18n/bn.json +7 -2
  49. package/dist/esm/i18n/de.json +7 -2
  50. package/dist/esm/i18n/en.json +7 -2
  51. package/dist/esm/i18n/es.json +7 -2
  52. package/dist/esm/i18n/fr.json +7 -2
  53. package/dist/esm/i18n/hi.json +7 -2
  54. package/dist/esm/i18n/id.json +7 -2
  55. package/dist/esm/i18n/it.json +7 -2
  56. package/dist/esm/i18n/ja.json +7 -2
  57. package/dist/esm/i18n/ko.json +7 -2
  58. package/dist/esm/i18n/pl.json +7 -2
  59. package/dist/esm/i18n/pt.json +7 -2
  60. package/dist/esm/i18n/th.json +7 -2
  61. package/dist/esm/i18n/tr.json +7 -2
  62. package/dist/esm/i18n/uk.json +7 -2
  63. package/dist/esm/i18n/vi.json +7 -2
  64. package/dist/esm/i18n/zh.json +7 -2
  65. package/dist/esm/pages/SendToWallet/BookmarkAddressSheet.js +7 -4
  66. package/dist/esm/pages/SendToWallet/BookmarkAddressSheet.js.map +1 -1
  67. package/dist/esm/pages/SendToWallet/BookmarksPage.js +35 -31
  68. package/dist/esm/pages/SendToWallet/BookmarksPage.js.map +1 -1
  69. package/dist/esm/pages/SendToWallet/ConnectedWalletsPage.js +4 -3
  70. package/dist/esm/pages/SendToWallet/ConnectedWalletsPage.js.map +1 -1
  71. package/dist/esm/pages/SendToWallet/RecentWalletsPage.js +43 -39
  72. package/dist/esm/pages/SendToWallet/RecentWalletsPage.js.map +1 -1
  73. package/dist/esm/pages/SendToWallet/SendToConfiguredWalletPage.js +32 -26
  74. package/dist/esm/pages/SendToWallet/SendToConfiguredWalletPage.js.map +1 -1
  75. package/dist/esm/pages/SendToWallet/SendToWalletPage.js +5 -10
  76. package/dist/esm/pages/SendToWallet/SendToWalletPage.js.map +1 -1
  77. package/dist/esm/providers/WalletProvider/WalletProvider.d.ts.map +1 -1
  78. package/dist/esm/providers/WalletProvider/WalletProvider.js +14 -2
  79. package/dist/esm/providers/WalletProvider/WalletProvider.js.map +1 -1
  80. package/dist/esm/providers/WidgetProvider/WidgetProvider.d.ts.map +1 -1
  81. package/dist/esm/providers/WidgetProvider/WidgetProvider.js +10 -14
  82. package/dist/esm/providers/WidgetProvider/WidgetProvider.js.map +1 -1
  83. package/dist/esm/stores/bookmarks/types.d.ts +3 -0
  84. package/dist/esm/stores/bookmarks/types.d.ts.map +1 -1
  85. package/dist/esm/stores/chains/ChainOrderStore.d.ts.map +1 -1
  86. package/dist/esm/stores/chains/ChainOrderStore.js +4 -1
  87. package/dist/esm/stores/chains/ChainOrderStore.js.map +1 -1
  88. package/dist/esm/stores/form/FormStore.js +3 -2
  89. package/dist/esm/stores/form/FormStore.js.map +1 -1
  90. package/dist/esm/stores/form/URLSearchParamsBuilder.js +7 -3
  91. package/dist/esm/stores/form/URLSearchParamsBuilder.js.map +1 -1
  92. package/dist/esm/stores/form/getDefaultValuesFromQueryString.js +4 -1
  93. package/dist/esm/stores/form/getDefaultValuesFromQueryString.js.map +1 -1
  94. package/dist/esm/utils/chainType.js +32 -1
  95. package/dist/esm/utils/chainType.js.map +1 -1
  96. package/dist/esm/utils/customReceiver.js +6 -1
  97. package/dist/esm/utils/customReceiver.js.map +1 -1
  98. package/dist/esm/utils/token.js +13 -1
  99. package/dist/esm/utils/token.js.map +1 -1
  100. package/package.json +7 -7
  101. package/src/components/AmountInputCard/AmountInputCardPair.tsx +6 -2
  102. package/src/components/SelectChainAndToken.tsx +5 -1
  103. package/src/components/SendToWallet/SendToWalletButton.tsx +13 -8
  104. package/src/components/TokenList/TokenDetailsSheet.tsx +5 -9
  105. package/src/components/TokenList/TokenDetailsSheetContent.tsx +46 -4
  106. package/src/components/TokenList/TokenListItem.tsx +44 -68
  107. package/src/components/TokenList/VirtualizedTokenList.tsx +3 -6
  108. package/src/components/TokenList/tokenVerificationIcons.ts +15 -0
  109. package/src/components/TokenList/types.ts +3 -11
  110. package/src/components/TokenList/useTokenSelect.ts +15 -0
  111. package/src/config/constants.ts +3 -0
  112. package/src/config/version.ts +1 -1
  113. package/src/hooks/useAccountsBalancesData.ts +6 -2
  114. package/src/hooks/useAddressValidation.ts +87 -32
  115. package/src/hooks/useGasRefuel.ts +11 -1
  116. package/src/hooks/useRoutes.ts +23 -18
  117. package/src/hooks/useToAddressAutoPopulate.ts +13 -17
  118. package/src/hooks/useToAddressRequirements.ts +19 -4
  119. package/src/hooks/useToAddressReset.ts +13 -4
  120. package/src/hooks/useTokenBalance.ts +6 -1
  121. package/src/pages/SendToWallet/BookmarkAddressSheet.tsx +5 -1
  122. package/src/pages/SendToWallet/BookmarksPage.tsx +42 -52
  123. package/src/pages/SendToWallet/ConnectedWalletsPage.tsx +4 -14
  124. package/src/pages/SendToWallet/RecentWalletsPage.tsx +59 -66
  125. package/src/pages/SendToWallet/SendToConfiguredWalletPage.tsx +45 -50
  126. package/src/pages/SendToWallet/SendToWalletPage.tsx +4 -16
  127. package/src/providers/WalletProvider/WalletProvider.tsx +28 -3
  128. package/src/providers/WidgetProvider/WidgetProvider.tsx +20 -19
  129. package/src/stores/bookmarks/types.ts +3 -0
  130. package/src/stores/chains/ChainOrderStore.tsx +4 -1
  131. package/src/stores/form/FormStore.tsx +2 -0
  132. package/src/stores/form/URLSearchParamsBuilder.tsx +4 -2
  133. package/src/stores/form/getDefaultValuesFromQueryString.ts +19 -0
  134. package/src/utils/chainType.ts +73 -1
  135. package/src/utils/customReceiver.ts +36 -0
  136. package/src/utils/token.ts +48 -0
@@ -8,6 +8,11 @@ import { useTranslation } from 'react-i18next'
8
8
  import { useAvailableChains } from '../../hooks/useAvailableChains.js'
9
9
  import { useExplorer } from '../../hooks/useExplorer.js'
10
10
  import { useToken } from '../../hooks/useToken.js'
11
+ import type { TokenAmount } from '../../types/token.js'
12
+ import {
13
+ getTokenStatusTextKey,
14
+ getTokenVerificationBadge,
15
+ } from '../../utils/token.js'
11
16
  import { shortenAddress } from '../../utils/wallet.js'
12
17
  import { TokenAvatar } from '../Avatar/TokenAvatar.js'
13
18
  import { CardIconButton } from '../Card/CardIconButton.js'
@@ -17,10 +22,10 @@ import {
17
22
  TokenDetailsSheetContainer,
18
23
  TokenDetailsSheetHeader,
19
24
  } from './TokenDetailsSheetContent.style.js'
25
+ import { tokenVerificationIcons } from './tokenVerificationIcons.js'
20
26
 
21
27
  interface TokenDetailsSheetContentProps {
22
- tokenAddress: string | undefined
23
- chainId: number | undefined
28
+ listToken: TokenAmount | undefined
24
29
  withoutContractAddress: boolean
25
30
  onClose: () => void
26
31
  }
@@ -28,8 +33,7 @@ interface TokenDetailsSheetContentProps {
28
33
  const noDataLabel = '-'
29
34
 
30
35
  export const TokenDetailsSheetContent = ({
31
- tokenAddress,
32
- chainId,
36
+ listToken,
33
37
  withoutContractAddress,
34
38
  onClose,
35
39
  }: TokenDetailsSheetContentProps): JSX.Element => {
@@ -37,9 +41,22 @@ export const TokenDetailsSheetContent = ({
37
41
  const { getAddressLink } = useExplorer()
38
42
  const { getChainById } = useAvailableChains()
39
43
 
44
+ const tokenAddress = listToken?.address
45
+ const chainId = listToken?.chainId
40
46
  const { token, isLoading } = useToken(chainId, tokenAddress, true)
41
47
  const chain = useMemo(() => getChainById(chainId), [chainId, getChainById])
42
48
 
49
+ // The row token, not the fetched one: only the list sets the flags.
50
+ const statusBadge = listToken
51
+ ? getTokenVerificationBadge(listToken)
52
+ : undefined
53
+ const statusIcon = statusBadge
54
+ ? tokenVerificationIcons[statusBadge]
55
+ : undefined
56
+ const statusText = listToken
57
+ ? t(getTokenStatusTextKey(listToken))
58
+ : noDataLabel
59
+
43
60
  const copyContractAddress = async (e: React.MouseEvent) => {
44
61
  e.stopPropagation()
45
62
  try {
@@ -215,6 +232,31 @@ export const TokenDetailsSheetContent = ({
215
232
  : noDataLabel}
216
233
  </Typography>
217
234
  </MetricWithSkeleton>
235
+ <MetricContainer>
236
+ <Label>{t('tokenMetric.status')}</Label>
237
+ <Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1 }}>
238
+ {statusIcon ? (
239
+ <statusIcon.Icon
240
+ sx={{
241
+ flexShrink: 0,
242
+ fontSize: 20,
243
+ color: statusIcon.color,
244
+ }}
245
+ />
246
+ ) : null}
247
+ <Typography
248
+ sx={{
249
+ minWidth: 0,
250
+ fontWeight: 500,
251
+ fontSize: '14px',
252
+ lineHeight: '20px',
253
+ color: 'text.primary',
254
+ }}
255
+ >
256
+ {statusText}
257
+ </Typography>
258
+ </Box>
259
+ </MetricContainer>
218
260
  </TokenDetailsSheetContainer>
219
261
  )
220
262
  }
@@ -1,8 +1,6 @@
1
1
  import type { StaticToken } from '@lifi/sdk'
2
2
  import { ChainType } from '@lifi/sdk'
3
3
  import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'
4
- import ReportRoundedIcon from '@mui/icons-material/ReportRounded'
5
- import VerifiedIcon from '@mui/icons-material/Verified'
6
4
  import {
7
5
  Avatar,
8
6
  Box,
@@ -18,14 +16,19 @@ import type { JSX, MouseEventHandler } from 'react'
18
16
  import { memo, useMemo, useRef, useState } from 'react'
19
17
  import { useTranslation } from 'react-i18next'
20
18
  import { useLongPress } from '../../hooks/useLongPress.js'
19
+ import type { TokenAmount } from '../../types/token.js'
21
20
  import { formatTokenAmount, formatTokenPrice } from '../../utils/format.js'
22
- import { getTokenVerificationProvider } from '../../utils/token.js'
21
+ import {
22
+ getTokenVerificationBadge,
23
+ getTokenVerificationProvider,
24
+ } from '../../utils/token.js'
23
25
  import { shortenAddress } from '../../utils/wallet.js'
24
26
  import { TokenAvatar } from '../Avatar/TokenAvatar.js'
25
27
  import { ListItemButton } from '../ListItem/ListItemButton.js'
26
28
  import { PinTokenButton } from './PinTokenButton.js'
27
29
  import { RemoveRecentTokenButton } from './RemoveRecentTokenButton.js'
28
30
  import { IconButton, ListItem } from './TokenList.style.js'
31
+ import { tokenVerificationIcons } from './tokenVerificationIcons.js'
29
32
  import type {
30
33
  TokenListItemAvatarProps,
31
34
  TokenListItemButtonProps,
@@ -88,23 +91,17 @@ const TokenListItemAvatar = memo(({ token }: TokenListItemAvatarProps) => {
88
91
  })
89
92
 
90
93
  interface OpenTokenDetailsButtonProps {
91
- tokenAddress: string | undefined
94
+ token: TokenAmount
92
95
  withoutContractAddress: boolean
93
- chainId: number
94
- onClick: (
95
- tokenAddress: string,
96
- withoutContractAddress: boolean,
97
- chainId: number
98
- ) => void
96
+ onClick: (token: TokenAmount, withoutContractAddress: boolean) => void
99
97
  }
100
98
 
101
99
  const OpenTokenDetailsButton = ({
102
- tokenAddress,
100
+ token,
103
101
  withoutContractAddress,
104
- chainId,
105
102
  onClick,
106
103
  }: OpenTokenDetailsButtonProps) => {
107
- if (!tokenAddress) {
104
+ if (!token.address) {
108
105
  return null
109
106
  }
110
107
  return (
@@ -113,7 +110,7 @@ const OpenTokenDetailsButton = ({
113
110
  onClick={(e) => {
114
111
  e.stopPropagation()
115
112
  e.currentTarget.blur() // Remove focus to prevent accessibility issues when opening drawer
116
- onClick(tokenAddress, withoutContractAddress, chainId)
113
+ onClick(token, withoutContractAddress)
117
114
  }}
118
115
  >
119
116
  <InfoOutlinedIcon />
@@ -169,65 +166,46 @@ const TokenListItemButton: React.FC<TokenListItemButtonProps> = memo(
169
166
  )
170
167
 
171
168
  const longPressEvents = useLongPress(() =>
172
- onShowTokenDetails(token.address, withoutContractAddress, token.chainId)
169
+ onShowTokenDetails(token, withoutContractAddress)
173
170
  )
174
171
 
175
- // The native token is badged ahead of any verdict: the provider screens
176
- // the native-address convention and calls it a scam on some chains.
177
- // A token the main list or the integrator vouches for needs no warning;
178
- // anything else without a clean verdict keeps the amber one.
179
- let verificationBadge:
180
- | { Icon: typeof VerifiedIcon; color: string; title: string }
181
- | undefined
182
- if (token.native) {
183
- verificationBadge = {
184
- Icon: VerifiedIcon,
185
- color: 'info.main',
186
- title: chainName
172
+ const verificationBadge = getTokenVerificationBadge(token)
173
+ const verificationIcon = verificationBadge
174
+ ? tokenVerificationIcons[verificationBadge]
175
+ : undefined
176
+ let verificationTitle = ''
177
+ switch (verificationBadge) {
178
+ case 'native':
179
+ verificationTitle = chainName
187
180
  ? t('tooltip.tokenNativeOnChain', {
188
181
  tokenSymbol: token.symbol,
189
182
  chainName,
190
183
  })
191
- : t('tooltip.tokenNative', { tokenSymbol: token.symbol }),
192
- }
193
- } else if (token.verificationStatus === 'flagged') {
194
- verificationBadge = {
195
- Icon: ReportRoundedIcon,
196
- color: 'error.main',
197
- title: t('warning.message.tokenFlagged', {
184
+ : t('tooltip.tokenNative', { tokenSymbol: token.symbol })
185
+ break
186
+ case 'flagged':
187
+ verificationTitle = t('warning.message.tokenFlagged', {
198
188
  tokenSymbol: token.symbol,
199
- }),
200
- }
201
- } else if (token.verificationStatus === 'verified') {
202
- const provider = getTokenVerificationProvider(token)
203
- verificationBadge = {
204
- Icon: VerifiedIcon,
205
- color: 'success.main',
206
- title: provider
189
+ })
190
+ break
191
+ case 'verified': {
192
+ const provider = getTokenVerificationProvider(token)
193
+ verificationTitle = provider
207
194
  ? t('tooltip.tokenVerifiedByProvider', {
208
195
  tokenSymbol: token.symbol,
209
196
  provider,
210
197
  })
211
- : t('tooltip.tokenVerified', { tokenSymbol: token.symbol }),
198
+ : t('tooltip.tokenVerified', { tokenSymbol: token.symbol })
199
+ break
212
200
  }
213
- } else if (
214
- token.verificationStatus === 'unverified' &&
215
- !token.listed &&
216
- !token.verified
217
- ) {
218
- verificationBadge = {
219
- Icon: ReportRoundedIcon,
220
- color: 'warning.main',
221
- title: t('warning.message.tokenUnverifiedByProvider', {
201
+ case 'unverifiedByProvider':
202
+ verificationTitle = t('warning.message.tokenUnverifiedByProvider', {
222
203
  tokenSymbol: token.symbol,
223
- }),
224
- }
225
- } else if (!token.verificationStatus && !token.listed && !token.verified) {
226
- verificationBadge = {
227
- Icon: ReportRoundedIcon,
228
- color: 'warning.main',
229
- title: t('warning.message.tokenUnverified'),
230
- }
204
+ })
205
+ break
206
+ case 'unverified':
207
+ verificationTitle = t('warning.message.tokenUnverified')
208
+ break
231
209
  }
232
210
 
233
211
  return (
@@ -261,13 +239,13 @@ const TokenListItemButton: React.FC<TokenListItemButtonProps> = memo(
261
239
  primary={
262
240
  <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
263
241
  {token.symbol}
264
- {verificationBadge && (
265
- <Tooltip title={verificationBadge.title} placement="top" arrow>
266
- <verificationBadge.Icon
242
+ {verificationIcon && (
243
+ <Tooltip title={verificationTitle} placement="top" arrow>
244
+ <verificationIcon.Icon
267
245
  sx={{
268
246
  display: 'flex',
269
247
  fontSize: 16,
270
- color: verificationBadge.color,
248
+ color: verificationIcon.color,
271
249
  cursor: 'help',
272
250
  }}
273
251
  />
@@ -313,9 +291,8 @@ const TokenListItemButton: React.FC<TokenListItemButtonProps> = memo(
313
291
  >
314
292
  <Box>
315
293
  <OpenTokenDetailsButton
316
- tokenAddress={token.address}
294
+ token={token}
317
295
  withoutContractAddress={withoutContractAddress}
318
- chainId={token.chainId}
319
296
  onClick={onShowTokenDetails}
320
297
  />
321
298
  <PinTokenButton
@@ -385,9 +362,8 @@ const TokenListItemButton: React.FC<TokenListItemButtonProps> = memo(
385
362
  </Box>
386
363
  <Box>
387
364
  <OpenTokenDetailsButton
388
- tokenAddress={token.address}
365
+ token={token}
389
366
  withoutContractAddress={withoutContractAddress}
390
- chainId={token.chainId}
391
367
  onClick={onShowTokenDetails}
392
368
  />
393
369
  <PinTokenButton
@@ -5,6 +5,7 @@ import { useCallback, useEffect, useMemo, useRef } from 'react'
5
5
  import { useTranslation } from 'react-i18next'
6
6
  import { useAvailableChains } from '../../hooks/useAvailableChains.js'
7
7
  import { getTokenKey } from '../../stores/recentTokens/utils.js'
8
+ import type { TokenAmount } from '../../types/token.js'
8
9
  import { isHoistableNative } from '../../utils/tokenList.js'
9
10
  import { createBandResolver } from '../../utils/tokenListBands.js'
10
11
  import { RecentTokensHeader } from './RecentTokensHeader.js'
@@ -53,12 +54,8 @@ export const VirtualizedTokenList: FC<VirtualizedTokenListProps> = ({
53
54
  const tokenDetailsSheetRef = useRef<TokenDetailsSheetBase>(null)
54
55
 
55
56
  const onShowTokenDetails = useCallback(
56
- (tokenAddress: string, noContractAddress: boolean, chainId: number) => {
57
- tokenDetailsSheetRef.current?.open(
58
- tokenAddress,
59
- noContractAddress,
60
- chainId
61
- )
57
+ (token: TokenAmount, noContractAddress: boolean) => {
58
+ tokenDetailsSheetRef.current?.open(token, noContractAddress)
62
59
  },
63
60
  []
64
61
  )
@@ -0,0 +1,15 @@
1
+ import ReportRoundedIcon from '@mui/icons-material/ReportRounded'
2
+ import VerifiedIcon from '@mui/icons-material/Verified'
3
+ import type { SvgIcon } from '@mui/material'
4
+ import type { TokenVerificationBadge } from '../../utils/token.js'
5
+
6
+ export const tokenVerificationIcons: Record<
7
+ TokenVerificationBadge,
8
+ { Icon: typeof SvgIcon; color: string }
9
+ > = {
10
+ native: { Icon: VerifiedIcon, color: 'info.main' },
11
+ flagged: { Icon: ReportRoundedIcon, color: 'error.main' },
12
+ verified: { Icon: VerifiedIcon, color: 'success.main' },
13
+ unverifiedByProvider: { Icon: ReportRoundedIcon, color: 'warning.main' },
14
+ unverified: { Icon: ReportRoundedIcon, color: 'warning.main' },
15
+ }
@@ -38,11 +38,7 @@ interface TokenListItemBaseProps {
38
38
 
39
39
  export interface TokenListItemProps extends TokenListItemBaseProps {
40
40
  token: TokenAmount
41
- onShowTokenDetails: (
42
- tokenAddress: string,
43
- noContractAddress: boolean,
44
- chainId: number
45
- ) => void
41
+ onShowTokenDetails: (token: TokenAmount, noContractAddress: boolean) => void
46
42
  chain?: ExtendedChain
47
43
  /** Resolved for every row, unlike `chain`, which the list passes only in all-networks mode. */
48
44
  chainName?: string
@@ -53,11 +49,7 @@ export interface TokenListItemProps extends TokenListItemBaseProps {
53
49
  }
54
50
 
55
51
  export interface TokenListItemButtonProps {
56
- onShowTokenDetails: (
57
- tokenAddress: string,
58
- noContractAddress: boolean,
59
- chainId: number
60
- ) => void
52
+ onShowTokenDetails: (token: TokenAmount, noContractAddress: boolean) => void
61
53
  onClick?(tokenAddress: string, chainId?: number): void
62
54
  token: TokenAmount
63
55
  chain?: ExtendedChain
@@ -73,6 +65,6 @@ export interface TokenListItemAvatarProps {
73
65
 
74
66
  export interface TokenDetailsSheetBase {
75
67
  isOpen(): void
76
- open(address: string, noContractAddress: boolean, chainId: number): void
68
+ open(token: TokenAmount, noContractAddress: boolean): void
77
69
  close(): void
78
70
  }
@@ -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.10.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,