diff --git a/src/custom/hooks/useApproveCallback/index.ts b/src/custom/hooks/useApproveCallback/index.ts index aa8c041f5..89cac7fde 100644 --- a/src/custom/hooks/useApproveCallback/index.ts +++ b/src/custom/hooks/useApproveCallback/index.ts @@ -32,6 +32,10 @@ export function useApproveCallbackFromTrade( return useApproveCallback(openTransactionConfirmationModal, closeModals, amountToApprove, vaultRelayer) } +export type OptionalApproveCallbackParams = { + transactionSummary: string +} + export function useApproveCallbackFromClaim( openTransactionConfirmationModal: (message: string) => void, closeModals: () => void, diff --git a/src/custom/hooks/useApproveCallback/useApproveCallbackMod.ts b/src/custom/hooks/useApproveCallback/useApproveCallbackMod.ts index 1b7fe3973..83eefd19f 100644 --- a/src/custom/hooks/useApproveCallback/useApproveCallbackMod.ts +++ b/src/custom/hooks/useApproveCallback/useApproveCallbackMod.ts @@ -12,6 +12,7 @@ import { calculateGasMargin } from 'utils/calculateGasMargin' import { useTokenContract } from 'hooks/useContract' import { useTokenAllowance } from 'hooks/useTokenAllowance' import { useActiveWeb3React } from 'hooks/web3' +import { OptionalApproveCallbackParams } from '.' // Use a 150K gas as a fallback if there's issue calculating the gas estimation (fixes some issues with some nodes failing to calculate gas costs for SC wallets) const APPROVE_GAS_LIMIT_DEFAULT = BigNumber.from('150000') @@ -29,7 +30,7 @@ export function useApproveCallback( closeModals: () => void, amountToApprove?: CurrencyAmount, spender?: string -): [ApprovalState, () => Promise] { +): [ApprovalState, (optionalParams?: OptionalApproveCallbackParams) => Promise] { const { account, chainId } = useActiveWeb3React() const token = amountToApprove?.currency?.isToken ? amountToApprove.currency : undefined const currentAllowance = useTokenAllowance(token, account ?? undefined, spender) @@ -54,81 +55,84 @@ export function useApproveCallback( const tokenContract = useTokenContract(token?.address) const addTransaction = useTransactionAdder() - const approve = useCallback(async (): Promise => { - if (approvalState !== ApprovalState.NOT_APPROVED) { - console.error('approve was called unnecessarily') - return - } - if (!chainId) { - console.error('no chainId') - return - } - - if (!token) { - console.error('no token') - return - } - - if (!tokenContract) { - console.error('tokenContract is null') - return - } - - if (!amountToApprove) { - console.error('missing amount to approve') - return - } - - if (!spender) { - console.error('no spender') - return - } - - let useExact = false - const estimatedGas = await tokenContract.estimateGas.approve(spender, MaxUint256).catch(() => { - // general fallback for tokens who restrict approval amounts - useExact = true - return tokenContract.estimateGas.approve(spender, amountToApprove.quotient.toString()).catch((error) => { - console.log( - '[useApproveCallbackMod] Error estimating gas for approval. Using default gas limit ' + - APPROVE_GAS_LIMIT_DEFAULT.toString(), - error - ) - useExact = false - return APPROVE_GAS_LIMIT_DEFAULT + const approve = useCallback( + async (optionalParams?: OptionalApproveCallbackParams): Promise => { + if (approvalState !== ApprovalState.NOT_APPROVED) { + console.error('approve was called unnecessarily') + return + } + if (!chainId) { + console.error('no chainId') + return + } + + if (!token) { + console.error('no token') + return + } + + if (!tokenContract) { + console.error('tokenContract is null') + return + } + + if (!amountToApprove) { + console.error('missing amount to approve') + return + } + + if (!spender) { + console.error('no spender') + return + } + + let useExact = false + const estimatedGas = await tokenContract.estimateGas.approve(spender, MaxUint256).catch(() => { + // general fallback for tokens who restrict approval amounts + useExact = true + return tokenContract.estimateGas.approve(spender, amountToApprove.quotient.toString()).catch((error) => { + console.log( + '[useApproveCallbackMod] Error estimating gas for approval. Using default gas limit ' + + APPROVE_GAS_LIMIT_DEFAULT.toString(), + error + ) + useExact = false + return APPROVE_GAS_LIMIT_DEFAULT + }) }) - }) - openTransactionConfirmationModal(`Approving ${amountToApprove.currency.symbol} for trading`) - return ( - tokenContract - .approve(spender, useExact ? amountToApprove.quotient.toString() : MaxUint256, { - gasLimit: calculateGasMargin(chainId, estimatedGas), - }) - .then((response: TransactionResponse) => { - addTransaction({ - hash: response.hash, - summary: 'Approve ' + amountToApprove.currency.symbol, - approval: { tokenAddress: token.address, spender }, + openTransactionConfirmationModal(`Approving ${amountToApprove.currency.symbol} for trading`) + return ( + tokenContract + .approve(spender, useExact ? amountToApprove.quotient.toString() : MaxUint256, { + gasLimit: calculateGasMargin(chainId, estimatedGas), }) - }) - // .catch((error: Error) => { - // console.debug('Failed to approve token', error) - // throw error - // }) - .finally(closeModals) - ) - }, [ - chainId, - approvalState, - token, - tokenContract, - amountToApprove, - spender, - addTransaction, - openTransactionConfirmationModal, - closeModals, - ]) + .then((response: TransactionResponse) => { + addTransaction({ + hash: response.hash, + summary: optionalParams?.transactionSummary || 'Approve ' + amountToApprove.currency.symbol, + approval: { tokenAddress: token.address, spender }, + }) + }) + // .catch((error: Error) => { + // console.debug('Failed to approve token', error) + // throw error + // }) + .finally(closeModals) + ) + }, + [ + chainId, + approvalState, + token, + tokenContract, + amountToApprove, + spender, + addTransaction, + openTransactionConfirmationModal, + closeModals, + ] + ) return [approvalState, approve] } diff --git a/src/custom/pages/Claim/ClaimsTable.tsx b/src/custom/pages/Claim/ClaimsTable.tsx index b48ddd0e3..bb2656ef7 100644 --- a/src/custom/pages/Claim/ClaimsTable.tsx +++ b/src/custom/pages/Claim/ClaimsTable.tsx @@ -1,3 +1,4 @@ +import styled from 'styled-components/macro' import { ClaimType, useClaimState } from 'state/claim/hooks' import { ClaimTable, ClaimBreakdown } from 'pages/Claim/styled' import CowProtocolLogo from 'components/CowProtocolLogo' @@ -5,6 +6,7 @@ import { ClaimStatus } from 'state/claim/actions' // import { UserClaimDataDetails } from './types' TODO: fix in another PR import { formatSmart } from 'utils/format' import { EnhancedUserClaimData } from './types' +import { useAllClaimingTransactionIndices } from 'state/enhancedTransactions/hooks' type ClaimsTableProps = { handleSelectAll: (event: React.ChangeEvent) => void @@ -18,12 +20,28 @@ type ClaimsTableProps = { type ClaimsTableRowProps = EnhancedUserClaimData & Pick & { selected: number[] + isPendingClaim: boolean } +const ClaimTr = styled.tr<{ isPending?: boolean }>` + > td { + background-color: ${({ isPending }) => (isPending ? '#221954' : 'rgb(255 255 255 / 6%)')}; + cursor: ${({ isPending }) => (isPending ? 'pointer' : 'initial')}; + + &:first-child { + border-radius: 8px 0 0 8px; + } + &:last-child { + border-radius: 0 8px 8px 0; + } + } +` + const ClaimsTableRow = ({ index, type, isFree, + isPendingClaim, claimAmount, currencyAmount, price, @@ -32,7 +50,7 @@ const ClaimsTableRow = ({ selected, }: ClaimsTableRowProps) => { return ( - + {' '} ) } @@ -84,6 +104,7 @@ export default function ClaimsTable({ hasClaims, }: ClaimsTableProps) { const { selectedAll, selected, activeClaimAccount, claimStatus, isInvestFlowActive } = useClaimState() + const pendingClaimsSet = useAllClaimingTransactionIndices() const hideTable = isAirdropOnly || !hasClaims || !activeClaimAccount || claimStatus !== ClaimStatus.DEFAULT || isInvestFlowActive @@ -109,7 +130,13 @@ export default function ClaimsTable({ {userClaimData.map((claim: EnhancedUserClaimData) => ( - + ))} diff --git a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx index c085cc5c2..1064d2985 100644 --- a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx +++ b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo } from 'react' +import { useCallback, useMemo, useState } from 'react' import styled from 'styled-components/macro' import CowProtocolLogo from 'components/CowProtocolLogo' import { formatUnits, parseUnits } from '@ethersproject/units' @@ -13,6 +13,10 @@ import { ApprovalState } from 'hooks/useApproveCallback' import { useCurrencyBalance } from 'state/wallet/hooks' import { useActiveWeb3React } from 'hooks/web3' +import { ButtonConfirmed } from 'components/Button' +import { ButtonSize } from 'theme' +import Loader from 'components/Loader' + const RangeSteps = styled.div` display: flex; align-items: center; @@ -60,6 +64,25 @@ export default function InvestOption({ approveData, updateInvestAmount, claim }: updateInvestAmount(claim.index, investAmount) }, [balance, claim.index, maxCost, 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 () => { + if (!approveCallback) return + + try { + // for pending state pre-BC + setApproving(true) + await approveCallback({ transactionSummary: `Approve ${token?.symbol || 'token'} for investing in vCOW` }) + } catch (error) { + console.error('[InvestOption]: Issue approving.', error) + } finally { + setApproving(false) + } + }, [approveCallback, token?.symbol]) + const vCowAmount = useMemo(() => { if (!token || !price) { return @@ -107,8 +130,22 @@ export default function InvestOption({ approveData, updateInvestAmount, claim }: )} - {approveData && approveData.approveState !== ApprovalState.APPROVED && ( - + {/* Approve button - @biocom styles for this found in ./styled > InputSummary > ${ButtonPrimary}*/} + {approveState !== ApprovalState.APPROVED && ( + + {approving || approveState === ApprovalState.PENDING ? ( + + ) : ( + Approve {currencyAmount?.currency?.symbol} + )} + )} diff --git a/src/custom/pages/Claim/InvestmentFlow/index.tsx b/src/custom/pages/Claim/InvestmentFlow/index.tsx index 1d77eb449..571ad517c 100644 --- a/src/custom/pages/Claim/InvestmentFlow/index.tsx +++ b/src/custom/pages/Claim/InvestmentFlow/index.tsx @@ -11,7 +11,7 @@ import { ClaimType, useClaimState, useUserEnhancedClaimData } from 'state/claim/ import { ClaimCommonTypes, EnhancedUserClaimData } from '../types' import { ClaimStatus } from 'state/claim/actions' import { useActiveWeb3React } from 'hooks/web3' -import { ApprovalState } from 'hooks/useApproveCallback' +import { ApprovalState, OptionalApproveCallbackParams } from 'hooks/useApproveCallback' import InvestOption from './InvestOption' export type InvestmentClaimProps = EnhancedUserClaimData & { @@ -21,7 +21,9 @@ export type InvestmentClaimProps = EnhancedUserClaimData & { export type InvestOptionProps = { claim: InvestmentClaimProps updateInvestAmount: (idx: number, investAmount: string) => void - approveData: { approveState: ApprovalState; approveCallback: () => void } | undefined + approveData: + | { approveState: ApprovalState; approveCallback: (optionalParams?: OptionalApproveCallbackParams) => void } + | undefined } type InvestmentFlowProps = Pick & { diff --git a/src/custom/pages/Claim/styled.ts b/src/custom/pages/Claim/styled.ts index 52c8ffb46..0cb9dcbf4 100644 --- a/src/custom/pages/Claim/styled.ts +++ b/src/custom/pages/Claim/styled.ts @@ -684,6 +684,12 @@ export const InvestSummary = styled.div` display: flex; flex-flow: column wrap; margin: 0 0 12px; + + > ${ButtonPrimary} { + font-size: 16px; + padding: 8px; + margin: 8px 0; + } } ` diff --git a/src/custom/state/claim/hooks/index.ts b/src/custom/state/claim/hooks/index.ts index d250a2dd4..e669a4fe1 100644 --- a/src/custom/state/claim/hooks/index.ts +++ b/src/custom/state/claim/hooks/index.ts @@ -271,8 +271,7 @@ export function useUserClaims(account: Account): UserClaims | null { const createMockTx = (data: number[]) => ({ hash: '0x' + Math.round(Math.random() * 10).toString() + 'AxAFjAhG89G89AfnLK3CCxAfnLKQffQ782G89AfnLK3CCxxx123FF', summary: `Claimed ${Math.random() * 3337} vCOW`, - claim: { recipient: '0x97EC4fcD5F78cA6f6E4E1EAC6c0Ec8421bA518B7' }, - data, // add the claim indices to state + claim: { recipient: '0x97EC4fcD5F78cA6f6E4E1EAC6c0Ec8421bA518B7', indices: data }, }) /** @@ -446,8 +445,7 @@ export function useClaimCallback(account: string | null | undefined): { addTransaction({ hash: response.hash, summary: `Claimed ${formatSmart(vCowAmount)} vCOW`, - claim: { recipient: account }, - data: args[0], // add the claim indices to state + claim: { recipient: account, indices: args[0] as number[] }, }) return response.hash }) diff --git a/src/custom/state/enhancedTransactions/hooks/index.ts b/src/custom/state/enhancedTransactions/hooks/index.ts index 2b73f03f4..bbc53545f 100644 --- a/src/custom/state/enhancedTransactions/hooks/index.ts +++ b/src/custom/state/enhancedTransactions/hooks/index.ts @@ -131,9 +131,9 @@ export function useAllClaimingTransactions() { export function useAllClaimingTransactionIndices() { const claimingTransactions = useAllClaimingTransactions() return useMemo(() => { - const flattenedClaimingTransactions = claimingTransactions.reduce((acc, { data }) => { - if (data) { - acc.push(...data) + const flattenedClaimingTransactions = claimingTransactions.reduce((acc, { claim }) => { + if (claim) { + acc.push(...claim.indices) } return acc }, []) diff --git a/src/custom/state/enhancedTransactions/reducer.ts b/src/custom/state/enhancedTransactions/reducer.ts index fa5877ce4..0cbe587d3 100644 --- a/src/custom/state/enhancedTransactions/reducer.ts +++ b/src/custom/state/enhancedTransactions/reducer.ts @@ -35,7 +35,7 @@ export interface EnhancedTransactionDetails { // Operations approval?: { tokenAddress: string; spender: string } presign?: { orderId: string } - claim?: { recipient: string; cowAmountRaw?: string } + claim?: { recipient: string; cowAmountRaw?: string; indices: number[] } // Wallet specific safeTransaction?: SafeMultisigTransactionResponse // Gnosis Safe transaction info