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

[Claim Approve] Approve button async logic and styles #2155

Merged
merged 6 commits into from
Jan 14, 2022
Merged
Show file tree
Hide file tree
Changes from 5 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
4 changes: 4 additions & 0 deletions src/custom/hooks/useApproveCallback/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
150 changes: 77 additions & 73 deletions src/custom/hooks/useApproveCallback/useApproveCallbackMod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -29,7 +30,7 @@ export function useApproveCallback(
closeModals: () => void,
amountToApprove?: CurrencyAmount<Currency>,
spender?: string
): [ApprovalState, () => Promise<void>] {
): [ApprovalState, (optionalParams?: OptionalApproveCallbackParams) => Promise<void>] {
const { account, chainId } = useActiveWeb3React()
const token = amountToApprove?.currency?.isToken ? amountToApprove.currency : undefined
const currentAllowance = useTokenAllowance(token, account ?? undefined, spender)
Expand All @@ -54,81 +55,84 @@ export function useApproveCallback(
const tokenContract = useTokenContract(token?.address)
const addTransaction = useTransactionAdder()

const approve = useCallback(async (): Promise<void> => {
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<void> => {
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]
}
Expand Down
31 changes: 28 additions & 3 deletions src/custom/pages/Claim/ClaimsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
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'
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<HTMLInputElement>) => void
Expand All @@ -18,12 +20,28 @@ type ClaimsTableProps = {
type ClaimsTableRowProps = EnhancedUserClaimData &
Pick<ClaimsTableProps, 'handleSelect'> & {
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,
Expand All @@ -32,7 +50,7 @@ const ClaimsTableRow = ({
selected,
}: ClaimsTableRowProps) => {
return (
<tr key={index}>
<ClaimTr key={index} isPending={isPendingClaim}>
<td>
{' '}
<label className="checkAll">
Expand All @@ -59,7 +77,7 @@ const ClaimsTableRow = ({
</td>
<td>{type === ClaimType.Airdrop ? 'No' : '4 years (linear)'}</td>
<td>28 days, 10h, 50m</td>
</tr>
</ClaimTr>
)
}

Expand All @@ -71,6 +89,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
Expand Down Expand Up @@ -99,7 +118,13 @@ export default function ClaimsTable({
</thead>
<tbody>
{userClaimData.map((claim: EnhancedUserClaimData) => (
<ClaimsTableRow key={claim.index} {...claim} selected={selected} handleSelect={handleSelect} />
<ClaimsTableRow
key={claim.index}
{...claim}
isPendingClaim={pendingClaimsSet.has(claim.index)}
selected={selected}
handleSelect={handleSelect}
/>
))}
</tbody>
</table>
Expand Down
43 changes: 40 additions & 3 deletions src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -107,8 +130,22 @@ export default function InvestOption({ approveData, updateInvestAmount, claim }:
</Row>
</i>
)}
{approveData && approveData.approveState !== ApprovalState.APPROVED && (
<button onClick={approveData.approveCallback}>Approve {currencyAmount?.currency?.symbol}</button>
{/* Approve button - @biocom styles for this found in ./styled > InputSummary > ${ButtonPrimary}*/}
{approveState !== ApprovalState.APPROVED && (
<ButtonConfirmed
buttonSize={ButtonSize.SMALL}
onClick={handleApprove}
disabled={
approving || approveState === ApprovalState.PENDING || approveState !== ApprovalState.NOT_APPROVED
}
altDisabledStyle={approveState === ApprovalState.PENDING} // show solid button while waiting
>
{approving || approveState === ApprovalState.PENDING ? (
<Loader stroke="white" />
) : (
<span>Approve {currencyAmount?.currency?.symbol}</span>
)}
</ButtonConfirmed>
)}
</span>
<span>
Expand Down
6 changes: 4 additions & 2 deletions src/custom/pages/Claim/InvestmentFlow/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 & {
Expand All @@ -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<ClaimCommonTypes, 'hasClaims'> & {
Expand Down
6 changes: 6 additions & 0 deletions src/custom/pages/Claim/styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,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;
}
}
`

Expand Down
6 changes: 2 additions & 4 deletions src/custom/state/claim/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
})

/**
Expand Down Expand Up @@ -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
})
Expand Down
Loading