Skip to content

Commit

Permalink
feat(swap): add sell amount to regular approve tx
Browse files Browse the repository at this point in the history
  • Loading branch information
shoom3301 committed Dec 24, 2024
1 parent 8c617cc commit 9cf72b4
Show file tree
Hide file tree
Showing 6 changed files with 30 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,16 @@ export interface TradeApproveButtonProps {
amountToApprove: CurrencyAmount<Currency>
children?: React.ReactNode
isDisabled?: boolean
isPartialApprove?: boolean
}

export function TradeApproveButton(props: TradeApproveButtonProps) {
const { amountToApprove, children, isDisabled } = props
const { amountToApprove, children, isDisabled, isPartialApprove } = props

const currency = amountToApprove.currency

const { state: approvalState } = useApproveState(amountToApprove)
const tradeApproveCallback = useTradeApproveCallback(amountToApprove)
const tradeApproveCallback = useTradeApproveCallback(amountToApprove, isPartialApprove)
const shouldZeroApprove = useShouldZeroApprove(amountToApprove)
const zeroApprove = useZeroApprove(amountToApprove.currency)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ export interface TradeApproveCallback {
(params?: TradeApproveCallbackParams): Promise<TransactionResponse | undefined>
}

export function useTradeApproveCallback(amountToApprove?: CurrencyAmount<Currency>): TradeApproveCallback {
export function useTradeApproveCallback(
amountToApprove?: CurrencyAmount<Currency>,
isPartialApprove?: boolean,
): TradeApproveCallback {
const updateTradeApproveState = useUpdateTradeApproveState()
const spender = useTradeSpenderAddress()
const currency = amountToApprove?.currency
const symbol = currency?.symbol

const approveCallback = useApproveCallback(amountToApprove, spender)
const approveCallback = useApproveCallback(amountToApprove, spender, isPartialApprove)

return useCallback(
async ({ useModals = true }: TradeApproveCallbackParams = { useModals: true }) => {
Expand Down Expand Up @@ -58,6 +61,6 @@ export function useTradeApproveCallback(amountToApprove?: CurrencyAmount<Currenc
return undefined
})
},
[symbol, approveCallback, updateTradeApproveState, currency]
[symbol, approveCallback, updateTradeApproveState, currency],
)
}
15 changes: 10 additions & 5 deletions apps/cowswap-frontend/src/common/hooks/useApproveCallback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,18 @@ export async function estimateApprove(
tokenContract: Erc20,
spender: string,
amountToApprove: CurrencyAmount<Currency>,
isPartialApprove?: boolean,
): Promise<{
approveAmount: BigNumber | string
gasLimit: BigNumber
}> {
const approveAmount =
isPartialApprove && amountToApprove ? BigNumber.from(amountToApprove.quotient.toString()) : MaxUint256

try {
return {
approveAmount: MaxUint256,
gasLimit: await tokenContract.estimateGas.approve(spender, MaxUint256),
approveAmount,
gasLimit: await tokenContract.estimateGas.approve(spender, approveAmount),
}
} catch {
// Fallback: Attempt to set an approval for the maximum wallet balance (instead of the MaxUint256).
Expand All @@ -45,7 +49,7 @@ export async function estimateApprove(
)

return {
approveAmount: MaxUint256,
approveAmount,
gasLimit: GAS_LIMIT_DEFAULT,
}
}
Expand All @@ -55,6 +59,7 @@ export async function estimateApprove(
export function useApproveCallback(
amountToApprove?: CurrencyAmount<Currency>,
spender?: string,
isPartialApprove?: boolean,
): (summary?: string) => Promise<TransactionResponse | undefined> {
const { chainId } = useWalletInfo()
const currency = amountToApprove?.currency
Expand All @@ -68,7 +73,7 @@ export function useApproveCallback(
return
}

const estimation = await estimateApprove(tokenContract, spender, amountToApprove)
const estimation = await estimateApprove(tokenContract, spender, amountToApprove, isPartialApprove)
return tokenContract
.approve(spender, estimation.approveAmount, {
gasLimit: calculateGasMargin(estimation.gasLimit),
Expand All @@ -81,5 +86,5 @@ export function useApproveCallback(
})
return response
})
}, [chainId, token, tokenContract, amountToApprove, spender, addTransaction])
}, [chainId, token, tokenContract, amountToApprove, spender, addTransaction, isPartialApprove])
}
74 changes: 4 additions & 70 deletions apps/cowswap-frontend/src/lib/hooks/useApproval.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import { useCallback, useMemo } from 'react'
import { useMemo } from 'react'

import { calculateGasMargin, getIsNativeToken } from '@cowprotocol/common-utils'
import { getIsNativeToken } from '@cowprotocol/common-utils'
import { useWalletInfo } from '@cowprotocol/wallet'
import { MaxUint256 } from '@ethersproject/constants'
import { TransactionResponse } from '@ethersproject/providers'
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'

import { Nullish } from 'types'

import { useTokenAllowance } from 'legacy/hooks/useTokenAllowance'

import { ApprovalState } from 'common/hooks/useApproveState'
import { useTokenContract } from 'common/hooks/useContract'

