@lifi/widget 4.9.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 (43) hide show
  1. package/README.md +26 -3
  2. package/dist/esm/components/TokenList/TokenDetailsSheet.js +5 -8
  3. package/dist/esm/components/TokenList/TokenDetailsSheet.js.map +1 -1
  4. package/dist/esm/components/TokenList/TokenDetailsSheetContent.js +30 -2
  5. package/dist/esm/components/TokenList/TokenDetailsSheetContent.js.map +1 -1
  6. package/dist/esm/components/TokenList/TokenListItem.js +36 -47
  7. package/dist/esm/components/TokenList/TokenListItem.js.map +1 -1
  8. package/dist/esm/components/TokenList/VirtualizedTokenList.d.ts.map +1 -1
  9. package/dist/esm/components/TokenList/VirtualizedTokenList.js +2 -2
  10. package/dist/esm/components/TokenList/VirtualizedTokenList.js.map +1 -1
  11. package/dist/esm/components/TokenList/tokenVerificationIcons.js +29 -0
  12. package/dist/esm/components/TokenList/tokenVerificationIcons.js.map +1 -0
  13. package/dist/esm/config/version.d.ts +1 -1
  14. package/dist/esm/config/version.js +1 -1
  15. package/dist/esm/config/version.js.map +1 -1
  16. package/dist/esm/i18n/bn.json +5 -1
  17. package/dist/esm/i18n/de.json +5 -1
  18. package/dist/esm/i18n/en.json +5 -1
  19. package/dist/esm/i18n/es.json +5 -1
  20. package/dist/esm/i18n/fr.json +5 -1
  21. package/dist/esm/i18n/hi.json +5 -1
  22. package/dist/esm/i18n/id.json +5 -1
  23. package/dist/esm/i18n/it.json +5 -1
  24. package/dist/esm/i18n/ja.json +5 -1
  25. package/dist/esm/i18n/ko.json +5 -1
  26. package/dist/esm/i18n/pl.json +5 -1
  27. package/dist/esm/i18n/pt.json +5 -1
  28. package/dist/esm/i18n/th.json +5 -1
  29. package/dist/esm/i18n/tr.json +5 -1
  30. package/dist/esm/i18n/uk.json +5 -1
  31. package/dist/esm/i18n/vi.json +5 -1
  32. package/dist/esm/i18n/zh.json +5 -1
  33. package/dist/esm/utils/token.js +13 -1
  34. package/dist/esm/utils/token.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/components/TokenList/TokenDetailsSheet.tsx +5 -9
  37. package/src/components/TokenList/TokenDetailsSheetContent.tsx +46 -4
  38. package/src/components/TokenList/TokenListItem.tsx +44 -68
  39. package/src/components/TokenList/VirtualizedTokenList.tsx +3 -6
  40. package/src/components/TokenList/tokenVerificationIcons.ts +15 -0
  41. package/src/components/TokenList/types.ts +3 -11
  42. package/src/config/version.ts +1 -1
  43. package/src/utils/token.ts +48 -0
@@ -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,2 +1,2 @@
1
1
  export const name = '@lifi/widget'
2
- export const version = '4.9.0'
2
+ export const version = '4.10.0'
@@ -280,6 +280,54 @@ export const getTokenVerificationProvider = (
280
280
  : undefined
281
281
  }
282
282
 
283
+ export type TokenVerificationBadge =
284
+ | 'native'
285
+ | 'flagged'
286
+ | 'verified'
287
+ | 'unverifiedByProvider'
288
+ | 'unverified'
289
+
290
+ export const getTokenVerificationBadge = (
291
+ token: Pick<
292
+ TokenWithFlags,
293
+ 'native' | 'listed' | 'verified' | 'verificationStatus'
294
+ >
295
+ ): TokenVerificationBadge | undefined => {
296
+ // The provider calls the native-address convention a scam on some chains.
297
+ if (token.native) {
298
+ return 'native'
299
+ }
300
+ if (token.verificationStatus === 'flagged') {
301
+ return 'flagged'
302
+ }
303
+ if (token.verificationStatus === 'verified') {
304
+ return 'verified'
305
+ }
306
+ if (token.listed || token.verified) {
307
+ return undefined
308
+ }
309
+ return token.verificationStatus === 'unverified'
310
+ ? 'unverifiedByProvider'
311
+ : 'unverified'
312
+ }
313
+
314
+ export type TokenStatusTextKey =
315
+ | 'tokenMetric.statusVerified'
316
+ | 'tokenMetric.statusFlagged'
317
+ | 'tokenMetric.statusUnverified'
318
+
319
+ export const getTokenStatusTextKey = (
320
+ token: Pick<TokenWithFlags, 'native' | 'verificationStatus'>
321
+ ): TokenStatusTextKey => {
322
+ if (token.verificationStatus === 'verified') {
323
+ return 'tokenMetric.statusVerified'
324
+ }
325
+ if (token.verificationStatus === 'flagged' && !token.native) {
326
+ return 'tokenMetric.statusFlagged'
327
+ }
328
+ return 'tokenMetric.statusUnverified'
329
+ }
330
+
283
331
  /**
284
332
  * Hyperliquid declares Arbitrum USDC as its native token, Lighter declares
285
333
  * Ethereum USDC. Neither is a gas token you hold there. A chain-local