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 {