diff --git a/apps/cowswap-frontend/src/common/containers/TradeApprove/TradeApproveButton.tsx b/apps/cowswap-frontend/src/common/containers/TradeApprove/TradeApproveButton.tsx index 68e68ef2b3..3e31105e1d 100644 --- a/apps/cowswap-frontend/src/common/containers/TradeApprove/TradeApproveButton.tsx +++ b/apps/cowswap-frontend/src/common/containers/TradeApprove/TradeApproveButton.tsx @@ -14,15 +14,16 @@ export interface TradeApproveButtonProps { amountToApprove: CurrencyAmount 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) diff --git a/apps/cowswap-frontend/src/common/containers/TradeApprove/useTradeApproveCallback.ts b/apps/cowswap-frontend/src/common/containers/TradeApprove/useTradeApproveCallback.ts index 3e5136344c..552a32d42b 100644 --- a/apps/cowswap-frontend/src/common/containers/TradeApprove/useTradeApproveCallback.ts +++ b/apps/cowswap-frontend/src/common/containers/TradeApprove/useTradeApproveCallback.ts @@ -19,13 +19,16 @@ export interface TradeApproveCallback { (params?: TradeApproveCallbackParams): Promise } -export function useTradeApproveCallback(amountToApprove?: CurrencyAmount): TradeApproveCallback { +export function useTradeApproveCallback( + amountToApprove?: CurrencyAmount, + 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 }) => { @@ -58,6 +61,6 @@ export function useTradeApproveCallback(amountToApprove?: CurrencyAmount, + 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). @@ -45,7 +49,7 @@ export async function estimateApprove( ) return { - approveAmount: MaxUint256, + approveAmount, gasLimit: GAS_LIMIT_DEFAULT, } } @@ -55,6 +59,7 @@ export async function estimateApprove( export function useApproveCallback( amountToApprove?: CurrencyAmount, spender?: string, + isPartialApprove?: boolean, ): (summary?: string) => Promise { const { chainId } = useWalletInfo() const currency = amountToApprove?.currency @@ -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), @@ -81,5 +86,5 @@ export function useApproveCallback( }) return response }) - }, [chainId, token, tokenContract, amountToApprove, spender, addTransaction]) + }, [chainId, token, tokenContract, amountToApprove, spender, addTransaction, isPartialApprove]) } diff --git a/apps/cowswap-frontend/src/lib/hooks/useApproval.ts b/apps/cowswap-frontend/src/lib/hooks/useApproval.ts index 285b4afb2a..bfe49fa6c2 100644 --- a/apps/cowswap-frontend/src/lib/hooks/useApproval.ts +++ b/apps/cowswap-frontend/src/lib/hooks/useApproval.ts @@ -1,9 +1,7 @@ -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' @@ -11,7 +9,6 @@ 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 @@ -22,7 +19,7 @@ function toApprovalState( amountToApprove: Nullish>, spender: string | undefined, currentAllowance?: CurrencyAmount, - pendingApproval?: boolean + pendingApproval?: boolean, ): ApprovalState { // Unknown amount or spender if (!amountToApprove || !spender) { @@ -50,7 +47,7 @@ function toApprovalState( export function useApprovalStateForSpender( amountToApprove: Nullish>, spender: string | undefined, - useIsPendingApproval: (token?: Token, spender?: string) => boolean + useIsPendingApproval: (token?: Token, spender?: string) => boolean, ): ApprovalStateForSpenderResult { const { account } = useWalletInfo() const currency = amountToApprove?.currency @@ -64,66 +61,3 @@ export function useApprovalStateForSpender( return { approvalState, currentAllowance } }, [amountToApprove, currentAllowance, pendingApproval, spender]) } - -export function useApproval( - amountToApprove: CurrencyAmount | 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] -} diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts index 91fe6ec473..5195998bff 100644 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts +++ b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts @@ -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 @@ -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] @@ -143,6 +146,7 @@ export function useSwapButtonContext(input: SwapButtonInput, actions: TradeWidge onCurrencySelection, widgetStandaloneMode: standaloneMode, quoteDeadlineParams, + isPartialApprove, }), [ swapButtonState, @@ -159,6 +163,7 @@ export function useSwapButtonContext(input: SwapButtonInput, actions: TradeWidge onCurrencySelection, standaloneMode, quoteDeadlineParams, + isPartialApprove, ], ) } diff --git a/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.tsx b/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.tsx index 28c5618757..a0dd7e1e41 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.tsx @@ -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 } = { @@ -126,7 +127,7 @@ const swapButtonStateMap: { [key in SwapButtonState]: (props: SwapButtonsContext {props.inputAmount && ( - + Swap