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 };