@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
@@ -1,3 +1,4 @@
1
+ import { useChainTypeFromAddress } from '@lifi/widget-provider'
1
2
  import ContentCopyRounded from '@mui/icons-material/ContentCopyRounded'
2
3
  import OpenInNewRounded from '@mui/icons-material/OpenInNewRounded'
3
4
  import { List, ListItemAvatar, ListItemText } from '@mui/material'
@@ -25,7 +26,8 @@ export const SendToConfiguredWalletPage = (): JSX.Element => {
25
26
  const { t } = useTranslation()
26
27
  const navigateBack = useNavigateBack()
27
28
  const { toAddresses } = useWidgetConfig()
28
- const { requiredToChainType } = useToAddressRequirements()
29
+ const { isValidReceiver } = useToAddressRequirements()
30
+ const { getChainFromAddress } = useChainTypeFromAddress()
29
31
  const { setSelectedBookmark } = useBookmarkActions()
30
32
  const { setFieldValue } = useFieldActions()
31
33
  const { getAddressLink } = useExplorer()
@@ -53,57 +55,50 @@ export const SendToConfiguredWalletPage = (): JSX.Element => {
53
55
  disablePadding
54
56
  sx={{ paddingTop: 1.5 }}
55
57
  >
56
- {toAddresses?.map((toAddress) => (
57
- <ListItem key={toAddress.address} sx={{ position: 'relative' }}>
58
- <ListItemButton
59
- disabled={
60
- requiredToChainType &&
61
- requiredToChainType !== toAddress.chainType
62
- }
63
- onClick={() => handleCuratedSelected(toAddress)}
64
- >
65
- <ListItemAvatar>
66
- <AccountAvatar
67
- chainId={defaultChainIdsByType[toAddress.chainType]}
68
- toAddress={toAddress}
58
+ {toAddresses?.map((toAddress) => {
59
+ const disabled = !isValidReceiver(toAddress.address)
60
+ // A configured receiver has no chainId; its provider knows the chain.
61
+ const chainId =
62
+ getChainFromAddress(toAddress.address)?.chainId ??
63
+ defaultChainIdsByType[toAddress.chainType]
64
+ return (
65
+ <ListItem key={toAddress.address} sx={{ position: 'relative' }}>
66
+ <ListItemButton
67
+ disabled={disabled}
68
+ onClick={() => handleCuratedSelected(toAddress)}
69
+ >
70
+ <ListItemAvatar>
71
+ <AccountAvatar chainId={chainId} toAddress={toAddress} />
72
+ </ListItemAvatar>
73
+ <ListItemText
74
+ primary={toAddress.name || shortenAddress(toAddress.address)}
75
+ secondary={
76
+ toAddress.name
77
+ ? shortenAddress(toAddress.address)
78
+ : undefined
79
+ }
69
80
  />
70
- </ListItemAvatar>
71
- <ListItemText
72
- primary={toAddress.name || shortenAddress(toAddress.address)}
73
- secondary={
74
- toAddress.name ? shortenAddress(toAddress.address) : undefined
75
- }
81
+ </ListItemButton>
82
+ <ContextMenu
83
+ disabled={disabled}
84
+ items={[
85
+ {
86
+ icon: <ContentCopyRounded />,
87
+ label: t('button.copyAddress'),
88
+ onClick: () =>
89
+ navigator.clipboard.writeText(toAddress.address),
90
+ },
91
+ {
92
+ icon: <OpenInNewRounded />,
93
+ label: t('button.viewOnExplorer'),
94
+ onClick: () =>
95
+ openInNewTab(getAddressLink(toAddress.address, chainId)),
96
+ },
97
+ ]}
76
98
  />
77
- </ListItemButton>
78
- <ContextMenu
79
- disabled={
80
- !!(
81
- requiredToChainType &&
82
- requiredToChainType !== toAddress.chainType
83
- )
84
- }
85
- items={[
86
- {
87
- icon: <ContentCopyRounded />,
88
- label: t('button.copyAddress'),
89
- onClick: () =>
90
- navigator.clipboard.writeText(toAddress.address),
91
- },
92
- {
93
- icon: <OpenInNewRounded />,
94
- label: t('button.viewOnExplorer'),
95
- onClick: () =>
96
- openInNewTab(
97
- getAddressLink(
98
- toAddress.address,
99
- defaultChainIdsByType[toAddress.chainType]
100
- )
101
- ),
102
- },
103
- ]}
104
- />
105
- </ListItem>
106
- ))}
99
+ </ListItem>
100
+ )
101
+ })}
107
102
  </List>
108
103
  </PageContainer>
109
104
  )
@@ -12,13 +12,13 @@ import type { BottomSheetBase } from '../../components/BottomSheet/types.js'
12
12
  import { ButtonTertiary } from '../../components/ButtonTertiary.js'
13
13
  import { CardButton } from '../../components/Card/CardButton.js'
14
14
  import { AccountDeployedMessage } from '../../components/Messages/AccountDeployedMessage.js'
15
+ import { addressInputMaxLength } from '../../config/constants.js'
15
16
  import {
16
17
  AddressType,
17
18
  useAddressValidation,
18
19
  } from '../../hooks/useAddressValidation.js'
19
20
  import { useChain } from '../../hooks/useChain.js'
20
21
  import { useHeader } from '../../hooks/useHeader.js'
21
- import { useToAddressRequirements } from '../../hooks/useToAddressRequirements.js'
22
22
  import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'
23
23
  import type { Bookmark } from '../../stores/bookmarks/types.js'
24
24
  import { useBookmarkActions } from '../../stores/bookmarks/useBookmarkActions.js'
@@ -57,7 +57,6 @@ export const SendToWalletPage: React.FC = () => {
57
57
  const [validatedWallet, setValidatedWallet] = useState<Bookmark>()
58
58
  const [errorMessage, setErrorMessage] = useState('')
59
59
  const { validateAddress, isValidating } = useAddressValidation()
60
- const { requiredToChainType } = useToAddressRequirements()
61
60
  const [toChainId] = useFieldValues('toChain')
62
61
  const { chain: toChain } = useChain(toChainId)
63
62
  const [isDoneButtonLoading, setIsDoneButtonLoading] = useState(false)
@@ -91,7 +90,6 @@ export const SendToWalletPage: React.FC = () => {
91
90
  setIsDoneButtonLoading(true)
92
91
  const validationResult = await validateAddress({
93
92
  value: inputAddressValue,
94
- chainType: requiredToChainType,
95
93
  chain: toChain,
96
94
  })
97
95
  setIsDoneButtonLoading(false)
@@ -100,18 +98,6 @@ export const SendToWalletPage: React.FC = () => {
100
98
  return
101
99
  }
102
100
 
103
- if (
104
- requiredToChainType &&
105
- requiredToChainType !== validationResult.chainType
106
- ) {
107
- setErrorMessage(
108
- t('error.title.walletChainTypeInvalid', {
109
- chainName: toChain?.name,
110
- })
111
- )
112
- return
113
- }
114
-
115
101
  setValidatedWallet({
116
102
  name:
117
103
  validationResult.addressType === AddressType.NameService
@@ -119,6 +105,7 @@ export const SendToWalletPage: React.FC = () => {
119
105
  : undefined,
120
106
  address: validationResult.address,
121
107
  chainType: validationResult.chainType,
108
+ chainId: validationResult.chainId,
122
109
  })
123
110
  confirmAddressSheetRef.current?.open()
124
111
  }
@@ -155,6 +142,7 @@ export const SendToWalletPage: React.FC = () => {
155
142
  : undefined,
156
143
  address: validationResult.address,
157
144
  chainType: validationResult.chainType,
145
+ chainId: validationResult.chainId,
158
146
  })
159
147
  bookmarkAddressSheetRef.current?.open()
160
148
  } else {
@@ -213,7 +201,7 @@ export const SendToWalletPage: React.FC = () => {
213
201
  placeholder={placeholder}
214
202
  aria-label={placeholder}
215
203
  maxRows={2}
216
- inputProps={{ maxLength: 128 }}
204
+ inputProps={{ maxLength: addressInputMaxLength }}
217
205
  multiline
218
206
  />
219
207
  {errorMessage ? (
@@ -1,5 +1,8 @@
1
1
  import { WalletManagementProviders } from '@lifi/wallet-management'
2
- import type { WidgetProviderProps } from '@lifi/widget-provider'
2
+ import {
3
+ useSDKProviders,
4
+ type WidgetProviderProps,
5
+ } from '@lifi/widget-provider'
3
6
  import {
4
7
  type FC,
5
8
  type JSX,
@@ -10,8 +13,15 @@ import {
10
13
  import { useTranslation } from 'react-i18next'
11
14
  import { useAvailableChains } from '../../hooks/useAvailableChains.js'
12
15
  import { useInitializeSDKProviders } from '../../hooks/useInitializeSDKProviders.js'
16
+ import {
17
+ withoutUnservedToChain,
18
+ withServedDestinationChains,
19
+ } from '../../utils/chainType.js'
13
20
  import { getConfigItemSets, isItemAllowedForSets } from '../../utils/item.js'
14
- import { useWidgetConfig } from '../WidgetProvider/WidgetProvider.js'
21
+ import {
22
+ useWidgetConfig,
23
+ WidgetContext,
24
+ } from '../WidgetProvider/WidgetProvider.js'
15
25
  import { useExternalWalletProvider } from './useExternalWalletProvider.js'
16
26
 
17
27
  interface WalletProviderProps extends PropsWithChildren {
@@ -78,5 +88,20 @@ export const WalletProvider = ({
78
88
 
79
89
  const SDKProviderInitializer: FC<PropsWithChildren> = ({ children }) => {
80
90
  useInitializeSDKProviders()
81
- return children
91
+ const config = useWidgetConfig()
92
+ const sdkProviders = useSDKProviders()
93
+ // Only below the provider components is it known which chains are served.
94
+ // Keyed on `config.chains`: the config object is new on every render.
95
+ const chains = useMemo(
96
+ () => withServedDestinationChains(config.chains, sdkProviders),
97
+ [config.chains, sdkProviders]
98
+ )
99
+ const value = useMemo(
100
+ () =>
101
+ withoutUnservedToChain(
102
+ chains === config.chains ? config : { ...config, chains }
103
+ ),
104
+ [config, chains]
105
+ )
106
+ return <WidgetContext value={value}>{children}</WidgetContext>
82
107
  }
@@ -1,6 +1,10 @@
1
1
  import type { Context } from 'react'
2
2
  import { createContext, use, useId, useMemo } from 'react'
3
3
  import { useSettingsActions } from '../../stores/settings/useSettingsActions.js'
4
+ import {
5
+ isDestinationOnlyChain,
6
+ withDestinationOnlyChains,
7
+ } from '../../utils/chainType.js'
4
8
  import {
5
9
  defaultQueryScopeKey,
6
10
  resolveQueryScopeKey,
@@ -29,33 +33,30 @@ export const WidgetProvider: React.FC<
29
33
  throw new Error('Required property "integrator" is missing.')
30
34
  }
31
35
 
36
+ // Keyed on the integrator's `chains`: the config object is new on every render.
37
+ const chains = useMemo(
38
+ () => withDestinationOnlyChains(widgetConfig.chains),
39
+ [widgetConfig.chains]
40
+ )
41
+
32
42
  const value = useMemo((): WidgetContextProps => {
33
- // Resolved once, so nothing below reads the deprecated keyPrefix.
34
- const scopeKeys = {
43
+ // Omitted, not undefined: an own undefined key resets the form field.
44
+ const { fromChain, fromToken, ...config } = widgetConfig
45
+ const value = {
46
+ ...(isDestinationOnlyChain(fromChain) ? config : widgetConfig),
47
+ chains,
48
+ // Resolved once, so nothing below reads the deprecated keyPrefix.
35
49
  storageScopeKey: resolveStorageScopeKey(widgetConfig),
36
50
  queryScopeKey: resolveQueryScopeKey(widgetConfig),
37
- }
51
+ elementId,
52
+ } as WidgetContextProps
38
53
  try {
39
- // Create widget configuration object
40
- const value = {
41
- ...widgetConfig,
42
- ...scopeKeys,
43
- elementId,
44
- } as WidgetContextProps
45
-
46
54
  // Set default settings for widget settings store
47
55
  setDefaultSettings(value)
48
-
49
- return value
50
56
  } catch (e) {
51
57
  console.warn(e)
52
- return {
53
- ...widgetConfig,
54
- ...scopeKeys,
55
- elementId,
56
- integrator: widgetConfig.integrator,
57
- }
58
58
  }
59
- }, [elementId, widgetConfig, setDefaultSettings])
59
+ return value
60
+ }, [elementId, widgetConfig, chains, setDefaultSettings])
60
61
  return <WidgetContext value={value}>{children}</WidgetContext>
61
62
  }
@@ -1,8 +1,11 @@
1
+ import type { ChainId } from '@lifi/sdk'
1
2
  import type { StoreApi, UseBoundStore } from 'zustand'
2
3
  import type { ToAddress } from '../../types/widget.js'
3
4
 
4
5
  export interface Bookmark extends ToAddress {
5
6
  isConnectedAccount?: boolean
7
+ /** Display only: set when the ecosystem's default chain rejects the address. */
8
+ chainId?: ChainId
6
9
  }
7
10
 
8
11
  export interface BookmarkProps {
@@ -80,7 +80,10 @@ export function ChainOrderStoreProvider({
80
80
 
81
81
  // Initialize the isAllNetworks with true if the tab is shown,
82
82
  // there is no config chain value and no url chain value
83
- const urlValues = getDefaultValuesFromQueryString({ buildUrl })
83
+ const urlValues = getDefaultValuesFromQueryString({
84
+ buildUrl,
85
+ chains: chainsConfig,
86
+ })
84
87
  const urlChainValue =
85
88
  key === 'from' ? urlValues.fromChain : urlValues.toChain
86
89
  const configChainValue =
@@ -45,6 +45,7 @@ export const FormStoreProvider: React.FC<FormStoreProviderProps> = ({
45
45
  toAddress,
46
46
  formUpdateKey,
47
47
  buildUrl,
48
+ chains,
48
49
  } = widgetConfig
49
50
 
50
51
  const storeRef = useRef<FormStoreStore>(null)
@@ -113,6 +114,7 @@ export const FormStoreProvider: React.FC<FormStoreProviderProps> = ({
113
114
  const queryDefaults = getDefaultValuesFromQueryString({
114
115
  buildUrl,
115
116
  includeToAddress: false,
117
+ chains,
116
118
  })
117
119
  storeRef.current = createFormStore(
118
120
  initialiseDefaultValues({
@@ -34,16 +34,17 @@ export const URLSearchParamsBuilder = () => {
34
34
  const values = useFieldValues(...formValueKeys, ...limitFormValueKeys)
35
35
  const { setSelectedBookmark, addRecentWallet } = useBookmarkActions()
36
36
  const { validateAddress } = useAddressValidation()
37
- const { buildUrl, mode } = useWidgetConfig()
37
+ const { buildUrl, mode, chains } = useWidgetConfig()
38
38
  // Using these methods as trying to use the touchedFields and values above
39
39
  // often has a lag that can effect the widgets initialisation sequence
40
40
  // and accidentally cause values to be wiped from the query string
41
41
  const { getFieldValues, isTouched, setUserAndDefaultValues } =
42
42
  useFieldActions()
43
43
 
44
+ // biome-ignore lint/correctness/useExhaustiveDependencies: the URL is read once, not again on each config render
44
45
  useEffect(() => {
45
46
  // get the initial values from the querystring
46
- const formValues = getDefaultValuesFromQueryString({ buildUrl })
47
+ const formValues = getDefaultValuesFromQueryString({ buildUrl, chains })
47
48
  const { toAddress, ...initialFormValues } = formValues
48
49
 
49
50
  /**
@@ -62,6 +63,7 @@ export const URLSearchParamsBuilder = () => {
62
63
  const bookmark = {
63
64
  address: validationResult.address,
64
65
  chainType: validationResult.chainType,
66
+ chainId: validationResult.chainId,
65
67
  }
66
68
  setUserAndDefaultValues({ toAddress })
67
69
  setSelectedBookmark(bookmark)
@@ -1,14 +1,21 @@
1
+ import type { WidgetChains } from '../../types/widget.js'
2
+ import {
3
+ isDestinationOnlyChain,
4
+ isUnservedDestinationChain,
5
+ } from '../../utils/chainType.js'
1
6
  import { formatInputAmount } from '../../utils/format.js'
2
7
  import type { DefaultValues } from './types.js'
3
8
 
4
9
  interface GetDefaultValuesFromQueryStringOptions {
5
10
  includeToAddress?: boolean
6
11
  buildUrl?: boolean
12
+ chains?: WidgetChains
7
13
  }
8
14
 
9
15
  export const getDefaultValuesFromQueryString = ({
10
16
  buildUrl = false,
11
17
  includeToAddress = true,
18
+ chains,
12
19
  }: GetDefaultValuesFromQueryStringOptions): Partial<DefaultValues> => {
13
20
  if (typeof window === 'undefined') {
14
21
  return {}
@@ -22,6 +29,18 @@ export const getDefaultValuesFromQueryString = ({
22
29
  new URLSearchParams(window.location.search)
23
30
  )
24
31
 
32
+ // The loop below then drops their tokens too.
33
+ if (isDestinationOnlyChain(Number.parseInt(searchParams.fromChain, 10))) {
34
+ delete searchParams.fromChain
35
+ }
36
+ if (
37
+ isUnservedDestinationChain(
38
+ Number.parseInt(searchParams.toChain, 10),
39
+ chains
40
+ )
41
+ ) {
42
+ delete searchParams.toChain
43
+ }
25
44
  // Prevent using fromToken/toToken params if chain is not selected.
26
45
  ;(['from', 'to'] as const).forEach((key) => {
27
46
  if (searchParams[`${key}Token`] && !searchParams[`${key}Chain`]) {
@@ -1,4 +1,6 @@
1
- import { ChainId, ChainType } from '@lifi/sdk'
1
+ import { ChainId, ChainType, type SDKProvider } from '@lifi/sdk'
2
+ import type { WidgetChains, WidgetConfig } from '../types/widget.js'
3
+ import { isItemAllowed } from './item.js'
2
4
 
3
5
  export const defaultChainIdsByType: Record<ChainType, ChainId> = {
4
6
  [ChainType.EVM]: ChainId.ETH,
@@ -8,3 +10,73 @@ export const defaultChainIdsByType: Record<ChainType, ChainId> = {
8
10
  [ChainType.TVM]: ChainId.TRN,
9
11
  [ChainType.STL]: ChainId.XLM,
10
12
  }
13
+
14
+ // No wallet can sign on these chains yet, so a route may only deliver to them.
15
+ const destinationOnlyChainIds: ReadonlySet<number> = new Set<number>([
16
+ ChainId.ZEC,
17
+ ])
18
+
19
+ export const isDestinationOnlyChain = (chainId?: number): boolean =>
20
+ chainId !== undefined && destinationOnlyChainIds.has(chainId)
21
+
22
+ export const withDestinationOnlyChains = (
23
+ chains: WidgetChains | undefined
24
+ ): WidgetChains => {
25
+ const allow = chains?.from?.allow?.filter(
26
+ (chainId) => !isDestinationOnlyChain(chainId)
27
+ )
28
+ return {
29
+ ...chains,
30
+ from: {
31
+ ...chains?.from,
32
+ ...(allow && { allow }),
33
+ deny: [...(chains?.from?.deny ?? []), ...destinationOnlyChainIds],
34
+ },
35
+ }
36
+ }
37
+
38
+ // A destination-only chain takes a receiver only through a provider that lists it.
39
+ export const withServedDestinationChains = (
40
+ chains: WidgetChains | undefined,
41
+ providers: readonly Pick<SDKProvider, 'chainIds'>[]
42
+ ): WidgetChains | undefined => {
43
+ const unserved = [...destinationOnlyChainIds].filter(
44
+ (chainId) =>
45
+ !providers.some((provider) =>
46
+ provider.chainIds?.some((id) => id === chainId)
47
+ )
48
+ )
49
+ if (!unserved.length) {
50
+ return chains
51
+ }
52
+ const allow = chains?.allow?.filter((chainId) => !unserved.includes(chainId))
53
+ return {
54
+ ...chains,
55
+ ...(allow && { allow }),
56
+ deny: [...(chains?.deny ?? []), ...unserved],
57
+ }
58
+ }
59
+
60
+ // The form keeps a preset chain that the chain list does not offer.
61
+ export const isUnservedDestinationChain = (
62
+ chainId: number | undefined,
63
+ chains: WidgetChains | undefined
64
+ ): boolean => isDestinationOnlyChain(chainId) && !isItemAllowed(chainId, chains)
65
+
66
+ // Omitted, not undefined: an own undefined key resets the form field.
67
+ export const withoutUnservedToChain = <
68
+ T extends Pick<WidgetConfig, 'chains' | 'toChain' | 'toToken'>,
69
+ >(
70
+ config: T
71
+ ): T => {
72
+ if (!isUnservedDestinationChain(config.toChain, config.chains)) {
73
+ return config
74
+ }
75
+ const { toChain: _toChain, toToken: _toToken, ...rest } = config
76
+ return rest as T
77
+ }
78
+
79
+ export const isDestinationAllowedAsSource = (
80
+ toChainId: number | undefined,
81
+ chains: WidgetChains | undefined
82
+ ): boolean => !toChainId || isItemAllowed(toChainId, chains?.from)
@@ -1,4 +1,5 @@
1
1
  import { ChainType } from '@lifi/sdk'
2
+ import type { ChainRef, IsAddressForChain } from '@lifi/widget-provider'
2
3
 
3
4
  // Stellar routes always settle to the account that signs them.
4
5
  export const isCustomReceiverUnsupported = (
@@ -29,3 +30,38 @@ export const isCustomReceiverBlocked = ({
29
30
  }
30
31
  return toAddress.toLowerCase() !== signerAddress?.toLowerCase()
31
32
  }
33
+
34
+ // Without a receiver the API delivers to the signer when both chains share a
35
+ // chain type, and a Bitcoin signer cannot receive on ZEC.
36
+ export const isSignerInvalidReceiver = ({
37
+ signerAddress,
38
+ toAddress,
39
+ fromChain,
40
+ toChain,
41
+ isAddressForChain,
42
+ }: {
43
+ signerAddress: string | undefined
44
+ toAddress: string | undefined
45
+ fromChain: ChainRef | undefined
46
+ toChain: ChainRef | undefined
47
+ isAddressForChain: IsAddressForChain
48
+ }): boolean =>
49
+ Boolean(
50
+ signerAddress &&
51
+ !toAddress &&
52
+ fromChain &&
53
+ toChain &&
54
+ fromChain.chainType === toChain.chainType &&
55
+ !isAddressForChain(signerAddress, toChain)
56
+ )
57
+
58
+ export const canQuoteWithToAddress = (
59
+ toAddress: string | undefined,
60
+ toChain: ChainRef | undefined,
61
+ isAddressForChain: IsAddressForChain
62
+ ): boolean => {
63
+ if (!toAddress) {
64
+ return true
65
+ }
66
+ return toChain ? isAddressForChain(toAddress, toChain) : false
67
+ }
@@ -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