Skip to content

Commit 9a92b50

Browse files
authored
fix: usdt approval reset on supply and repay (#2560)
1 parent f2bd4d0 commit 9a92b50

File tree

13 files changed

+181
-23
lines changed

13 files changed

+181
-23
lines changed

src/components/transactions/Repay/RepayActions.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ export interface RepayActionProps extends BoxProps {
3030
repayWithATokens: boolean;
3131
blocked?: boolean;
3232
maxApproveNeeded: string;
33+
setShowUSDTResetWarning?: (showUSDTResetWarning: boolean) => void;
34+
chainId?: number;
3335
maxAmountToRepay: string;
3436
}
3537

@@ -43,6 +45,8 @@ export const RepayActions = ({
4345
repayWithATokens,
4446
blocked,
4547
maxApproveNeeded,
48+
setShowUSDTResetWarning,
49+
chainId,
4650
maxAmountToRepay,
4751
...props
4852
}: RepayActionProps) => {
@@ -115,7 +119,7 @@ export const RepayActions = ({
115119
setApprovalTxState({});
116120
}
117121

118-
const { approval } = useApprovalTx({
122+
const { approval, requiresApprovalReset } = useApprovalTx({
119123
usePermit,
120124
approvedAmount,
121125
requiresApproval,
@@ -125,6 +129,8 @@ export const RepayActions = ({
125129
signatureAmount: amountToRepay,
126130
onApprovalTxConfirmed: fetchApprovedAmount,
127131
onSignTxCompleted: (signedParams) => setSignatureParams(signedParams),
132+
chainId,
133+
setShowUSDTResetWarning,
128134
});
129135

130136
useEffect(() => {
@@ -250,6 +256,7 @@ export const RepayActions = ({
250256
actionText={<Trans>Repay {symbol}</Trans>}
251257
actionInProgressText={<Trans>Repaying {symbol}</Trans>}
252258
tryPermit={permitAvailable}
259+
requiresApprovalReset={requiresApprovalReset}
253260
/>
254261
);
255262
};

src/components/transactions/Repay/RepayModalContent.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Trans } from '@lingui/macro';
99
import Typography from '@mui/material/Typography';
1010
import { BigNumber } from 'bignumber.js';
1111
import React, { useEffect, useRef, useState } from 'react';
12+
import { Warning } from 'src/components/primitives/Warning';
1213
import {
1314
ExtendedFormattedUser,
1415
useAppDataContext,
@@ -67,6 +68,7 @@ export const RepayModalContent = ({
6768
const [repayMax, setRepayMax] = useState('');
6869
const [_amount, setAmount] = useState('');
6970
const amountRef = useRef<string>();
71+
const [showUSDTResetWarning, setShowUSDTResetWarning] = useState(false);
7072

7173
const networkConfig = getNetworkConfig(currentChainId);
7274

@@ -279,6 +281,17 @@ export const RepayModalContent = ({
279281

280282
{txError && <GasEstimationError txError={txError} />}
281283

284+
{showUSDTResetWarning && (
285+
<Warning severity="info" sx={{ mt: 5 }}>
286+
<Typography variant="caption">
287+
<Trans>
288+
USDT on Ethereum requires approval reset before a new approval. This will require an
289+
additional transaction.
290+
</Trans>
291+
</Typography>
292+
</Warning>
293+
)}
294+
282295
<RepayActions
283296
maxApproveNeeded={safeAmountToRepayAll.toString()}
284297
poolReserve={poolReserve}
@@ -289,6 +302,8 @@ export const RepayModalContent = ({
289302
isWrongNetwork={isWrongNetwork}
290303
symbol={modalSymbol}
291304
repayWithATokens={repayWithATokens}
305+
setShowUSDTResetWarning={setShowUSDTResetWarning}
306+
chainId={currentChainId}
292307
maxAmountToRepay={maxAmountToRepay.toString(10)}
293308
/>
294309
</>

src/components/transactions/Supply/SupplyActions.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ export interface SupplyActionProps extends BoxProps {
2929
blocked: boolean;
3030
decimals: number;
3131
isWrappedBaseAsset: boolean;
32+
setShowUSDTResetWarning?: (showUSDTResetWarning: boolean) => void;
33+
chainId?: number;
3234
}
3335

3436
export const SupplyActions = React.memo(
@@ -41,6 +43,8 @@ export const SupplyActions = React.memo(
4143
blocked,
4244
decimals,
4345
isWrappedBaseAsset,
46+
setShowUSDTResetWarning,
47+
chainId,
4448
...props
4549
}: SupplyActionProps) => {
4650
const [
@@ -104,7 +108,7 @@ export const SupplyActions = React.memo(
104108

105109
const usePermit = permitAvailable && walletApprovalMethodPreference === ApprovalMethod.PERMIT;
106110

107-
const { approval } = useApprovalTx({
111+
const { approval, requiresApprovalReset } = useApprovalTx({
108112
usePermit,
109113
approvedAmount,
110114
requiresApproval,
@@ -114,6 +118,8 @@ export const SupplyActions = React.memo(
114118
signatureAmount: amountToSupply,
115119
onApprovalTxConfirmed: fetchApprovedAmount,
116120
onSignTxCompleted: (signedParams) => setSignatureParams(signedParams),
121+
chainId,
122+
setShowUSDTResetWarning,
117123
});
118124

119125
useEffect(() => {
@@ -221,6 +227,7 @@ export const SupplyActions = React.memo(
221227
tryPermit={permitAvailable}
222228
sx={sx}
223229
{...props}
230+
requiresApprovalReset={requiresApprovalReset}
224231
/>
225232
);
226233
}

src/components/transactions/Supply/SupplyModalContent.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
import { useAssetCaps } from 'src/hooks/useAssetCaps';
2020
import { useModalContext } from 'src/hooks/useModal';
2121
import { useWrappedTokens, WrappedTokenConfig } from 'src/hooks/useWrappedTokens';
22+
import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
2223
import { ERC20TokenType } from 'src/libs/web3-data-provider/Web3Provider';
2324
import { useRootStore } from 'src/store/root';
2425
import { GENERAL } from 'src/utils/events';
@@ -146,6 +147,7 @@ export const SupplyModalContent = React.memo(
146147
}: SupplyModalContentProps) => {
147148
const { marketReferencePriceInUsd } = useAppDataContext();
148149
const { mainTxState: supplyTxState, gasLimit, txError } = useModalContext();
150+
const { chainId } = useWeb3Context();
149151
const [minRemainingBaseTokenBalance, currentMarketData, currentNetworkConfig] = useRootStore(
150152
useShallow((state) => [
151153
state.poolComputed.minRemainingBaseTokenBalance,
@@ -156,6 +158,7 @@ export const SupplyModalContent = React.memo(
156158

157159
// states
158160
const [amount, setAmount] = useState('');
161+
const [showUSDTResetWarning, setShowUSDTResetWarning] = useState(false);
159162
const supplyUnWrapped = underlyingAsset.toLowerCase() === API_ETH_MOCK_ADDRESS.toLowerCase();
160163

161164
const walletBalance = supplyUnWrapped ? nativeBalance : tokenBalance;
@@ -201,6 +204,8 @@ export const SupplyModalContent = React.memo(
201204
blocked: false,
202205
decimals: poolReserve.decimals,
203206
isWrappedBaseAsset: poolReserve.isWrappedBaseAsset,
207+
setShowUSDTResetWarning,
208+
chainId,
204209
};
205210

206211
if (supplyTxState.success)
@@ -272,6 +277,17 @@ export const SupplyModalContent = React.memo(
272277

273278
{txError && <GasEstimationError txError={txError} />}
274279

280+
{showUSDTResetWarning && (
281+
<Warning severity="info" sx={{ mt: 5 }}>
282+
<Typography variant="caption">
283+
<Trans>
284+
USDT on Ethereum requires approval reset before a new approval. This will require an
285+
additional transaction.
286+
</Trans>
287+
</Typography>
288+
</Warning>
289+
)}
290+
275291
<SupplyActions {...supplyActionsProps} />
276292
</>
277293
);

src/components/transactions/Switch/SwitchActions.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,7 @@ export const SwitchActions = ({
153153
0
154154
);
155155

156-
if (
157-
needsUSDTApprovalReset(inputSymbol, chainId, BigInt(currentApproved), BigInt(amountToApprove))
158-
) {
156+
if (needsUSDTApprovalReset(inputSymbol, chainId, currentApproved, amountToApprove)) {
159157
setShowUSDTResetWarning(true);
160158
setRequiresApprovalReset(true);
161159
} else {

src/components/transactions/TxActionsWrapper.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ interface TxActionsWrapperProps extends BoxProps {
2222
preparingTransactions: boolean;
2323
requiresAmount?: boolean;
2424
requiresApproval: boolean;
25+
requiresApprovalReset?: boolean;
2526
symbol?: string;
2627
blocked?: boolean;
2728
fetchingData?: boolean;
@@ -47,6 +48,7 @@ export const TxActionsWrapper = ({
4748
preparingTransactions,
4849
requiresAmount,
4950
requiresApproval,
51+
requiresApprovalReset,
5052
sx,
5153
symbol,
5254
blocked,
@@ -95,7 +97,15 @@ export const TxActionsWrapper = ({
9597
)
9698
return null;
9799
if (approvalTxState?.loading)
98-
return { loading: true, disabled: true, content: <Trans>Approving {symbol}...</Trans> };
100+
return {
101+
loading: true,
102+
disabled: true,
103+
content: (
104+
<Trans>
105+
{requiresApprovalReset ? 'Resetting' : 'Approving'} {symbol}...
106+
</Trans>
107+
),
108+
};
99109
if (approvalTxState?.success)
100110
return {
101111
disabled: true,
@@ -116,7 +126,11 @@ export const TxActionsWrapper = ({
116126
iconSize={20}
117127
iconMargin={2}
118128
color="white"
119-
text={<Trans>Approve {symbol} to continue</Trans>}
129+
text={
130+
<Trans>
131+
{requiresApprovalReset ? 'Reset' : 'Approve'} {symbol} to continue
132+
</Trans>
133+
}
120134
/>
121135
),
122136
handleClick: handleApproval,

src/hooks/useApprovalTx.tsx

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { ApproveType, MAX_UINT_AMOUNT, ProtocolAction } from '@aave/contract-helpers';
22
import { SignatureLike } from '@ethersproject/bytes';
3-
import { constants } from 'ethers';
3+
import { constants, ethers } from 'ethers';
44
import { parseUnits } from 'ethers/lib/utils';
5+
import { useEffect, useState } from 'react';
56
import { MOCK_SIGNED_HASH } from 'src/helpers/useTransactionHandler';
67
import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
78
import { useRootStore } from 'src/store/root';
89
import { getErrorTextFromError, TxAction } from 'src/ui-config/errorMapping';
10+
import { isUSDTOnEthereum, needsUSDTApprovalReset } from 'src/utils/usdtHelpers';
911
import { useShallow } from 'zustand/shallow';
1012

1113
import { useModalContext } from './useModal';
@@ -28,6 +30,7 @@ export const useApprovalTx = ({
2830
onSignTxCompleted,
2931
chainId,
3032
amountToApprove,
33+
setShowUSDTResetWarning,
3134
}: {
3235
usePermit: boolean;
3336
approvedAmount: ApproveType | undefined;
@@ -40,6 +43,7 @@ export const useApprovalTx = ({
4043
onSignTxCompleted?: (signedParams: SignedParams) => void;
4144
chainId?: number;
4245
amountToApprove?: string;
46+
setShowUSDTResetWarning?: (showUSDTResetWarning: boolean) => void;
4347
}) => {
4448
const [generateApproval, generateSignatureRequest, estimateGasLimit, addTransaction] =
4549
useRootStore(
@@ -55,9 +59,100 @@ export const useApprovalTx = ({
5559

5660
const { approvalTxState, setApprovalTxState, setTxError } = useModalContext();
5761

62+
const [requiresApprovalReset, setRequiresApprovalReset] = useState(false);
63+
64+
// Warning for USDT on Ethereum approval reset
65+
useEffect(() => {
66+
if (
67+
!chainId ||
68+
!isUSDTOnEthereum(symbol, chainId) ||
69+
!setShowUSDTResetWarning ||
70+
!signatureAmount ||
71+
signatureAmount === '0'
72+
) {
73+
return;
74+
}
75+
76+
const amountToApprove = parseUnits(signatureAmount, decimals).toString();
77+
const currentApproved = parseUnits(
78+
approvedAmount?.amount?.toString() || '0',
79+
decimals
80+
).toString();
81+
82+
if (needsUSDTApprovalReset(symbol, chainId, currentApproved, amountToApprove)) {
83+
setShowUSDTResetWarning(true);
84+
setRequiresApprovalReset(true);
85+
} else {
86+
setShowUSDTResetWarning(false);
87+
setRequiresApprovalReset(false);
88+
}
89+
}, [symbol, chainId, approvedAmount?.amount, signatureAmount, setShowUSDTResetWarning, decimals]);
90+
5891
const approval = async () => {
5992
try {
6093
if (requiresApproval && approvedAmount) {
94+
// Handle USDT approval reset first
95+
if (requiresApprovalReset) {
96+
const resetData = {
97+
spender: approvedAmount.spender,
98+
user: approvedAmount.user,
99+
token: approvedAmount.token,
100+
amount: '0',
101+
};
102+
103+
try {
104+
if (usePermit) {
105+
const deadline = Math.floor(Date.now() / 1000 + 3600).toString();
106+
const signatureRequest = await generateSignatureRequest(
107+
{
108+
...resetData,
109+
deadline,
110+
},
111+
{ chainId }
112+
);
113+
setApprovalTxState({ ...approvalTxState, loading: true });
114+
await signTxData(signatureRequest);
115+
setApprovalTxState({
116+
loading: false,
117+
success: false,
118+
});
119+
} else {
120+
// Create direct ERC20 approval transaction for reset to 0
121+
const abi = new ethers.utils.Interface([
122+
'function approve(address spender, uint256 amount)',
123+
]);
124+
const encodedData = abi.encodeFunctionData('approve', [approvedAmount.spender, '0']);
125+
const resetTx = {
126+
data: encodedData,
127+
to: approvedAmount.token,
128+
from: approvedAmount.user,
129+
};
130+
const resetTxWithGasEstimation = await estimateGasLimit(resetTx, chainId);
131+
setApprovalTxState({ ...approvalTxState, loading: true });
132+
const resetResponse = await sendTx(resetTxWithGasEstimation);
133+
await resetResponse.wait(1);
134+
setApprovalTxState({
135+
loading: false,
136+
success: false,
137+
});
138+
}
139+
} catch (error) {
140+
const parsedError = getErrorTextFromError(error, TxAction.APPROVAL, false);
141+
console.error(parsedError);
142+
setTxError(parsedError);
143+
setApprovalTxState({
144+
txHash: undefined,
145+
loading: false,
146+
});
147+
}
148+
if (onApprovalTxConfirmed) {
149+
onApprovalTxConfirmed();
150+
}
151+
152+
return;
153+
}
154+
155+
// Normal approval logic
61156
if (usePermit) {
62157
setApprovalTxState({ ...approvalTxState, loading: true });
63158
const deadline = Math.floor(Date.now() / 1000 + 3600).toString();
@@ -119,5 +214,6 @@ export const useApprovalTx = ({
119214

120215
return {
121216
approval,
217+
requiresApprovalReset,
122218
};
123219
};

src/locales/el/messages.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/locales/en/messages.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)