Skip to content
This repository was 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 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
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
33 changes: 30 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 Down Expand Up @@ -72,7 +90,9 @@ const ClaimsTableRow = ({
Ends in: <b>28 days, 10h, 50m</b>
</span>
</td>
</tr>
<td>{type === ClaimType.Airdrop ? 'No' : '4 years (linear)'}</td>
<td>28 days, 10h, 50m</td>
</ClaimTr>
)
}

Expand All @@ -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
Expand All @@ -109,7 +130,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 @@ -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;
}
}
`

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