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

Commit

Permalink
[CLAIM - Approve] Wire new approve logic into app (fixes broken merge…
Browse files Browse the repository at this point in the history
… stuff also) (#2233)

* move approve logic from Claim > InvestOption

* helper utils

* tweak claim approve hook

* apply @nenadV91's suggested fix

* path
  • Loading branch information
W3stside authored Jan 20, 2022
1 parent 268a1ba commit 5a787bd
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 104 deletions.
37 changes: 31 additions & 6 deletions src/custom/hooks/useApproveCallback/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Percent } 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'
Expand All @@ -9,6 +9,11 @@ import TradeGp from 'state/swap/TradeGp'
import { ApproveCallbackParams, useApproveCallback } from './useApproveCallbackMod'
export { ApprovalState, useApproveCallback } from './useApproveCallbackMod'

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

type ApproveCallbackFromTradeParams = Pick<
ApproveCallbackParams,
'openTransactionConfirmationModal' | 'closeModals' | 'amountToCheckAgainstAllowance'
Expand Down Expand Up @@ -50,24 +55,44 @@ export type OptionalApproveCallbackParams = {
transactionSummary: string
}

type ApproveCallbackFromClaimParams = Omit<ApproveCallbackParams, 'spender'>
type ApproveCallbackFromClaimParams = Omit<
ApproveCallbackParams,
'spender' | 'amountToApprove' | 'amountToCheckAgainstAllowance'
> & {
claim: EnhancedUserClaimData
investmentAmount: string | undefined
}

export function useApproveCallbackFromClaim({
openTransactionConfirmationModal,
closeModals,
amountToApprove,
amountToCheckAgainstAllowance,
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
const amountToCheckAgainstAllowance = tryAtomsToCurrency(investmentAmount, investmentCurrency)
return {
amountToApprove: CurrencyAmount.fromRawAmount(investmentCurrency, MaxUint256),
amountToCheckAgainstAllowance,
}
}
return undefined
}, [claim, investmentAmount, supportedChain])

// Params: modal cbs, amountToApprove: token user is investing e.g, spender: vcow token contract
return useApproveCallback({
openTransactionConfirmationModal,
closeModals,
amountToApprove,
spender: vCowContract,
amountToCheckAgainstAllowance,
amountToApprove: approveAmounts?.amountToApprove,
amountToCheckAgainstAllowance: approveAmounts?.amountToCheckAgainstAllowance,
})
}
66 changes: 47 additions & 19 deletions src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import { InvestTokenGroup, TokenLogo, InvestSummary, InvestInput, InvestAvailabl
import { formatSmart } 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 @@ -18,27 +18,58 @@ import Loader from 'components/Loader'
import { useErrorModal } from 'hooks/useErrorMessageAndModal'
import { tryParseAmount } from 'state/swap/hooks'
import { calculateInvestmentAmounts, calculatePercentage } from 'state/claim/hooks/utils'
import { EnhancedUserClaimData } from '../types'
import { OperationType } from 'components/TransactionConfirmationModal'

enum ErrorMsgs {
InsufficientBalance = 'Insufficient balance to cover investment amount',
OverMaxInvestment = `Your investment amount can't be above the maximum investment allowed`,
}

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 } = useActiveWeb3React()
const { updateInvestAmount } = useClaimDispatchers()
const { investFlowData, activeClaimAccount } = useClaimState()

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

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

const isEtherApproveState = approveState === ApprovalState.UNKNOWN

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

const [percentage, setPercentage] = useState<string>('0')
const [typedValue, setTypedValue] = useState<string>('0')
const [inputError, setInputError] = useState<string>('')

const investedAmount = investFlowData[optionIndex].investedAmount

const token = currencyAmount?.currency
const balance = useCurrencyBalance(account || undefined, token)

Expand Down Expand Up @@ -98,9 +129,6 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest
[balance, maxCost, optionIndex, token, updateInvestAmount]
)

// 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 @@ -122,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 its claiming for someone else we will set values to max
Expand Down Expand Up @@ -170,9 +198,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 @@ -189,8 +217,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 @@ -201,9 +229,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
44 changes: 8 additions & 36 deletions src/custom/pages/Claim/InvestmentFlow/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,45 +11,22 @@ import {
} from 'pages/Claim/styled'
import { InvestSummaryRow } from 'pages/Claim/InvestmentFlow/InvestSummaryRow'

