import { BridgeWidgetParams, Checkout, IMTBLWidgetEvents, WrappedBrowserProvider, } from '@imtbl/checkout-sdk'; import { useCallback, useContext, useEffect, useMemo, useReducer, } from 'react'; import { BridgeConfiguration, ETH_MAINNET_TO_ZKEVM_MAINNET, ETH_SEPOLIA_TO_ZKEVM_DEVNET, ETH_SEPOLIA_TO_ZKEVM_TESTNET, TokenBridge, } from '@imtbl/bridge-sdk'; import { useTranslation } from 'react-i18next'; import { ImmutableConfiguration } from '@imtbl/config'; import { JsonRpcProvider } from 'ethers'; import { StrongCheckoutWidgetsConfig } from '../../lib/withDefaultWidgetConfig'; import { CryptoFiatProvider } from '../../context/crypto-fiat-context/CryptoFiatProvider'; import { StatusView } from '../../components/Status/StatusView'; import { StatusType } from '../../components/Status/StatusType'; import { Transactions } from '../../components/Transactions/Transactions'; import { TopUpView } from '../../views/top-up/TopUpView'; import { ClaimWithdrawalInProgress } from '../../components/Transactions/ClaimWithdrawalInProgress'; import { getDefaultTokenImage } from '../../lib/utils'; import { ViewActions, ViewContext, initialViewState, viewReducer, SharedViews, } from '../../context/view-context/ViewContext'; import { BridgeContext, bridgeReducer, initialBridgeState, BridgeActions, } from './context/BridgeContext'; import { WalletNetworkSelectionView } from './views/WalletNetworkSelectionView'; import { Bridge } from './views/Bridge'; import { BridgeReview } from './views/BridgeReview'; import { MoveInProgress } from './views/MoveInProgress'; import { ApproveTransaction } from './views/ApproveTransaction'; import { ErrorView } from '../../views/error/ErrorView'; import { EventTargetContext } from '../../context/event-target-context/EventTargetContext'; import { sendBridgeClaimWithdrawalFailedEvent, sendBridgeClaimWithdrawalSuccessEvent, sendBridgeFailedEvent, sendBridgeWidgetCloseEvent, } from './BridgeWidgetEvents'; import { BridgeClaimWithdrawalSuccess, BridgeClaimWithdrawalFailure, BridgeWidgetViews, } from '../../context/view-context/BridgeViewContextTypes'; import { ClaimWithdrawal } from './views/ClaimWithdrawal'; import { ServiceUnavailableErrorView } from '../../views/error/ServiceUnavailableErrorView'; export type BridgeWidgetInputs = BridgeWidgetParams & { config: StrongCheckoutWidgetsConfig, checkout: Checkout; browserProvider?: WrappedBrowserProvider; }; export default function BridgeWidget({ checkout, browserProvider, config, amount, tokenAddress, showBackButton, }: BridgeWidgetInputs) { const { t } = useTranslation(); const { environment, isOnRampEnabled, isSwapEnabled, isBridgeEnabled, theme, themeOverrides, } = config; const defaultTokenImage = getDefaultTokenImage(checkout.config.environment, theme); const { eventTargetState: { eventTarget } } = useContext(EventTargetContext); const [viewState, viewDispatch] = useReducer( viewReducer, { ...initialViewState, view: { type: BridgeWidgetViews.WALLET_NETWORK_SELECTION }, history: [{ type: BridgeWidgetViews.WALLET_NETWORK_SELECTION }], }, ); const [bridgeState, bridgeDispatch] = useReducer( bridgeReducer, { ...initialBridgeState, checkout, browserProvider: browserProvider ?? null, tokenBridge: (() => { let bridgeInstance = ETH_SEPOLIA_TO_ZKEVM_TESTNET; if (checkout.config.isDevelopment) bridgeInstance = ETH_SEPOLIA_TO_ZKEVM_DEVNET; if (checkout.config.isProduction) bridgeInstance = ETH_MAINNET_TO_ZKEVM_MAINNET; // Root provider is always L1 const rootProvider = new JsonRpcProvider( checkout.config.networkMap.get(checkout.config.l1ChainId)?.rpcUrls[0], ); // Child provider is always L2 const childProvider = new JsonRpcProvider( checkout.config.networkMap.get(checkout.config.l2ChainId)?.rpcUrls[0], ); const bridgeConfiguration = new BridgeConfiguration({ baseConfig: new ImmutableConfiguration({ environment: checkout.config.environment }), bridgeInstance, rootProvider, childProvider, }); return new TokenBridge(bridgeConfiguration); })(), }, ); const viewReducerValues = useMemo(() => ({ viewState, viewDispatch }), [viewState, viewDispatch]); const bridgeReducerValues = useMemo(() => ({ bridgeState, bridgeDispatch }), [bridgeState, bridgeDispatch]); const goBackToWalletNetworkSelectorClearState = useCallback(() => { bridgeDispatch({ payload: { type: BridgeActions.SET_WALLETS_AND_NETWORKS, from: null, to: null, }, }); bridgeDispatch({ payload: { type: BridgeActions.SET_TOKEN_AND_AMOUNT, amount: '', token: null, }, }); viewDispatch({ payload: { type: ViewActions.GO_BACK_TO, view: { type: BridgeWidgetViews.WALLET_NETWORK_SELECTION }, }, }); }, [viewDispatch]); const goBackToWalletNetworkSelector = useCallback(() => { viewDispatch({ payload: { type: ViewActions.GO_BACK_TO, view: { type: BridgeWidgetViews.WALLET_NETWORK_SELECTION }, }, }); }, [viewDispatch]); const updateToTransactionsPage = useCallback(() => { viewDispatch({ payload: { type: ViewActions.UPDATE_VIEW, view: { type: BridgeWidgetViews.TRANSACTIONS, }, }, }); }, [viewDispatch]); const goBackToReview = useCallback(() => { viewDispatch({ payload: { type: ViewActions.GO_BACK_TO, view: { type: BridgeWidgetViews.BRIDGE_REVIEW }, }, }); }, [viewDispatch]); useEffect(() => { (async () => { bridgeDispatch({ payload: { type: BridgeActions.SET_PROVIDER, browserProvider: browserProvider ?? null, }, }); })(); }, [browserProvider]); return ( {viewState.view.type === BridgeWidgetViews.WALLET_NETWORK_SELECTION && ( )} {viewState.view.type === BridgeWidgetViews.BRIDGE_FORM && ( )} {viewState.view.type === BridgeWidgetViews.BRIDGE_REVIEW && ( )} {viewState.view.type === BridgeWidgetViews.IN_PROGRESS && ( )} {viewState.view.type === BridgeWidgetViews.BRIDGE_FAILURE && ( { let reason = ''; if (viewState.view.type === BridgeWidgetViews.BRIDGE_FAILURE) { reason = viewState.view.reason; } sendBridgeFailedEvent(eventTarget, reason); }} /> )} {viewState.view.type === BridgeWidgetViews.APPROVE_TRANSACTION && ( )} {viewState.view.type === BridgeWidgetViews.TRANSACTIONS && ( )} {viewState.view.type === BridgeWidgetViews.CLAIM_WITHDRAWAL && ( )} {viewState.view.type === SharedViews.ERROR_VIEW && ( sendBridgeWidgetCloseEvent(eventTarget)} /> )} {viewState.view.type === SharedViews.TOP_UP_VIEW && ( sendBridgeWidgetCloseEvent(eventTarget)} /> )} {viewState.view.type === BridgeWidgetViews.CLAIM_WITHDRAWAL_IN_PROGRESS && ( )} {viewState.view.type === BridgeWidgetViews.CLAIM_WITHDRAWAL_SUCCESS && ( { sendBridgeClaimWithdrawalSuccessEvent( eventTarget, (viewState.view as BridgeClaimWithdrawalSuccess).transactionHash, ); }} onActionClick={updateToTransactionsPage} statusType={StatusType.SUCCESS} testId="claim-withdrawal-success-view" /> )} {viewState.view.type === BridgeWidgetViews.CLAIM_WITHDRAWAL_FAILURE && ( { sendBridgeClaimWithdrawalFailedEvent( eventTarget, (viewState.view as BridgeClaimWithdrawalFailure).transactionHash, 'Transaction failed', ); }} onActionClick={updateToTransactionsPage} statusType={StatusType.FAILURE} onCloseClick={() => sendBridgeWidgetCloseEvent(eventTarget)} testId="claim-withdrawal-fail-view" /> )} {viewState.view.type === BridgeWidgetViews.SERVICE_UNAVAILABLE && ( sendBridgeWidgetCloseEvent(eventTarget)} onBackButtonClick={() => { viewDispatch({ payload: { type: ViewActions.UPDATE_VIEW, view: { type: BridgeWidgetViews.WALLET_NETWORK_SELECTION }, }, }); }} /> )} ); }