export interface ApprovalStateForSpenderResult {
approvalState: ApprovalState
Expand All @@ -22,7 +19,7 @@ function toApprovalState(
amountToApprove: Nullish<CurrencyAmount<Currency>>,
spender: string | undefined,
currentAllowance?: CurrencyAmount<Token>,
pendingApproval?: boolean
pendingApproval?: boolean,
): ApprovalState {
// Unknown amount or spender
if (!amountToApprove || !spender) {
Expand Down Expand Up @@ -50,7 +47,7 @@ function toApprovalState(
export function useApprovalStateForSpender(
amountToApprove: Nullish<CurrencyAmount<Currency>>,
spender: string | undefined,
useIsPendingApproval: (token?: Token, spender?: string) => boolean
useIsPendingApproval: (token?: Token, spender?: string) => boolean,
): ApprovalStateForSpenderResult {
const { account } = useWalletInfo()
const currency = amountToApprove?.currency
Expand All @@ -64,66 +61,3 @@ export function useApprovalStateForSpender(
return { approvalState, currentAllowance }
}, [amountToApprove, currentAllowance, pendingApproval, spender])
}

export function useApproval(
amountToApprove: CurrencyAmount<Currency> | undefined,
spender: string | undefined,
useIsPendingApproval: (token?: Token, spender?: string) => boolean
): [
ApprovalState,
() => Promise<{ response: TransactionResponse; tokenAddress: string; spenderAddress: string } | undefined>
] {
const { chainId } = useWalletInfo()
const currency = amountToApprove?.currency
const token = currency && !getIsNativeToken(currency) ? currency : undefined

// check the current approval status
const approvalState = useApprovalStateForSpender(amountToApprove, spender, useIsPendingApproval).approvalState

const tokenContract = useTokenContract(token?.address)

const approve = useCallback(async () => {
function logFailure(error: Error | string): undefined {
console.warn(`${token?.symbol || 'Token'} approval failed:`, error)
return
}

// Bail early if there is an issue.
if (approvalState !== ApprovalState.NOT_APPROVED) {
return logFailure('approve was called unnecessarily')
} else if (!chainId) {
return logFailure('no chainId')
} else if (!token) {
return logFailure('no token')
} else if (!tokenContract) {
return logFailure('tokenContract is null')
} else if (!amountToApprove) {
return logFailure('missing amount to approve')
} else if (!spender) {
return logFailure('no spender')
}

let useExact = false
const estimatedGas = await tokenContract.estimateGas.approve(spender, MaxUint256).catch(() => {
// general fallback for tokens which restrict approval amounts
useExact = true
return tokenContract.estimateGas.approve(spender, amountToApprove.quotient.toString())
})

return tokenContract
.approve(spender, useExact ? amountToApprove.quotient.toString() : MaxUint256, {
gasLimit: calculateGasMargin(estimatedGas),
})
.then((response) => ({
response,
tokenAddress: token.address,
spenderAddress: spender,
}))
.catch((error: Error) => {
logFailure(error)
throw error
})
}, [approvalState, token, tokenContract, amountToApprove, spender, chainId])

return [approvalState, approve]
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import { useSafeMemo } from 'common/hooks/useSafeMemo'
import { useHandleSwapOrEthFlow } from './useHandleSwapOrEthFlow'
import { useDerivedSwapInfo, useSwapActionHandlers } from './useSwapState'

import { usePartialApprove } from '../../../legacy/state/user/hooks'

export interface SwapButtonInput {
feeWarningAccepted: boolean
impactWarningAccepted: boolean
Expand All @@ -63,6 +65,7 @@ export function useSwapButtonContext(input: SwapButtonInput, actions: TradeWidge
const tradeConfirmActions = useTradeConfirmActions()
const { standaloneMode } = useInjectedWidgetParams()
const isHooksStore = useIsHooksTradeType()
const [isPartialApprove] = usePartialApprove()

const currencyIn = currencies[Field.INPUT]
const currencyOut = currencies[Field.OUTPUT]
Expand Down Expand Up @@ -143,6 +146,7 @@ export function useSwapButtonContext(input: SwapButtonInput, actions: TradeWidge
onCurrencySelection,
widgetStandaloneMode: standaloneMode,
quoteDeadlineParams,
isPartialApprove,
}),
[
swapButtonState,
Expand All @@ -159,6 +163,7 @@ export function useSwapButtonContext(input: SwapButtonInput, actions: TradeWidge
onCurrencySelection,
standaloneMode,
quoteDeadlineParams,
isPartialApprove,
],
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface SwapButtonsContext {
onCurrencySelection: (field: Field, currency: Currency) => void
widgetStandaloneMode?: boolean
quoteDeadlineParams: QuoteDeadlineParams
isPartialApprove: boolean
}

const swapButtonStateMap: { [key in SwapButtonState]: (props: SwapButtonsContext) => JSX.Element } = {
Expand Down Expand Up @@ -126,7 +127,7 @@ const swapButtonStateMap: { [key in SwapButtonState]: (props: SwapButtonsContext
<AutoRow style={{ flexWrap: 'nowrap', width: '100%' }}>
<AutoColumn style={{ width: '100%' }} gap="12px">
{props.inputAmount && (
<TradeApproveButton amountToApprove={props.inputAmount}>
<TradeApproveButton amountToApprove={props.inputAmount} isPartialApprove={props.isPartialApprove}>
<ButtonError disabled={true} buttonSize={ButtonSize.BIG}>
<styledEl.SwapButtonBox>
<Trans>Swap</Trans>
Expand Down

0 comments on commit 9cf72b4

Please sign in to comment.