diff --git a/src/custom/hooks/useApproveCallback/index.ts b/src/custom/hooks/useApproveCallback/index.ts index c8d6f6512..c9b6396d7 100644 --- a/src/custom/hooks/useApproveCallback/index.ts +++ b/src/custom/hooks/useApproveCallback/index.ts @@ -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' @@ -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' @@ -50,24 +55,44 @@ export type OptionalApproveCallbackParams = { transactionSummary: string } -type ApproveCallbackFromClaimParams = Omit +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, }) } diff --git a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx index 0dcc0182f..15b5f1357 100644 --- a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx +++ b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx @@ -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' @@ -20,6 +20,8 @@ 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', @@ -27,21 +29,50 @@ enum ErrorMsgs { 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('0') const [typedValue, setTypedValue] = useState('0') const [inputError, setInputError] = useState('') - const investedAmount = investFlowData[optionIndex].investedAmount - const token = currencyAmount?.currency const balance = useCurrencyBalance(account || undefined, token) @@ -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 () => { @@ -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 @@ -177,9 +205,9 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest Token approval - {approveData ? ( + {!isEtherApproveState ? ( - {approveData.approveState !== ApprovalState.APPROVED ? ( + {approveState !== ApprovalState.APPROVED ? ( `${currencyAmount?.currency?.symbol} not approved` ) : ( @@ -196,8 +224,8 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest )} - {/* 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 && ( {approving || approveState === ApprovalState.PENDING ? ( - ) : approveData ? ( + ) : ( Approve {currencyAmount?.currency?.symbol} - ) : null} + )} )} diff --git a/src/custom/pages/Claim/InvestmentFlow/index.tsx b/src/custom/pages/Claim/InvestmentFlow/index.tsx index 186b66c14..915b1bf98 100644 --- a/src/custom/pages/Claim/InvestmentFlow/index.tsx +++ b/src/custom/pages/Claim/InvestmentFlow/index.tsx @@ -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 & { +export type InvestmentFlowProps = Pick & { 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() @@ -99,12 +76,7 @@ export default function InvestmentFlow({ hasClaims, isAirdropOnly, ...tokenAppro

{selectedClaims.map((claim, index) => ( - + ))} {/* TODO: Update this with real data */} diff --git a/src/custom/pages/Claim/index.tsx b/src/custom/pages/Claim/index.tsx index ab5563f97..3a89c7bf3 100644 --- a/src/custom/pages/Claim/index.tsx +++ b/src/custom/pages/Claim/index.tsx @@ -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' @@ -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 @@ -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 ( {/* Approve confirmation modal */} @@ -248,18 +221,7 @@ export default function Claim() { hasClaims={hasClaims} /> {/* Investing vCOW flow (advanced) */} - + {/* General claim vCOW button (no invest) */} diff --git a/src/custom/state/claim/hooks/utils.ts b/src/custom/state/claim/hooks/utils.ts index b16cf522e..e811a3047 100644 --- a/src/custom/state/claim/hooks/utils.ts +++ b/src/custom/state/claim/hooks/utils.ts @@ -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 } diff --git a/src/custom/state/swap/extension.ts b/src/custom/state/swap/extension.ts index a15d5a440..082644396 100644 --- a/src/custom/state/swap/extension.ts +++ b/src/custom/state/swap/extension.ts @@ -15,6 +15,12 @@ interface TradeParams { export const stringToCurrency = (amount: string, currency: Currency) => CurrencyAmount.fromRawAmount(currency, JSBI.BigInt(amount)) +export const tryAtomsToCurrency = (atoms: string | undefined, currency: Currency | undefined) => { + if (!atoms || !currency) return undefined + + return stringToCurrency(atoms, currency) +} + /** * useTradeExactInWithFee * @description wraps useTradeExactIn and returns an extended trade object with the fee adjusted values