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

[CLAIM - Approve] - Tweak useApproveCallbackFromClaim + wire into app #2230

Merged
merged 3 commits into from
Jan 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just out of curiosity, could we also use claim.currencyAmount?.currency from the claim object here?

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