@lifi/widget 3.15.2 → 3.16.1-beta.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/CHANGELOG.md +7 -0
- package/dist/esm/components/Messages/WarningMessages.d.ts +7 -0
- package/dist/esm/components/Messages/{MainMessages.js → WarningMessages.js} +3 -6
- package/dist/esm/components/Messages/WarningMessages.js.map +1 -0
- package/dist/esm/components/Messages/useMessageQueue.d.ts +1 -0
- package/dist/esm/components/Messages/useMessageQueue.js +10 -5
- package/dist/esm/components/Messages/useMessageQueue.js.map +1 -1
- package/dist/esm/components/Routes/RoutesExpanded.js +4 -1
- package/dist/esm/components/Routes/RoutesExpanded.js.map +1 -1
- package/dist/esm/components/StepActions/StepActions.js +1 -1
- package/dist/esm/components/StepActions/StepActions.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/useAddressActivity.d.ts +8 -0
- package/dist/esm/hooks/useAddressActivity.js +23 -0
- package/dist/esm/hooks/useAddressActivity.js.map +1 -0
- package/dist/esm/hooks/useGasSufficiency.js +1 -1
- package/dist/esm/hooks/useGasSufficiency.js.map +1 -1
- package/dist/esm/hooks/useIsCompatibleDestinationAccount.d.ts +8 -0
- package/dist/esm/hooks/useIsCompatibleDestinationAccount.js +37 -0
- package/dist/esm/hooks/useIsCompatibleDestinationAccount.js.map +1 -0
- package/dist/esm/hooks/useIsContractAddress.d.ts +2 -0
- package/dist/esm/hooks/useIsContractAddress.js +7 -2
- package/dist/esm/hooks/useIsContractAddress.js.map +1 -1
- package/dist/esm/hooks/useRoutes.js +6 -1
- package/dist/esm/hooks/useRoutes.js.map +1 -1
- package/dist/esm/hooks/useToAddressRequirements.d.ts +2 -2
- package/dist/esm/hooks/useToAddressRequirements.js +10 -12
- package/dist/esm/hooks/useToAddressRequirements.js.map +1 -1
- package/dist/esm/i18n/en.json +5 -2
- package/dist/esm/pages/MainPage/MainPage.js +2 -2
- package/dist/esm/pages/MainPage/MainPage.js.map +1 -1
- package/dist/esm/pages/MainPage/MainWarningMessages.d.ts +7 -0
- package/dist/esm/pages/MainPage/MainWarningMessages.js +9 -0
- package/dist/esm/pages/MainPage/MainWarningMessages.js.map +1 -0
- package/dist/esm/pages/MainPage/ReviewButton.js +14 -13
- package/dist/esm/pages/MainPage/ReviewButton.js.map +1 -1
- package/dist/esm/pages/RoutesPage/RoutesPage.js +5 -2
- package/dist/esm/pages/RoutesPage/RoutesPage.js.map +1 -1
- package/dist/esm/pages/TransactionPage/ConfirmToAddressSheet.d.ts +8 -0
- package/dist/esm/pages/TransactionPage/ConfirmToAddressSheet.js +38 -0
- package/dist/esm/pages/TransactionPage/ConfirmToAddressSheet.js.map +1 -0
- package/dist/esm/pages/TransactionPage/StartTransactionButton.js +3 -6
- package/dist/esm/pages/TransactionPage/StartTransactionButton.js.map +1 -1
- package/dist/esm/pages/TransactionPage/TransactionPage.js +15 -4
- package/dist/esm/pages/TransactionPage/TransactionPage.js.map +1 -1
- package/dist/esm/stores/form/URLSearchParamsBuilder.js +4 -1
- package/dist/esm/stores/form/URLSearchParamsBuilder.js.map +1 -1
- package/dist/esm/types/events.d.ts +6 -1
- package/dist/esm/types/events.js +1 -0
- package/dist/esm/types/events.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Messages/{MainMessages.tsx → WarningMessages.tsx} +10 -5
- package/src/components/Messages/useMessageQueue.ts +16 -6
- package/src/components/Routes/RoutesExpanded.tsx +5 -1
- package/src/components/StepActions/StepActions.tsx +1 -1
- package/src/config/version.ts +1 -1
- package/src/hooks/useAddressActivity.ts +39 -0
- package/src/hooks/useGasSufficiency.ts +1 -1
- package/src/hooks/useIsCompatibleDestinationAccount.ts +58 -0
- package/src/hooks/useIsContractAddress.ts +11 -2
- package/src/hooks/useRoutes.ts +7 -1
- package/src/hooks/useToAddressRequirements.ts +16 -20
- package/src/i18n/en.json +5 -2
- package/src/pages/MainPage/MainPage.tsx +2 -2
- package/src/pages/MainPage/MainWarningMessages.tsx +17 -0
- package/src/pages/MainPage/ReviewButton.tsx +15 -14
- package/src/pages/RoutesPage/RoutesPage.tsx +6 -3
- package/src/pages/TransactionPage/ConfirmToAddressSheet.tsx +96 -0
- package/src/pages/TransactionPage/StartTransactionButton.tsx +4 -12
- package/src/pages/TransactionPage/TransactionPage.tsx +29 -3
- package/src/stores/form/URLSearchParamsBuilder.tsx +4 -1
- package/src/types/events.ts +5 -0
- package/dist/esm/components/Messages/MainMessages.d.ts +0 -2
- package/dist/esm/components/Messages/MainMessages.js.map +0 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Address } from 'viem'
|
|
2
|
+
import { isAddress } from 'viem'
|
|
3
|
+
import { useTransactionCount } from 'wagmi'
|
|
4
|
+
import { useFieldValues } from '../stores/form/useFieldValues.js'
|
|
5
|
+
|
|
6
|
+
interface AddressActivity {
|
|
7
|
+
hasActivity: boolean
|
|
8
|
+
isLoading: boolean
|
|
9
|
+
isFetched: boolean
|
|
10
|
+
toAddress: string | undefined
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const useAddressActivity = (chainId?: number): AddressActivity => {
|
|
14
|
+
const [toAddress, toChainId] = useFieldValues('toAddress', 'toChain')
|
|
15
|
+
|
|
16
|
+
const destinationChainId = chainId ?? toChainId
|
|
17
|
+
|
|
18
|
+
const {
|
|
19
|
+
data: transactionCount,
|
|
20
|
+
isLoading,
|
|
21
|
+
isFetched,
|
|
22
|
+
error,
|
|
23
|
+
} = useTransactionCount({
|
|
24
|
+
address: toAddress as Address,
|
|
25
|
+
chainId: destinationChainId,
|
|
26
|
+
query: {
|
|
27
|
+
enabled: Boolean(toAddress && destinationChainId && isAddress(toAddress)),
|
|
28
|
+
refetchInterval: 300_000,
|
|
29
|
+
staleTime: 300_000,
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
toAddress,
|
|
35
|
+
hasActivity: Boolean(transactionCount && transactionCount > 0),
|
|
36
|
+
isLoading,
|
|
37
|
+
isFetched: isFetched && !error,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -38,7 +38,7 @@ export const useGasSufficiency = (route?: RouteExtended) => {
|
|
|
38
38
|
// We assume that LI.Fuel protocol always refuels the destination chain
|
|
39
39
|
const hasRefuelStep = route.steps
|
|
40
40
|
.flatMap((step) => step.includedSteps)
|
|
41
|
-
.some((includedStep) => includedStep.tool === '
|
|
41
|
+
.some((includedStep) => includedStep.tool === 'gasZip')
|
|
42
42
|
|
|
43
43
|
const gasCosts = route.steps
|
|
44
44
|
.filter((step) => !step.execution || step.execution.status !== 'DONE')
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { RouteExtended } from '@lifi/sdk'
|
|
2
|
+
import { useAccount } from '@lifi/wallet-management'
|
|
3
|
+
import { useFieldValues } from '../stores/form/useFieldValues.js'
|
|
4
|
+
import { isDelegationDesignatorCode } from '../utils/eip7702.js'
|
|
5
|
+
import { useChain } from './useChain.js'
|
|
6
|
+
import { useIsContractAddress } from './useIsContractAddress.js'
|
|
7
|
+
|
|
8
|
+
export const useIsCompatibleDestinationAccount = (route?: RouteExtended) => {
|
|
9
|
+
const [formFromChainId, formToChainId, formToAddress] = useFieldValues(
|
|
10
|
+
'fromChain',
|
|
11
|
+
'toChain',
|
|
12
|
+
'toAddress'
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
const fromChainId = route?.fromChainId ?? formFromChainId
|
|
16
|
+
const toChainId = route?.toChainId ?? formToChainId
|
|
17
|
+
|
|
18
|
+
const { chain: fromChain } = useChain(fromChainId)
|
|
19
|
+
const { chain: toChain } = useChain(toChainId)
|
|
20
|
+
const { account } = useAccount({
|
|
21
|
+
chainType: fromChain?.chainType,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const fromAddress = route?.fromAddress ?? account.address
|
|
25
|
+
const toAddress = route
|
|
26
|
+
? route.fromAddress !== route.toAddress
|
|
27
|
+
? route.toAddress
|
|
28
|
+
: formToAddress
|
|
29
|
+
: formToAddress
|
|
30
|
+
|
|
31
|
+
const {
|
|
32
|
+
isContractAddress: isFromContractAddress,
|
|
33
|
+
contractCode: fromContractCode,
|
|
34
|
+
isLoading: isFromContractLoading,
|
|
35
|
+
isFetched: isFromContractFetched,
|
|
36
|
+
} = useIsContractAddress(fromAddress, fromChainId, fromChain?.chainType)
|
|
37
|
+
const {
|
|
38
|
+
isContractAddress: isToContractAddress,
|
|
39
|
+
isLoading: isToContractLoading,
|
|
40
|
+
isFetched: isToContractFetched,
|
|
41
|
+
} = useIsContractAddress(toAddress, toChainId, toChain?.chainType)
|
|
42
|
+
|
|
43
|
+
const accountNotDeployedAtDestination =
|
|
44
|
+
isFromContractAddress &&
|
|
45
|
+
// We don't want to block transfers for EIP-7702 accounts since they are designed
|
|
46
|
+
// to maintain EOA-like properties while delegating execution.
|
|
47
|
+
!isDelegationDesignatorCode(fromContractCode) &&
|
|
48
|
+
!isToContractAddress &&
|
|
49
|
+
fromAddress?.toLowerCase() === toAddress?.toLowerCase()
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
isCompatibleDestinationAccount: !accountNotDeployedAtDestination,
|
|
53
|
+
isFromContractAddress,
|
|
54
|
+
isToContractAddress,
|
|
55
|
+
isLoading: isFromContractLoading || isToContractLoading,
|
|
56
|
+
isFetched: isFromContractFetched && isToContractFetched,
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -7,7 +7,11 @@ export const useIsContractAddress = (
|
|
|
7
7
|
chainId?: number,
|
|
8
8
|
chainType?: ChainType
|
|
9
9
|
) => {
|
|
10
|
-
const {
|
|
10
|
+
const {
|
|
11
|
+
data: contractCode,
|
|
12
|
+
isLoading,
|
|
13
|
+
isFetched,
|
|
14
|
+
} = useBytecode({
|
|
11
15
|
address: address as Address,
|
|
12
16
|
chainId: chainId,
|
|
13
17
|
query: {
|
|
@@ -17,5 +21,10 @@ export const useIsContractAddress = (
|
|
|
17
21
|
},
|
|
18
22
|
})
|
|
19
23
|
|
|
20
|
-
return {
|
|
24
|
+
return {
|
|
25
|
+
isContractAddress: !!contractCode,
|
|
26
|
+
contractCode,
|
|
27
|
+
isLoading,
|
|
28
|
+
isFetched,
|
|
29
|
+
}
|
|
21
30
|
}
|
package/src/hooks/useRoutes.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { getChainTypeFromAddress } from '../utils/chainType.js'
|
|
|
13
13
|
import { useChain } from './useChain.js'
|
|
14
14
|
import { useDebouncedWatch } from './useDebouncedWatch.js'
|
|
15
15
|
import { useGasRefuel } from './useGasRefuel.js'
|
|
16
|
+
import { useIsCompatibleDestinationAccount } from './useIsCompatibleDestinationAccount.js'
|
|
16
17
|
import { useSwapOnly } from './useSwapOnly.js'
|
|
17
18
|
import { useToken } from './useToken.js'
|
|
18
19
|
import { useWidgetEvents } from './useWidgetEvents.js'
|
|
@@ -76,6 +77,8 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
|
|
|
76
77
|
const { token: toToken } = useToken(toChainId, toTokenAddress)
|
|
77
78
|
const { chain: fromChain } = useChain(fromChainId)
|
|
78
79
|
const { chain: toChain } = useChain(toChainId)
|
|
80
|
+
const { isCompatibleDestinationAccount } =
|
|
81
|
+
useIsCompatibleDestinationAccount(observableRoute)
|
|
79
82
|
const { enabled: enabledRefuel, fromAmount: gasRecommendationFromAmount } =
|
|
80
83
|
useGasRefuel()
|
|
81
84
|
|
|
@@ -107,6 +110,9 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
|
|
|
107
110
|
exchanges?.allow?.length || exchanges?.deny?.length
|
|
108
111
|
? enabledExchanges
|
|
109
112
|
: undefined
|
|
113
|
+
const allowSwitchChain = isCompatibleDestinationAccount
|
|
114
|
+
? sdkConfig?.routeOptions?.allowSwitchChain
|
|
115
|
+
: false
|
|
110
116
|
|
|
111
117
|
const isEnabled =
|
|
112
118
|
Boolean(Number(fromChainId)) &&
|
|
@@ -138,7 +144,7 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
|
|
|
138
144
|
allowedExchanges,
|
|
139
145
|
routePriority,
|
|
140
146
|
subvariant,
|
|
141
|
-
|
|
147
|
+
allowSwitchChain,
|
|
142
148
|
enabledRefuel && enabledAutoRefuel,
|
|
143
149
|
gasRecommendationFromAmount,
|
|
144
150
|
feeConfig?.fee || fee,
|
|
@@ -1,31 +1,36 @@
|
|
|
1
|
+
import type { RouteExtended } from '@lifi/sdk'
|
|
1
2
|
import { useAccount } from '@lifi/wallet-management'
|
|
2
3
|
import { useChain } from '../hooks/useChain.js'
|
|
3
4
|
import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'
|
|
4
5
|
import { useFieldValues } from '../stores/form/useFieldValues.js'
|
|
5
6
|
import { RequiredUI } from '../types/widget.js'
|
|
6
|
-
import { isDelegationDesignatorCode } from '../utils/eip7702.js'
|
|
7
7
|
import { useIsContractAddress } from './useIsContractAddress.js'
|
|
8
8
|
|
|
9
|
-
export const useToAddressRequirements = () => {
|
|
9
|
+
export const useToAddressRequirements = (route?: RouteExtended) => {
|
|
10
10
|
const { requiredUI } = useWidgetConfig()
|
|
11
|
-
const [
|
|
11
|
+
const [formFromChainId, formToChainId, formToAddress] = useFieldValues(
|
|
12
12
|
'fromChain',
|
|
13
13
|
'toChain',
|
|
14
14
|
'toAddress'
|
|
15
15
|
)
|
|
16
|
+
|
|
17
|
+
const fromChainId = route?.fromChainId ?? formFromChainId
|
|
18
|
+
const toChainId = route?.toChainId ?? formToChainId
|
|
19
|
+
const toAddress = route
|
|
20
|
+
? route.fromAddress !== route.toAddress
|
|
21
|
+
? route.toAddress
|
|
22
|
+
: formToAddress
|
|
23
|
+
: formToAddress
|
|
24
|
+
|
|
16
25
|
const { chain: fromChain } = useChain(fromChainId)
|
|
17
26
|
const { chain: toChain } = useChain(toChainId)
|
|
18
27
|
const { account } = useAccount({
|
|
19
28
|
chainType: fromChain?.chainType,
|
|
20
29
|
})
|
|
21
|
-
const {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const { isContractAddress: isToContractAddress } = useIsContractAddress(
|
|
26
|
-
toAddress,
|
|
27
|
-
toChainId,
|
|
28
|
-
toChain?.chainType
|
|
30
|
+
const { isContractAddress: isFromContractAddress } = useIsContractAddress(
|
|
31
|
+
account.address,
|
|
32
|
+
fromChainId,
|
|
33
|
+
account.chainType
|
|
29
34
|
)
|
|
30
35
|
|
|
31
36
|
const isDifferentChainType =
|
|
@@ -34,14 +39,6 @@ export const useToAddressRequirements = () => {
|
|
|
34
39
|
const isCrossChainContractAddress =
|
|
35
40
|
isFromContractAddress && fromChainId !== toChainId
|
|
36
41
|
|
|
37
|
-
const accountNotDeployedAtDestination =
|
|
38
|
-
isFromContractAddress &&
|
|
39
|
-
// We don't want to block transfers for EIP-7702 accounts since they are designed
|
|
40
|
-
// to maintain EOA-like properties while delegating execution.
|
|
41
|
-
!isDelegationDesignatorCode(fromContractCode) &&
|
|
42
|
-
!isToContractAddress &&
|
|
43
|
-
account.address?.toLowerCase() === toAddress?.toLowerCase()
|
|
44
|
-
|
|
45
42
|
const requiredToAddress =
|
|
46
43
|
requiredUI?.includes(RequiredUI.ToAddress) ||
|
|
47
44
|
isDifferentChainType ||
|
|
@@ -50,7 +47,6 @@ export const useToAddressRequirements = () => {
|
|
|
50
47
|
return {
|
|
51
48
|
requiredToAddress,
|
|
52
49
|
requiredToChainType: toChain?.chainType,
|
|
53
|
-
accountNotDeployedAtDestination,
|
|
54
50
|
toAddress,
|
|
55
51
|
}
|
|
56
52
|
}
|
package/src/i18n/en.json
CHANGED
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"emptyTokenList": "We couldn't find tokens on {{chainName}} chain or you don't have any. Please search by contract address if your token doesn't appear or choose another chain.",
|
|
91
91
|
"emptyTransactionHistory": "Transaction history is only stored locally and will be deleted if you clear your browser data.",
|
|
92
92
|
"routeNotFound": "Reasons for that could be: low liquidity, amount selected is too low, gas costs are too high or there are no routes for the selected combination.",
|
|
93
|
-
"toAddressIsRequired": "
|
|
93
|
+
"toAddressIsRequired": "The destination wallet address is required to proceed with the transfer."
|
|
94
94
|
},
|
|
95
95
|
"title": {
|
|
96
96
|
"autoRefuel": "Get {{chainName}} gas",
|
|
@@ -118,6 +118,8 @@
|
|
|
118
118
|
"warning": {
|
|
119
119
|
"message": {
|
|
120
120
|
"accountNotDeployedMessage": "Smart contract account is not deployed on the destination chain. Sending funds to a non-existent contract would result in permanent loss.",
|
|
121
|
+
"noAddressActivity": "This address has never been used on this network. Please verify you're sending to the correct address to prevent potential loss of funds.",
|
|
122
|
+
"lowAddressActivity": "This address has low activity on {{chainName}} network. Please verify you're sending to the correct address and network to prevent potential loss of funds.",
|
|
121
123
|
"deleteActiveTransactions": "Active transactions are only stored locally and can't be recovered if you delete them.",
|
|
122
124
|
"deleteTransactionHistory": "Transaction history is only stored locally and can't be recovered if you delete it.",
|
|
123
125
|
"fundsLossPrevention": "Always ensure smart contract accounts are properly set up on the destination chain and avoid direct transfers to exchanges to prevent fund loss.",
|
|
@@ -135,7 +137,8 @@
|
|
|
135
137
|
"highValueLoss": "High value loss",
|
|
136
138
|
"insufficientGas": "Insufficient gas",
|
|
137
139
|
"rateChanged": "Rate changed",
|
|
138
|
-
"resetSettings": "Reset settings?"
|
|
140
|
+
"resetSettings": "Reset settings?",
|
|
141
|
+
"lowAddressActivity": "Low Activity Address"
|
|
139
142
|
}
|
|
140
143
|
},
|
|
141
144
|
"error": {
|
|
@@ -4,7 +4,6 @@ import { ActiveTransactions } from '../../components/ActiveTransactions/ActiveTr
|
|
|
4
4
|
import { AmountInput } from '../../components/AmountInput/AmountInput.js'
|
|
5
5
|
import { ContractComponent } from '../../components/ContractComponent/ContractComponent.js'
|
|
6
6
|
import { GasRefuelMessage } from '../../components/Messages/GasRefuelMessage.js'
|
|
7
|
-
import { MainMessages } from '../../components/Messages/MainMessages.js'
|
|
8
7
|
import { PageContainer } from '../../components/PageContainer.js'
|
|
9
8
|
import { PoweredBy } from '../../components/PoweredBy/PoweredBy.js'
|
|
10
9
|
import { Routes } from '../../components/Routes/Routes.js'
|
|
@@ -15,6 +14,7 @@ import { useHeader } from '../../hooks/useHeader.js'
|
|
|
15
14
|
import { useWideVariant } from '../../hooks/useWideVariant.js'
|
|
16
15
|
import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'
|
|
17
16
|
import { HiddenUI } from '../../types/widget.js'
|
|
17
|
+
import { MainWarningMessages } from './MainWarningMessages.js'
|
|
18
18
|
import { ReviewButton } from './ReviewButton.js'
|
|
19
19
|
|
|
20
20
|
export const MainPage: React.FC = () => {
|
|
@@ -49,7 +49,7 @@ export const MainPage: React.FC = () => {
|
|
|
49
49
|
{!wideVariant ? <Routes sx={marginSx} /> : null}
|
|
50
50
|
<SendToWalletButton sx={marginSx} />
|
|
51
51
|
<GasRefuelMessage mb={2} />
|
|
52
|
-
<
|
|
52
|
+
<MainWarningMessages mb={2} />
|
|
53
53
|
<Box
|
|
54
54
|
sx={{
|
|
55
55
|
display: 'flex',
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Route } from '@lifi/sdk'
|
|
2
|
+
import type { BoxProps } from '@mui/material'
|
|
3
|
+
import { WarningMessages } from '../../components/Messages/WarningMessages.js'
|
|
4
|
+
import { useRoutes } from '../../hooks/useRoutes.js'
|
|
5
|
+
|
|
6
|
+
interface MainWarningMessagesProps extends BoxProps {
|
|
7
|
+
route?: Route
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const MainWarningMessages: React.FC<MainWarningMessagesProps> = (
|
|
11
|
+
props
|
|
12
|
+
) => {
|
|
13
|
+
const { routes } = useRoutes()
|
|
14
|
+
const currentRoute = routes?.[0]
|
|
15
|
+
|
|
16
|
+
return <WarningMessages route={currentRoute} {...props} />
|
|
17
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useTranslation } from 'react-i18next'
|
|
2
2
|
import { useNavigate } from 'react-router-dom'
|
|
3
3
|
import { BaseTransactionButton } from '../../components/BaseTransactionButton/BaseTransactionButton.js'
|
|
4
|
+
import { useIsCompatibleDestinationAccount } from '../../hooks/useIsCompatibleDestinationAccount.js'
|
|
4
5
|
import { useRoutes } from '../../hooks/useRoutes.js'
|
|
5
6
|
import { useToAddressRequirements } from '../../hooks/useToAddressRequirements.js'
|
|
6
7
|
import { useWidgetEvents } from '../../hooks/useWidgetEvents.js'
|
|
7
8
|
import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'
|
|
8
|
-
import { useFieldValues } from '../../stores/form/useFieldValues.js'
|
|
9
9
|
import { useSplitSubvariantStore } from '../../stores/settings/useSplitSubvariantStore.js'
|
|
10
10
|
import { WidgetEvent } from '../../types/events.js'
|
|
11
11
|
import { navigationRoutes } from '../../utils/navigationRoutes.js'
|
|
@@ -16,24 +16,25 @@ export const ReviewButton: React.FC = () => {
|
|
|
16
16
|
const emitter = useWidgetEvents()
|
|
17
17
|
const { subvariant, subvariantOptions } = useWidgetConfig()
|
|
18
18
|
const splitState = useSplitSubvariantStore((state) => state.state)
|
|
19
|
-
const
|
|
20
|
-
const {
|
|
21
|
-
useToAddressRequirements()
|
|
19
|
+
const { toAddress, requiredToAddress } = useToAddressRequirements()
|
|
20
|
+
const { isCompatibleDestinationAccount } = useIsCompatibleDestinationAccount()
|
|
22
21
|
const { routes, setReviewableRoute } = useRoutes()
|
|
23
22
|
|
|
24
23
|
const currentRoute = routes?.[0]
|
|
25
24
|
|
|
26
25
|
const handleClick = async () => {
|
|
27
|
-
if (currentRoute) {
|
|
28
|
-
|
|
29
|
-
navigate(navigationRoutes.transactionExecution, {
|
|
30
|
-
state: { routeId: currentRoute.id },
|
|
31
|
-
})
|
|
32
|
-
emitter.emit(WidgetEvent.RouteSelected, {
|
|
33
|
-
route: currentRoute,
|
|
34
|
-
routes: routes!,
|
|
35
|
-
})
|
|
26
|
+
if (!currentRoute) {
|
|
27
|
+
return
|
|
36
28
|
}
|
|
29
|
+
|
|
30
|
+
setReviewableRoute(currentRoute)
|
|
31
|
+
navigate(navigationRoutes.transactionExecution, {
|
|
32
|
+
state: { routeId: currentRoute.id },
|
|
33
|
+
})
|
|
34
|
+
emitter.emit(WidgetEvent.RouteSelected, {
|
|
35
|
+
route: currentRoute,
|
|
36
|
+
routes: routes!,
|
|
37
|
+
})
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
const getButtonText = (): string => {
|
|
@@ -75,7 +76,7 @@ export const ReviewButton: React.FC = () => {
|
|
|
75
76
|
onClick={handleClick}
|
|
76
77
|
disabled={
|
|
77
78
|
(currentRoute && requiredToAddress && !toAddress) ||
|
|
78
|
-
|
|
79
|
+
!isCompatibleDestinationAccount
|
|
79
80
|
}
|
|
80
81
|
/>
|
|
81
82
|
)
|
|
@@ -8,6 +8,7 @@ import { RouteCard } from '../../components/RouteCard/RouteCard.js'
|
|
|
8
8
|
import { RouteCardSkeleton } from '../../components/RouteCard/RouteCardSkeleton.js'
|
|
9
9
|
import { RouteNotFoundCard } from '../../components/RouteCard/RouteNotFoundCard.js'
|
|
10
10
|
import { useHeader } from '../../hooks/useHeader.js'
|
|
11
|
+
import { useIsCompatibleDestinationAccount } from '../../hooks/useIsCompatibleDestinationAccount.js'
|
|
11
12
|
import { useNavigateBack } from '../../hooks/useNavigateBack.js'
|
|
12
13
|
import { useRoutes } from '../../hooks/useRoutes.js'
|
|
13
14
|
import { useToAddressRequirements } from '../../hooks/useToAddressRequirements.js'
|
|
@@ -18,6 +19,7 @@ import { navigationRoutes } from '../../utils/navigationRoutes.js'
|
|
|
18
19
|
import { Stack } from './RoutesPage.style.js'
|
|
19
20
|
|
|
20
21
|
export const RoutesPage: React.FC<BoxProps> = () => {
|
|
22
|
+
const { t } = useTranslation()
|
|
21
23
|
const { navigate } = useNavigateBack()
|
|
22
24
|
const emitter = useWidgetEvents()
|
|
23
25
|
const {
|
|
@@ -33,8 +35,7 @@ export const RoutesPage: React.FC<BoxProps> = () => {
|
|
|
33
35
|
const { account } = useAccount({ chainType: fromChain?.chainType })
|
|
34
36
|
const [toAddress] = useFieldValues('toAddress')
|
|
35
37
|
const { requiredToAddress } = useToAddressRequirements()
|
|
36
|
-
|
|
37
|
-
const { t } = useTranslation()
|
|
38
|
+
const { isCompatibleDestinationAccount } = useIsCompatibleDestinationAccount()
|
|
38
39
|
|
|
39
40
|
const headerAction = useMemo(
|
|
40
41
|
() => (
|
|
@@ -65,7 +66,9 @@ export const RoutesPage: React.FC<BoxProps> = () => {
|
|
|
65
66
|
|
|
66
67
|
const routeNotFound = !routes?.length && !isLoading && !isFetching
|
|
67
68
|
|
|
68
|
-
const toAddressUnsatisfied =
|
|
69
|
+
const toAddressUnsatisfied =
|
|
70
|
+
(routes?.[0] && requiredToAddress && !toAddress) ||
|
|
71
|
+
!isCompatibleDestinationAccount
|
|
69
72
|
const allowInteraction = account.isConnected && !toAddressUnsatisfied
|
|
70
73
|
|
|
71
74
|
return (
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Wallet, WarningRounded } from '@mui/icons-material'
|
|
2
|
+
import { Button, Typography } from '@mui/material'
|
|
3
|
+
import type { MutableRefObject } from 'react'
|
|
4
|
+
import { forwardRef, useRef } from 'react'
|
|
5
|
+
import { useTranslation } from 'react-i18next'
|
|
6
|
+
import { BottomSheet } from '../../components/BottomSheet/BottomSheet.js'
|
|
7
|
+
import type { BottomSheetBase } from '../../components/BottomSheet/types.js'
|
|
8
|
+
import { AlertMessage } from '../../components/Messages/AlertMessage.js'
|
|
9
|
+
import { useChain } from '../../hooks/useChain.js'
|
|
10
|
+
import { useSetContentHeight } from '../../hooks/useSetContentHeight.js'
|
|
11
|
+
import { useWidgetEvents } from '../../hooks/useWidgetEvents.js'
|
|
12
|
+
import { WidgetEvent } from '../../types/events.js'
|
|
13
|
+
import {
|
|
14
|
+
IconContainer,
|
|
15
|
+
SendToWalletButtonRow,
|
|
16
|
+
SendToWalletSheetContainer,
|
|
17
|
+
SheetAddressContainer,
|
|
18
|
+
} from '../SendToWallet/SendToWalletPage.style.js'
|
|
19
|
+
|
|
20
|
+
interface ConfirmToAddressSheetProps {
|
|
21
|
+
onContinue: () => void
|
|
22
|
+
toAddress: string
|
|
23
|
+
toChainId: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface ConfirmToAddressSheetContentProps extends ConfirmToAddressSheetProps {
|
|
27
|
+
onClose: () => void
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const ConfirmToAddressSheet = forwardRef<
|
|
31
|
+
BottomSheetBase,
|
|
32
|
+
ConfirmToAddressSheetProps
|
|
33
|
+
>((props, ref) => {
|
|
34
|
+
const handleClose = () => {
|
|
35
|
+
;(ref as MutableRefObject<BottomSheetBase>).current?.close()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<BottomSheet ref={ref}>
|
|
40
|
+
<ConfirmToAddressSheetContent {...props} onClose={handleClose} />
|
|
41
|
+
</BottomSheet>
|
|
42
|
+
)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const ConfirmToAddressSheetContent: React.FC<
|
|
46
|
+
ConfirmToAddressSheetContentProps
|
|
47
|
+
> = ({ onContinue, onClose, toAddress, toChainId }) => {
|
|
48
|
+
const { t } = useTranslation()
|
|
49
|
+
const { chain } = useChain(toChainId)
|
|
50
|
+
const emitter = useWidgetEvents()
|
|
51
|
+
const ref = useRef<HTMLElement>(null)
|
|
52
|
+
useSetContentHeight(ref)
|
|
53
|
+
|
|
54
|
+
const handleContinue = () => {
|
|
55
|
+
emitter.emit(WidgetEvent.LowAddressActivityConfirmed, {
|
|
56
|
+
address: toAddress,
|
|
57
|
+
chainId: toChainId,
|
|
58
|
+
})
|
|
59
|
+
onClose()
|
|
60
|
+
onContinue()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<SendToWalletSheetContainer ref={ref}>
|
|
65
|
+
<IconContainer>
|
|
66
|
+
<Wallet sx={{ fontSize: 40 }} />
|
|
67
|
+
</IconContainer>
|
|
68
|
+
<Typography variant="h6" sx={{ textAlign: 'center', mb: 2 }}>
|
|
69
|
+
{t('warning.title.lowAddressActivity')}
|
|
70
|
+
</Typography>
|
|
71
|
+
<SheetAddressContainer>
|
|
72
|
+
<Typography>{toAddress}</Typography>
|
|
73
|
+
</SheetAddressContainer>
|
|
74
|
+
<AlertMessage
|
|
75
|
+
severity="warning"
|
|
76
|
+
title={
|
|
77
|
+
<Typography variant="body2" sx={{ color: 'text.primary' }}>
|
|
78
|
+
{t('warning.message.lowAddressActivity', {
|
|
79
|
+
chainName: chain?.name,
|
|
80
|
+
})}
|
|
81
|
+
</Typography>
|
|
82
|
+
}
|
|
83
|
+
icon={<WarningRounded />}
|
|
84
|
+
multiline
|
|
85
|
+
/>
|
|
86
|
+
<SendToWalletButtonRow>
|
|
87
|
+
<Button variant="text" onClick={onClose} fullWidth>
|
|
88
|
+
{t('button.cancel')}
|
|
89
|
+
</Button>
|
|
90
|
+
<Button variant="contained" onClick={handleContinue} fullWidth>
|
|
91
|
+
{t('button.continue')}
|
|
92
|
+
</Button>
|
|
93
|
+
</SendToWalletButtonRow>
|
|
94
|
+
</SendToWalletSheetContainer>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { BaseTransactionButton } from '../../components/BaseTransactionButton/BaseTransactionButton.js'
|
|
2
|
-
import {
|
|
3
|
-
import { useGasSufficiency } from '../../hooks/useGasSufficiency.js'
|
|
2
|
+
import { useMessageQueue } from '../../components/Messages/useMessageQueue.js'
|
|
4
3
|
import type { StartTransactionButtonProps } from './types.js'
|
|
5
4
|
|
|
6
5
|
export const StartTransactionButton: React.FC<StartTransactionButtonProps> = ({
|
|
@@ -9,21 +8,14 @@ export const StartTransactionButton: React.FC<StartTransactionButtonProps> = ({
|
|
|
9
8
|
text,
|
|
10
9
|
loading,
|
|
11
10
|
}) => {
|
|
12
|
-
const {
|
|
13
|
-
useGasSufficiency(route)
|
|
14
|
-
const { insufficientFromToken, isLoading: isFromTokenSufficiencyLoading } =
|
|
15
|
-
useFromTokenSufficiency(route)
|
|
16
|
-
|
|
17
|
-
const shouldDisableButton = insufficientFromToken || !!insufficientGas?.length
|
|
11
|
+
const { hasMessages, isLoading } = useMessageQueue(route)
|
|
18
12
|
|
|
19
13
|
return (
|
|
20
14
|
<BaseTransactionButton
|
|
21
15
|
onClick={onClick}
|
|
22
16
|
text={text}
|
|
23
|
-
disabled={
|
|
24
|
-
loading={
|
|
25
|
-
isFromTokenSufficiencyLoading || isGasSufficiencyLoading || loading
|
|
26
|
-
}
|
|
17
|
+
disabled={hasMessages}
|
|
18
|
+
loading={isLoading || loading}
|
|
27
19
|
/>
|
|
28
20
|
)
|
|
29
21
|
}
|
|
@@ -6,10 +6,11 @@ import { useTranslation } from 'react-i18next'
|
|
|
6
6
|
import { useLocation } from 'react-router-dom'
|
|
7
7
|
import type { BottomSheetBase } from '../../components/BottomSheet/types.js'
|
|
8
8
|
import { ContractComponent } from '../../components/ContractComponent/ContractComponent.js'
|
|
9
|
-
import {
|
|
9
|
+
import { WarningMessages } from '../../components/Messages/WarningMessages.js'
|
|
10
10
|
import { PageContainer } from '../../components/PageContainer.js'
|
|
11
11
|
import { getStepList } from '../../components/Step/StepList.js'
|
|
12
12
|
import { TransactionDetails } from '../../components/TransactionDetails.js'
|
|
13
|
+
import { useAddressActivity } from '../../hooks/useAddressActivity.js'
|
|
13
14
|
import { useHeader } from '../../hooks/useHeader.js'
|
|
14
15
|
import { useNavigateBack } from '../../hooks/useNavigateBack.js'
|
|
15
16
|
import { useRouteExecution } from '../../hooks/useRouteExecution.js'
|
|
@@ -19,6 +20,7 @@ import { useFieldActions } from '../../stores/form/useFieldActions.js'
|
|
|
19
20
|
import { RouteExecutionStatus } from '../../stores/routes/types.js'
|
|
20
21
|
import { WidgetEvent } from '../../types/events.js'
|
|
21
22
|
import { getAccumulatedFeeCostsBreakdown } from '../../utils/fees.js'
|
|
23
|
+
import { ConfirmToAddressSheet } from './ConfirmToAddressSheet.js'
|
|
22
24
|
import type { ExchangeRateBottomSheetBase } from './ExchangeRateBottomSheet.js'
|
|
23
25
|
import { ExchangeRateBottomSheet } from './ExchangeRateBottomSheet.js'
|
|
24
26
|
import { RouteTracker } from './RouteTracker.js'
|
|
@@ -44,6 +46,7 @@ export const TransactionPage: React.FC = () => {
|
|
|
44
46
|
|
|
45
47
|
const tokenValueBottomSheetRef = useRef<BottomSheetBase>(null)
|
|
46
48
|
const exchangeRateBottomSheetRef = useRef<ExchangeRateBottomSheetBase>(null)
|
|
49
|
+
const confirmToAddressSheetRef = useRef<BottomSheetBase>(null)
|
|
47
50
|
|
|
48
51
|
const onAcceptExchangeRateUpdate = (
|
|
49
52
|
resolver: (value: boolean) => void,
|
|
@@ -58,6 +61,13 @@ export const TransactionPage: React.FC = () => {
|
|
|
58
61
|
onAcceptExchangeRateUpdate,
|
|
59
62
|
})
|
|
60
63
|
|
|
64
|
+
const {
|
|
65
|
+
toAddress,
|
|
66
|
+
hasActivity,
|
|
67
|
+
isLoading: isLoadingAddressActivity,
|
|
68
|
+
isFetched: isActivityAddressFetched,
|
|
69
|
+
} = useAddressActivity(route?.toChainId)
|
|
70
|
+
|
|
61
71
|
const getHeaderTitle = () => {
|
|
62
72
|
if (subvariant === 'custom') {
|
|
63
73
|
return t(`header.${subvariantOptions?.custom ?? 'checkout'}`)
|
|
@@ -127,6 +137,16 @@ export const TransactionPage: React.FC = () => {
|
|
|
127
137
|
|
|
128
138
|
const handleStartClick = async () => {
|
|
129
139
|
if (status === RouteExecutionStatus.Idle) {
|
|
140
|
+
if (
|
|
141
|
+
toAddress &&
|
|
142
|
+
!hasActivity &&
|
|
143
|
+
!isLoadingAddressActivity &&
|
|
144
|
+
isActivityAddressFetched
|
|
145
|
+
) {
|
|
146
|
+
confirmToAddressSheetRef.current?.open()
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
|
|
130
150
|
const { gasCostUSD, feeCostUSD } = getAccumulatedFeeCostsBreakdown(route)
|
|
131
151
|
const fromAmountUSD = Number.parseFloat(route.fromAmountUSD)
|
|
132
152
|
const toAmountUSD = Number.parseFloat(route.toAmountUSD)
|
|
@@ -187,7 +207,7 @@ export const TransactionPage: React.FC = () => {
|
|
|
187
207
|
{status === RouteExecutionStatus.Idle ||
|
|
188
208
|
status === RouteExecutionStatus.Failed ? (
|
|
189
209
|
<>
|
|
190
|
-
<
|
|
210
|
+
<WarningMessages mt={2} route={route} />
|
|
191
211
|
<Box
|
|
192
212
|
sx={{
|
|
193
213
|
mt: 2,
|
|
@@ -198,7 +218,7 @@ export const TransactionPage: React.FC = () => {
|
|
|
198
218
|
text={getButtonText()}
|
|
199
219
|
onClick={handleStartClick}
|
|
200
220
|
route={route}
|
|
201
|
-
loading={routeRefreshing}
|
|
221
|
+
loading={routeRefreshing || isLoadingAddressActivity}
|
|
202
222
|
/>
|
|
203
223
|
{status === RouteExecutionStatus.Failed ? (
|
|
204
224
|
<Tooltip
|
|
@@ -228,6 +248,12 @@ export const TransactionPage: React.FC = () => {
|
|
|
228
248
|
/>
|
|
229
249
|
) : null}
|
|
230
250
|
<ExchangeRateBottomSheet ref={exchangeRateBottomSheetRef} />
|
|
251
|
+
<ConfirmToAddressSheet
|
|
252
|
+
ref={confirmToAddressSheetRef}
|
|
253
|
+
onContinue={handleExecuteRoute}
|
|
254
|
+
toAddress={toAddress!}
|
|
255
|
+
toChainId={route.toChainId!}
|
|
256
|
+
/>
|
|
231
257
|
</PageContainer>
|
|
232
258
|
)
|
|
233
259
|
}
|
|
@@ -76,7 +76,10 @@ export const URLSearchParamsBuilder = () => {
|
|
|
76
76
|
const validationResult = await validateAddress({
|
|
77
77
|
value: formValues.toAddress,
|
|
78
78
|
})
|
|
79
|
-
if
|
|
79
|
+
// Check if the toAddress is still in the query string
|
|
80
|
+
// Could be modified by the user before the validation is done
|
|
81
|
+
const { toAddress } = getDefaultValuesFromQueryString()
|
|
82
|
+
if (validationResult.isValid && toAddress) {
|
|
80
83
|
const bookmark = {
|
|
81
84
|
address: validationResult.address,
|
|
82
85
|
chainType: validationResult.chainType,
|
package/src/types/events.ts
CHANGED
|
@@ -29,6 +29,7 @@ export enum WidgetEvent {
|
|
|
29
29
|
FormFieldChanged = 'formFieldChanged',
|
|
30
30
|
SettingUpdated = 'settingUpdated',
|
|
31
31
|
TokenSearch = 'tokenSearch',
|
|
32
|
+
LowAddressActivityConfirmed = 'lowAddressActivityConfirmed',
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
export type WidgetEvents = {
|
|
@@ -50,6 +51,10 @@ export type WidgetEvents = {
|
|
|
50
51
|
pageEntered: NavigationRouteType
|
|
51
52
|
settingUpdated: SettingUpdated
|
|
52
53
|
tokenSearch: TokenSearch
|
|
54
|
+
[WidgetEvent.LowAddressActivityConfirmed]: {
|
|
55
|
+
address: string
|
|
56
|
+
chainId: number
|
|
57
|
+
}
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
export type ContactSupport = {
|