From 8b16f454caf6eeeb2546972e11f434cf1f951afe Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Tue, 13 Jun 2023 13:55:45 -0700 Subject: [PATCH] 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 --- .../AccountDetails/TransactionSummary.tsx | 7 +++- .../MiniPortfolio/Activity/parseLocal.test.ts | 33 +++++++++++++++++-- .../MiniPortfolio/Activity/parseLocal.ts | 12 +++++-- .../AccountDrawer/MiniPortfolio/constants.ts | 5 +++ src/hooks/useApproveCallback.ts | 9 +++-- src/hooks/useTokenAllowance.ts | 4 +-- src/lib/hooks/useApproval.ts | 6 +++- src/state/transactions/reducer.test.ts | 12 ++++--- src/state/transactions/types.ts | 1 + 9 files changed, 73 insertions(+), 16 deletions(-) diff --git a/src/components/AccountDetails/TransactionSummary.tsx b/src/components/AccountDetails/TransactionSummary.tsx index 924329f3b8..c556370922 100644 --- a/src/components/AccountDetails/TransactionSummary.tsx +++ b/src/components/AccountDetails/TransactionSummary.tsx @@ -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 Approve {token?.symbol} + return BigNumber.from(info.amount)?.eq(0) ? ( + Revoke {token?.symbol} + ) : ( + Approve {token?.symbol} + ) } function VoteSummary({ info }: { info: VoteTransactionInfo }) { diff --git a/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.test.ts b/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.test.ts index e16383bbd8..8b5fe063e7 100644 --- a/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.test.ts +++ b/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.test.ts @@ -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] diff --git a/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.ts b/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.ts index ab6cd54ece..cde68aff0e 100644 --- a/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.ts +++ b/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.ts @@ -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 { - // 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 ( diff --git a/src/components/AccountDrawer/MiniPortfolio/constants.ts b/src/components/AccountDrawer/MiniPortfolio/constants.ts index 6c3bebc475..ffe1fad676 100644 --- a/src/components/AccountDrawer/MiniPortfolio/constants.ts +++ b/src/components/AccountDrawer/MiniPortfolio/constants.ts @@ -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) { diff --git a/src/hooks/useApproveCallback.ts b/src/hooks/useApproveCallback.ts index c5377455c2..4c847d5475 100644 --- a/src/hooks/useApproveCallback.ts +++ b/src/hooks/useApproveCallback.ts @@ -11,8 +11,13 @@ function useGetAndTrackApproval(getApproval: ReturnType[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]) diff --git a/src/hooks/useTokenAllowance.ts b/src/hooks/useTokenAllowance.ts index 1ade2aec12..23e8b0c2af 100644 --- a/src/hooks/useTokenAllowance.ts +++ b/src/hooks/useTokenAllowance.ts @@ -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) { diff --git a/src/lib/hooks/useApproval.ts b/src/lib/hooks/useApproval.ts index 133547ff2b..c90f03ca77 100644 --- a/src/lib/hooks/useApproval.ts +++ b/src/lib/hooks/useApproval.ts @@ -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 } + | 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) => { diff --git a/src/state/transactions/reducer.test.ts b/src/state/transactions/reducer.test.ts index 9c5db374b5..804bbd9d74 100644 --- a/src/state/transactions/reducer.test.ts +++ b/src/state/transactions/reducer.test.ts @@ -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', }) ) diff --git a/src/state/transactions/types.ts b/src/state/transactions/types.ts index 92c1fa94e5..2aba7f89cf 100644 --- a/src/state/transactions/types.ts +++ b/src/state/transactions/types.ts @@ -79,6 +79,7 @@ export interface ApproveTransactionInfo extends BaseTransactionInfo { type: TransactionType.APPROVAL tokenAddress: string spender: string + amount: string } interface BaseSwapTransactionInfo extends BaseTransactionInfo {