import { ClaimType, useClaimState, useUserEnhancedClaimData, useClaimDispatchers } from 'state/claim/hooks'
import { useClaimState, useUserEnhancedClaimData, useClaimDispatchers } from 'state/claim/hooks'
import { ClaimStatus } from 'state/claim/actions'
import { InvestClaim } from 'state/claim/reducer'
import { calculateInvestmentAmounts } from 'state/claim/hooks/utils'

import { ApprovalState, OptionalApproveCallbackParams } from 'hooks/useApproveCallback'
import { useActiveWeb3React } from 'hooks/web3'

import InvestOption from './InvestOption'
import { ClaimCommonTypes, ClaimWithInvestmentData, EnhancedUserClaimData } from '../types'
import { OperationType } from 'components/TransactionConfirmationModal'

export type InvestOptionProps = {
claim: EnhancedUserClaimData
optionIndex: number
approveData:
| { approveState: ApprovalState; approveCallback: (optionalParams?: OptionalApproveCallbackParams) => void }
| undefined
}

type InvestmentFlowProps = Pick<ClaimCommonTypes, 'hasClaims'> & {
export type InvestmentFlowProps = Pick<ClaimCommonTypes, 'hasClaims'> & {
isAirdropOnly: boolean
gnoApproveData: InvestOptionProps['approveData']
usdcApproveData: InvestOptionProps['approveData']
}

type TokenApproveName = 'gnoApproveData' | 'usdcApproveData'
type TokenApproveData = {
[key in TokenApproveName]: InvestOptionProps['approveData'] | undefined
}

// map claim type to token approve data
function _claimToTokenApproveData(claimType: ClaimType, tokenApproveData: TokenApproveData) {
switch (claimType) {
case ClaimType.GnoOption:
return tokenApproveData.gnoApproveData
case ClaimType.Investor:
return tokenApproveData.usdcApproveData
default:
return undefined
modalCbs: {
openModal: (message: string, operationType: OperationType) => void
closeModal: () => void
}
}

Expand Down Expand Up @@ -78,7 +55,7 @@ function _enhancedUserClaimToClaimWithInvestment(
return { ...claim, ...calculateInvestmentAmounts(claim, investmentAmount) }
}

export default function InvestmentFlow({ hasClaims, isAirdropOnly, ...tokenApproveData }: InvestmentFlowProps) {
export default function InvestmentFlow({ hasClaims, isAirdropOnly, modalCbs }: InvestmentFlowProps) {
const { account } = useActiveWeb3React()
const { selected, activeClaimAccount, claimStatus, isInvestFlowActive, investFlowStep, investFlowData } =
useClaimState()
Expand Down Expand Up @@ -140,12 +117,7 @@ export default function InvestmentFlow({ hasClaims, isAirdropOnly, ...tokenAppro
</p>

{selectedClaims.map((claim, index) => (
<InvestOption
key={claim.index}
optionIndex={index}
approveData={_claimToTokenApproveData(claim.type, tokenApproveData)}
claim={claim}
/>
<InvestOption key={claim.index} optionIndex={index} claim={claim} {...modalCbs} />
))}

<InvestFlowValidation>Approve all investment tokens before continuing</InvestFlowValidation>
Expand Down
42 changes: 2 additions & 40 deletions src/custom/pages/Claim/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useCallback, useEffect, useMemo } from 'react'
import { CurrencyAmount, MaxUint256 } from '@uniswap/sdk-core'
import { useActiveWeb3React } from 'hooks/web3'
import { useUserEnhancedClaimData, useUserUnclaimedAmount, useClaimCallback, ClaimInput } from 'state/claim/hooks'
import { PageWrapper } from 'pages/Claim/styled'
Expand All @@ -21,21 +20,15 @@ import InvestmentFlow from './InvestmentFlow'
import { useClaimDispatchers, useClaimState } from 'state/claim/hooks'
import { ClaimStatus } from 'state/claim/actions'

import { useApproveCallbackFromClaim } from 'hooks/useApproveCallback'
import { OperationType } from 'components/TransactionConfirmationModal'
import useTransactionConfirmationModal from 'hooks/useTransactionConfirmationModal'

import { GNO, USDC_BY_CHAIN } from 'constants/tokens'
import { isSupportedChain } from 'utils/supportedChainId'
import { useErrorModal } from 'hooks/useErrorMessageAndModal'
import { EnhancedUserClaimData } from './types'
import FooterNavButtons from './FooterNavButtons'

const GNO_CLAIM_APPROVE_MESSAGE = 'Approving GNO for investing in vCOW'
const USDC_CLAIM_APPROVE_MESSAGE = 'Approving USDC for investing in vCOW'

export default function Claim() {
const { account, chainId } = useActiveWeb3React()
const { account } = useActiveWeb3React()

const {
// address/ENS address
Expand Down Expand Up @@ -184,26 +177,6 @@ export default function Claim() {
OperationType.APPROVE_TOKEN
)

const [gnoApproveState, gnoApproveCallback] = useApproveCallbackFromClaim({
openTransactionConfirmationModal: () => openModal(GNO_CLAIM_APPROVE_MESSAGE, OperationType.APPROVE_TOKEN),
closeModals: closeModal,
// approve max unit256 amount
amountToApprove: isSupportedChain(chainId) ? CurrencyAmount.fromRawAmount(GNO[chainId], MaxUint256) : undefined,
// TODO: enable, fix this
// amountToCheckAgainstAllowance: investmentAmountAsCurrency,
})

const [usdcApproveState, usdcApproveCallback] = useApproveCallbackFromClaim({
openTransactionConfirmationModal: () => openModal(USDC_CLAIM_APPROVE_MESSAGE, OperationType.APPROVE_TOKEN),
closeModals: closeModal,
// approve max unit256 amount
amountToApprove: isSupportedChain(chainId)
? CurrencyAmount.fromRawAmount(USDC_BY_CHAIN[chainId], MaxUint256)
: undefined,
// TODO: enable, fix this
// amountToCheckAgainstAllowance: investmentAmountAsCurrency,
})

return (
<PageWrapper>
{/* Approve confirmation modal */}
Expand All @@ -229,18 +202,7 @@ export default function Claim() {
{/* IS Airdrop + investing (advanced) */}
<ClaimsTable isAirdropOnly={isAirdropOnly} hasClaims={hasClaims} />
{/* Investing vCOW flow (advanced) */}
<InvestmentFlow
isAirdropOnly={isAirdropOnly}
hasClaims={hasClaims}
gnoApproveData={{
approveCallback: gnoApproveCallback,
approveState: gnoApproveState,
}}
usdcApproveData={{
approveCallback: usdcApproveCallback,
approveState: usdcApproveState,
}}
/>
<InvestmentFlow isAirdropOnly={isAirdropOnly} hasClaims={hasClaims} modalCbs={{ openModal, closeModal }} />

<FooterNavButtons
handleCheckClaim={handleCheckClaim}
Expand Down
21 changes: 18 additions & 3 deletions src/custom/state/claim/hooks/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,17 +164,32 @@ export type PaidClaimTypeToPriceMap = {
[type in ClaimType]: { token: Token; amount: string } | undefined
}

export function claimTypeToToken(type: ClaimType, chainId: SupportedChainId) {
switch (type) {
case ClaimType.GnoOption:
return GNO[chainId]
case ClaimType.Investor:
return USDC_BY_CHAIN[chainId]
case ClaimType.UserOption:
return GpEther.onChain(chainId)
case ClaimType.Advisor:
case ClaimType.Airdrop:
case ClaimType.Team:
return undefined
}
}

/**
* Helper function to get vCow price based on claim type and chainId
*/
export function claimTypeToTokenAmount(type: ClaimType, chainId: SupportedChainId, prices: VCowPrices) {
switch (type) {
case ClaimType.GnoOption:
return { token: GNO[chainId], amount: prices.gno as string }
return { token: claimTypeToToken(ClaimType.GnoOption, chainId) as Token, amount: prices.gno as string }
case ClaimType.Investor:
return { token: USDC_BY_CHAIN[chainId], amount: prices.usdc as string }
return { token: claimTypeToToken(ClaimType.Investor, chainId) as Token, amount: prices.usdc as string }
case ClaimType.UserOption:
return { token: GpEther.onChain(chainId), amount: prices.native as string }
return { token: claimTypeToToken(ClaimType.UserOption, chainId) as GpEther, amount: prices.native as string }
default:
return undefined
}
Expand Down
Loading

0 comments on commit 5a787bd

Please sign in to comment.