Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ export type SequenceCheckoutProviderProps = {
const getDefaultLocationCheckout = (): NavigationCheckout => {
return {
location: 'payment-method-selection',
params: {}
params: {
isInitialBalanceChecked: false
}
}
}
export const SequenceCheckoutProvider = ({ children, config }: SequenceCheckoutProviderProps) => {
Expand Down
1 change: 1 addition & 0 deletions packages/checkout/src/contexts/NavigationCheckout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface PaymentMethodSelectionParams {
address: string
chainId: number
}
isInitialBalanceChecked: boolean
}

export interface PaymentMehodSelection {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@ import { useAccount, useChainId, usePublicClient, useReadContract, useSwitchChai

import { ERC_20_CONTRACT_ABI } from '../../../../constants/abi.js'
import { EVENT_SOURCE } from '../../../../constants/index.js'
import { type PaymentMethodSelectionParams } from '../../../../contexts/NavigationCheckout.js'
import type { SelectPaymentSettings } from '../../../../contexts/SelectPaymentModal.js'
import { useAddFundsModal } from '../../../../hooks/index.js'
import { useSelectPaymentModal, useTransactionStatusModal } from '../../../../hooks/index.js'
import { useNavigationCheckout } from '../../../../hooks/useNavigationCheckout.js'

import { useInitialBalanceCheck } from './useInitialBalanceCheck.js'

interface PayWithCryptoTabProps {
skipOnCloseCallback: () => void
isSwitchingChainRef: RefObject<boolean>
Expand Down Expand Up @@ -156,7 +159,8 @@ export const PayWithCryptoTab = ({ skipOnCloseCallback, isSwitchingChainRef }: P
const isNotEnoughBalanceError =
typeof swapQuoteError?.cause === 'string' && swapQuoteError?.cause?.includes('not enough balance for swap')

const selectedCurrencyPrice = isSwapTransaction ? swapQuote?.maxPrice || 0 : price || 0
const maxPrice = swapQuote?.maxPrice && swapQuote.maxPrice !== '' ? swapQuote.maxPrice : 0
const selectedCurrencyPrice = isSwapTransaction ? maxPrice : price || 0

const { data: allowanceData, isLoading: allowanceIsLoading } = useReadContract({
abi: ERC_20_CONTRACT_ABI,
Expand All @@ -169,20 +173,33 @@ export const PayWithCryptoTab = ({ skipOnCloseCallback, isSwitchingChainRef }: P
}
})

const isInitialBalanceChecked = (navigation.params as PaymentMethodSelectionParams).isInitialBalanceChecked

const isLoading =
isLoadingCoinPrice ||
isLoadingCurrencyInfo ||
(allowanceIsLoading && !isNativeToken) ||
isLoadingSwapQuote ||
tokenBalancesIsLoading ||
isLoadingSelectedCurrencyInfo
isLoadingSelectedCurrencyInfo ||
!isInitialBalanceChecked

const tokenBalance = tokenBalancesData?.pages?.[0]?.balances?.find(balance =>
compareAddress(balance.contractAddress, selectedCurrency.address)
)

const isInsufficientBalance =
tokenBalance === undefined || (tokenBalance?.balance && BigInt(tokenBalance.balance) < BigInt(selectedCurrencyPrice))
tokenBalance === undefined ||
(tokenBalance?.balance && tokenBalance.balance !== '' && BigInt(tokenBalance.balance) < BigInt(selectedCurrencyPrice))

useInitialBalanceCheck({
userAddress: userAddress || '',
buyCurrencyAddress,
price,
chainId,
isInsufficientBalance: isInsufficientBalance as boolean,
tokenBalancesIsLoading
})

const isApproved: boolean = (allowanceData as bigint) >= BigInt(price) || isNativeToken

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { compareAddress, ContractVerificationStatus } from '@0xsequence/connect'
import { useGetSwapRoutes, useGetTokenBalancesSummary } from '@0xsequence/hooks'
import { useEffect } from 'react'
import { zeroAddress } from 'viem'

import { type PaymentMethodSelectionParams } from '../../../../contexts/NavigationCheckout.js'
import { useNavigationCheckout } from '../../../../hooks/useNavigationCheckout.js'

interface UseInitialBalanceCheckArgs {
userAddress: string
buyCurrencyAddress: string
price: string
chainId: number
isInsufficientBalance: boolean
tokenBalancesIsLoading: boolean
}

// Hook to check if the user has enough of a balance of the
// initial currency to purchase the item
// If not, a swap route for which he has enough balance will be selected
export const useInitialBalanceCheck = ({
userAddress,
buyCurrencyAddress,
price,
chainId,
isInsufficientBalance,
tokenBalancesIsLoading
}: UseInitialBalanceCheckArgs) => {
const { navigation, setNavigation } = useNavigationCheckout()

const isInitialBalanceChecked = (navigation.params as PaymentMethodSelectionParams).isInitialBalanceChecked

const { data: swapRoutes = [], isLoading: swapRoutesIsLoading } = useGetSwapRoutes(
{
walletAddress: userAddress ?? '',
toTokenAddress: buyCurrencyAddress,
toTokenAmount: price,
chainId: chainId
},
{
disabled: isInitialBalanceChecked || !isInsufficientBalance
}
)

const { data: swapRoutesTokenBalancesData, isLoading: swapRoutesTokenBalancesIsLoading } = useGetTokenBalancesSummary(
{
chainIds: [chainId],
filter: {
accountAddresses: userAddress ? [userAddress] : [],
contractStatus: ContractVerificationStatus.ALL,
contractWhitelist: swapRoutes
.flatMap(route => route.fromTokens)
.map(token => token.address)
.filter(address => compareAddress(address, zeroAddress)),
omitNativeBalances: false
},
omitMetadata: true
},
{
disabled: isInitialBalanceChecked || !isInsufficientBalance || swapRoutesIsLoading
}
)

const findSwapQuote = async () => {
let validSwapRoute: string | undefined

const route = swapRoutes[0]
for (let j = 0; j < route.fromTokens.length; j++) {
const fromToken = route.fromTokens[j]
const balance = swapRoutesTokenBalancesData?.pages?.[0]?.balances?.find(balance =>
compareAddress(balance.contractAddress, fromToken.address)
)

if (!balance) {
continue
}
if (BigInt(balance.balance || '0') >= BigInt(fromToken.price || '0')) {
validSwapRoute = fromToken.address
break
}
}

setNavigation({
location: 'payment-method-selection',
params: {
...navigation.params,
selectedCurrency: {
address: validSwapRoute || buyCurrencyAddress,
chainId: chainId
},
isInitialBalanceChecked: true
}
})
}

useEffect(() => {
if (!isInitialBalanceChecked && !tokenBalancesIsLoading && !swapRoutesIsLoading && !swapRoutesTokenBalancesIsLoading) {
if (isInsufficientBalance) {
findSwapQuote()
} else {
setNavigation({
location: 'payment-method-selection',
params: {
...navigation.params,
isInitialBalanceChecked: true
}
})
}
}
}, [
isInitialBalanceChecked,
isInsufficientBalance,
tokenBalancesIsLoading,
swapRoutesIsLoading,
swapRoutesTokenBalancesIsLoading
])
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ export const TokenSelectionContent = () => {
selectedCurrency: {
address: token.contractAddress,
chainId: token.chainId
}
},
isInitialBalanceChecked: true
}
}
])
Expand Down