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 { Trans } from '@lingui/macro'
import { Fraction, TradeType } from '@uniswap/sdk-core' import { Fraction, TradeType } from '@uniswap/sdk-core'
import { BigNumber } from 'ethers/lib/ethers'
import JSBI from 'jsbi' import JSBI from 'jsbi'
import { nativeOnChain } from '../../constants/tokens' import { nativeOnChain } from '../../constants/tokens'
@ -87,7 +88,11 @@ function SubmitProposalTransactionSummary() {
function ApprovalSummary({ info }: { info: ApproveTransactionInfo }) { function ApprovalSummary({ info }: { info: ApproveTransactionInfo }) {
const token = useToken(info.tokenAddress) 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 }) { function VoteSummary({ info }: { info: VoteTransactionInfo }) {

@ -1,6 +1,6 @@
import { SupportedChainId, Token, TradeType as MockTradeType } from '@uniswap/sdk-core' import { SupportedChainId, Token, TradeType as MockTradeType } from '@uniswap/sdk-core'
import { PERMIT2_ADDRESS } from '@uniswap/universal-router-sdk' 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 { TransactionStatus as MockTxStatus } from 'graphql/data/__generated__/types-and-hooks'
import { ChainTokenMap } from 'hooks/Tokens' import { ChainTokenMap } from 'hooks/Tokens'
import { import {
@ -50,6 +50,7 @@ const mockChainId = SupportedChainId.MAINNET
const mockSpenderAddress = PERMIT2_ADDRESS[mockChainId] const mockSpenderAddress = PERMIT2_ADDRESS[mockChainId]
const mockCurrencyAmountRaw = '1000000000000000000' const mockCurrencyAmountRaw = '1000000000000000000'
const mockCurrencyAmountRawUSDC = '1000000' const mockCurrencyAmountRawUSDC = '1000000'
const mockApprovalAmountRaw = '10000000'
function mockHash(id: string, status: MockTxStatus = MockTxStatus.Confirmed) { function mockHash(id: string, status: MockTxStatus = MockTxStatus.Confirmed) {
return id + status return id + status
@ -93,6 +94,7 @@ const mockTokenAddressMap: ChainTokenMap = {
[mockChainId]: { [mockChainId]: {
[MockDAI.address]: MockDAI, [MockDAI.address]: MockDAI,
[MockUSDC_MAINNET.address]: MockUSDC_MAINNET, [MockUSDC_MAINNET.address]: MockUSDC_MAINNET,
[MockUSDT.address]: MockUSDT,
}, },
} }
@ -142,9 +144,19 @@ jest.mock('../../../../state/transactions/hooks', () => {
type: MockTxType.APPROVAL, type: MockTxType.APPROVAL,
tokenAddress: MockDAI.address, tokenAddress: MockDAI.address,
spender: mockSpenderAddress, spender: mockSpenderAddress,
amount: mockApprovalAmountRaw,
}, },
'0xapproval' '0xapproval'
), ),
...mockMultiStatus(
{
type: MockTxType.APPROVAL,
tokenAddress: MockUSDT.address,
spender: mockSpenderAddress,
amount: '0',
},
'0xrevoke_approval'
),
...mockMultiStatus( ...mockMultiStatus(
{ {
type: MockTxType.WRAP, type: MockTxType.WRAP,
@ -315,7 +327,7 @@ describe('parseLocalActivity', () => {
const account2Activites = renderHook(() => useLocalActivities(mockAccount2)).result.current const account2Activites = renderHook(() => useLocalActivities(mockAccount2)).result.current
expect(Object.values(account1Activites)).toHaveLength(1) 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', () => { 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', () => { it('Adapts Wrap to Activity type', () => {
const hash = mockHash('0xwrap') const hash = mockHash('0xwrap')
const activity = renderHook(() => useLocalActivities(mockAccount2)).result.current[hash] const activity = renderHook(() => useLocalActivities(mockAccount2)).result.current[hash]

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

@ -144,6 +144,11 @@ const AlternateTransactionTitleTable: { [key in TransactionType]?: { [state in T
[TransactionStatus.Confirmed]: t`Unwrapped`, [TransactionStatus.Confirmed]: t`Unwrapped`,
[TransactionStatus.Failed]: t`Unwrap failed`, [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) { export function getActivityTitle(type: TransactionType, status: TransactionStatus, alternate?: boolean) {

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

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

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

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

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