Skip to content

Commit

Permalink
Merge branch 'main' into LF-10831-transaction-history-page-virtualiza…
Browse files Browse the repository at this point in the history
…tion
  • Loading branch information
tomiiide authored Feb 12, 2025
2 parents b92bc90 + 6f120f9 commit 1881413
Show file tree
Hide file tree
Showing 18 changed files with 111 additions and 43 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

### [3.16.1](https://github.com/lifinance/widget/compare/v3.16.0...v3.16.1) (2025-02-11)


### Bug Fixes

* improve SCA as destination flow ([#350](https://github.com/lifinance/widget/issues/350)) ([c58e5dd](https://github.com/lifinance/widget/commit/c58e5dd5835f8b25411f9b33070c914702f659ab))
* replace lifuel protocol with gasZip ([#347](https://github.com/lifinance/widget/issues/347)) ([8b4bdf2](https://github.com/lifinance/widget/commit/8b4bdf24936c01f4efd24f8a370262992cd545a0))
* restrict 2-step routes if account is not deployed on destination… ([#346](https://github.com/lifinance/widget/issues/346)) ([bf48c3c](https://github.com/lifinance/widget/commit/bf48c3c0ed5501bc720dfa8003f1b0ffd3ec571d))

## [3.16.0](https://github.com/lifinance/widget/compare/v3.15.2...v3.16.0) (2025-02-01)


Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "3.16.0",
"version": "3.16.1",
"private": true,
"sideEffects": false,
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion packages/widget/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lifi/widget",
"version": "3.16.0",
"version": "3.16.1",
"description": "LI.FI Widget for cross-chain bridging and swapping. It will drive your multi-chain strategy and attract new users from everywhere.",
"type": "module",
"main": "./src/index.ts",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import type { Route } from '@lifi/sdk'
import type { BoxProps } from '@mui/material'
import { Collapse } from '@mui/material'
import { useRoutes } from '../../hooks/useRoutes.js'
import { AccountNotDeployedMessage } from './AccountNotDeployedMessage.js'
import { FundsSufficiencyMessage } from './FundsSufficiencyMessage.js'
import { GasSufficiencyMessage } from './GasSufficiencyMessage.js'
import { ToAddressRequiredMessage } from './ToAddressRequiredMessage.js'
import { useMessageQueue } from './useMessageQueue.js'

export const MainMessages: React.FC<BoxProps> = (props) => {
const { routes } = useRoutes()
const currentRoute = routes?.[0]
const { currentMessage, hasMessages } = useMessageQueue(currentRoute)
type WarningMessagesProps = BoxProps & {
route?: Route
}

export const WarningMessages: React.FC<WarningMessagesProps> = ({
route,
...props
}) => {
const { currentMessage, hasMessages } = useMessageQueue(route)

const getMessage = () => {
switch (currentMessage?.id) {
Expand Down
17 changes: 13 additions & 4 deletions packages/widget/src/components/Messages/useMessageQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@ interface QueuedMessage {
}

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

const messageQueue = useMemo(() => {
const queue: QueuedMessage[] = []
Expand Down Expand Up @@ -61,5 +66,9 @@ export const useMessageQueue = (route?: Route) => {
return {
currentMessage: messageQueue[0],
hasMessages: messageQueue.length > 0,
isLoading:
isGasSufficiencyLoading ||
isFromTokenSufficiencyLoading ||
isCompatibleDestinationAccountLoading,
}
}
6 changes: 5 additions & 1 deletion packages/widget/src/components/Routes/RoutesExpanded.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { type PropsWithChildren, useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import type { RouteObject } from 'react-router-dom'
import { useRoutes as useDOMRoutes, useNavigate } from 'react-router-dom'
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 Down Expand Up @@ -82,6 +83,7 @@ export const RoutesExpandedElement = () => {
const { account } = useAccount({ chainType: fromChain?.chainType })
const [toAddress] = useFieldValues('toAddress')
const { requiredToAddress } = useToAddressRequirements()
const { isCompatibleDestinationAccount } = useIsCompatibleDestinationAccount()

const handleRouteClick = (route: Route) => {
setReviewableRoute(route)
Expand Down Expand Up @@ -111,7 +113,9 @@ export const RoutesExpandedElement = () => {
)

const routeNotFound = !currentRoute && !isLoading && !isFetching && expanded
const toAddressUnsatisfied = currentRoute && requiredToAddress && !toAddress
const toAddressUnsatisfied =
(currentRoute && requiredToAddress && !toAddress) ||
!isCompatibleDestinationAccount
const allowInteraction = account.isConnected && !toAddressUnsatisfied

useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion packages/widget/src/config/version.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export const name = '@lifi/widget'
export const version = '3.16.0'
export const version = '3.16.1'
20 changes: 16 additions & 4 deletions packages/widget/src/hooks/useIsCompatibleDestinationAccount.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,39 @@
import type { RouteExtended } from '@lifi/sdk'
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(
export const useIsCompatibleDestinationAccount = (route?: RouteExtended) => {
const [formFromChainId, formToChainId, formToAddress] = useFieldValues(
'fromChain',
'toChain',
'toAddress'
)

const fromChainId = route?.fromChainId ?? formFromChainId
const toChainId = route?.toChainId ?? formToChainId

const { chain: fromChain } = useChain(fromChainId)
const { chain: toChain } = useChain(toChainId)
const { account } = useAccount({
chainType: fromChain?.chainType,
})

const fromAddress = route?.fromAddress ?? account.address
const toAddress = route
? route.fromAddress !== route.toAddress
? route.toAddress
: formToAddress
: formToAddress

const {
isContractAddress: isFromContractAddress,
contractCode: fromContractCode,
isLoading: isFromContractLoading,
isFetched: isFromContractFetched,
} = useIsContractAddress(account.address, fromChainId, account.chainType)
} = useIsContractAddress(fromAddress, fromChainId, fromChain?.chainType)
const {
isContractAddress: isToContractAddress,
isLoading: isToContractLoading,
Expand All @@ -34,7 +46,7 @@ export const useIsCompatibleDestinationAccount = () => {
// to maintain EOA-like properties while delegating execution.
!isDelegationDesignatorCode(fromContractCode) &&
!isToContractAddress &&
account.address?.toLowerCase() === toAddress?.toLowerCase()
fromAddress?.toLowerCase() === toAddress?.toLowerCase()

return {
isCompatibleDestinationAccount: !accountNotDeployedAtDestination,
Expand Down
3 changes: 2 additions & 1 deletion packages/widget/src/hooks/useRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ 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 { isCompatibleDestinationAccount } =
useIsCompatibleDestinationAccount(observableRoute)
const { enabled: enabledRefuel, fromAmount: gasRecommendationFromAmount } =
useGasRefuel()

Expand Down
14 changes: 12 additions & 2 deletions packages/widget/src/hooks/useToAddressRequirements.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
import type { RouteExtended } from '@lifi/sdk'
import { useAccount } from '@lifi/wallet-management'
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 { useIsContractAddress } from './useIsContractAddress.js'

export const useToAddressRequirements = () => {
export const useToAddressRequirements = (route?: RouteExtended) => {
const { requiredUI } = useWidgetConfig()
const [fromChainId, toChainId, toAddress] = useFieldValues(
const [formFromChainId, formToChainId, formToAddress] = useFieldValues(
'fromChain',
'toChain',
'toAddress'
)

const fromChainId = route?.fromChainId ?? formFromChainId
const toChainId = route?.toChainId ?? formToChainId
const toAddress = route
? route.fromAddress !== route.toAddress
? route.toAddress
: formToAddress
: formToAddress

const { chain: fromChain } = useChain(fromChainId)
const { chain: toChain } = useChain(toChainId)
const { account } = useAccount({
Expand Down
2 changes: 1 addition & 1 deletion packages/widget/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
"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.",
"emptyTransactionHistory": "Transaction history is only stored locally and will be deleted if you clear your browser data.",
"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.",
"toAddressIsRequired": "Please provide the destination wallet address to which the funds will be transferred."
"toAddressIsRequired": "The destination wallet address is required to proceed with the transfer."
},
"title": {
"autoRefuel": "Get {{chainName}} gas",
Expand Down
4 changes: 2 additions & 2 deletions packages/widget/src/pages/MainPage/MainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { ActiveTransactions } from '../../components/ActiveTransactions/ActiveTr
import { AmountInput } from '../../components/AmountInput/AmountInput.js'
import { ContractComponent } from '../../components/ContractComponent/ContractComponent.js'
import { GasRefuelMessage } from '../../components/Messages/GasRefuelMessage.js'
import { MainMessages } from '../../components/Messages/MainMessages.js'
import { PageContainer } from '../../components/PageContainer.js'
import { PoweredBy } from '../../components/PoweredBy/PoweredBy.js'
import { Routes } from '../../components/Routes/Routes.js'
Expand All @@ -15,6 +14,7 @@ import { useHeader } from '../../hooks/useHeader.js'
import { useWideVariant } from '../../hooks/useWideVariant.js'
import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'
import { HiddenUI } from '../../types/widget.js'
import { MainWarningMessages } from './MainWarningMessages.js'
import { ReviewButton } from './ReviewButton.js'

export const MainPage: React.FC = () => {
Expand Down Expand Up @@ -49,7 +49,7 @@ export const MainPage: React.FC = () => {
{!wideVariant ? <Routes sx={marginSx} /> : null}
<SendToWalletButton sx={marginSx} />
<GasRefuelMessage mb={2} />
<MainMessages mb={2} />
<MainWarningMessages mb={2} />
<Box
sx={{
display: 'flex',
Expand Down
17 changes: 17 additions & 0 deletions packages/widget/src/pages/MainPage/MainWarningMessages.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { Route } from '@lifi/sdk'
import type { BoxProps } from '@mui/material'
import { WarningMessages } from '../../components/Messages/WarningMessages.js'
import { useRoutes } from '../../hooks/useRoutes.js'

interface MainWarningMessagesProps extends BoxProps {
route?: Route
}

export const MainWarningMessages: React.FC<MainWarningMessagesProps> = (
props
) => {
const { routes } = useRoutes()
const currentRoute = routes?.[0]

return <WarningMessages route={currentRoute} {...props} />
}
9 changes: 6 additions & 3 deletions packages/widget/src/pages/RoutesPage/RoutesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { RouteCard } from '../../components/RouteCard/RouteCard.js'
import { RouteCardSkeleton } from '../../components/RouteCard/RouteCardSkeleton.js'
import { RouteNotFoundCard } from '../../components/RouteCard/RouteNotFoundCard.js'
import { useHeader } from '../../hooks/useHeader.js'
import { useIsCompatibleDestinationAccount } from '../../hooks/useIsCompatibleDestinationAccount.js'
import { useNavigateBack } from '../../hooks/useNavigateBack.js'
import { useRoutes } from '../../hooks/useRoutes.js'
import { useToAddressRequirements } from '../../hooks/useToAddressRequirements.js'
Expand All @@ -18,6 +19,7 @@ import { navigationRoutes } from '../../utils/navigationRoutes.js'
import { Stack } from './RoutesPage.style.js'

export const RoutesPage: React.FC<BoxProps> = () => {
const { t } = useTranslation()
const { navigate } = useNavigateBack()
const emitter = useWidgetEvents()
const {
Expand All @@ -33,8 +35,7 @@ export const RoutesPage: React.FC<BoxProps> = () => {
const { account } = useAccount({ chainType: fromChain?.chainType })
const [toAddress] = useFieldValues('toAddress')
const { requiredToAddress } = useToAddressRequirements()

const { t } = useTranslation()
const { isCompatibleDestinationAccount } = useIsCompatibleDestinationAccount()

const headerAction = useMemo(
() => (
Expand Down Expand Up @@ -65,7 +66,9 @@ export const RoutesPage: React.FC<BoxProps> = () => {

const routeNotFound = !routes?.length && !isLoading && !isFetching

const toAddressUnsatisfied = routes?.[0] && requiredToAddress && !toAddress
const toAddressUnsatisfied =
(routes?.[0] && requiredToAddress && !toAddress) ||
!isCompatibleDestinationAccount
const allowInteraction = account.isConnected && !toAddressUnsatisfied

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Wallet, WarningRounded } from '@mui/icons-material'
import { Button, Typography } from '@mui/material'
import type { MutableRefObject } from 'react'
import { forwardRef } from 'react'
import { forwardRef, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { BottomSheet } from '../../components/BottomSheet/BottomSheet.js'
import type { BottomSheetBase } from '../../components/BottomSheet/types.js'
import { AlertMessage } from '../../components/Messages/AlertMessage.js'
import { useChain } from '../../hooks/useChain.js'
import { useSetContentHeight } from '../../hooks/useSetContentHeight.js'
import { useWidgetEvents } from '../../hooks/useWidgetEvents.js'
import { WidgetEvent } from '../../types/events.js'
import {
Expand Down Expand Up @@ -47,6 +48,8 @@ const ConfirmToAddressSheetContent: React.FC<
const { t } = useTranslation()
const { chain } = useChain(toChainId)
const emitter = useWidgetEvents()
const ref = useRef<HTMLElement>(null)
useSetContentHeight(ref)

const handleContinue = () => {
emitter.emit(WidgetEvent.LowAddressActivityConfirmed, {
Expand All @@ -58,7 +61,7 @@ const ConfirmToAddressSheetContent: React.FC<
}

return (
<SendToWalletSheetContainer>
<SendToWalletSheetContainer ref={ref}>
<IconContainer>
<Wallet sx={{ fontSize: 40 }} />
</IconContainer>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { BaseTransactionButton } from '../../components/BaseTransactionButton/BaseTransactionButton.js'
import { useFromTokenSufficiency } from '../../hooks/useFromTokenSufficiency.js'
import { useGasSufficiency } from '../../hooks/useGasSufficiency.js'
import { useMessageQueue } from '../../components/Messages/useMessageQueue.js'
import type { StartTransactionButtonProps } from './types.js'

export const StartTransactionButton: React.FC<StartTransactionButtonProps> = ({
Expand All @@ -9,21 +8,14 @@ export const StartTransactionButton: React.FC<StartTransactionButtonProps> = ({
text,
loading,
}) => {
const { insufficientGas, isLoading: isGasSufficiencyLoading } =
useGasSufficiency(route)
const { insufficientFromToken, isLoading: isFromTokenSufficiencyLoading } =
useFromTokenSufficiency(route)

const shouldDisableButton = insufficientFromToken || !!insufficientGas?.length
const { hasMessages, isLoading } = useMessageQueue(route)

return (
<BaseTransactionButton
onClick={onClick}
text={text}
disabled={shouldDisableButton}
loading={
isFromTokenSufficiencyLoading || isGasSufficiencyLoading || loading
}
disabled={hasMessages}
loading={isLoading || loading}
/>
)
}
4 changes: 2 additions & 2 deletions packages/widget/src/pages/TransactionPage/TransactionPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'
import { useLocation } from 'react-router-dom'
import type { BottomSheetBase } from '../../components/BottomSheet/types.js'
import { ContractComponent } from '../../components/ContractComponent/ContractComponent.js'
import { GasMessage } from '../../components/Messages/GasMessage.js'
import { WarningMessages } from '../../components/Messages/WarningMessages.js'
import { PageContainer } from '../../components/PageContainer.js'
import { getStepList } from '../../components/Step/StepList.js'
import { TransactionDetails } from '../../components/TransactionDetails.js'
Expand Down Expand Up @@ -207,7 +207,7 @@ export const TransactionPage: React.FC = () => {
{status === RouteExecutionStatus.Idle ||
status === RouteExecutionStatus.Failed ? (
<>
<GasMessage mt={2} route={route} />
<WarningMessages mt={2} route={route} />
<Box
sx={{
mt: 2,
Expand Down
5 changes: 4 additions & 1 deletion packages/widget/src/stores/form/URLSearchParamsBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,10 @@ export const URLSearchParamsBuilder = () => {
const validationResult = await validateAddress({
value: formValues.toAddress,
})
if (validationResult.isValid) {
// Check if the toAddress is still in the query string
// Could be modified by the user before the validation is done
const { toAddress } = getDefaultValuesFromQueryString()
if (validationResult.isValid && toAddress) {
const bookmark = {
address: validationResult.address,
chainType: validationResult.chainType,
Expand Down

0 comments on commit 1881413

Please sign in to comment.