/ src / components / transactions / Bridge / BridgeActions.tsx
BridgeActions.tsx
  1  import { Trans } from '@lingui/macro';
  2  import { BoxProps } from '@mui/material';
  3  import { useQueryClient } from '@tanstack/react-query';
  4  import { Contract } from 'ethers';
  5  import { parseUnits } from 'ethers/lib/utils';
  6  import React, { useEffect } from 'react';
  7  import { useApprovalTx } from 'src/hooks/useApprovalTx';
  8  import { useApprovedAmount } from 'src/hooks/useApprovedAmount';
  9  import { useModalContext } from 'src/hooks/useModal';
 10  import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
 11  import { useRootStore } from 'src/store/root';
 12  import { getErrorTextFromError, TxAction } from 'src/ui-config/errorMapping';
 13  import { GHO_SYMBOL } from 'src/utils/ghoUtilities';
 14  import { getProvider } from 'src/utils/marketsAndNetworksConfig';
 15  
 16  import { TxActionsWrapper } from '../TxActionsWrapper';
 17  import { APPROVAL_GAS_LIMIT, checkRequiresApproval } from '../utils';
 18  import { bridgeGasLimit, getChainSelectorFor, getRouterFor } from './BridgeConfig';
 19  import routerAbi from './Router-abi.json';
 20  
 21  export interface TokenAmount {
 22    token: string;
 23    amount: string;
 24  }
 25  
 26  export interface MessageDetails {
 27    receiver: string;
 28    data: string;
 29    tokenAmounts: TokenAmount[];
 30    feeToken: string;
 31    extraArgs: string;
 32  }
 33  
 34  export interface BridgeActionProps extends BoxProps {
 35    amountToBridge: string;
 36    isWrongNetwork: boolean;
 37    symbol: string;
 38    blocked: boolean;
 39    decimals: number;
 40    sourceChainId: number;
 41    destinationChainId: number;
 42    tokenAddress: string;
 43    fees: string;
 44    message: MessageDetails | undefined;
 45    isCustomFeeToken: boolean;
 46  }
 47  
 48  export const BridgeActions = React.memo(
 49    ({
 50      amountToBridge,
 51      isWrongNetwork,
 52      sx,
 53      symbol,
 54      blocked,
 55      decimals,
 56      tokenAddress,
 57      sourceChainId,
 58      destinationChainId,
 59      message,
 60      fees,
 61      isCustomFeeToken,
 62      ...props
 63    }: BridgeActionProps) => {
 64      const { sendTx } = useWeb3Context();
 65      const queryClient = useQueryClient();
 66      const addTransaction = useRootStore((state) => state.addTransaction);
 67  
 68      const {
 69        approvalTxState,
 70        mainTxState,
 71        loadingTxns,
 72        setLoadingTxns,
 73        setApprovalTxState,
 74        setMainTxState,
 75        setGasLimit,
 76        setTxError,
 77      } = useModalContext();
 78      const user = useRootStore((state) => state.account);
 79  
 80      const {
 81        data: approvedAmount,
 82        refetch: fetchApprovedAmount,
 83        isFetching: fetchingApprovedAmount,
 84        isFetchedAfterMount,
 85      } = useApprovedAmount({
 86        chainId: sourceChainId,
 87        token: tokenAddress,
 88        spender: getRouterFor(sourceChainId),
 89      });
 90  
 91      setLoadingTxns(fetchingApprovedAmount);
 92  
 93      const requiresApproval =
 94        Number(amountToBridge) !== 0 &&
 95        checkRequiresApproval({
 96          approvedAmount: approvedAmount ? approvedAmount.toString() : '0',
 97          amount: amountToBridge,
 98          signedAmount: '0',
 99        });
100  
101      if (requiresApproval && approvalTxState?.success) {
102        // There was a successful approval tx, but the approval amount is not enough.
103        // Clear the state to prompt for another approval.
104        setApprovalTxState({});
105      }
106  
107      useEffect(() => {
108        if (!isFetchedAfterMount) {
109          fetchApprovedAmount();
110        }
111      }, [fetchApprovedAmount, isFetchedAfterMount]);
112  
113      const { approval } = useApprovalTx({
114        usePermit: false,
115        approvedAmount: {
116          amount: approvedAmount?.toString() || '0',
117          user: user,
118          token: tokenAddress,
119          spender: getRouterFor(sourceChainId),
120        },
121        requiresApproval,
122        assetAddress: tokenAddress,
123        symbol: GHO_SYMBOL,
124        decimals: 18,
125        signatureAmount: amountToBridge,
126        onApprovalTxConfirmed: fetchApprovedAmount,
127        chainId: sourceChainId,
128        amountToApprove: parseUnits(amountToBridge || '0', 18).toString(),
129      });
130  
131      // Update gas estimation
132      useEffect(() => {
133        let gasLimit = 0;
134        gasLimit = Number(bridgeGasLimit);
135        if (requiresApproval && !approvalTxState.success) {
136          gasLimit += Number(APPROVAL_GAS_LIMIT);
137        }
138  
139        setGasLimit(gasLimit.toString());
140      }, [requiresApproval, approvalTxState, setGasLimit]);
141  
142      const action = async () => {
143        try {
144          setMainTxState({ ...mainTxState, loading: true });
145          const provider = getProvider(sourceChainId);
146  
147          const sourceRouterAddress = getRouterFor(sourceChainId);
148          const sourceRouter = new Contract(sourceRouterAddress, routerAbi, provider);
149  
150          const destinationChainSelector = getChainSelectorFor(destinationChainId);
151  
152          const txFeeOptions: { value?: string } = {};
153  
154          if (!isCustomFeeToken) {
155            txFeeOptions.value = fees;
156          }
157  
158          const populatedTransaction = await sourceRouter.populateTransaction.ccipSend(
159            destinationChainSelector,
160            message,
161            txFeeOptions
162          );
163  
164          const response = await sendTx(populatedTransaction);
165  
166          queryClient.invalidateQueries({ queryKey: ['sendRequests', user] });
167  
168          setMainTxState({
169            txHash: response.hash,
170            loading: false,
171            success: true,
172          });
173  
174          addTransaction(response.hash, {
175            action: 'bridge',
176            txState: 'success',
177            asset: tokenAddress,
178            amount: amountToBridge,
179            assetName: GHO_SYMBOL,
180          });
181  
182          setTxError(undefined);
183        } catch (error) {
184          const parsedError = getErrorTextFromError(error, TxAction.GAS_ESTIMATION, false);
185          setTxError(parsedError);
186          setMainTxState({
187            txHash: undefined,
188            loading: false,
189          });
190        }
191      };
192  
193      return (
194        <TxActionsWrapper
195          blocked={blocked}
196          mainTxState={mainTxState}
197          approvalTxState={approvalTxState}
198          isWrongNetwork={isWrongNetwork}
199          requiresAmount
200          amount={amountToBridge}
201          symbol={symbol}
202          preparingTransactions={loadingTxns || !fees}
203          actionText={<Trans>Bridge {symbol}</Trans>}
204          actionInProgressText={<Trans>Bridging {symbol}</Trans>}
205          handleApproval={approval}
206          handleAction={action}
207          requiresApproval={requiresApproval}
208          tryPermit={false} // permit not availabe
209          sx={sx}
210          {...props}
211        />
212      );
213    }
214  );