Skip to content

Commit

Permalink
feat: check allowance before approve (#166)
Browse files Browse the repository at this point in the history
  • Loading branch information
bayological authored Nov 28, 2024
1 parent 1f6f868 commit fdcdaea
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 10 deletions.
4 changes: 4 additions & 0 deletions src/config/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ export const MAX_GAS_LIMIT = '10000000' // 10 million
export const MIN_ROUNDED_VALUE = 0.0001
export const DISPLAY_DECIMALS = 4
export const MAX_EXCHANGE_SPREAD = 0.1 // 10%

export const ERC20_ABI = [
'function allowance(address owner, address spender) view returns (uint256)',
]
53 changes: 44 additions & 9 deletions src/features/swap/SwapConfirm.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import BigNumber from 'bignumber.js'
import Lottie from 'lottie-react'
import { SVGProps, useEffect, useState } from 'react'
import mentoLoaderBlue from 'src/animations/Mentoloader_blue.json'
Expand All @@ -8,6 +9,7 @@ import { TokenId, Tokens } from 'src/config/tokens'
import { useAppDispatch, useAppSelector } from 'src/features/store/hooks'
import { setConfirmView, setFormValues } from 'src/features/swap/swapSlice'
import { SwapFormValues } from 'src/features/swap/types'
import { useAllowance } from 'src/features/swap/useAllowance'
import { useApproveTransaction } from 'src/features/swap/useApproveTransaction'
import { useSwapQuote } from 'src/features/swap/useSwapQuote'
import { useSwapTransaction } from 'src/features/swap/useSwapTransaction'
Expand Down Expand Up @@ -90,6 +92,21 @@ export function SwapConfirmCard({ formValues }: Props) {
)
const [isApproveConfirmed, setApproveConfirmed] = useState(false)

const { allowance, isLoading: isAllowanceLoading } = useAllowance(chainId, fromTokenId, address)
const needsApproval = !isAllowanceLoading && new BigNumber(allowance).lte(approveAmount)
const skipApprove = !isAllowanceLoading && !needsApproval

logger.info(`Allowance loading: ${isAllowanceLoading}`)
logger.info(`Needs approval: ${needsApproval}`)

useEffect(() => {
if (skipApprove) {
// Enables swap transaction preparation when approval isn't needed
// See useSwapTransaction hook for more details
setApproveConfirmed(true)
}
}, [skipApprove])

const { sendSwapTx, isSwapTxLoading, isSwapTxSuccess } = useSwapTransaction(
chainId,
fromTokenId,
Expand All @@ -104,13 +121,29 @@ export function SwapConfirmCard({ formValues }: Props) {
const onSubmit = async () => {
if (!rate || !amountWei || !address || !isConnected) return

setIsModalOpen(true)

if (skipApprove && sendSwapTx) {
try {
logger.info('Skipping approve, sending swap tx directly')
const swapResult = await sendSwapTx()
const swapReceipt = await swapResult.wait(1)
logger.info(`Tx receipt received for swap: ${swapReceipt?.transactionHash}`)
toastToYourSuccess('Swap Complete!', swapReceipt?.transactionHash, chainId)
dispatch(setFormValues(null))
} catch (error) {
logger.error('Failed to execute swap', error)
} finally {
setIsModalOpen(false)
}
return
}

if (!sendApproveTx || isApproveTxSuccess || isApproveTxLoading) {
logger.debug('Approve already started or finished, ignoring submit')
return
}

setIsModalOpen(true)

try {
logger.info('Sending approve tx')
const approveResult = await sendApproveTx()
Expand Down Expand Up @@ -207,7 +240,7 @@ export function SwapConfirmCard({ formValues }: Props) {
close={() => setIsModalOpen(false)}
width="max-w-[432px]"
>
<MentoLogoLoader />
<MentoLogoLoader needsApproval={needsApproval} />
</Modal>
</FloatingBox>
)
Expand Down Expand Up @@ -271,7 +304,7 @@ const ChevronRight = (props: SVGProps<SVGSVGElement>) => (
</svg>
)

const MentoLogoLoader = () => {
const MentoLogoLoader = ({ needsApproval }: { needsApproval: boolean }) => {
const { connector } = useAccount()

return (
Expand All @@ -286,12 +319,14 @@ const MentoLogoLoader = () => {
</div>

<div className="my-6">
<div className=" text-sm text-center text-[#636768] dark:text-[#AAB3B6]">
Sending two transactions: Approve and Swap
<div className="text-sm text-center text-[#636768] dark:text-[#AAB3B6]">
{needsApproval
? 'Sending two transactions: Approve and Swap'
: 'Sending swap transaction'}
</div>
<div className="mt-3 text-sm text-center text-[#636768] dark:text-[#AAB3B6]">
{`Sign with ${connector?.name || 'wallet'} to proceed`}
</div>
<div className="mt-3 text-sm text-center text-[#636768] dark:text-[#AAB3B6]">{`Sign with ${
connector?.name || 'wallet'
} to proceed`}</div>
</div>
</>
)
Expand Down
43 changes: 43 additions & 0 deletions src/features/swap/useAllowance.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useQuery } from '@tanstack/react-query'
import { Contract } from 'ethers'
import { ERC20_ABI } from 'src/config/consts'
import { BrokerAddresses } from 'src/config/exchanges'
import { TokenId, getTokenAddress } from 'src/config/tokens'
import { getProvider } from 'src/features/providers'
import { logger } from 'src/utils/logger'

async function fetchAllowance(
tokenAddr: string,
accountAddress: string,
chainId: number
): Promise<string> {
logger.info(`Fetching allowance for token ${tokenAddr} on chain ${chainId}`)
const provider = getProvider(chainId)
const contract = new Contract(tokenAddr, ERC20_ABI, provider)
const brokerAddress = BrokerAddresses[chainId as keyof typeof BrokerAddresses]

const allowance = await contract.allowance(accountAddress, brokerAddress)
logger.info(`Allowance: ${allowance.toString()}`)
return allowance.toString()
}

export function useAllowance(chainId: number, tokenId: TokenId, accountAddress?: string) {
const { data: allowance, isLoading } = useQuery(
['tokenAllowance', chainId, tokenId, accountAddress],
async () => {
if (!accountAddress) return '0'
const tokenAddr = getTokenAddress(tokenId, chainId)
return fetchAllowance(tokenAddr, accountAddress, chainId)
},
{
retry: false,
enabled: Boolean(accountAddress && chainId && tokenId),
staleTime: 5000, // Consider allowance stale after 5 seconds
}
)

return {
allowance: allowance || '0',
isLoading,
}
}
4 changes: 3 additions & 1 deletion src/features/swap/useSwapTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ export function useSwapTransaction(
!isApproveConfirmed ||
new BigNumber(amountInWei).lte(0) ||
new BigNumber(thresholdAmountInWei).lte(0)
)
) {
logger.debug('Skipping swap transaction')
return null
}
const sdk = await getMentoSdk(chainId)
const fromTokenAddr = getTokenAddress(fromToken, chainId)
const toTokenAddr = getTokenAddress(toToken, chainId)
Expand Down

0 comments on commit fdcdaea

Please sign in to comment.