Skip to content

Commit

Permalink
fix: restrict 2-step routes if account is not deployed on destination… (
Browse files Browse the repository at this point in the history
  • Loading branch information
chybisov authored Feb 5, 2025
1 parent 8b4bdf2 commit bf48c3c
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 28 deletions.
9 changes: 5 additions & 4 deletions packages/widget/src/components/Messages/useMessageQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Route } from '@lifi/sdk'
import { useMemo } from 'react'
import { useFromTokenSufficiency } from '../../hooks/useFromTokenSufficiency.js'
import { useGasSufficiency } from '../../hooks/useGasSufficiency.js'
import { useIsCompatibleDestinationAccount } from '../../hooks/useIsCompatibleDestinationAccount.js'
import { useToAddressRequirements } from '../../hooks/useToAddressRequirements.js'

interface QueuedMessage {
Expand All @@ -11,8 +12,8 @@ interface QueuedMessage {
}

export const useMessageQueue = (route?: Route) => {
const { requiredToAddress, accountNotDeployedAtDestination, toAddress } =
useToAddressRequirements()
const { requiredToAddress, toAddress } = useToAddressRequirements()
const { isCompatibleDestinationAccount } = useIsCompatibleDestinationAccount()
const { insufficientFromToken } = useFromTokenSufficiency(route)
const { insufficientGas } = useGasSufficiency(route)

Expand All @@ -34,7 +35,7 @@ export const useMessageQueue = (route?: Route) => {
})
}

