Skip to content
This repository has been archived by the owner on Jun 24, 2022. It is now read-only.

Commit

Permalink
[Claim-Approve] Hook optional amountToCheckAgainstAllowance param (#2175
Browse files Browse the repository at this point in the history
)

* mod the mod

* edit the modded trade and claim approve hooks

* edit places in app using new hook params

* fix approve logic conditional

* change logic

* [CLAIM - Approve] Wire new approve logic into app (fixes broken merge stuff also) (#2233)

* move approve logic from Claim > InvestOption

* helper utils

* tweak claim approve hook

* apply @nenadV91's suggested fix

* path

* allow investmentAmount ro take maxCost

* remove investmentAmount

* && instead of ||

* tweak to approve logic

* fix rebase error
  • Loading branch information
W3stside authored Jan 25, 2022
1 parent 19adb51 commit a8d90c6
Show file tree
Hide file tree
Showing 8 changed files with 224 additions and 155 deletions.
79 changes: 63 additions & 16 deletions src/custom/hooks/useApproveCallback/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,34 @@
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { Currency, CurrencyAmount, MaxUint256, Percent } from '@uniswap/sdk-core'
import { useActiveWeb3React } from '@src/hooks/web3'
import { Field } from '@src/state/swap/actions'
import { computeSlippageAdjustedAmounts } from 'utils/prices'
import { useMemo } from 'react'
import { GP_VAULT_RELAYER, V_COW_CONTRACT_ADDRESS } from 'constants/index'
import TradeGp from 'state/swap/TradeGp'
import { ZERO_PERCENT } from 'constants/misc'

import { useApproveCallback } from './useApproveCallbackMod'
import { ApproveCallbackParams, useApproveCallback } from './useApproveCallbackMod'
export { ApprovalState, useApproveCallback } from './useApproveCallbackMod'

import { ClaimType } from 'state/claim/hooks'
import { supportedChainId } from 'utils/supportedChainId'
import { EnhancedUserClaimData } from 'pages/Claim/types'

type ApproveCallbackFromTradeParams = Pick<
ApproveCallbackParams,
'openTransactionConfirmationModal' | 'closeModals' | 'amountToCheckAgainstAllowance'
> & {
trade: TradeGp | undefined
allowedSlippage: Percent
}

// export function useApproveCallbackFromTrade(trade?: Trade, allowedSlippage = 0) {
export function useApproveCallbackFromTrade(
openTransactionConfirmationModal: (message: string) => void,
closeModals: () => void,
trade?: TradeGp,
allowedSlippage = ZERO_PERCENT
) {
export function useApproveCallbackFromTrade({
openTransactionConfirmationModal,
closeModals,
trade,
allowedSlippage,
amountToCheckAgainstAllowance,
}: ApproveCallbackFromTradeParams) {
const { chainId } = useActiveWeb3React()

const amountToApprove = useMemo(() => {
Expand All @@ -29,22 +41,57 @@ export function useApproveCallbackFromTrade(

const vaultRelayer = chainId ? GP_VAULT_RELAYER[chainId] : undefined

return useApproveCallback(openTransactionConfirmationModal, closeModals, amountToApprove, vaultRelayer)
return useApproveCallback({
openTransactionConfirmationModal,
closeModals,
amountToApprove,
spender: vaultRelayer,
amountToCheckAgainstAllowance,
})
}

export type OptionalApproveCallbackParams = {
transactionSummary: string
}

export function useApproveCallbackFromClaim(
openTransactionConfirmationModal: (message: string) => void,
closeModals: () => void,
amountToApprove: CurrencyAmount<Currency> | undefined
) {
type ApproveCallbackFromClaimParams = Omit<
ApproveCallbackParams,
'spender' | 'amountToApprove' | 'amountToCheckAgainstAllowance'
> & {
claim: EnhancedUserClaimData
investmentAmount?: CurrencyAmount<Currency>
}

export function useApproveCallbackFromClaim({
openTransactionConfirmationModal,
closeModals,
claim,
investmentAmount,
}: ApproveCallbackFromClaimParams) {
const { chainId } = useActiveWeb3React()
const supportedChain = supportedChainId(chainId)

const vCowContract = chainId ? V_COW_CONTRACT_ADDRESS[chainId] : undefined

// Claim only approves GNO and USDC (GnoOption & Investor, respectively.)
const approveAmounts = useMemo(() => {
if (supportedChain && (claim.type === ClaimType.GnoOption || claim.type === ClaimType.Investor)) {
const investmentCurrency = claim.currencyAmount?.currency as Currency
return {
amountToApprove: CurrencyAmount.fromRawAmount(investmentCurrency, MaxUint256),
// pass in a custom investmentAmount or just use the maxCost
amountToCheckAgainstAllowance: investmentAmount || claim.cost,
}
}
return undefined
}, [claim.cost, claim.currencyAmount?.currency, claim.type, investmentAmount, supportedChain])

// Params: modal cbs, amountToApprove: token user is investing e.g, spender: vcow token contract
return useApproveCallback(openTransactionConfirmationModal, closeModals, amountToApprove, vCowContract)
return useApproveCallback({
openTransactionConfirmationModal,
closeModals,
spender: vCowContract,
amountToApprove: approveAmounts?.amountToApprove,
amountToCheckAgainstAllowance: approveAmounts?.amountToCheckAgainstAllowance,
})
}
120 changes: 76 additions & 44 deletions src/custom/hooks/useApproveCallback/useApproveCallbackMod.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { MaxUint256 } from '@ethersproject/constants'
import { TransactionResponse } from '@ethersproject/providers'
import { BigNumber } from '@ethersproject/bignumber'
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk'
import { Currency, CurrencyAmount /* , Percent, TradeType */ } from '@uniswap/sdk-core'
// import { Trade as V2Trade } from '@uniswap/v2-sdk'
// import { Trade as V3Trade } from '@uniswap/v3-sdk'
import { useCallback, useMemo } from 'react'

import { SWAP_ROUTER_ADDRESSES, V2_ROUTER_ADDRESS } from 'constants/addresses'
// import { SWAP_ROUTER_ADDRESSES, V2_ROUTER_ADDRESS } from 'constants/addresses'
import { useHasPendingApproval, useTransactionAdder } from 'state/enhancedTransactions/hooks'
import { calculateGasMargin } from 'utils/calculateGasMargin'
import { useTokenContract } from 'hooks/useContract'
Expand All @@ -24,33 +24,65 @@ export enum ApprovalState {
APPROVED = 'APPROVED',
}

// returns a variable indicating the state of the approval and a function which approves if necessary or early returns
export function useApproveCallback(
openTransactionConfirmationModal: (message: string) => void,
closeModals: () => void,
amountToApprove?: CurrencyAmount<Currency>,
export interface ApproveCallbackParams {
openTransactionConfirmationModal: (message: string) => void
closeModals: () => void
amountToApprove?: CurrencyAmount<Currency>
spender?: string
): [ApprovalState, (optionalParams?: OptionalApproveCallbackParams) => Promise<void>] {
amountToCheckAgainstAllowance?: CurrencyAmount<Currency>
}

// returns a variable indicating the state of the approval and a function which approves if necessary or early returns
export function useApproveCallback({
openTransactionConfirmationModal,
closeModals,
amountToApprove,
spender,
amountToCheckAgainstAllowance,
}: ApproveCallbackParams): [ApprovalState, (optionalParams?: OptionalApproveCallbackParams) => Promise<void>] {
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)

// TODO: Nice to have, can be deleted
{
process.env.NODE_ENV !== 'production' &&
console.debug(`
$$$$Approval metrics:
====
CurrentAllowance: ${currentAllowance?.toExact()}
raw: ${currentAllowance?.quotient.toString()}
====
amountToCheckAgainstApproval: ${amountToCheckAgainstAllowance?.toExact()}
raw: ${amountToCheckAgainstAllowance?.quotient.toString()}
====
amountToApprove: ${amountToApprove?.toExact()}
raw: ${amountToApprove?.quotient.toString()}
====
Needs approval?: ${
!amountToCheckAgainstAllowance && !amountToApprove
? 'Unknown - no amounts'
: currentAllowance && amountToApprove
? currentAllowance.lessThan(amountToCheckAgainstAllowance || amountToApprove)
: 'unknown no currentAllowance'
}
`)
}
// check the current approval status
const approvalState: ApprovalState = useMemo(() => {
if (!amountToApprove || !spender) return ApprovalState.UNKNOWN
if (amountToApprove.currency.isNative) return ApprovalState.APPROVED
// we might not have enough data to know whether or not we need to approve
if (!currentAllowance) return ApprovalState.UNKNOWN

// Return approval state
if (currentAllowance.lessThan(amountToApprove)) {
return pendingApproval ? ApprovalState.PENDING : ApprovalState.NOT_APPROVED
} else {
// Enough allowance
return ApprovalState.APPROVED
}
}, [amountToApprove, currentAllowance, pendingApproval, spender])
// amountToApprove will be defined if currentAllowance is
return currentAllowance.lessThan(amountToCheckAgainstAllowance || amountToApprove)
? pendingApproval
? ApprovalState.PENDING
: ApprovalState.NOT_APPROVED
: ApprovalState.APPROVED
}, [amountToApprove, amountToCheckAgainstAllowance, currentAllowance, pendingApproval, spender])

const tokenContract = useTokenContract(token?.address)
const addTransaction = useTransactionAdder()
Expand Down Expand Up @@ -138,29 +170,29 @@ export function useApproveCallback(
}

// wraps useApproveCallback in the context of a swap
export function useApproveCallbackFromTrade(
openTransactionConfirmationModal: (message: string) => void,
closeModals: () => void,
trade: V2Trade<Currency, Currency, TradeType> | V3Trade<Currency, Currency, TradeType> | undefined,
allowedSlippage: Percent
) {
const { chainId } = useActiveWeb3React()
const v3SwapRouterAddress = chainId ? SWAP_ROUTER_ADDRESSES[chainId] : undefined

const amountToApprove = useMemo(
() => (trade && trade.inputAmount.currency.isToken ? trade.maximumAmountIn(allowedSlippage) : undefined),
[trade, allowedSlippage]
)
return useApproveCallback(
openTransactionConfirmationModal,
closeModals,
amountToApprove,
chainId
? trade instanceof V2Trade
? V2_ROUTER_ADDRESS[chainId]
: trade instanceof V3Trade
? v3SwapRouterAddress
: undefined
: undefined
)
}
// export function useApproveCallbackFromTrade(
// openTransactionConfirmationModal: (message: string) => void,
// closeModals: () => void,
// trade: V2Trade<Currency, Currency, TradeType> | V3Trade<Currency, Currency, TradeType> | undefined,
// allowedSlippage: Percent
// ) {
// const { chainId } = useActiveWeb3React()
// const v3SwapRouterAddress = chainId ? SWAP_ROUTER_ADDRESSES[chainId] : undefined

// const amountToApprove = useMemo(
// () => (trade && trade.inputAmount.currency.isToken ? trade.maximumAmountIn(allowedSlippage) : undefined),
// [trade, allowedSlippage]
// )
// return useApproveCallback(
// openTransactionConfirmationModal,
// closeModals,
// amountToApprove,
// chainId
// ? trade instanceof V2Trade
// ? V2_ROUTER_ADDRESS[chainId]
// : trade instanceof V3Trade
// ? v3SwapRouterAddress
// : undefined
// : undefined
// )
// }
65 changes: 47 additions & 18 deletions src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import { InvestTokenGroup, TokenLogo, InvestSummary, InvestInput, InvestAvailabl
import { formatSmartLocaleAware } from 'utils/format'
import Row from 'components/Row'
import CheckCircle from 'assets/cow-swap/check.svg'
import { InvestOptionProps } from '.'
import { ApprovalState } from 'hooks/useApproveCallback'
import { InvestmentFlowProps } from '.'
import { ApprovalState, useApproveCallbackFromClaim } from 'hooks/useApproveCallback'
import { useCurrencyBalance } from 'state/wallet/hooks'
import { useActiveWeb3React } from 'hooks/web3'
import { useClaimDispatchers, useClaimState } from 'state/claim/hooks'
import { ClaimType, useClaimDispatchers, useClaimState } from 'state/claim/hooks'
import { StyledNumericalInput } from 'components/CurrencyInputPanel/CurrencyInputPanelMod'

import { ButtonConfirmed } from 'components/Button'
Expand All @@ -23,6 +23,8 @@ import { calculateInvestmentAmounts, calculatePercentage } from 'state/claim/hoo
import { AMOUNT_PRECISION, PERCENTAGE_PRECISION } from 'constants/index'
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'

const ErrorMsgs = {
InsufficientBalance: (symbol = '') => `Insufficient ${symbol} balance to cover investment amount`,
Expand All @@ -33,14 +35,44 @@ const ErrorMsgs = {
`You might not have enough ${symbol} to pay for the network transaction fee (estimated ${amount} ${symbol})`,
}

export default function InvestOption({ approveData, claim, optionIndex }: InvestOptionProps) {
type InvestOptionProps = {
claim: EnhancedUserClaimData
optionIndex: number
openModal: InvestmentFlowProps['modalCbs']['openModal']
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.'
}
}

export default function InvestOption({ claim, optionIndex, openModal, closeModal }: InvestOptionProps) {
const { currencyAmount, price, cost: maxCost } = claim

const { account, chainId } = useActiveWeb3React()
const { updateInvestAmount, updateInvestError } = useClaimDispatchers()
const { investFlowData, activeClaimAccount, estimatedGas } = useClaimState()

const { handleSetError, handleCloseError, ErrorModal } = useErrorModal()
const investmentAmount = investFlowData[optionIndex].investedAmount

const { account, chainId } = useActiveWeb3React()
// Approve hooks
const [approveState, approveCallback] = useApproveCallbackFromClaim({
openTransactionConfirmationModal: () => openModal(_claimApproveMessageMap(claim.type), OperationType.APPROVE_TOKEN),
closeModals: closeModal,
claim,
})

const isEtherApproveState = approveState === ApprovalState.UNKNOWN

const { handleSetError, handleCloseError, ErrorModal } = useErrorModal()

const [percentage, setPercentage] = useState<string>('0')
const [typedValue, setTypedValue] = useState<string>('')
Expand Down Expand Up @@ -72,7 +104,7 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest
const isSelfClaiming = account === activeClaimAccount
const noBalance = !balance || balance.equalTo('0')

const isApproved = approveData?.approveState === ApprovalState.APPROVED
const isApproved = approveState === ApprovalState.APPROVED

const gasCost = useMemo(() => {
if (!estimatedGas || !isNative) {
Expand All @@ -97,9 +129,6 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest
setTypedValue(value.toExact() || '')
}, [balance, maxCost, noBalance])

// Cache approveData methods
const approveCallback = approveData?.approveCallback
const approveState = approveData?.approveState
// Save "local" approving state (pre-BC) for rendering spinners etc
const [approving, setApproving] = useState(false)
const handleApprove = useCallback(async () => {
Expand All @@ -121,8 +150,8 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest
}, [approveCallback, handleCloseError, handleSetError, token?.symbol])

const vCowAmount = useMemo(
() => calculateInvestmentAmounts(claim, investedAmount)?.vCowAmount,
[claim, investedAmount]
() => calculateInvestmentAmounts(claim, investmentAmount)?.vCowAmount,
[claim, investmentAmount]
)

// if there is investmentAmount in redux state for this option set it as typedValue
Expand Down Expand Up @@ -238,9 +267,9 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest

<span>
<b>Token approval</b>
{approveData ? (
{!isEtherApproveState ? (
<i>
{approveData.approveState !== ApprovalState.APPROVED ? (
{approveState !== ApprovalState.APPROVED ? (
`${currencyAmount?.currency?.symbol} not approved`
) : (
<Row>
Expand All @@ -257,8 +286,8 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest
</Row>
</i>
)}
{/* Approve button - @biocom styles for this found in ./styled > InputSummary > ${ButtonPrimary}*/}
{approveData && approveState !== ApprovalState.APPROVED && (
{/* Token Approve buton - not shown for ETH */}
{!isEtherApproveState && approveState !== ApprovalState.APPROVED && (
<ButtonConfirmed
buttonSize={ButtonSize.SMALL}
onClick={handleApprove}
Expand All @@ -269,9 +298,9 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest
>
{approving || approveState === ApprovalState.PENDING ? (
<Loader stroke="white" />
) : approveData ? (
) : (
<span>Approve {currencyAmount?.currency?.symbol}</span>
) : null}
)}
</ButtonConfirmed>
)}
</span>
Expand Down
Loading

0 comments on commit a8d90c6

Please sign in to comment.