feat: add amount to local copy of Approval transactions (#6726)

* feat: add amount to local copy of Approval transactions

* fix: use alternate

* fix: type problem

* feat: add test
This commit is contained in:
eddie 2023-06-13 13:55:45 -07:00 committed by GitHub
parent 094664dc7a
commit 8b16f454ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 73 additions and 16 deletions

@ -1,5 +1,6 @@
import { Trans } from '@lingui/macro'
import { Fraction, TradeType } from '@uniswap/sdk-core'
import { BigNumber } from 'ethers/lib/ethers'
import JSBI from 'jsbi'
import { nativeOnChain } from '../../constants/tokens'
@ -87,7 +88,11 @@ function SubmitProposalTransactionSummary() {
function ApprovalSummary({ info }: { info: ApproveTransactionInfo }) {
const token = useToken(info.tokenAddress)
return <Trans>Approve {token?.symbol}</Trans>
return BigNumber.from(info.amount)?.eq(0) ? (
<Trans>Revoke {token?.symbol}</Trans>
) : (
<Trans>Approve {token?.symbol}</Trans>
)
}
function VoteSummary({ info }: { info: VoteTransactionInfo }) {

@ -1,6 +1,6 @@
import { SupportedChainId, Token, TradeType as MockTradeType } from '@uniswap/sdk-core'
import { PERMIT2_ADDRESS } from '@uniswap/universal-router-sdk'
import { DAI as MockDAI, nativeOnChain, USDC_MAINNET as MockUSDC_MAINNET } from 'constants/tokens'
import { DAI as MockDAI, nativeOnChain, USDC_MAINNET as MockUSDC_MAINNET, USDT as MockUSDT } from 'constants/tokens'
import { TransactionStatus as MockTxStatus } from 'graphql/data/__generated__/types-and-hooks'
import { ChainTokenMap } from 'hooks/Tokens'
import {
@ -50,6 +50,7 @@ const mockChainId = SupportedChainId.MAINNET
const mockSpenderAddress = PERMIT2_ADDRESS[mockChainId]
const mockCurrencyAmountRaw = '1000000000000000000'
const mockCurrencyAmountRawUSDC = '1000000'
const mockApprovalAmountRaw = '10000000'
function mockHash(id: string, status: MockTxStatus = MockTxStatus.Confirmed) {
return id + status
@ -93,6 +94,7 @@ const mockTokenAddressMap: ChainTokenMap = {
[mockChainId]: {
[MockDAI.address]: MockDAI,
[MockUSDC_MAINNET.address]: MockUSDC_MAINNET,
[MockUSDT.address]: MockUSDT,
},
}
@ -142,9 +144,19 @@ jest.mock('../../../../state/transactions/hooks', () => {
type: MockTxType.APPROVAL,
tokenAddress: MockDAI.address,
spender: mockSpenderAddress,
amount: mockApprovalAmountRaw,
},
'0xapproval'
),
...mockMultiStatus(
{
type: MockTxType.APPROVAL,
tokenAddress: MockUSDT.address,
spender: mockSpenderAddress,
amount: '0',
},
'0xrevoke_approval'
),
...mockMultiStatus(
{
type: MockTxType.WRAP,
@ -315,7 +327,7 @@ describe('parseLocalActivity', () => {
const account2Activites = renderHook(() => useLocalActivities(mockAccount2)).result.current
expect(Object.values(account1Activites)).toHaveLength(1)
expect(Object.values(account2Activites)).toHaveLength(30)
expect(Object.values(account2Activites)).toHaveLength(33)
})
it('Properly uses correct tense of activity title based on tx status', () => {
@ -380,6 +392,23 @@ describe('parseLocalActivity', () => {
})
})
it('Adapts Revoke Approval to Activity type', () => {
const hash = mockHash('0xrevoke_approval')
const activity = renderHook(() => useLocalActivities(mockAccount2)).result.current[hash]
expect(activity).toMatchObject({
chainId: mockChainId,
currencies: [MockUSDT],
title: 'Revoked approval',
descriptor: MockUSDT.symbol,
hash,
status: MockTxStatus.Confirmed,
receipt: {
id: hash,
status: MockTxStatus.Confirmed,
},
})
})
it('Adapts Wrap to Activity type', () => {
const hash = mockHash('0xwrap')
const activity = renderHook(() => useLocalActivities(mockAccount2)).result.current[hash]

@ -1,3 +1,4 @@
import { BigNumber } from '@ethersproject/bignumber'
import { t } from '@lingui/macro'
import { formatCurrencyAmount } from '@uniswap/conedison/format'
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
@ -76,12 +77,17 @@ function parseWrap(wrap: WrapTransactionInfo, chainId: SupportedChainId, status:
function parseApproval(
approval: ApproveTransactionInfo,
chainId: SupportedChainId,
tokens: ChainTokenMap
tokens: ChainTokenMap,
status: TransactionStatus
): Partial<Activity> {
// TODO: Add 'amount' approved to ApproveTransactionInfo so we can distinguish between revoke and approve
const currency = getCurrency(approval.tokenAddress, chainId, tokens)
const descriptor = currency?.symbol ?? currency?.name ?? t`Unknown`
return {
title: getActivityTitle(
TransactionType.APPROVAL,
status,
BigNumber.from(approval.amount).eq(0) /* use alternate if it's a revoke */
),
descriptor,
currencies: [currency],
}
@ -165,7 +171,7 @@ export function parseLocalActivity(
if (info.type === TransactionType.SWAP) {
additionalFields = parseSwap(info, chainId, tokens)
} else if (info.type === TransactionType.APPROVAL) {
additionalFields = parseApproval(info, chainId, tokens)
additionalFields = parseApproval(info, chainId, tokens, status)
} else if (info.type === TransactionType.WRAP) {
additionalFields = parseWrap(info, chainId, status)
} else if (

@ -144,6 +144,11 @@ const AlternateTransactionTitleTable: { [key in TransactionType]?: { [state in T
[TransactionStatus.Confirmed]: t`Unwrapped`,
[TransactionStatus.Failed]: t`Unwrap failed`,
},
[TransactionType.APPROVAL]: {
[TransactionStatus.Pending]: t`Revoking approval`,
[TransactionStatus.Confirmed]: t`Revoked approval`,
[TransactionStatus.Failed]: t`Revoke approval failed`,
},
}
export function getActivityTitle(type: TransactionType, status: TransactionStatus, alternate?: boolean) {

@ -11,8 +11,13 @@ function useGetAndTrackApproval(getApproval: ReturnType<typeof useApproval>[1])
return useCallback(() => {
return getApproval().then((pending) => {
if (pending) {
const { response, tokenAddress, spenderAddress: spender } = pending
addTransaction(response, { type: TransactionType.APPROVAL, tokenAddress, spender })
const { response, tokenAddress, spenderAddress: spender, amount } = pending
addTransaction(response, {
type: TransactionType.APPROVAL,
tokenAddress,
spender,
amount: amount.quotient.toString(),
})
}
})
}, [addTransaction, getApproval])

@ -1,4 +1,3 @@
import { BigNumberish } from '@ethersproject/bignumber'
import { ContractTransaction } from '@ethersproject/contracts'
import { CurrencyAmount, MaxUint256, Token } from '@uniswap/sdk-core'
import { useTokenContract } from 'hooks/useContract'
@ -49,7 +48,7 @@ export function useUpdateTokenAllowance(
if (!contract) throw new Error('missing contract')
if (!spender) throw new Error('missing spender')
const allowance: BigNumberish = MaxUint256.toString()
const allowance = MaxUint256.toString()
const response = await contract.approve(spender, allowance)
return {
response,
@ -57,6 +56,7 @@ export function useUpdateTokenAllowance(
type: TransactionType.APPROVAL,
tokenAddress: contract.address,
spender,
amount: allowance,
},
}
} catch (e: unknown) {

@ -49,7 +49,10 @@ export function useApproval(
useIsPendingApproval: (token?: Token, spender?: string) => boolean
): [
ApprovalState,
() => Promise<{ response: TransactionResponse; tokenAddress: string; spenderAddress: string } | undefined>
() => Promise<
| { response: TransactionResponse; tokenAddress: string; spenderAddress: string; amount: CurrencyAmount<Currency> }
| undefined
>
] {
const { chainId } = useWeb3React()
const token = amountToApprove?.currency?.isToken ? amountToApprove.currency : undefined
@ -102,6 +105,7 @@ export function useApproval(
response,
tokenAddress: token.address,
spenderAddress: spender,
amount: amountToApprove,
}
})
.catch((error: Error) => {

@ -58,6 +58,7 @@ describe('transaction reducer', () => {
type: TransactionType.APPROVAL,
tokenAddress: 'abc',
spender: 'def',
amount: '10000',
},
})
)
@ -73,6 +74,7 @@ describe('transaction reducer', () => {
type: TransactionType.APPROVAL,
tokenAddress: 'abc',
spender: 'def',
amount: '10000',
})
})
})
@ -103,7 +105,7 @@ describe('transaction reducer', () => {
hash: '0x0',
chainId: SupportedChainId.MAINNET,
nonce: 2,
info: { type: TransactionType.APPROVAL, spender: '0x0', tokenAddress: '0x0' },
info: { type: TransactionType.APPROVAL, spender: '0x0', tokenAddress: '0x0', amount: '10000' },
from: '0x0',
})
)
@ -156,7 +158,7 @@ describe('transaction reducer', () => {
hash: '0x0',
chainId: SupportedChainId.MAINNET,
nonce: 3,
info: { type: TransactionType.APPROVAL, spender: '0x0', tokenAddress: '0x0' },
info: { type: TransactionType.APPROVAL, spender: '0x0', tokenAddress: '0x0', amount: '10000' },
from: '0x0',
})
)
@ -176,7 +178,7 @@ describe('transaction reducer', () => {
hash: '0x0',
chainId: SupportedChainId.MAINNET,
nonce: 4,
info: { type: TransactionType.APPROVAL, spender: '0x0', tokenAddress: '0x0' },
info: { type: TransactionType.APPROVAL, spender: '0x0', tokenAddress: '0x0', amount: '10000' },
from: '0x0',
})
)
@ -206,7 +208,7 @@ describe('transaction reducer', () => {
chainId: SupportedChainId.MAINNET,
hash: '0x0',
nonce: 5,
info: { type: TransactionType.APPROVAL, spender: 'abc', tokenAddress: 'def' },
info: { type: TransactionType.APPROVAL, spender: 'abc', tokenAddress: 'def', amount: '10000' },
from: 'abc',
})
)
@ -215,7 +217,7 @@ describe('transaction reducer', () => {
chainId: SupportedChainId.OPTIMISM,
nonce: 6,
hash: '0x1',
info: { type: TransactionType.APPROVAL, spender: 'abc', tokenAddress: 'def' },
info: { type: TransactionType.APPROVAL, spender: 'abc', tokenAddress: 'def', amount: '10000' },
from: 'abc',
})
)

@ -79,6 +79,7 @@ export interface ApproveTransactionInfo extends BaseTransactionInfo {
type: TransactionType.APPROVAL
tokenAddress: string
spender: string
amount: string
}
interface BaseSwapTransactionInfo extends BaseTransactionInfo {