if (accountNotDeployedAtDestination) {
if (!isCompatibleDestinationAccount) {
queue.push({
id: 'ACCOUNT_NOT_DEPLOYED',
priority: 3,
Expand All @@ -50,7 +51,7 @@ export const useMessageQueue = (route?: Route) => {

return queue.sort((a, b) => a.priority - b.priority)
}, [
accountNotDeployedAtDestination,
isCompatibleDestinationAccount,
insufficientFromToken,
insufficientGas,
requiredToAddress,
Expand Down
46 changes: 46 additions & 0 deletions packages/widget/src/hooks/useIsCompatibleDestinationAccount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useAccount } from '@lifi/wallet-management'
import { useFieldValues } from '../stores/form/useFieldValues.js'
import { isDelegationDesignatorCode } from '../utils/eip7702.js'
import { useChain } from './useChain.js'
import { useIsContractAddress } from './useIsContractAddress.js'

export const useIsCompatibleDestinationAccount = () => {
const [fromChainId, toChainId, toAddress] = useFieldValues(
'fromChain',
'toChain',
'toAddress'
)
const { chain: fromChain } = useChain(fromChainId)
const { chain: toChain } = useChain(toChainId)
const { account } = useAccount({
chainType: fromChain?.chainType,
})

const {
isContractAddress: isFromContractAddress,
contractCode: fromContractCode,
isLoading: isFromContractLoading,
isFetched: isFromContractFetched,
} = useIsContractAddress(account.address, fromChainId, account.chainType)
const {
isContractAddress: isToContractAddress,
isLoading: isToContractLoading,
isFetched: isToContractFetched,
} = useIsContractAddress(toAddress, toChainId, toChain?.chainType)

const accountNotDeployedAtDestination =
isFromContractAddress &&
// We don't want to block transfers for EIP-7702 accounts since they are designed
// to maintain EOA-like properties while delegating execution.
!isDelegationDesignatorCode(fromContractCode) &&
!isToContractAddress &&
account.address?.toLowerCase() === toAddress?.toLowerCase()

return {
isCompatibleDestinationAccount: !accountNotDeployedAtDestination,
isFromContractAddress,
isToContractAddress,
isLoading: isFromContractLoading || isToContractLoading,
isFetched: isFromContractFetched && isToContractFetched,
}
}
13 changes: 11 additions & 2 deletions packages/widget/src/hooks/useIsContractAddress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ export const useIsContractAddress = (
chainId?: number,
chainType?: ChainType
) => {
const { data: contractCode } = useBytecode({
const {
data: contractCode,
isLoading,
isFetched,
} = useBytecode({
address: address as Address,
chainId: chainId,
query: {
Expand All @@ -17,5 +21,10 @@ export const useIsContractAddress = (
},
})

return { isContractAddress: !!contractCode, contractCode }
return {
isContractAddress: !!contractCode,
contractCode,
isLoading,
isFetched,
}
}
7 changes: 6 additions & 1 deletion packages/widget/src/hooks/useRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { getChainTypeFromAddress } from '../utils/chainType.js'
import { useChain } from './useChain.js'
import { useDebouncedWatch } from './useDebouncedWatch.js'
import { useGasRefuel } from './useGasRefuel.js'
import { useIsCompatibleDestinationAccount } from './useIsCompatibleDestinationAccount.js'
import { useSwapOnly } from './useSwapOnly.js'
import { useToken } from './useToken.js'
import { useWidgetEvents } from './useWidgetEvents.js'
Expand Down Expand Up @@ -76,6 +77,7 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
const { token: toToken } = useToken(toChainId, toTokenAddress)
const { chain: fromChain } = useChain(fromChainId)
const { chain: toChain } = useChain(toChainId)
const { isCompatibleDestinationAccount } = useIsCompatibleDestinationAccount()
const { enabled: enabledRefuel, fromAmount: gasRecommendationFromAmount } =
useGasRefuel()

Expand Down Expand Up @@ -107,6 +109,9 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
exchanges?.allow?.length || exchanges?.deny?.length
? enabledExchanges
: undefined
const allowSwitchChain = isCompatibleDestinationAccount
? sdkConfig?.routeOptions?.allowSwitchChain
: false

const isEnabled =
Boolean(Number(fromChainId)) &&
Expand Down Expand Up @@ -138,7 +143,7 @@ export const useRoutes = ({ observableRoute }: RoutesProps = {}) => {
allowedExchanges,
routePriority,
subvariant,
sdkConfig?.routeOptions?.allowSwitchChain,
allowSwitchChain,
enabledRefuel && enabledAutoRefuel,
gasRecommendationFromAmount,
feeConfig?.fee || fee,
Expand Down
22 changes: 4 additions & 18 deletions packages/widget/src/hooks/useToAddressRequirements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { useChain } from '../hooks/useChain.js'
import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'
import { useFieldValues } from '../stores/form/useFieldValues.js'
import { RequiredUI } from '../types/widget.js'
import { isDelegationDesignatorCode } from '../utils/eip7702.js'
import { useIsContractAddress } from './useIsContractAddress.js'

export const useToAddressRequirements = () => {
Expand All @@ -18,14 +17,10 @@ export const useToAddressRequirements = () => {
const { account } = useAccount({
chainType: fromChain?.chainType,
})
const {
isContractAddress: isFromContractAddress,
contractCode: fromContractCode,
} = useIsContractAddress(account.address, fromChainId, account.chainType)
const { isContractAddress: isToContractAddress } = useIsContractAddress(
toAddress,
toChainId,
toChain?.chainType
const { isContractAddress: isFromContractAddress } = useIsContractAddress(
account.address,
fromChainId,
account.chainType
)

const isDifferentChainType =
Expand All @@ -34,14 +29,6 @@ export const useToAddressRequirements = () => {
const isCrossChainContractAddress =
isFromContractAddress && fromChainId !== toChainId

const accountNotDeployedAtDestination =
isFromContractAddress &&
// We don't want to block transfers for EIP-7702 accounts since they are designed
// to maintain EOA-like properties while delegating execution.
!isDelegationDesignatorCode(fromContractCode) &&
!isToContractAddress &&
account.address?.toLowerCase() === toAddress?.toLowerCase()

const requiredToAddress =
requiredUI?.includes(RequiredUI.ToAddress) ||
isDifferentChainType ||
Expand All @@ -50,7 +37,6 @@ export const useToAddressRequirements = () => {
return {
requiredToAddress,
requiredToChainType: toChain?.chainType,
accountNotDeployedAtDestination,
toAddress,
}
}
7 changes: 4 additions & 3 deletions packages/widget/src/pages/MainPage/ReviewButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom'
import { BaseTransactionButton } from '../../components/BaseTransactionButton/BaseTransactionButton.js'
import { useIsCompatibleDestinationAccount } from '../../hooks/useIsCompatibleDestinationAccount.js'
import { useRoutes } from '../../hooks/useRoutes.js'
import { useToAddressRequirements } from '../../hooks/useToAddressRequirements.js'
import { useWidgetEvents } from '../../hooks/useWidgetEvents.js'
Expand All @@ -15,8 +16,8 @@ export const ReviewButton: React.FC = () => {
const emitter = useWidgetEvents()
const { subvariant, subvariantOptions } = useWidgetConfig()
const splitState = useSplitSubvariantStore((state) => state.state)
const { toAddress, requiredToAddress, accountNotDeployedAtDestination } =
useToAddressRequirements()
const { toAddress, requiredToAddress } = useToAddressRequirements()
const { isCompatibleDestinationAccount } = useIsCompatibleDestinationAccount()
const { routes, setReviewableRoute } = useRoutes()

const currentRoute = routes?.[0]
Expand Down Expand Up @@ -75,7 +76,7 @@ export const ReviewButton: React.FC = () => {
onClick={handleClick}
disabled={
(currentRoute && requiredToAddress && !toAddress) ||
accountNotDeployedAtDestination
!isCompatibleDestinationAccount
}
/>
)
Expand Down

0 comments on commit bf48c3c

Please sign in to comment.