From 9395c942872af5e83e828d8d43e8affb0ceef615 Mon Sep 17 00:00:00 2001 From: David W3stside Date: Fri, 21 Jan 2022 13:17:22 +0000 Subject: [PATCH 01/11] create hook and button --- .../useApproveCallbackMod.ts | 2 +- src/custom/hooks/useRevokeApproveCallback.ts | 100 ++++++++++++++++++ .../Claim/InvestmentFlow/InvestOption.tsx | 21 ++++ 3 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 src/custom/hooks/useRevokeApproveCallback.ts diff --git a/src/custom/hooks/useApproveCallback/useApproveCallbackMod.ts b/src/custom/hooks/useApproveCallback/useApproveCallbackMod.ts index 77242d376..e40d893a1 100644 --- a/src/custom/hooks/useApproveCallback/useApproveCallbackMod.ts +++ b/src/custom/hooks/useApproveCallback/useApproveCallbackMod.ts @@ -15,7 +15,7 @@ import { useActiveWeb3React } from 'hooks/web3' import { OptionalApproveCallbackParams } from '.' // Use a 150K gas as a fallback if there's issue calculating the gas estimation (fixes some issues with some nodes failing to calculate gas costs for SC wallets) -const APPROVE_GAS_LIMIT_DEFAULT = BigNumber.from('150000') +export const APPROVE_GAS_LIMIT_DEFAULT = BigNumber.from('150000') export enum ApprovalState { UNKNOWN = 'UNKNOWN', diff --git a/src/custom/hooks/useRevokeApproveCallback.ts b/src/custom/hooks/useRevokeApproveCallback.ts new file mode 100644 index 000000000..7947775df --- /dev/null +++ b/src/custom/hooks/useRevokeApproveCallback.ts @@ -0,0 +1,100 @@ +import { useMemo, useCallback } from 'react' +import { Currency } from '@uniswap/sdk-core' +import { TransactionResponse } from '@ethersproject/providers' +import { calculateGasMargin } from 'utils/calculateGasMargin' +import { useActiveWeb3React } from 'hooks/web3' +import { useTransactionAdder } from 'state/enhancedTransactions/hooks' +import { ApproveCallbackParams, APPROVE_GAS_LIMIT_DEFAULT } from './useApproveCallback/useApproveCallbackMod' +import { useTokenContract } from './useContract' +import { useTokenAllowance } from 'hooks/useTokenAllowance' + +export default function useRevokeApproveCallback({ + openTransactionConfirmationModal, + closeModals, + spender, + token, +}: Pick & { + token?: Currency +}): [boolean, () => Promise] { + const { account, chainId } = useActiveWeb3React() + const currentAllowance = useTokenAllowance(token?.wrapped, account ?? undefined, spender) + + // check the current approval status + const isApproved: boolean = useMemo(() => { + if (!spender || !token || token.isNative || currentAllowance?.equalTo('0')) return false + + return true + }, [currentAllowance, spender, token]) + + const tokenContract = useTokenContract(token?.wrapped.address) + const addTransaction = useTransactionAdder() + + const approve = useCallback(async (): Promise => { + if (!isApproved) { + console.error('No approval, no need to revoke approval.') + return + } + if (!chainId) { + console.error('no chainId') + return + } + + if (!token) { + console.error('no token') + return + } + + if (!tokenContract) { + console.error('tokenContract is null') + return + } + + if (!spender) { + console.error('no spender') + return + } + + const estimatedGas = await tokenContract.estimateGas.approve(spender, '0').catch(() => { + // general fallback for tokens who restrict approval amounts + return tokenContract.estimateGas.approve(spender, '0').catch((error) => { + console.log( + '[useApproveCallbackMod] Error estimating gas for approval. Using default gas limit ' + + APPROVE_GAS_LIMIT_DEFAULT.toString(), + error + ) + return APPROVE_GAS_LIMIT_DEFAULT + }) + }) + + openTransactionConfirmationModal(`Revoking approval of ${token.symbol} from contract ${spender}`) + return ( + tokenContract + .approve(spender, '0', { + gasLimit: calculateGasMargin(chainId, estimatedGas), + }) + .then((response: TransactionResponse) => { + addTransaction({ + hash: response.hash, + summary: `Revoking approval of ${token.symbol} from contract ${spender}`, + approval: { tokenAddress: token.wrapped.address, spender }, + }) + }) + // .catch((error: Error) => { + // console.debug('Failed to approve token', error) + // throw error + // }) + .finally(closeModals) + ) + }, [ + isApproved, + chainId, + token, + tokenContract, + spender, + openTransactionConfirmationModal, + closeModals, + addTransaction, + ]) + + return [isApproved, approve] +} diff --git a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx index 6391f0594..232d0f2b2 100644 --- a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx +++ b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx @@ -25,6 +25,9 @@ import { useGasPrices } from 'state/gas/hooks' import { AVG_APPROVE_COST_GWEI } from 'components/swap/EthWethWrap/helpers' import { EnhancedUserClaimData } from '../types' import { OperationType } from 'components/TransactionConfirmationModal' +import useRevokeApproveCallback from 'hooks/useRevokeApproveCallback' +import { V_COW_CONTRACT_ADDRESS } from 'constants/index' +import styled from 'styled-components/macro' const ErrorMsgs = { InsufficientBalance: (symbol = '') => `Insufficient ${symbol} balance to cover investment amount`, @@ -54,6 +57,16 @@ const _claimApproveMessageMap = (type: ClaimType) => { } } +const UnderlineButton = styled.button` + background: none; + border: 0; + cursor: pointer; + color: #ff5d25; + text-decoration: underline; + text-align: left; + padding: 0; +` + export default function InvestOption({ claim, optionIndex, openModal, closeModal }: InvestOptionProps) { const { currencyAmount, price, cost: maxCost } = claim @@ -70,6 +83,13 @@ export default function InvestOption({ claim, optionIndex, openModal, closeModal claim, }) + const [isAlreadyApproved, revokeApprovalCallback] = useRevokeApproveCallback({ + openTransactionConfirmationModal: () => openModal(_claimApproveMessageMap(claim.type), OperationType.APPROVE_TOKEN), + closeModals: closeModal, + spender: chainId ? V_COW_CONTRACT_ADDRESS[chainId] : undefined, + token: claim?.currencyAmount?.currency, + }) + const isEtherApproveState = approveState === ApprovalState.UNKNOWN const { handleSetError, handleCloseError, ErrorModal } = useErrorModal() @@ -303,6 +323,7 @@ export default function InvestOption({ claim, optionIndex, openModal, closeModal )} )} + {isAlreadyApproved && Revoke approval} From 4a450d7f2bd91dc0cc2b6c29e7e4b423f22741ef Mon Sep 17 00:00:00 2001 From: David W3stside Date: Fri, 21 Jan 2022 15:24:46 +0000 Subject: [PATCH 02/11] change message --- src/custom/hooks/useRevokeApproveCallback.ts | 4 +- .../Claim/InvestmentFlow/InvestOption.tsx | 45 ++++++++++++++++++- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/custom/hooks/useRevokeApproveCallback.ts b/src/custom/hooks/useRevokeApproveCallback.ts index 7947775df..8883fbae4 100644 --- a/src/custom/hooks/useRevokeApproveCallback.ts +++ b/src/custom/hooks/useRevokeApproveCallback.ts @@ -66,7 +66,7 @@ export default function useRevokeApproveCallback({ }) }) - openTransactionConfirmationModal(`Revoking approval of ${token.symbol} from contract ${spender}`) + openTransactionConfirmationModal(`Revoke ${token.symbol} approval from ${spender}`) return ( tokenContract .approve(spender, '0', { @@ -75,7 +75,7 @@ export default function useRevokeApproveCallback({ .then((response: TransactionResponse) => { addTransaction({ hash: response.hash, - summary: `Revoking approval of ${token.symbol} from contract ${spender}`, + summary: `Revoke ${token.symbol} approval from ${spender}`, approval: { tokenAddress: token.wrapped.address, spender }, }) }) diff --git a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx index 232d0f2b2..44e10fc95 100644 --- a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx +++ b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx @@ -57,6 +57,18 @@ const _claimApproveMessageMap = (type: ClaimType) => { } } +const _claimRevokeApprovalMessageMap = (type: ClaimType) => { + switch (type) { + case ClaimType.GnoOption: + return 'Revoking GNO approval from vCOW contract' + case ClaimType.Investor: + return 'Revoking USDC approval from vCOW contract' + // Shouldn't happen, type safe + default: + return 'Unknown token. Please check configuration.' + } +} + const UnderlineButton = styled.button` background: none; border: 0; @@ -65,6 +77,12 @@ const UnderlineButton = styled.button` text-decoration: underline; text-align: left; padding: 0; + + &:disabled { + text-decoration: none; + color: darkgrey; + cursor: auto; + } ` export default function InvestOption({ claim, optionIndex, openModal, closeModal }: InvestOptionProps) { @@ -84,7 +102,8 @@ export default function InvestOption({ claim, optionIndex, openModal, closeModal }) const [isAlreadyApproved, revokeApprovalCallback] = useRevokeApproveCallback({ - openTransactionConfirmationModal: () => openModal(_claimApproveMessageMap(claim.type), OperationType.APPROVE_TOKEN), + openTransactionConfirmationModal: () => + openModal(_claimRevokeApprovalMessageMap(claim.type), OperationType.APPROVE_TOKEN), closeModals: closeModal, spender: chainId ? V_COW_CONTRACT_ADDRESS[chainId] : undefined, token: claim?.currencyAmount?.currency, @@ -169,6 +188,24 @@ export default function InvestOption({ claim, optionIndex, openModal, closeModal } }, [approveCallback, handleCloseError, handleSetError, token?.symbol]) + const handleRevokeApproval = useCallback(async () => { + // reset errors and close any modals + handleCloseError() + + if (!revokeApprovalCallback) return + + try { + // for pending state pre-BC + setApproving(true) + await revokeApprovalCallback() + } catch (error) { + console.error('[InvestOption]: Issue revoking approval.', error) + handleSetError(error?.message) + } finally { + setApproving(false) + } + }, [handleCloseError, handleSetError, revokeApprovalCallback]) + const vCowAmount = useMemo( () => calculateInvestmentAmounts(claim, investmentAmount)?.vCowAmount, [claim, investmentAmount] @@ -323,7 +360,11 @@ export default function InvestOption({ claim, optionIndex, openModal, closeModal )} )} - {isAlreadyApproved && Revoke approval} + {isAlreadyApproved && ( + + Revoke approval + + )} From 63a903126598abe27e09383f8d70f5c11935ddf2 Mon Sep 17 00:00:00 2001 From: David W3stside Date: Fri, 21 Jan 2022 15:35:26 +0000 Subject: [PATCH 03/11] messages --- src/custom/hooks/useApproveCallback/index.ts | 2 +- src/custom/hooks/useRevokeApproveCallback.ts | 115 +++++++++--------- .../Claim/InvestmentFlow/InvestOption.tsx | 10 +- 3 files changed, 62 insertions(+), 65 deletions(-) diff --git a/src/custom/hooks/useApproveCallback/index.ts b/src/custom/hooks/useApproveCallback/index.ts index 204b85082..fc1b72ea8 100644 --- a/src/custom/hooks/useApproveCallback/index.ts +++ b/src/custom/hooks/useApproveCallback/index.ts @@ -51,7 +51,7 @@ export function useApproveCallbackFromTrade({ } export type OptionalApproveCallbackParams = { - transactionSummary: string + transactionSummary?: string } type ApproveCallbackFromClaimParams = Omit< diff --git a/src/custom/hooks/useRevokeApproveCallback.ts b/src/custom/hooks/useRevokeApproveCallback.ts index 8883fbae4..0199fc3bb 100644 --- a/src/custom/hooks/useRevokeApproveCallback.ts +++ b/src/custom/hooks/useRevokeApproveCallback.ts @@ -7,6 +7,7 @@ import { useTransactionAdder } from 'state/enhancedTransactions/hooks' import { ApproveCallbackParams, APPROVE_GAS_LIMIT_DEFAULT } from './useApproveCallback/useApproveCallbackMod' import { useTokenContract } from './useContract' import { useTokenAllowance } from 'hooks/useTokenAllowance' +import { OptionalApproveCallbackParams } from './useApproveCallback' export default function useRevokeApproveCallback({ openTransactionConfirmationModal, @@ -15,7 +16,7 @@ export default function useRevokeApproveCallback({ token, }: Pick & { token?: Currency -}): [boolean, () => Promise] { +}): [boolean, ({ transactionSummary }: OptionalApproveCallbackParams) => Promise] { const { account, chainId } = useActiveWeb3React() const currentAllowance = useTokenAllowance(token?.wrapped, account ?? undefined, spender) @@ -29,72 +30,66 @@ export default function useRevokeApproveCallback({ const tokenContract = useTokenContract(token?.wrapped.address) const addTransaction = useTransactionAdder() - const approve = useCallback(async (): Promise => { - if (!isApproved) { - console.error('No approval, no need to revoke approval.') - return - } - if (!chainId) { - console.error('no chainId') - return - } + const approve = useCallback( + async ({ transactionSummary }: OptionalApproveCallbackParams): Promise => { + if (!isApproved) { + console.error('No approval, no need to revoke approval.') + return + } + if (!chainId) { + console.error('no chainId') + return + } - if (!token) { - console.error('no token') - return - } + if (!token) { + console.error('no token') + return + } - if (!tokenContract) { - console.error('tokenContract is null') - return - } + if (!tokenContract) { + console.error('tokenContract is null') + return + } - if (!spender) { - console.error('no spender') - return - } + if (!spender) { + console.error('no spender') + return + } - const estimatedGas = await tokenContract.estimateGas.approve(spender, '0').catch(() => { - // general fallback for tokens who restrict approval amounts - return tokenContract.estimateGas.approve(spender, '0').catch((error) => { - console.log( - '[useApproveCallbackMod] Error estimating gas for approval. Using default gas limit ' + - APPROVE_GAS_LIMIT_DEFAULT.toString(), - error - ) - return APPROVE_GAS_LIMIT_DEFAULT + const estimatedGas = await tokenContract.estimateGas.approve(spender, '0').catch(() => { + // general fallback for tokens who restrict approval amounts + return tokenContract.estimateGas.approve(spender, '0').catch((error) => { + console.log( + '[useApproveCallbackMod] Error estimating gas for approval. Using default gas limit ' + + APPROVE_GAS_LIMIT_DEFAULT.toString(), + error + ) + return APPROVE_GAS_LIMIT_DEFAULT + }) }) - }) - openTransactionConfirmationModal(`Revoke ${token.symbol} approval from ${spender}`) - return ( - tokenContract - .approve(spender, '0', { - gasLimit: calculateGasMargin(chainId, estimatedGas), - }) - .then((response: TransactionResponse) => { - addTransaction({ - hash: response.hash, - summary: `Revoke ${token.symbol} approval from ${spender}`, - approval: { tokenAddress: token.wrapped.address, spender }, + openTransactionConfirmationModal(`Revoke ${token.symbol} approval from ${spender}`) + return ( + tokenContract + .approve(spender, '0', { + gasLimit: calculateGasMargin(chainId, estimatedGas), }) - }) - // .catch((error: Error) => { - // console.debug('Failed to approve token', error) - // throw error - // }) - .finally(closeModals) - ) - }, [ - isApproved, - chainId, - token, - tokenContract, - spender, - openTransactionConfirmationModal, - closeModals, - addTransaction, - ]) + .then((response: TransactionResponse) => { + addTransaction({ + hash: response.hash, + summary: transactionSummary || `Revoke ${token.symbol} approval from ${spender}`, + approval: { tokenAddress: token.wrapped.address, spender }, + }) + }) + // .catch((error: Error) => { + // console.debug('Failed to approve token', error) + // throw error + // }) + .finally(closeModals) + ) + }, + [isApproved, chainId, token, tokenContract, spender, openTransactionConfirmationModal, closeModals, addTransaction] + ) return [isApproved, approve] } diff --git a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx index 44e10fc95..fa17ce1bc 100644 --- a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx +++ b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx @@ -60,9 +60,9 @@ const _claimApproveMessageMap = (type: ClaimType) => { const _claimRevokeApprovalMessageMap = (type: ClaimType) => { switch (type) { case ClaimType.GnoOption: - return 'Revoking GNO approval from vCOW contract' + return 'Revoking vCOW approval from GNO contract' case ClaimType.Investor: - return 'Revoking USDC approval from vCOW contract' + return 'Revoking vCOW approval from USDC contract' // Shouldn't happen, type safe default: return 'Unknown token. Please check configuration.' @@ -197,14 +197,16 @@ export default function InvestOption({ claim, optionIndex, openModal, closeModal try { // for pending state pre-BC setApproving(true) - await revokeApprovalCallback() + await revokeApprovalCallback({ + transactionSummary: claim.type ? _claimRevokeApprovalMessageMap(claim.type) : undefined, + }) } catch (error) { console.error('[InvestOption]: Issue revoking approval.', error) handleSetError(error?.message) } finally { setApproving(false) } - }, [handleCloseError, handleSetError, revokeApprovalCallback]) + }, [claim.type, handleCloseError, handleSetError, revokeApprovalCallback]) const vCowAmount = useMemo( () => calculateInvestmentAmounts(claim, investmentAmount)?.vCowAmount, From 950a50fd8b26533bb9c898a5287673849f712b0c Mon Sep 17 00:00:00 2001 From: David W3stside Date: Tue, 25 Jan 2022 10:16:08 -0300 Subject: [PATCH 04/11] add revoke approve operationtype --- src/custom/components/TransactionConfirmationModal/index.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/custom/components/TransactionConfirmationModal/index.tsx b/src/custom/components/TransactionConfirmationModal/index.tsx index 67b0924df..ae6c4a449 100644 --- a/src/custom/components/TransactionConfirmationModal/index.tsx +++ b/src/custom/components/TransactionConfirmationModal/index.tsx @@ -355,6 +355,7 @@ export enum OperationType { WRAP_ETHER, UNWRAP_WETH, APPROVE_TOKEN, + REVOKE_APPROVE_TOKEN, ORDER_SIGN, ORDER_CANCEL, } @@ -382,6 +383,8 @@ function getOperationMessage(operationType: OperationType, chainId: number): str return 'Approving token' case OperationType.ORDER_CANCEL: return 'Soft canceling your order' + case OperationType.REVOKE_APPROVE_TOKEN: + return 'Revoking token approval' default: return 'Almost there!' @@ -396,6 +399,8 @@ function getOperationLabel(operationType: OperationType): string { return t`unwrapping` case OperationType.APPROVE_TOKEN: return t`token approval` + case OperationType.REVOKE_APPROVE_TOKEN: + return t`revoking token approval` case OperationType.ORDER_SIGN: return t`order` case OperationType.ORDER_CANCEL: From e68f787af3f208b7678c2ecdb3d6764b4fc70412 Mon Sep 17 00:00:00 2001 From: David W3stside Date: Tue, 25 Jan 2022 10:16:16 -0300 Subject: [PATCH 05/11] delete previousr revoke approve cb --- src/custom/hooks/useRevokeApproveCallback.ts | 95 -------------------- 1 file changed, 95 deletions(-) delete mode 100644 src/custom/hooks/useRevokeApproveCallback.ts diff --git a/src/custom/hooks/useRevokeApproveCallback.ts b/src/custom/hooks/useRevokeApproveCallback.ts deleted file mode 100644 index 0199fc3bb..000000000 --- a/src/custom/hooks/useRevokeApproveCallback.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { useMemo, useCallback } from 'react' -import { Currency } from '@uniswap/sdk-core' -import { TransactionResponse } from '@ethersproject/providers' -import { calculateGasMargin } from 'utils/calculateGasMargin' -import { useActiveWeb3React } from 'hooks/web3' -import { useTransactionAdder } from 'state/enhancedTransactions/hooks' -import { ApproveCallbackParams, APPROVE_GAS_LIMIT_DEFAULT } from './useApproveCallback/useApproveCallbackMod' -import { useTokenContract } from './useContract' -import { useTokenAllowance } from 'hooks/useTokenAllowance' -import { OptionalApproveCallbackParams } from './useApproveCallback' - -export default function useRevokeApproveCallback({ - openTransactionConfirmationModal, - closeModals, - spender, - token, -}: Pick & { - token?: Currency -}): [boolean, ({ transactionSummary }: OptionalApproveCallbackParams) => Promise] { - const { account, chainId } = useActiveWeb3React() - const currentAllowance = useTokenAllowance(token?.wrapped, account ?? undefined, spender) - - // check the current approval status - const isApproved: boolean = useMemo(() => { - if (!spender || !token || token.isNative || currentAllowance?.equalTo('0')) return false - - return true - }, [currentAllowance, spender, token]) - - const tokenContract = useTokenContract(token?.wrapped.address) - const addTransaction = useTransactionAdder() - - const approve = useCallback( - async ({ transactionSummary }: OptionalApproveCallbackParams): Promise => { - if (!isApproved) { - console.error('No approval, no need to revoke approval.') - return - } - if (!chainId) { - console.error('no chainId') - return - } - - if (!token) { - console.error('no token') - return - } - - if (!tokenContract) { - console.error('tokenContract is null') - return - } - - if (!spender) { - console.error('no spender') - return - } - - const estimatedGas = await tokenContract.estimateGas.approve(spender, '0').catch(() => { - // general fallback for tokens who restrict approval amounts - return tokenContract.estimateGas.approve(spender, '0').catch((error) => { - console.log( - '[useApproveCallbackMod] Error estimating gas for approval. Using default gas limit ' + - APPROVE_GAS_LIMIT_DEFAULT.toString(), - error - ) - return APPROVE_GAS_LIMIT_DEFAULT - }) - }) - - openTransactionConfirmationModal(`Revoke ${token.symbol} approval from ${spender}`) - return ( - tokenContract - .approve(spender, '0', { - gasLimit: calculateGasMargin(chainId, estimatedGas), - }) - .then((response: TransactionResponse) => { - addTransaction({ - hash: response.hash, - summary: transactionSummary || `Revoke ${token.symbol} approval from ${spender}`, - approval: { tokenAddress: token.wrapped.address, spender }, - }) - }) - // .catch((error: Error) => { - // console.debug('Failed to approve token', error) - // throw error - // }) - .finally(closeModals) - ) - }, - [isApproved, chainId, token, tokenContract, spender, openTransactionConfirmationModal, closeModals, addTransaction] - ) - - return [isApproved, approve] -} From 2d10eadc0331c2afba652251503bbc3ddea878e5 Mon Sep 17 00:00:00 2001 From: David W3stside Date: Tue, 25 Jan 2022 10:16:29 -0300 Subject: [PATCH 06/11] add modalMessage optional type --- src/custom/hooks/useApproveCallback/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/custom/hooks/useApproveCallback/index.ts b/src/custom/hooks/useApproveCallback/index.ts index fc1b72ea8..2b958691a 100644 --- a/src/custom/hooks/useApproveCallback/index.ts +++ b/src/custom/hooks/useApproveCallback/index.ts @@ -52,6 +52,7 @@ export function useApproveCallbackFromTrade({ export type OptionalApproveCallbackParams = { transactionSummary?: string + modalMessage?: string } type ApproveCallbackFromClaimParams = Omit< From 0b4fafc49788bc10c0620922ca29a358f269567f Mon Sep 17 00:00:00 2001 From: David W3stside Date: Tue, 25 Jan 2022 10:18:05 -0300 Subject: [PATCH 07/11] add revokeApprovalCallback to original approve callback mod --- .../useApproveCallbackMod.ts | 92 ++++++++++++++++++- 1 file changed, 88 insertions(+), 4 deletions(-) diff --git a/src/custom/hooks/useApproveCallback/useApproveCallbackMod.ts b/src/custom/hooks/useApproveCallback/useApproveCallbackMod.ts index e40d893a1..fff370f72 100644 --- a/src/custom/hooks/useApproveCallback/useApproveCallbackMod.ts +++ b/src/custom/hooks/useApproveCallback/useApproveCallbackMod.ts @@ -13,6 +13,8 @@ import { useTokenContract } from 'hooks/useContract' import { useTokenAllowance } from 'hooks/useTokenAllowance' import { useActiveWeb3React } from 'hooks/web3' import { OptionalApproveCallbackParams } from '.' +import { useCurrency } from 'hooks/Tokens' +import { OperationType } from 'components/TransactionConfirmationModal' // Use a 150K gas as a fallback if there's issue calculating the gas estimation (fixes some issues with some nodes failing to calculate gas costs for SC wallets) export const APPROVE_GAS_LIMIT_DEFAULT = BigNumber.from('150000') @@ -25,7 +27,7 @@ export enum ApprovalState { } export interface ApproveCallbackParams { - openTransactionConfirmationModal: (message: string) => void + openTransactionConfirmationModal: (message: string, operationType: OperationType) => void closeModals: () => void amountToApprove?: CurrencyAmount spender?: string @@ -39,11 +41,16 @@ export function useApproveCallback({ amountToApprove, spender, amountToCheckAgainstAllowance, -}: ApproveCallbackParams): [ApprovalState, (optionalParams?: OptionalApproveCallbackParams) => Promise] { +}: ApproveCallbackParams): [ + ApprovalState, + (optionalParams?: OptionalApproveCallbackParams) => Promise, + (optionalParams?: OptionalApproveCallbackParams) => Promise +] { const { account, chainId } = useActiveWeb3React() const token = amountToApprove?.currency?.isToken ? amountToApprove.currency : undefined const currentAllowance = useTokenAllowance(token, account ?? undefined, spender) const pendingApproval = useHasPendingApproval(token?.address, spender) + const spenderCurrency = useCurrency(spender) // TODO: Nice to have, can be deleted { @@ -133,7 +140,10 @@ export function useApproveCallback({ }) }) - openTransactionConfirmationModal(`Approving ${amountToApprove.currency.symbol} for trading`) + openTransactionConfirmationModal( + optionalParams?.modalMessage || `Approving ${amountToApprove.currency.symbol} for trading`, + OperationType.APPROVE_TOKEN + ) return ( tokenContract .approve(spender, useExact ? amountToApprove.quotient.toString() : MaxUint256, { @@ -166,7 +176,81 @@ export function useApproveCallback({ ] ) - return [approvalState, approve] + const revokeApprove = useCallback( + async (optionalParams?: OptionalApproveCallbackParams): Promise => { + if (approvalState === ApprovalState.NOT_APPROVED) { + console.error('Revoke approve was called unnecessarily') + return + } + if (!chainId) { + console.error('no chainId') + return + } + + if (!token) { + console.error('no token') + return + } + + if (!tokenContract) { + console.error('tokenContract is null') + return + } + + if (!spender) { + console.error('no spender') + return + } + + const estimatedGas = await tokenContract.estimateGas.approve(spender, MaxUint256).catch(() => { + // general fallback for tokens who restrict approval amounts + return tokenContract.estimateGas.approve(spender, '0').catch((error) => { + console.log( + '[useApproveCallbackMod] Error estimating gas for revoking approval. Using default gas limit ' + + APPROVE_GAS_LIMIT_DEFAULT.toString(), + error + ) + return APPROVE_GAS_LIMIT_DEFAULT + }) + }) + + openTransactionConfirmationModal( + optionalParams?.modalMessage || `Revoke ${token.symbol} approval from ${spenderCurrency?.symbol || spender}`, + OperationType.REVOKE_APPROVE_TOKEN + ) + return ( + tokenContract + .approve(spender, '0', { + gasLimit: calculateGasMargin(chainId, estimatedGas), + }) + .then((response: TransactionResponse) => { + addTransaction({ + hash: response.hash, + summary: optionalParams?.transactionSummary || `Revoke ${token.symbol} approval from ${spender}`, + approval: { tokenAddress: token.wrapped.address, spender }, + }) + }) + // .catch((error: Error) => { + // console.debug('Failed to approve token', error) + // throw error + // }) + .finally(closeModals) + ) + }, + [ + approvalState, + chainId, + token, + tokenContract, + spender, + spenderCurrency?.symbol, + openTransactionConfirmationModal, + closeModals, + addTransaction, + ] + ) + + return [approvalState, approve, revokeApprove] } // wraps useApproveCallback in the context of a swap From f4d3403f5570311c04cb6adc86aac4c08d11f6b7 Mon Sep 17 00:00:00 2001 From: David W3stside Date: Tue, 25 Jan 2022 10:18:13 -0300 Subject: [PATCH 08/11] add revoke to investOption and change labels --- .../Claim/InvestmentFlow/InvestOption.tsx | 59 +++++-------------- 1 file changed, 15 insertions(+), 44 deletions(-) diff --git a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx index fa17ce1bc..0e859a622 100644 --- a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx +++ b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx @@ -11,7 +11,7 @@ import { InvestmentFlowProps } from '.' import { ApprovalState, useApproveCallbackFromClaim } from 'hooks/useApproveCallback' import { useCurrencyBalance } from 'state/wallet/hooks' import { useActiveWeb3React } from 'hooks/web3' -import { ClaimType, useClaimDispatchers, useClaimState } from 'state/claim/hooks' +import { useClaimDispatchers, useClaimState } from 'state/claim/hooks' import { StyledNumericalInput } from 'components/CurrencyInputPanel/CurrencyInputPanelMod' import { ButtonConfirmed } from 'components/Button' @@ -25,8 +25,6 @@ import { useGasPrices } from 'state/gas/hooks' import { AVG_APPROVE_COST_GWEI } from 'components/swap/EthWethWrap/helpers' import { EnhancedUserClaimData } from '../types' import { OperationType } from 'components/TransactionConfirmationModal' -import useRevokeApproveCallback from 'hooks/useRevokeApproveCallback' -import { V_COW_CONTRACT_ADDRESS } from 'constants/index' import styled from 'styled-components/macro' const ErrorMsgs = { @@ -45,30 +43,6 @@ type InvestOptionProps = { closeModal: InvestmentFlowProps['modalCbs']['closeModal'] } -const _claimApproveMessageMap = (type: ClaimType) => { - switch (type) { - case ClaimType.GnoOption: - return 'Approving GNO for investing in vCOW' - case ClaimType.Investor: - return 'Approving USDC for investing in vCOW' - // Shouldn't happen, type safe - default: - return 'Unknown token approval. Please check configuration.' - } -} - -const _claimRevokeApprovalMessageMap = (type: ClaimType) => { - switch (type) { - case ClaimType.GnoOption: - return 'Revoking vCOW approval from GNO contract' - case ClaimType.Investor: - return 'Revoking vCOW approval from USDC contract' - // Shouldn't happen, type safe - default: - return 'Unknown token. Please check configuration.' - } -} - const UnderlineButton = styled.button` background: none; border: 0; @@ -95,20 +69,13 @@ export default function InvestOption({ claim, optionIndex, openModal, closeModal const investmentAmount = investFlowData[optionIndex].investedAmount // Approve hooks - const [approveState, approveCallback] = useApproveCallbackFromClaim({ - openTransactionConfirmationModal: () => openModal(_claimApproveMessageMap(claim.type), OperationType.APPROVE_TOKEN), + const [approveState, approveCallback, revokeApprovalCallback] = useApproveCallbackFromClaim({ + openTransactionConfirmationModal: (message: string, operationType: OperationType) => + openModal(message, operationType), closeModals: closeModal, claim, }) - const [isAlreadyApproved, revokeApprovalCallback] = useRevokeApproveCallback({ - openTransactionConfirmationModal: () => - openModal(_claimRevokeApprovalMessageMap(claim.type), OperationType.APPROVE_TOKEN), - closeModals: closeModal, - spender: chainId ? V_COW_CONTRACT_ADDRESS[chainId] : undefined, - token: claim?.currencyAmount?.currency, - }) - const isEtherApproveState = approveState === ApprovalState.UNKNOWN const { handleSetError, handleCloseError, ErrorModal } = useErrorModal() @@ -177,9 +144,9 @@ export default function InvestOption({ claim, optionIndex, openModal, closeModal if (!approveCallback) return try { - // for pending state pre-BC setApproving(true) - await approveCallback({ transactionSummary: `Approve ${token?.symbol || 'token'} for investing in vCOW` }) + const summary = `Approve ${token?.symbol || 'token'} for investing in vCOW` + await approveCallback({ modalMessage: summary, transactionSummary: summary }) } catch (error) { console.error('[InvestOption]: Issue approving.', error) handleSetError(error?.message) @@ -195,10 +162,11 @@ export default function InvestOption({ claim, optionIndex, openModal, closeModal if (!revokeApprovalCallback) return try { - // for pending state pre-BC setApproving(true) + const summary = `Revoke ${token?.symbol || 'token'} approval for vCOW contract` await revokeApprovalCallback({ - transactionSummary: claim.type ? _claimRevokeApprovalMessageMap(claim.type) : undefined, + modalMessage: summary, + transactionSummary: summary, }) } catch (error) { console.error('[InvestOption]: Issue revoking approval.', error) @@ -206,7 +174,7 @@ export default function InvestOption({ claim, optionIndex, openModal, closeModal } finally { setApproving(false) } - }, [claim.type, handleCloseError, handleSetError, revokeApprovalCallback]) + }, [handleCloseError, handleSetError, revokeApprovalCallback, token?.symbol]) const vCowAmount = useMemo( () => calculateInvestmentAmounts(claim, investmentAmount)?.vCowAmount, @@ -362,8 +330,11 @@ export default function InvestOption({ claim, optionIndex, openModal, closeModal )} )} - {isAlreadyApproved && ( - + {(approveState === ApprovalState.APPROVED || approveState === ApprovalState.PENDING) && ( + Revoke approval )} From 0f249e6540b12776768806e759e591161a1d7f63 Mon Sep 17 00:00:00 2001 From: David W3stside Date: Tue, 25 Jan 2022 10:37:58 -0300 Subject: [PATCH 09/11] change return type to object --- .../hooks/useApproveCallback/useApproveCallbackMod.ts | 8 ++------ src/custom/pages/Swap/SwapMod.tsx | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/custom/hooks/useApproveCallback/useApproveCallbackMod.ts b/src/custom/hooks/useApproveCallback/useApproveCallbackMod.ts index fff370f72..823d80319 100644 --- a/src/custom/hooks/useApproveCallback/useApproveCallbackMod.ts +++ b/src/custom/hooks/useApproveCallback/useApproveCallbackMod.ts @@ -41,11 +41,7 @@ export function useApproveCallback({ amountToApprove, spender, amountToCheckAgainstAllowance, -}: ApproveCallbackParams): [ - ApprovalState, - (optionalParams?: OptionalApproveCallbackParams) => Promise, - (optionalParams?: OptionalApproveCallbackParams) => Promise -] { +}: ApproveCallbackParams) { const { account, chainId } = useActiveWeb3React() const token = amountToApprove?.currency?.isToken ? amountToApprove.currency : undefined const currentAllowance = useTokenAllowance(token, account ?? undefined, spender) @@ -250,7 +246,7 @@ export function useApproveCallback({ ] ) - return [approvalState, approve, revokeApprove] + return { approvalState, approve, revokeApprove, isPendingApproval: pendingApproval } } // wraps useApproveCallback in the context of a swap diff --git a/src/custom/pages/Swap/SwapMod.tsx b/src/custom/pages/Swap/SwapMod.tsx index ce4eed56b..4011cff78 100644 --- a/src/custom/pages/Swap/SwapMod.tsx +++ b/src/custom/pages/Swap/SwapMod.tsx @@ -329,7 +329,7 @@ export default function Swap({ const isLoadingRoute = toggledVersion === Version.v3 && V3TradeState.LOADING === v3TradeState */ // check whether the user has approved the router on the input token - const [approvalState, approveCallback] = useApproveCallbackFromTrade({ + const { approvalState, approve: approveCallback } = useApproveCallbackFromTrade({ openTransactionConfirmationModal: (message: string) => openTransactionConfirmationModal(message, OperationType.APPROVE_TOKEN), closeModals, From f838db7b5464a6bc2deaafa37001896bdbb60f81 Mon Sep 17 00:00:00 2001 From: David W3stside Date: Tue, 25 Jan 2022 10:38:04 -0300 Subject: [PATCH 10/11] styles --- .../Claim/InvestmentFlow/InvestOption.tsx | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx index 0e859a622..1c3f98c9e 100644 --- a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx +++ b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx @@ -44,6 +44,10 @@ type InvestOptionProps = { } const UnderlineButton = styled.button` + display: flex; + align-items: center; + gap: 0 6px; + background: none; border: 0; cursor: pointer; @@ -69,7 +73,12 @@ export default function InvestOption({ claim, optionIndex, openModal, closeModal const investmentAmount = investFlowData[optionIndex].investedAmount // Approve hooks - const [approveState, approveCallback, revokeApprovalCallback] = useApproveCallbackFromClaim({ + const { + approvalState: approveState, + approve: approveCallback, + revokeApprove: revokeApprovalCallback, + isPendingApproval, + } = useApproveCallbackFromClaim({ openTransactionConfirmationModal: (message: string, operationType: OperationType) => openModal(message, operationType), closeModals: closeModal, @@ -330,12 +339,9 @@ export default function InvestOption({ claim, optionIndex, openModal, closeModal )} )} - {(approveState === ApprovalState.APPROVED || approveState === ApprovalState.PENDING) && ( - - Revoke approval + {approveState === ApprovalState.APPROVED && ( + + Revoke approval {approving || (isPendingApproval && )} )} From 38f701f218296aab6d547ea9e51d1706747b7195 Mon Sep 17 00:00:00 2001 From: David W3stside Date: Tue, 25 Jan 2022 12:30:08 -0300 Subject: [PATCH 11/11] remove revoke UI elements and handlers --- .../Claim/InvestmentFlow/InvestOption.tsx | 49 +++++++------------ src/custom/pages/Claim/styled.ts | 36 ++++++++++---- 2 files changed, 43 insertions(+), 42 deletions(-) diff --git a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx index 1c3f98c9e..acfe1df63 100644 --- a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx +++ b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx @@ -3,7 +3,7 @@ import { CurrencyAmount, Percent } from '@uniswap/sdk-core' import { BigNumber } from '@ethersproject/bignumber' import CowProtocolLogo from 'components/CowProtocolLogo' -import { InvestTokenGroup, TokenLogo, InvestSummary, InvestInput, InvestAvailableBar } from '../styled' +import { InvestTokenGroup, TokenLogo, InvestSummary, InvestInput, InvestAvailableBar, UnderlineButton } from '../styled' import { formatSmartLocaleAware } from 'utils/format' import Row from 'components/Row' import CheckCircle from 'assets/cow-swap/check.svg' @@ -25,7 +25,6 @@ import { useGasPrices } from 'state/gas/hooks' import { AVG_APPROVE_COST_GWEI } from 'components/swap/EthWethWrap/helpers' import { EnhancedUserClaimData } from '../types' import { OperationType } from 'components/TransactionConfirmationModal' -import styled from 'styled-components/macro' const ErrorMsgs = { InsufficientBalance: (symbol = '') => `Insufficient ${symbol} balance to cover investment amount`, @@ -43,26 +42,6 @@ type InvestOptionProps = { closeModal: InvestmentFlowProps['modalCbs']['closeModal'] } -const UnderlineButton = styled.button` - display: flex; - align-items: center; - gap: 0 6px; - - background: none; - border: 0; - cursor: pointer; - color: #ff5d25; - text-decoration: underline; - text-align: left; - padding: 0; - - &:disabled { - text-decoration: none; - color: darkgrey; - cursor: auto; - } -` - export default function InvestOption({ claim, optionIndex, openModal, closeModal }: InvestOptionProps) { const { currencyAmount, price, cost: maxCost } = claim @@ -76,8 +55,8 @@ export default function InvestOption({ claim, optionIndex, openModal, closeModal const { approvalState: approveState, approve: approveCallback, - revokeApprove: revokeApprovalCallback, - isPendingApproval, + // revokeApprove: revokeApprovalCallback, // CURRENTLY TEST ONLY + // isPendingApproval, // CURRENTLY TEST ONLY } = useApproveCallbackFromClaim({ openTransactionConfirmationModal: (message: string, operationType: OperationType) => openModal(message, operationType), @@ -164,6 +143,7 @@ export default function InvestOption({ claim, optionIndex, openModal, closeModal } }, [approveCallback, handleCloseError, handleSetError, token?.symbol]) + /* // CURRENTLY TEST ONLY const handleRevokeApproval = useCallback(async () => { // reset errors and close any modals handleCloseError() @@ -183,7 +163,8 @@ export default function InvestOption({ claim, optionIndex, openModal, closeModal } finally { setApproving(false) } - }, [handleCloseError, handleSetError, revokeApprovalCallback, token?.symbol]) + }, [handleCloseError, handleSetError, revokeApprovalCallback, token?.symbol]) + */ const vCowAmount = useMemo( () => calculateInvestmentAmounts(claim, investmentAmount)?.vCowAmount, @@ -339,11 +320,14 @@ export default function InvestOption({ claim, optionIndex, openModal, closeModal )} )} - {approveState === ApprovalState.APPROVED && ( - - Revoke approval {approving || (isPendingApproval && )} - - )} + {/* + // CURRENTLY TEST ONLY + approveState === ApprovalState.APPROVED && ( + + Revoke approval {approving || (isPendingApproval && )} + + ) + */} @@ -364,9 +348,10 @@ export default function InvestOption({ claim, optionIndex, openModal, closeModal {/* Button should use the max possible amount the user can invest, considering their balance + max investment allowed */} {!noBalance && isSelfClaiming && ( - + )} theme.primary4}; + text-decoration: underline; + text-align: left; + padding: 0; + + &:hover { + color: ${({ theme }) => theme.text1}; + } + + &:disabled { + text-decoration: none; + color: ${({ theme }) => theme.disabled}; + cursor: auto; + } +` + export const InvestInput = styled.span` display: flex; flex-flow: column wrap; @@ -984,16 +1008,8 @@ export const InvestInput = styled.span` font-style: normal; } - > div > label > span > button { - background: none; - border: 0; - cursor: pointer; - color: ${({ theme }) => theme.primary4}; - text-decoration: underline; - - &:hover { - color: ${({ theme }) => theme.text1}; - } + > div > label > span > ${UnderlineButton} { + margin-left: 4px; } `