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

Commit

Permalink
[CLAIM - Approve] - Tweak useApproveCallbackFromClaim + wire into app (
Browse files Browse the repository at this point in the history
…#2230)

* move approve logic from Claim > InvestOption

* helper utils

* tweak claim approve hook
  • Loading branch information
W3stside authored Jan 20, 2022
1 parent e272016 commit d77208e
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 106 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 { CurrencyAmount, MaxUint256, Percent, Token } 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 { claimTypeToToken } from 'state/claim/hooks/utils'

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'
> & {
claimType: ClaimType
investmentAmount: string | undefined
}

export function useApproveCallbackFromClaim({
openTransactionConfirmationModal,
closeModals,
amountToApprove,
amountToCheckAgainstAllowance,
claimType,
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 && (claimType === ClaimType.GnoOption || claimType === ClaimType.Investor)) {
const investmentCurrency = claimTypeToToken(claimType, supportedChain) as Token
const amountToCheckAgainstAllowance = tryAtomsToCurrency(investmentAmount, investmentCurrency)
return {
amountToApprove: CurrencyAmount.fromRawAmount(investmentCurrency, MaxUint256),
amountToCheckAgainstAllowance,
}
}
return undefined
}, [claimType, 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,
})
}
68 changes: 48 additions & 20 deletions src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,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 @@ -20,28 +20,59 @@ import { useErrorModal } from 'hooks/useErrorMessageAndModal'
import { tryParseAmount } from 'state/swap/hooks'
import { ONE_HUNDRED_PERCENT, ZERO_PERCENT } from 'constants/misc'
import { PERCENTAGE_PRECISION } from 'constants/index'
import { EnhancedUserClaimData } from '../types'
import { OperationType } from 'components/TransactionConfirmationModal'

enum ErrorMsgs {
Initial = 'Insufficient balance to cover the selected investment amount. Either modify the investment amount or unselect the investment option to move forward',
Balance = 'Insufficient balance to cover the selected investment amount, please modify the selected amount to move forward',
MaxCost = 'Selected amount is higher than available investment amount, please modify the input amount to move forward',
}

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,
claimType: claim.type,
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 @@ -101,9 +132,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 @@ -125,13 +153,13 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest
}, [approveCallback, handleCloseError, handleSetError, token?.symbol])

const vCowAmount = useMemo(() => {
if (!token || !price || !investedAmount) {
if (!token || !price || !investmentAmount) {
return
}

const investA = CurrencyAmount.fromRawAmount(token, investedAmount)
const investA = CurrencyAmount.fromRawAmount(token, investmentAmount)
return price.quote(investA)
}, [investedAmount, price, token])
}, [investmentAmount, price, token])

// if its claiming for someone else we will set values to max
// if there is not enough balance then we will set an error
Expand Down Expand Up @@ -177,9 +205,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 @@ -196,8 +224,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 @@ -208,9 +236,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
46 changes: 9 additions & 37 deletions src/custom/pages/Claim/InvestmentFlow/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,46 +10,23 @@ import {
AccountClaimSummary,
TokenLogo,
} from 'pages/Claim/styled'
import { ClaimType, useClaimState, useUserEnhancedClaimData, useClaimDispatchers } from 'state/claim/hooks'
import { ClaimCommonTypes, EnhancedUserClaimData } from '../types'
import { useClaimState, useUserEnhancedClaimData, useClaimDispatchers } from 'state/claim/hooks'
import { ClaimCommonTypes } from '../types'
import { ClaimStatus } from 'state/claim/actions'
import { useActiveWeb3React } from 'hooks/web3'
import { ApprovalState, OptionalApproveCallbackParams } from 'hooks/useApproveCallback'
import InvestOption from './InvestOption'
import CowProtocolLogo from 'components/CowProtocolLogo'
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
}
}

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 } = useClaimState()
const { initInvestFlowData } = useClaimDispatchers()
Expand Down Expand Up @@ -99,12 +76,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} />
))}

{/* TODO: Update this with real data */}
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,6 +1,5 @@
import { useEffect, useMemo } from 'react'
import { Trans } from '@lingui/macro'
import { CurrencyAmount, MaxUint256 } from '@uniswap/sdk-core'
import { useActiveWeb3React } from 'hooks/web3'
import { useUserEnhancedClaimData, useUserUnclaimedAmount, useClaimCallback, ClaimInput } from 'state/claim/hooks'
import { ButtonPrimary, ButtonSecondary } from 'components/Button'
Expand All @@ -23,20 +22,14 @@ 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'

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 @@ -197,26 +190,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 Down Expand Up @@ -248,18 +221,7 @@ export default function Claim() {
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>
{/* General claim vCOW button (no invest) */}
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 @@ -159,17 +159,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 d77208e

Please sign in to comment.