@lifi/widget 4.5.0 → 4.6.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.
- package/dist/esm/AppDrawer.js +2 -1
- package/dist/esm/AppDrawer.js.map +1 -1
- package/dist/esm/components/AmountInput/AmountInput.js +13 -7
- package/dist/esm/components/AmountInput/AmountInput.js.map +1 -1
- package/dist/esm/components/AmountInput/AmountInputEndAdornment.js +2 -13
- package/dist/esm/components/AmountInput/AmountInputEndAdornment.js.map +1 -1
- package/dist/esm/components/AmountInputCard/PercentageChips.js +2 -13
- package/dist/esm/components/AmountInputCard/PercentageChips.js.map +1 -1
- package/dist/esm/components/AmountInputCard/PercentageChips.style.js +1 -1
- package/dist/esm/components/AmountInputCard/PercentageChips.style.js.map +1 -1
- package/dist/esm/components/AmountInputCard/SendAmountCard.js +13 -6
- package/dist/esm/components/AmountInputCard/SendAmountCard.js.map +1 -1
- package/dist/esm/components/Chains/AllChainsAvatar.js +7 -1
- package/dist/esm/components/Chains/AllChainsAvatar.js.map +1 -1
- package/dist/esm/components/Dialog/Dialog.js +2 -1
- package/dist/esm/components/Dialog/Dialog.js.map +1 -1
- package/dist/esm/components/Expansion/Expansion.js +4 -3
- package/dist/esm/components/Expansion/Expansion.js.map +1 -1
- package/dist/esm/components/Header/SettingsButton.js +2 -1
- package/dist/esm/components/Header/SettingsButton.js.map +1 -1
- package/dist/esm/components/IconCircle/IconCircle.js +2 -1
- package/dist/esm/components/IconCircle/IconCircle.js.map +1 -1
- package/dist/esm/components/Messages/UnsupportedToAddressMessage.js +27 -0
- package/dist/esm/components/Messages/UnsupportedToAddressMessage.js.map +1 -0
- package/dist/esm/components/Messages/WarningMessages.d.ts.map +1 -1
- package/dist/esm/components/Messages/WarningMessages.js +2 -0
- package/dist/esm/components/Messages/WarningMessages.js.map +1 -1
- package/dist/esm/components/Messages/useMessageQueue.js +6 -1
- package/dist/esm/components/Messages/useMessageQueue.js.map +1 -1
- package/dist/esm/components/RouteCard/RouteDetails.js +2 -1
- package/dist/esm/components/RouteCard/RouteDetails.js.map +1 -1
- package/dist/esm/components/Routes/LimitOrderRoutes.js +2 -2
- package/dist/esm/components/Routes/LimitOrderRoutes.js.map +1 -1
- package/dist/esm/components/Routes/RoutesContent.js +2 -2
- package/dist/esm/components/Routes/RoutesContent.js.map +1 -1
- package/dist/esm/components/SendToWallet/SendToWalletExpandButton.js +2 -2
- package/dist/esm/components/SendToWallet/SendToWalletExpandButton.js.map +1 -1
- package/dist/esm/components/Skeleton/WidgetSkeleton.js +2 -1
- package/dist/esm/components/Skeleton/WidgetSkeleton.js.map +1 -1
- package/dist/esm/components/Step/StepActions.js +18 -16
- package/dist/esm/components/Step/StepActions.js.map +1 -1
- package/dist/esm/components/StepActions/StepActionRow.js +2 -1
- package/dist/esm/components/StepActions/StepActionRow.js.map +1 -1
- package/dist/esm/components/Switch.js +1 -1
- package/dist/esm/components/Switch.js.map +1 -1
- package/dist/esm/components/Tabs/Tabs.style.js +4 -2
- package/dist/esm/components/Tabs/Tabs.style.js.map +1 -1
- package/dist/esm/components/TokenList/TokenListItem.js +46 -8
- package/dist/esm/components/TokenList/TokenListItem.js.map +1 -1
- package/dist/esm/components/TokenList/VirtualizedTokenList.js +22 -9
- package/dist/esm/components/TokenList/VirtualizedTokenList.js.map +1 -1
- package/dist/esm/components/TokenRate/TokenRate.js +2 -1
- package/dist/esm/components/TokenRate/TokenRate.js.map +1 -1
- package/dist/esm/components/TransactionCard/ActiveTransactionCard.js +22 -21
- package/dist/esm/components/TransactionCard/ActiveTransactionCard.js.map +1 -1
- package/dist/esm/config/version.d.ts +1 -1
- package/dist/esm/config/version.js +1 -1
- package/dist/esm/config/version.js.map +1 -1
- package/dist/esm/hooks/timer/time.js +2 -2
- package/dist/esm/hooks/timer/time.js.map +1 -1
- package/dist/esm/hooks/useAvailableChains.d.ts.map +1 -1
- package/dist/esm/hooks/useAvailableChains.js +2 -1
- package/dist/esm/hooks/useAvailableChains.js.map +1 -1
- package/dist/esm/hooks/useExplorer.d.ts.map +1 -1
- package/dist/esm/hooks/useExplorer.js +4 -0
- package/dist/esm/hooks/useExplorer.js.map +1 -1
- package/dist/esm/hooks/useFlaggedTokenGuard.js +28 -0
- package/dist/esm/hooks/useFlaggedTokenGuard.js.map +1 -0
- package/dist/esm/hooks/useGasSufficiency.js +3 -1
- package/dist/esm/hooks/useGasSufficiency.js.map +1 -1
- package/dist/esm/hooks/useMaxSendAmount.js +15 -0
- package/dist/esm/hooks/useMaxSendAmount.js.map +1 -0
- package/dist/esm/hooks/usePacedRouteExecution.js +3 -2
- package/dist/esm/hooks/usePacedRouteExecution.js.map +1 -1
- package/dist/esm/hooks/useRouteExecutionMessage.js +3 -2
- package/dist/esm/hooks/useRouteExecutionMessage.js.map +1 -1
- package/dist/esm/hooks/useRoutes.d.ts.map +1 -1
- package/dist/esm/hooks/useRoutes.js +3 -1
- package/dist/esm/hooks/useRoutes.js.map +1 -1
- package/dist/esm/hooks/useToAddressRequirements.d.ts +2 -0
- package/dist/esm/hooks/useToAddressRequirements.d.ts.map +1 -1
- package/dist/esm/hooks/useToAddressRequirements.js +7 -1
- package/dist/esm/hooks/useToAddressRequirements.js.map +1 -1
- package/dist/esm/hooks/useTokenBalances.d.ts.map +1 -1
- package/dist/esm/hooks/useTokenBalances.js +7 -2
- package/dist/esm/hooks/useTokenBalances.js.map +1 -1
- package/dist/esm/hooks/useTokenBalancesQueries.js +29 -28
- package/dist/esm/hooks/useTokenBalancesQueries.js.map +1 -1
- package/dist/esm/hooks/useTokenSearch.js +3 -5
- package/dist/esm/hooks/useTokenSearch.js.map +1 -1
- package/dist/esm/hooks/useTokens.js +14 -10
- package/dist/esm/hooks/useTokens.js.map +1 -1
- package/dist/esm/hooks/useWideVariant.js +2 -1
- package/dist/esm/hooks/useWideVariant.js.map +1 -1
- package/dist/esm/i18n/bn.json +3 -1
- package/dist/esm/i18n/de.json +3 -1
- package/dist/esm/i18n/en.json +12 -3
- package/dist/esm/i18n/es.json +3 -1
- package/dist/esm/i18n/fr.json +3 -1
- package/dist/esm/i18n/hi.json +3 -1
- package/dist/esm/i18n/id.json +3 -1
- package/dist/esm/i18n/it.json +3 -1
- package/dist/esm/i18n/ja.json +3 -1
- package/dist/esm/i18n/ko.json +3 -1
- package/dist/esm/i18n/pl.json +3 -1
- package/dist/esm/i18n/pt.json +3 -1
- package/dist/esm/i18n/th.json +3 -1
- package/dist/esm/i18n/tr.json +3 -1
- package/dist/esm/i18n/uk.json +3 -1
- package/dist/esm/i18n/vi.json +3 -1
- package/dist/esm/i18n/zh.json +3 -1
- package/dist/esm/pages/MainPage/MainPage.js +2 -1
- package/dist/esm/pages/MainPage/MainPage.js.map +1 -1
- package/dist/esm/pages/MainPage/ReviewButton.js +2 -2
- package/dist/esm/pages/MainPage/ReviewButton.js.map +1 -1
- package/dist/esm/pages/RoutesPage/RoutesPage.js +2 -2
- package/dist/esm/pages/RoutesPage/RoutesPage.js.map +1 -1
- package/dist/esm/pages/SelectChainPage/SelectChainPage.js +13 -12
- package/dist/esm/pages/SelectChainPage/SelectChainPage.js.map +1 -1
- package/dist/esm/pages/SelectEnabledToolsPage.js +8 -5
- package/dist/esm/pages/SelectEnabledToolsPage.js.map +1 -1
- package/dist/esm/pages/SelectTokenPage/SelectTokenPage.js +2 -1
- package/dist/esm/pages/SelectTokenPage/SelectTokenPage.js.map +1 -1
- package/dist/esm/pages/SettingsPage/SmallBalanceFilterSettings.js +2 -1
- package/dist/esm/pages/SettingsPage/SmallBalanceFilterSettings.js.map +1 -1
- package/dist/esm/pages/TransactionDetailsPage/ContactSupportButton.js +2 -1
- package/dist/esm/pages/TransactionDetailsPage/ContactSupportButton.js.map +1 -1
- package/dist/esm/pages/TransactionDetailsPage/TransactionDetailsPage.js +2 -1
- package/dist/esm/pages/TransactionDetailsPage/TransactionDetailsPage.js.map +1 -1
- package/dist/esm/pages/TransactionPage/StartTransactionButton.js +4 -3
- package/dist/esm/pages/TransactionPage/StartTransactionButton.js.map +1 -1
- package/dist/esm/pages/TransactionPage/TokenVerificationBottomSheet.js +64 -0
- package/dist/esm/pages/TransactionPage/TokenVerificationBottomSheet.js.map +1 -0
- package/dist/esm/pages/TransactionPage/TransactionExecutionContent.js +19 -16
- package/dist/esm/pages/TransactionPage/TransactionExecutionContent.js.map +1 -1
- package/dist/esm/pages/TransactionPage/TransactionFailedButtons.js +3 -1
- package/dist/esm/pages/TransactionPage/TransactionFailedButtons.js.map +1 -1
- package/dist/esm/pages/TransactionPage/TransactionReview.js +24 -2
- package/dist/esm/pages/TransactionPage/TransactionReview.js.map +1 -1
- package/dist/esm/providers/I18nProvider/I18nProvider.js +37 -36
- package/dist/esm/providers/I18nProvider/I18nProvider.js.map +1 -1
- package/dist/esm/providers/ThemeProvider/ThemeProvider.js +2 -1
- package/dist/esm/providers/ThemeProvider/ThemeProvider.js.map +1 -1
- package/dist/esm/providers/WalletProvider/WalletProvider.js +10 -9
- package/dist/esm/providers/WalletProvider/WalletProvider.js.map +1 -1
- package/dist/esm/providers/WalletProvider/useExternalWalletProvider.js +7 -3
- package/dist/esm/providers/WalletProvider/useExternalWalletProvider.js.map +1 -1
- package/dist/esm/stores/routes/useRouteExecutionIndicator.js +4 -3
- package/dist/esm/stores/routes/useRouteExecutionIndicator.js.map +1 -1
- package/dist/esm/types/token.d.ts +4 -1
- package/dist/esm/types/token.d.ts.map +1 -1
- package/dist/esm/utils/compactNumberFormatter.js +1 -3
- package/dist/esm/utils/compactNumberFormatter.js.map +1 -1
- package/dist/esm/utils/customReceiver.js +7 -0
- package/dist/esm/utils/customReceiver.js.map +1 -0
- package/dist/esm/utils/gas.js +24 -0
- package/dist/esm/utils/gas.js.map +1 -0
- package/dist/esm/utils/getErrorMessage.js +0 -1
- package/dist/esm/utils/getErrorMessage.js.map +1 -1
- package/dist/esm/utils/limitOrder.js +1 -1
- package/dist/esm/utils/limitOrder.js.map +1 -1
- package/dist/esm/utils/token.js +21 -4
- package/dist/esm/utils/token.js.map +1 -1
- package/dist/esm/utils/tokenList.js +38 -21
- package/dist/esm/utils/tokenList.js.map +1 -1
- package/package.json +15 -15
- package/src/components/AmountInput/AmountInputEndAdornment.tsx +2 -21
- package/src/components/AmountInputCard/PercentageChips.tsx +2 -18
- package/src/components/Chains/AllChainsAvatar.tsx +7 -1
- package/src/components/Messages/UnsupportedToAddressMessage.tsx +24 -0
- package/src/components/Messages/WarningMessages.tsx +3 -0
- package/src/components/Messages/useMessageQueue.ts +9 -0
- package/src/components/Routes/LimitOrderRoutes.tsx +5 -2
- package/src/components/Routes/RoutesContent.tsx +5 -2
- package/src/components/SendToWallet/SendToWalletExpandButton.tsx +2 -1
- package/src/components/TokenList/TokenListItem.tsx +68 -9
- package/src/components/TokenList/VirtualizedTokenList.tsx +33 -9
- package/src/components/TokenList/types.ts +4 -0
- package/src/config/version.ts +1 -1
- package/src/hooks/useAvailableChains.ts +1 -0
- package/src/hooks/useExplorer.ts +2 -0
- package/src/hooks/useFlaggedTokenGuard.ts +33 -0
- package/src/hooks/useGasSufficiency.ts +9 -1
- package/src/hooks/useMaxSendAmount.ts +21 -0
- package/src/hooks/useRoutes.ts +7 -0
- package/src/hooks/useToAddressRequirements.ts +17 -1
- package/src/hooks/useTokenBalances.ts +18 -2
- package/src/hooks/useTokenSearch.ts +1 -5
- package/src/hooks/useTokens.ts +28 -13
- package/src/pages/MainPage/ReviewButton.tsx +6 -2
- package/src/pages/RoutesPage/RoutesPage.tsx +5 -2
- package/src/pages/TransactionPage/TokenVerificationBottomSheet.tsx +93 -0
- package/src/pages/TransactionPage/TransactionReview.tsx +28 -1
- package/src/providers/WalletProvider/useExternalWalletProvider.ts +9 -1
- package/src/types/token.ts +6 -3
- package/src/utils/customReceiver.ts +7 -0
- package/src/utils/gas.ts +41 -0
- package/src/utils/token.ts +53 -9
- package/src/utils/tokenList.ts +28 -0
|
@@ -1,7 +1,8 @@
|
|
|
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
|
|
4
|
+
import ReportRoundedIcon from '@mui/icons-material/ReportRounded'
|
|
5
|
+
import VerifiedIcon from '@mui/icons-material/Verified'
|
|
5
6
|
import {
|
|
6
7
|
Avatar,
|
|
7
8
|
Box,
|
|
@@ -18,6 +19,7 @@ import { memo, useMemo, useRef, useState } from 'react'
|
|
|
18
19
|
import { useTranslation } from 'react-i18next'
|
|
19
20
|
import { useLongPress } from '../../hooks/useLongPress.js'
|
|
20
21
|
import { formatTokenAmount, formatTokenPrice } from '../../utils/format.js'
|
|
22
|
+
import { getTokenVerificationProvider } from '../../utils/token.js'
|
|
21
23
|
import { shortenAddress } from '../../utils/wallet.js'
|
|
22
24
|
import { TokenAvatar } from '../Avatar/TokenAvatar.js'
|
|
23
25
|
import { ListItemButton } from '../ListItem/ListItemButton.js'
|
|
@@ -36,6 +38,7 @@ export const TokenListItem: React.FC<TokenListItemProps> = memo(
|
|
|
36
38
|
start,
|
|
37
39
|
token,
|
|
38
40
|
chain,
|
|
41
|
+
chainName,
|
|
39
42
|
isBalanceLoading,
|
|
40
43
|
startAdornment,
|
|
41
44
|
endAdornment,
|
|
@@ -56,6 +59,7 @@ export const TokenListItem: React.FC<TokenListItemProps> = memo(
|
|
|
56
59
|
isBalanceLoading={isBalanceLoading}
|
|
57
60
|
onClick={onClick}
|
|
58
61
|
chain={chain}
|
|
62
|
+
chainName={chainName}
|
|
59
63
|
selected={selected}
|
|
60
64
|
onShowTokenDetails={onShowTokenDetails}
|
|
61
65
|
/>
|
|
@@ -121,6 +125,7 @@ const TokenListItemButton: React.FC<TokenListItemButtonProps> = memo(
|
|
|
121
125
|
onClick,
|
|
122
126
|
token,
|
|
123
127
|
chain,
|
|
128
|
+
chainName,
|
|
124
129
|
isBalanceLoading,
|
|
125
130
|
selected,
|
|
126
131
|
onShowTokenDetails,
|
|
@@ -166,6 +171,64 @@ const TokenListItemButton: React.FC<TokenListItemButtonProps> = memo(
|
|
|
166
171
|
onShowTokenDetails(token.address, withoutContractAddress, token.chainId)
|
|
167
172
|
)
|
|
168
173
|
|
|
174
|
+
// The native token is badged ahead of any verdict: the provider screens
|
|
175
|
+
// the native-address convention and calls it a scam on some chains.
|
|
176
|
+
// A token the main list or the integrator vouches for needs no warning;
|
|
177
|
+
// anything else without a clean verdict keeps the amber one.
|
|
178
|
+
let verificationBadge:
|
|
179
|
+
| { Icon: typeof VerifiedIcon; color: string; title: string }
|
|
180
|
+
| undefined
|
|
181
|
+
if (token.native) {
|
|
182
|
+
verificationBadge = {
|
|
183
|
+
Icon: VerifiedIcon,
|
|
184
|
+
color: 'info.main',
|
|
185
|
+
title: chainName
|
|
186
|
+
? t('tooltip.tokenNativeOnChain', {
|
|
187
|
+
tokenSymbol: token.symbol,
|
|
188
|
+
chainName,
|
|
189
|
+
})
|
|
190
|
+
: t('tooltip.tokenNative', { tokenSymbol: token.symbol }),
|
|
191
|
+
}
|
|
192
|
+
} else if (token.verificationStatus === 'flagged') {
|
|
193
|
+
verificationBadge = {
|
|
194
|
+
Icon: ReportRoundedIcon,
|
|
195
|
+
color: 'error.main',
|
|
196
|
+
title: t('warning.message.tokenFlagged', {
|
|
197
|
+
tokenSymbol: token.symbol,
|
|
198
|
+
}),
|
|
199
|
+
}
|
|
200
|
+
} else if (token.verificationStatus === 'verified') {
|
|
201
|
+
const provider = getTokenVerificationProvider(token)
|
|
202
|
+
verificationBadge = {
|
|
203
|
+
Icon: VerifiedIcon,
|
|
204
|
+
color: 'success.main',
|
|
205
|
+
title: provider
|
|
206
|
+
? t('tooltip.tokenVerifiedByProvider', {
|
|
207
|
+
tokenSymbol: token.symbol,
|
|
208
|
+
provider,
|
|
209
|
+
})
|
|
210
|
+
: t('tooltip.tokenVerified', { tokenSymbol: token.symbol }),
|
|
211
|
+
}
|
|
212
|
+
} else if (
|
|
213
|
+
token.verificationStatus === 'unverified' &&
|
|
214
|
+
!token.listed &&
|
|
215
|
+
!token.verified
|
|
216
|
+
) {
|
|
217
|
+
verificationBadge = {
|
|
218
|
+
Icon: ReportRoundedIcon,
|
|
219
|
+
color: 'warning.main',
|
|
220
|
+
title: t('warning.message.tokenUnverifiedByProvider', {
|
|
221
|
+
tokenSymbol: token.symbol,
|
|
222
|
+
}),
|
|
223
|
+
}
|
|
224
|
+
} else if (!token.verificationStatus && !token.listed && !token.verified) {
|
|
225
|
+
verificationBadge = {
|
|
226
|
+
Icon: ReportRoundedIcon,
|
|
227
|
+
color: 'warning.main',
|
|
228
|
+
title: t('warning.message.tokenUnverified'),
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
169
232
|
return (
|
|
170
233
|
<ListItemButton
|
|
171
234
|
onClick={handleClick}
|
|
@@ -195,17 +258,13 @@ const TokenListItemButton: React.FC<TokenListItemButtonProps> = memo(
|
|
|
195
258
|
primary={
|
|
196
259
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
197
260
|
{token.symbol}
|
|
198
|
-
{
|
|
199
|
-
<Tooltip
|
|
200
|
-
|
|
201
|
-
placement="top"
|
|
202
|
-
arrow
|
|
203
|
-
>
|
|
204
|
-
<WarningRounded
|
|
261
|
+
{verificationBadge && (
|
|
262
|
+
<Tooltip title={verificationBadge.title} placement="top" arrow>
|
|
263
|
+
<verificationBadge.Icon
|
|
205
264
|
sx={{
|
|
206
265
|
display: 'flex',
|
|
207
266
|
fontSize: 16,
|
|
208
|
-
color:
|
|
267
|
+
color: verificationBadge.color,
|
|
209
268
|
cursor: 'help',
|
|
210
269
|
}}
|
|
211
270
|
/>
|
|
@@ -60,6 +60,24 @@ export const VirtualizedTokenList: FC<VirtualizedTokenListProps> = ({
|
|
|
60
60
|
[tokens]
|
|
61
61
|
)
|
|
62
62
|
|
|
63
|
+
// The hoisted native token sits above every category, so the row after it
|
|
64
|
+
// opens the first one. Only a row the hoist could have moved counts.
|
|
65
|
+
const nativeTokenHoisted = useMemo(() => {
|
|
66
|
+
const first = tokens[0]
|
|
67
|
+
return (
|
|
68
|
+
!!first?.native &&
|
|
69
|
+
!first.pinned &&
|
|
70
|
+
!first.featured &&
|
|
71
|
+
!first.popular &&
|
|
72
|
+
!first.verified
|
|
73
|
+
)
|
|
74
|
+
}, [tokens])
|
|
75
|
+
|
|
76
|
+
const isListStartIndex = useCallback(
|
|
77
|
+
(index: number) => index === 0 || (index === 1 && nativeTokenHoisted),
|
|
78
|
+
[nativeTokenHoisted]
|
|
79
|
+
)
|
|
80
|
+
|
|
63
81
|
const estimateSize = useCallback(
|
|
64
82
|
(index: number) => {
|
|
65
83
|
const currentToken = tokens[index]
|
|
@@ -67,7 +85,7 @@ export const VirtualizedTokenList: FC<VirtualizedTokenListProps> = ({
|
|
|
67
85
|
let size = tokenItemHeight
|
|
68
86
|
|
|
69
87
|
// Pinned tokens (always shown, even in all networks mode)
|
|
70
|
-
if (currentToken.pinned && index
|
|
88
|
+
if (currentToken.pinned && isListStartIndex(index)) {
|
|
71
89
|
size += 24
|
|
72
90
|
}
|
|
73
91
|
if (previousToken?.pinned && !currentToken.pinned) {
|
|
@@ -78,13 +96,19 @@ export const VirtualizedTokenList: FC<VirtualizedTokenListProps> = ({
|
|
|
78
96
|
return size
|
|
79
97
|
}
|
|
80
98
|
|
|
81
|
-
if (
|
|
99
|
+
if (
|
|
100
|
+
currentToken.featured &&
|
|
101
|
+
!currentToken.pinned &&
|
|
102
|
+
isListStartIndex(index)
|
|
103
|
+
) {
|
|
82
104
|
size += 24
|
|
83
105
|
}
|
|
84
106
|
|
|
85
|
-
// Category transition (excluding pinned tokens)
|
|
107
|
+
// Category transition (excluding pinned tokens). The hoisted native row
|
|
108
|
+
// sits above every band, so the row after it ends nothing.
|
|
86
109
|
const isNotPinned = !currentToken.pinned && !previousToken?.pinned
|
|
87
110
|
if (
|
|
111
|
+
!isListStartIndex(index) &&
|
|
88
112
|
isNotPinned &&
|
|
89
113
|
((previousToken?.amount && !currentToken.amount) ||
|
|
90
114
|
(previousToken?.featured && !currentToken.featured) ||
|
|
@@ -95,7 +119,7 @@ export const VirtualizedTokenList: FC<VirtualizedTokenListProps> = ({
|
|
|
95
119
|
|
|
96
120
|
return size
|
|
97
121
|
},
|
|
98
|
-
[tokens, showCategories]
|
|
122
|
+
[tokens, showCategories, isListStartIndex]
|
|
99
123
|
)
|
|
100
124
|
|
|
101
125
|
const virtualizerConfig = useMemo(
|
|
@@ -142,8 +166,9 @@ export const VirtualizedTokenList: FC<VirtualizedTokenListProps> = ({
|
|
|
142
166
|
const previousToken: TokenAmount | undefined = tokens[item.index - 1]
|
|
143
167
|
const chain = chainsSet?.get(currentToken.chainId)
|
|
144
168
|
|
|
169
|
+
const isListStart = isListStartIndex(item.index)
|
|
145
170
|
const isNotPinned = !currentToken.pinned
|
|
146
|
-
const isFirstPinnedToken = currentToken.pinned &&
|
|
171
|
+
const isFirstPinnedToken = currentToken.pinned && isListStart
|
|
147
172
|
const isTransitionFromPinned = previousToken?.pinned && isNotPinned
|
|
148
173
|
|
|
149
174
|
// Category transitions (excluding pinned)
|
|
@@ -168,7 +193,7 @@ export const VirtualizedTokenList: FC<VirtualizedTokenListProps> = ({
|
|
|
168
193
|
|
|
169
194
|
if (
|
|
170
195
|
(isTransitionFromPinned && currentToken.featured) ||
|
|
171
|
-
(currentToken.featured && isNotPinned &&
|
|
196
|
+
(currentToken.featured && isNotPinned && isListStart)
|
|
172
197
|
) {
|
|
173
198
|
return t('main.featuredTokens')
|
|
174
199
|
}
|
|
@@ -211,6 +236,7 @@ export const VirtualizedTokenList: FC<VirtualizedTokenListProps> = ({
|
|
|
211
236
|
start={item.start}
|
|
212
237
|
token={currentToken}
|
|
213
238
|
chain={isAllNetworks ? chain : undefined}
|
|
239
|
+
chainName={chain?.name}
|
|
214
240
|
selected={isSelected}
|
|
215
241
|
onShowTokenDetails={onShowTokenDetails}
|
|
216
242
|
isBalanceLoading={isBalanceLoading}
|
|
@@ -224,9 +250,7 @@ export const VirtualizedTokenList: FC<VirtualizedTokenListProps> = ({
|
|
|
224
250
|
px: 1.5,
|
|
225
251
|
pt:
|
|
226
252
|
isFirstPinnedToken ||
|
|
227
|
-
(currentToken.featured &&
|
|
228
|
-
isNotPinned &&
|
|
229
|
-
item.index === 0)
|
|
253
|
+
(currentToken.featured && isNotPinned && isListStart)
|
|
230
254
|
? 0
|
|
231
255
|
: 1,
|
|
232
256
|
pb: 1,
|
|
@@ -35,6 +35,8 @@ export interface TokenListItemProps extends TokenListItemBaseProps {
|
|
|
35
35
|
chainId: number
|
|
36
36
|
) => void
|
|
37
37
|
chain?: ExtendedChain
|
|
38
|
+
/** Resolved for every row, unlike `chain`, which the list passes only in all-networks mode. */
|
|
39
|
+
chainName?: string
|
|
38
40
|
isBalanceLoading?: boolean
|
|
39
41
|
startAdornment?: React.ReactNode
|
|
40
42
|
endAdornment?: React.ReactNode
|
|
@@ -50,6 +52,8 @@ export interface TokenListItemButtonProps {
|
|
|
50
52
|
onClick?(tokenAddress: string, chainId?: number): void
|
|
51
53
|
token: TokenAmount
|
|
52
54
|
chain?: ExtendedChain
|
|
55
|
+
/** Resolved for every row, unlike `chain`, which the list passes only in all-networks mode. */
|
|
56
|
+
chainName?: string
|
|
53
57
|
isBalanceLoading?: boolean
|
|
54
58
|
selected?: boolean
|
|
55
59
|
}
|
package/src/config/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export const name = '@lifi/widget'
|
|
2
|
-
export const version = '4.
|
|
2
|
+
export const version = '4.6.0'
|
package/src/hooks/useExplorer.ts
CHANGED
|
@@ -13,6 +13,8 @@ const explorerPathOverrides: Partial<
|
|
|
13
13
|
[ChainId.SUI]: { txPath: 'txblock', addressPath: 'coin' },
|
|
14
14
|
[ChainId.LTR]: { txPath: 'logs', addressPath: 'accounts' },
|
|
15
15
|
[ChainType.TVM]: { txPath: '#/transaction', addressPath: '#/address' },
|
|
16
|
+
// StellarExpert and steexp both route accounts at /account, never /address.
|
|
17
|
+
[ChainType.STL]: { txPath: 'tx', addressPath: 'account' },
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
type TransactionLinkProps = {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { RouteExtended, Token } from '@lifi/sdk'
|
|
2
|
+
import type { RefObject } from 'react'
|
|
3
|
+
import { useMemo, useRef } from 'react'
|
|
4
|
+
import type { BottomSheetBase } from '../components/BottomSheet/types.js'
|
|
5
|
+
import { getNativeTokenAddresses } from '../utils/token.js'
|
|
6
|
+
import { useAvailableChains } from './useAvailableChains.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Route tokens the screening provider flagged, and the sheet that warns about
|
|
10
|
+
* them before the transaction starts. A native token is exempt: the provider
|
|
11
|
+
* screens the native-address convention and calls it a scam on some chains.
|
|
12
|
+
*/
|
|
13
|
+
export const useFlaggedTokenGuard = (
|
|
14
|
+
route: RouteExtended
|
|
15
|
+
): {
|
|
16
|
+
flaggedTokens: Token[]
|
|
17
|
+
flaggedTokenSheetRef: RefObject<BottomSheetBase | null>
|
|
18
|
+
} => {
|
|
19
|
+
const { chains } = useAvailableChains()
|
|
20
|
+
const flaggedTokenSheetRef = useRef<BottomSheetBase>(null)
|
|
21
|
+
|
|
22
|
+
const flaggedTokens = useMemo(() => {
|
|
23
|
+
const nativeTokenAddresses = getNativeTokenAddresses(chains)
|
|
24
|
+
return [route.fromToken, route.toToken].filter(
|
|
25
|
+
(token) =>
|
|
26
|
+
token.verificationStatus === 'flagged' &&
|
|
27
|
+
nativeTokenAddresses.get(token.chainId)?.toLowerCase() !==
|
|
28
|
+
token.address.toLowerCase()
|
|
29
|
+
)
|
|
30
|
+
}, [chains, route.fromToken, route.toToken])
|
|
31
|
+
|
|
32
|
+
return { flaggedTokens, flaggedTokenSheetRef }
|
|
33
|
+
}
|
|
@@ -5,6 +5,7 @@ import { useQuery } from '@tanstack/react-query'
|
|
|
5
5
|
import { useMemo } from 'react'
|
|
6
6
|
import { useSDKClient } from '../providers/SDKClientProvider.js'
|
|
7
7
|
import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'
|
|
8
|
+
import { getSelfFundedGasAmount } from '../utils/gas.js'
|
|
8
9
|
import { getQueryKey } from '../utils/queries.js'
|
|
9
10
|
import { useAvailableChains } from './useAvailableChains.js'
|
|
10
11
|
import { useIsContractAddress } from './useIsContractAddress.js'
|
|
@@ -108,7 +109,14 @@ export const useGasSufficiency = (
|
|
|
108
109
|
amount + BigInt(Number(gasCost.amount).toFixed(0)),
|
|
109
110
|
0n
|
|
110
111
|
)
|
|
111
|
-
|
|
112
|
+
// A previous step can deliver this step's gas token (XLM bridged to
|
|
113
|
+
// Stellar), so the user does not have to hold it up front
|
|
114
|
+
const selfFundedGasAmount = getSelfFundedGasAmount(
|
|
115
|
+
route,
|
|
116
|
+
step,
|
|
117
|
+
getChainById(token.chainId)?.chainType
|
|
118
|
+
)
|
|
119
|
+
if (gasCostAmount > 0n && gasCostAmount > selfFundedGasAmount) {
|
|
112
120
|
groupedGasCosts[token.chainId] = {
|
|
113
121
|
gasAmount: groupedGasCosts[token.chainId]
|
|
114
122
|
? groupedGasCosts[token.chainId].gasAmount + gasCostAmount
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useGasRecommendation } from './useGasRecommendation.js'
|
|
2
|
+
import { useTokenAddressBalance } from './useTokenAddressBalance.js'
|
|
3
|
+
|
|
4
|
+
// Largest amount of the token the wallet can send: for the native token that is
|
|
5
|
+
// the balance less the gas the chain recommends keeping back.
|
|
6
|
+
export const useMaxSendAmount = (
|
|
7
|
+
chainId?: number,
|
|
8
|
+
tokenAddress?: string
|
|
9
|
+
): bigint => {
|
|
10
|
+
const { token, chain } = useTokenAddressBalance(chainId, tokenAddress)
|
|
11
|
+
const { data } = useGasRecommendation(chainId)
|
|
12
|
+
|
|
13
|
+
if (!token?.amount) {
|
|
14
|
+
return 0n
|
|
15
|
+
}
|
|
16
|
+
if (chain?.nativeToken.address !== tokenAddress) {
|
|
17
|
+
return token.amount
|
|
18
|
+
}
|
|
19
|
+
const reserved = BigInt(data?.recommended?.amount ?? 0)
|
|
20
|
+
return token.amount > reserved ? token.amount - reserved : token.amount
|
|
21
|
+
}
|
package/src/hooks/useRoutes.ts
CHANGED
|
@@ -29,6 +29,7 @@ import { defaultSlippage } from '../stores/settings/createSettingsStore.js'
|
|
|
29
29
|
import { useSettings } from '../stores/settings/useSettings.js'
|
|
30
30
|
import { WidgetEvent } from '../types/events.js'
|
|
31
31
|
import type { TokensByChain } from '../types/token.js'
|
|
32
|
+
import { isCustomReceiverUnsupported } from '../utils/customReceiver.js'
|
|
32
33
|
import { getQueryKey } from '../utils/queries.js'
|
|
33
34
|
import { updateTokenInCache } from '../utils/token.js'
|
|
34
35
|
import { useChain } from './useChain.js'
|
|
@@ -177,6 +178,11 @@ export const useRoutes = ({
|
|
|
177
178
|
? hasToAddressAndChainTypeSatisfied
|
|
178
179
|
: true
|
|
179
180
|
|
|
181
|
+
// Stellar settles to the signer, so a set receiver makes the route unfulfillable.
|
|
182
|
+
const customReceiverBlocked =
|
|
183
|
+
isCustomReceiverUnsupported(fromChain?.chainType, toChain?.chainType) &&
|
|
184
|
+
Boolean(toAddress)
|
|
185
|
+
|
|
180
186
|
// toAddress might be an empty string, but we need to pass undefined if there is no value
|
|
181
187
|
const toWalletAddress = toAddress || undefined
|
|
182
188
|
|
|
@@ -197,6 +203,7 @@ export const useRoutes = ({
|
|
|
197
203
|
!Number.isNaN(slippage) &&
|
|
198
204
|
hasAmount &&
|
|
199
205
|
isToAddressSatisfied &&
|
|
206
|
+
!customReceiverBlocked &&
|
|
200
207
|
contractCallQuoteEnabled &&
|
|
201
208
|
!isBatchingSupportedLoading
|
|
202
209
|
|
|
@@ -4,12 +4,15 @@ import { 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'
|
|
7
|
+
import { isCustomReceiverUnsupported } from '../utils/customReceiver.js'
|
|
7
8
|
import { useIsContractAddress } from './useIsContractAddress.js'
|
|
8
9
|
|
|
9
10
|
export const useToAddressRequirements = (
|
|
10
11
|
route?: RouteExtended
|
|
11
12
|
): {
|
|
12
13
|
requiredToAddress: boolean
|
|
14
|
+
unsupportedToAddress: boolean
|
|
15
|
+
unsupportedReceiverBlocking: boolean
|
|
13
16
|
requiredToChainType: ChainType | undefined
|
|
14
17
|
accountNotDeployedAtDestination: boolean
|
|
15
18
|
accountDeployedAtDestination: boolean
|
|
@@ -68,11 +71,22 @@ export const useToAddressRequirements = (
|
|
|
68
71
|
fromChainId !== toChainId &&
|
|
69
72
|
!fromContractCodeHasDelegationIndicator
|
|
70
73
|
|
|
74
|
+
const unsupportedToAddress = isCustomReceiverUnsupported(
|
|
75
|
+
fromChain?.chainType,
|
|
76
|
+
toChain?.chainType
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
// Stellar routes settle to the signer, so a required receiver is never honoured.
|
|
80
|
+
const unsupportedReceiverBlocking = Boolean(
|
|
81
|
+
unsupportedToAddress && (toAddress || requiredUI?.toAddress)
|
|
82
|
+
)
|
|
83
|
+
|
|
71
84
|
const requiredToAddress = Boolean(
|
|
72
85
|
(isDifferentChainType ||
|
|
73
86
|
isCrossChainContractAddress ||
|
|
74
87
|
requiredUI?.toAddress) &&
|
|
75
|
-
!hiddenUI?.toAddress
|
|
88
|
+
!hiddenUI?.toAddress &&
|
|
89
|
+
!unsupportedToAddress
|
|
76
90
|
)
|
|
77
91
|
|
|
78
92
|
const accountNotDeployedAtDestination = Boolean(
|
|
@@ -91,6 +105,8 @@ export const useToAddressRequirements = (
|
|
|
91
105
|
|
|
92
106
|
return {
|
|
93
107
|
requiredToAddress,
|
|
108
|
+
unsupportedToAddress,
|
|
109
|
+
unsupportedReceiverBlocking,
|
|
94
110
|
requiredToChainType: toChain?.chainType,
|
|
95
111
|
accountNotDeployedAtDestination,
|
|
96
112
|
accountDeployedAtDestination,
|
|
@@ -4,7 +4,11 @@ import type { FormType } from '../stores/form/types.js'
|
|
|
4
4
|
import { useSettings } from '../stores/settings/useSettings.js'
|
|
5
5
|
import type { TokenAmount } from '../types/token.js'
|
|
6
6
|
import { formatTokenPrice } from '../utils/format.js'
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
hoistNativeToken,
|
|
9
|
+
isSearchMatch,
|
|
10
|
+
processTokenList,
|
|
11
|
+
} from '../utils/tokenList.js'
|
|
8
12
|
import { useAccountsBalancesData } from './useAccountsBalancesData.js'
|
|
9
13
|
import { useDisplayedTokens } from './useDisplayedTokens.js'
|
|
10
14
|
import { useTokenBalancesQueries } from './useTokenBalancesQueries.js'
|
|
@@ -115,7 +119,7 @@ export const useTokenBalances = (
|
|
|
115
119
|
])
|
|
116
120
|
|
|
117
121
|
const { processedTokens, withCategories, withPinnedTokens } = useMemo(() => {
|
|
118
|
-
|
|
122
|
+
const result = processTokenList(
|
|
119
123
|
isBalanceLoading,
|
|
120
124
|
isAllNetworks || !!search,
|
|
121
125
|
configTokens,
|
|
@@ -124,6 +128,18 @@ export const useTokenBalances = (
|
|
|
124
128
|
displayedTokensWithBalances,
|
|
125
129
|
isPinnedToken
|
|
126
130
|
)
|
|
131
|
+
// A search keeps its results ranked by match, and "All networks" has no
|
|
132
|
+
// single native token to lead with.
|
|
133
|
+
if (isAllNetworks || search) {
|
|
134
|
+
return result
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
...result,
|
|
138
|
+
processedTokens: hoistNativeToken(
|
|
139
|
+
result.processedTokens,
|
|
140
|
+
selectedChainId
|
|
141
|
+
),
|
|
142
|
+
}
|
|
127
143
|
}, [
|
|
128
144
|
isBalanceLoading,
|
|
129
145
|
isAllNetworks,
|
|
@@ -74,13 +74,9 @@ export const useTokenSearch = (
|
|
|
74
74
|
) {
|
|
75
75
|
return data
|
|
76
76
|
}
|
|
77
|
-
// Mark token from search as unverified
|
|
78
77
|
return {
|
|
79
78
|
...data,
|
|
80
|
-
[chainId as number]: [
|
|
81
|
-
...(chainTokens ?? []),
|
|
82
|
-
{ ...token, verified: false },
|
|
83
|
-
],
|
|
79
|
+
[chainId as number]: [...(chainTokens ?? []), token],
|
|
84
80
|
}
|
|
85
81
|
}
|
|
86
82
|
)
|
package/src/hooks/useTokens.ts
CHANGED
|
@@ -17,8 +17,10 @@ import { isItemAllowed } from '../utils/item.js'
|
|
|
17
17
|
import { getQueryKey } from '../utils/queries.js'
|
|
18
18
|
import {
|
|
19
19
|
filterAllowedTokens,
|
|
20
|
+
getNativeTokenAddresses,
|
|
20
21
|
mergeVerifiedWithSearchTokens,
|
|
21
22
|
} from '../utils/token.js'
|
|
23
|
+
import { useAvailableChains } from './useAvailableChains.js'
|
|
22
24
|
|
|
23
25
|
const refetchInterval = 300_000
|
|
24
26
|
|
|
@@ -49,6 +51,7 @@ export const useTokens = (
|
|
|
49
51
|
ChainType.UTXO,
|
|
50
52
|
ChainType.MVM,
|
|
51
53
|
ChainType.TVM,
|
|
54
|
+
ChainType.STL,
|
|
52
55
|
].filter((chainType) => isItemAllowed(chainType, chainsConfig?.types))
|
|
53
56
|
|
|
54
57
|
const tokensResponse: TokensExtendedResponse = await getTokens(
|
|
@@ -63,11 +66,11 @@ export const useTokens = (
|
|
|
63
66
|
{ signal }
|
|
64
67
|
)
|
|
65
68
|
|
|
66
|
-
//
|
|
69
|
+
// `listed` tells a main-list token from a searched one
|
|
67
70
|
const tokens: TokensByChain = Object.fromEntries(
|
|
68
71
|
Object.entries(tokensResponse.tokens).map(([chainId, tokens]) => [
|
|
69
72
|
chainId,
|
|
70
|
-
tokens.map((token) => ({ ...token,
|
|
73
|
+
tokens.map((token) => ({ ...token, listed: true })),
|
|
71
74
|
])
|
|
72
75
|
)
|
|
73
76
|
|
|
@@ -92,6 +95,7 @@ export const useTokens = (
|
|
|
92
95
|
ChainType.UTXO,
|
|
93
96
|
ChainType.MVM,
|
|
94
97
|
ChainType.TVM,
|
|
98
|
+
ChainType.STL,
|
|
95
99
|
].filter((chainType) => isItemAllowed(chainType, chainsConfig?.types))
|
|
96
100
|
|
|
97
101
|
const tokensResponse: TokensExtendedResponse = await getTokens(
|
|
@@ -130,26 +134,37 @@ export const useTokens = (
|
|
|
130
134
|
}
|
|
131
135
|
}
|
|
132
136
|
|
|
133
|
-
|
|
134
|
-
const tokens: TokensByChain = Object.fromEntries(
|
|
135
|
-
Object.entries(tokensResponse.tokens).map(([chainId, tokens]) => [
|
|
136
|
-
chainId,
|
|
137
|
-
tokens.map((token) => ({ ...token, verified: false })),
|
|
138
|
-
])
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
return tokens
|
|
137
|
+
return tokensResponse.tokens as TokensByChain
|
|
142
138
|
},
|
|
143
139
|
enabled: !!search,
|
|
144
140
|
refetchInterval,
|
|
145
141
|
staleTime: refetchInterval,
|
|
146
142
|
})
|
|
147
143
|
|
|
144
|
+
const { chains } = useAvailableChains()
|
|
145
|
+
const nativeTokenAddresses = useMemo(
|
|
146
|
+
() => getNativeTokenAddresses(chains),
|
|
147
|
+
[chains]
|
|
148
|
+
)
|
|
149
|
+
|
|
148
150
|
// Merge tokens at read time - single place where caches are combined
|
|
149
151
|
const allTokens = useMemo(() => {
|
|
150
152
|
const merged = mergeVerifiedWithSearchTokens(verifiedTokens, searchTokens)
|
|
151
|
-
return filterAllowedTokens(
|
|
152
|
-
|
|
153
|
+
return filterAllowedTokens(
|
|
154
|
+
merged,
|
|
155
|
+
configTokens,
|
|
156
|
+
chainsConfig,
|
|
157
|
+
formType,
|
|
158
|
+
nativeTokenAddresses
|
|
159
|
+
)
|
|
160
|
+
}, [
|
|
161
|
+
verifiedTokens,
|
|
162
|
+
searchTokens,
|
|
163
|
+
configTokens,
|
|
164
|
+
chainsConfig,
|
|
165
|
+
formType,
|
|
166
|
+
nativeTokenAddresses,
|
|
167
|
+
])
|
|
153
168
|
|
|
154
169
|
return {
|
|
155
170
|
allTokens,
|
|
@@ -16,7 +16,8 @@ export const ReviewButton: React.FC = () => {
|
|
|
16
16
|
const emitter = useWidgetEvents()
|
|
17
17
|
const { mode, modeOptions } = useWidgetConfig()
|
|
18
18
|
const splitState = useSplitMode()
|
|
19
|
-
const { toAddress, requiredToAddress } =
|
|
19
|
+
const { toAddress, requiredToAddress, unsupportedReceiverBlocking } =
|
|
20
|
+
useToAddressRequirements()
|
|
20
21
|
const { routes, setReviewableRoute } = useRoutes()
|
|
21
22
|
const [selectedRouteId] = useFieldValues('selectedRouteId')
|
|
22
23
|
|
|
@@ -85,7 +86,10 @@ export const ReviewButton: React.FC = () => {
|
|
|
85
86
|
<BaseTransactionButton
|
|
86
87
|
text={getButtonText()}
|
|
87
88
|
onClick={handleClick}
|
|
88
|
-
disabled={
|
|
89
|
+
disabled={
|
|
90
|
+
currentRoute &&
|
|
91
|
+
(unsupportedReceiverBlocking || (requiredToAddress && !toAddress))
|
|
92
|
+
}
|
|
89
93
|
route={currentRoute}
|
|
90
94
|
sx={{ flex: 1 }}
|
|
91
95
|
/>
|
|
@@ -36,7 +36,8 @@ export const RoutesPage = (): JSX.Element => {
|
|
|
36
36
|
} = useRoutes()
|
|
37
37
|
const { account } = useAccount({ chainType: fromChain?.chainType })
|
|
38
38
|
const [toAddress] = useFieldValues('toAddress')
|
|
39
|
-
const { requiredToAddress } =
|
|
39
|
+
const { requiredToAddress, unsupportedReceiverBlocking } =
|
|
40
|
+
useToAddressRequirements()
|
|
40
41
|
|
|
41
42
|
const headerAction = useMemo(
|
|
42
43
|
() => (
|
|
@@ -72,7 +73,9 @@ export const RoutesPage = (): JSX.Element => {
|
|
|
72
73
|
|
|
73
74
|
const routeNotFound = !routes?.length && !isLoading && !isFetching
|
|
74
75
|
|
|
75
|
-
const toAddressUnsatisfied =
|
|
76
|
+
const toAddressUnsatisfied =
|
|
77
|
+
routes?.[0] &&
|
|
78
|
+
(unsupportedReceiverBlocking || (requiredToAddress && !toAddress))
|
|
76
79
|
|
|
77
80
|
const allowInteraction = account.isConnected && !toAddressUnsatisfied
|
|
78
81
|
|