/ src / components / transactions / Repay / RepayActions.tsx
RepayActions.tsx
  1  import { gasLimitRecommendations, InterestRate, ProtocolAction } from '@aave/contract-helpers';
  2  import { TransactionResponse } from '@ethersproject/providers';
  3  import { Trans } from '@lingui/macro';
  4  import { BoxProps } from '@mui/material';
  5  import { useQueryClient } from '@tanstack/react-query';
  6  import { parseUnits } from 'ethers/lib/utils';
  7  import { useEffect, useState } from 'react';
  8  import { ComputedReserveData } from 'src/hooks/app-data-provider/useAppDataProvider';
  9  import { SignedParams, useApprovalTx } from 'src/hooks/useApprovalTx';
 10  import { usePoolApprovedAmount } from 'src/hooks/useApprovedAmount';
 11  import { useModalContext } from 'src/hooks/useModal';
 12  import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
 13  import { useRootStore } from 'src/store/root';
 14  import { ApprovalMethod } from 'src/store/walletSlice';
 15  import { getErrorTextFromError, TxAction } from 'src/ui-config/errorMapping';
 16  import { queryKeysFactory } from 'src/ui-config/queries';
 17  import { useShallow } from 'zustand/shallow';
 18  
 19  import { TxActionsWrapper } from '../TxActionsWrapper';
 20  import { APPROVAL_GAS_LIMIT, checkRequiresApproval } from '../utils';
 21  
 22  export interface RepayActionProps extends BoxProps {
 23    amountToRepay: string;
 24    poolReserve: ComputedReserveData;
 25    isWrongNetwork: boolean;
 26    customGasPrice?: string;
 27    poolAddress: string;
 28    symbol: string;
 29    repayWithATokens: boolean;
 30    blocked?: boolean;
 31    maxApproveNeeded: string;
 32  }
 33  
 34  export const RepayActions = ({
 35    amountToRepay,
 36    poolReserve,
 37    poolAddress,
 38    isWrongNetwork,
 39    sx,
 40    symbol,
 41    repayWithATokens,
 42    blocked,
 43    maxApproveNeeded,
 44    ...props
 45  }: RepayActionProps) => {
 46    const [
 47      repay,
 48      repayWithPermit,
 49      encodeRepayParams,
 50      encodeRepayWithPermit,
 51      tryPermit,
 52      walletApprovalMethodPreference,
 53      estimateGasLimit,
 54      addTransaction,
 55      optimizedPath,
 56      currentMarketData,
 57    ] = useRootStore(
 58      useShallow((store) => [
 59        store.repay,
 60        store.repayWithPermit,
 61        store.encodeRepayParams,
 62        store.encodeRepayWithPermitParams,
 63        store.tryPermit,
 64        store.walletApprovalMethodPreference,
 65        store.estimateGasLimit,
 66        store.addTransaction,
 67        store.useOptimizedPath,
 68        store.currentMarketData,
 69      ])
 70    );
 71    const { sendTx } = useWeb3Context();
 72    const queryClient = useQueryClient();
 73    const [signatureParams, setSignatureParams] = useState<SignedParams | undefined>();
 74    const {
 75      approvalTxState,
 76      mainTxState,
 77      loadingTxns,
 78      setMainTxState,
 79      setTxError,
 80      setGasLimit,
 81      setLoadingTxns,
 82      setApprovalTxState,
 83    } = useModalContext();
 84  
 85    const {
 86      data: approvedAmount,
 87      refetch: fetchApprovedAmount,
 88      isFetching: fetchingApprovedAmount,
 89      isFetchedAfterMount,
 90    } = usePoolApprovedAmount(currentMarketData, poolAddress);
 91  
 92    const permitAvailable = tryPermit({
 93      reserveAddress: poolAddress,
 94      isWrappedBaseAsset: poolReserve.isWrappedBaseAsset,
 95    });
 96    const usePermit = permitAvailable && walletApprovalMethodPreference === ApprovalMethod.PERMIT;
 97  
 98    setLoadingTxns(fetchingApprovedAmount);
 99  
100    const requiresApproval =
101      !repayWithATokens &&
102      Number(amountToRepay) !== 0 &&
103      checkRequiresApproval({
104        approvedAmount: approvedAmount?.amount || '0',
105        amount: Number(amountToRepay) === -1 ? maxApproveNeeded : amountToRepay,
106        signedAmount: signatureParams ? signatureParams.amount : '0',
107      });
108  
109    if (requiresApproval && approvalTxState?.success) {
110      // There was a successful approval tx, but the approval amount is not enough.
111      // Clear the state to prompt for another approval.
112      setApprovalTxState({});
113    }
114  
115    const { approval } = useApprovalTx({
116      usePermit,
117      approvedAmount,
118      requiresApproval,
119      assetAddress: poolAddress,
120      symbol,
121      decimals: poolReserve.decimals,
122      signatureAmount: amountToRepay,
123      onApprovalTxConfirmed: fetchApprovedAmount,
124      onSignTxCompleted: (signedParams) => setSignatureParams(signedParams),
125    });
126  
127    useEffect(() => {
128      if (!isFetchedAfterMount && !repayWithATokens) {
129        fetchApprovedAmount();
130      }
131    }, [fetchApprovedAmount, isFetchedAfterMount, repayWithATokens]);
132  
133    const action = async () => {
134      try {
135        setMainTxState({ ...mainTxState, loading: true });
136  
137        let response: TransactionResponse;
138        let action = ProtocolAction.default;
139  
140        if (usePermit && signatureParams) {
141          const repayWithPermitParams = {
142            amount:
143              amountToRepay === '-1'
144                ? amountToRepay
145                : parseUnits(amountToRepay, poolReserve.decimals).toString(),
146            reserve: poolAddress,
147            interestRateMode: InterestRate.Variable,
148            signature: signatureParams.signature,
149            deadline: signatureParams.deadline,
150          };
151  
152          let encodedParams: [string, string, string] | undefined;
153          if (optimizedPath()) {
154            encodedParams = await encodeRepayWithPermit(repayWithPermitParams);
155          }
156  
157          action = ProtocolAction.repayWithPermit;
158          let signedRepayWithPermitTxData = repayWithPermit({
159            ...repayWithPermitParams,
160            encodedTxData: encodedParams ? encodedParams[0] : undefined,
161          });
162  
163          signedRepayWithPermitTxData = await estimateGasLimit(signedRepayWithPermitTxData);
164          response = await sendTx(signedRepayWithPermitTxData);
165          await response.wait(1);
166        } else {
167          const repayParams = {
168            amountToRepay:
169              amountToRepay === '-1'
170                ? amountToRepay
171                : parseUnits(amountToRepay, poolReserve.decimals).toString(),
172            poolAddress,
173            repayWithATokens,
174            debtType: InterestRate.Variable,
175          };
176  
177          let encodedParams: string | undefined;
178          if (optimizedPath()) {
179            encodedParams = await encodeRepayParams(repayParams);
180          }
181  
182          action = ProtocolAction.repay;
183          let repayTxData = repay({
184            ...repayParams,
185            encodedTxData: encodedParams,
186          });
187          repayTxData = await estimateGasLimit(repayTxData);
188          response = await sendTx(repayTxData);
189          await response.wait(1);
190        }
191        setMainTxState({
192          txHash: response.hash,
193          loading: false,
194          success: true,
195        });
196        addTransaction(response.hash, {
197          action,
198          txState: 'success',
199          asset: poolAddress,
200          amount: amountToRepay,
201          assetName: symbol,
202        });
203  
204        queryClient.invalidateQueries({ queryKey: queryKeysFactory.pool });
205        queryClient.invalidateQueries({ queryKey: queryKeysFactory.gho });
206      } catch (error) {
207        const parsedError = getErrorTextFromError(error, TxAction.GAS_ESTIMATION, false);
208        setTxError(parsedError);
209        setMainTxState({
210          txHash: undefined,
211          loading: false,
212        });
213      }
214    };
215  
216    useEffect(() => {
217      let supplyGasLimit = 0;
218      if (usePermit) {
219        supplyGasLimit = Number(gasLimitRecommendations[ProtocolAction.supplyWithPermit].recommended);
220      } else {
221        supplyGasLimit = Number(gasLimitRecommendations[ProtocolAction.supply].recommended);
222        if (requiresApproval && !approvalTxState.success) {
223          supplyGasLimit += Number(APPROVAL_GAS_LIMIT);
224        }
225      }
226      setGasLimit(supplyGasLimit.toString());
227    }, [requiresApproval, approvalTxState, usePermit, setGasLimit]);
228  
229    return (
230      <TxActionsWrapper
231        blocked={blocked}
232        preparingTransactions={loadingTxns || !approvedAmount}
233        symbol={poolReserve.symbol}
234        mainTxState={mainTxState}
235        approvalTxState={approvalTxState}
236        requiresAmount
237        amount={amountToRepay}
238        requiresApproval={requiresApproval}
239        isWrongNetwork={isWrongNetwork}
240        sx={sx}
241        {...props}
242        handleAction={action}
243        handleApproval={approval}
244        actionText={<Trans>Repay {symbol}</Trans>}
245        actionInProgressText={<Trans>Repaying {symbol}</Trans>}
246        tryPermit={permitAvailable}
247      />
248    );
249  };