From 8518b144b66ab531fc8d341b2bf1f05959a486a2 Mon Sep 17 00:00:00 2001 From: Anton Shalimov Date: Tue, 24 Dec 2024 10:27:47 +0300 Subject: [PATCH 01/39] feat: add the 'chain id toggler' instead of the 'chain type toggler' --- features/wsteth/shared/wallet/wallet.tsx | 21 +-- features/wsteth/shared/wrap-faq/wrap-faq.tsx | 16 +- .../hooks/use-unwrap-tx-on-l2-approve.ts | 8 +- .../unwrap/unwrap-form/unwrap-stats.tsx | 12 +- .../wrap/hooks/use-wrap-tx-on-l1-approve.ts | 9 +- features/wsteth/wrap/wrap-form/wrap-stats.tsx | 12 +- modules/web3/hooks/use-dapp-status.ts | 4 +- modules/web3/web3-provider/dapp-chain.tsx | 138 +++++++++--------- .../chain-switcher/chain-switcher.tsx | 54 ++++--- .../chain-switcher-options.tsx | 21 +-- shared/wallet/fallback/useErrorMessage.ts | 14 +- 11 files changed, 166 insertions(+), 143 deletions(-) diff --git a/features/wsteth/shared/wallet/wallet.tsx b/features/wsteth/shared/wallet/wallet.tsx index 2c5f4c5ab..18b6b4a6c 100644 --- a/features/wsteth/shared/wallet/wallet.tsx +++ b/features/wsteth/shared/wallet/wallet.tsx @@ -1,5 +1,9 @@ +import { useConnectorInfo } from 'reef-knot/core-react'; import { Divider, Text } from '@lidofinance/lido-ui'; +import { CHAINS } from 'consts/chains'; +import { useConfig } from 'config'; + import { FormatToken } from 'shared/formatters'; import { TokenToWallet } from 'shared/components'; import { @@ -8,15 +12,12 @@ import { useStethBalance, useWstethBalance, useWstethBySteth, - DAPP_CHAIN_TYPE, useStETHByWstETH, } from 'modules/web3'; +import { useIsLedgerLive } from 'shared/hooks/useIsLedgerLive'; import { CardBalance, CardRow, CardAccount, Fallback } from 'shared/wallet'; import { StyledCard } from './styles'; -import { useIsLedgerLive } from 'shared/hooks/useIsLedgerLive'; -import { useConfig } from 'config'; -import { useConnectorInfo } from 'reef-knot/core-react'; const WalletComponent = () => { const { isDappActiveOnL2 } = useDappStatus(); @@ -110,14 +111,16 @@ export const Wallet = ({ isUnwrapMode }: WrapWalletProps) => { const isLedgerLive = useIsLedgerLive(); const { isLedger: isLedgerHardware } = useConnectorInfo(); const { featureFlags } = useConfig().externalConfig; - const { chainType } = useDappStatus(); + const { chainId } = useDappStatus(); const isLedgerLiveOptimism = - !featureFlags.ledgerLiveL2 && - isLedgerLive && - chainType === DAPP_CHAIN_TYPE.Optimism; + (!featureFlags.ledgerLiveL2 && + isLedgerLive && + chainId === CHAINS.Optimism) || + chainId === CHAINS.OptimismSepolia; const isLedgerHardwareOptimism = - isLedgerHardware && chainType === DAPP_CHAIN_TYPE.Optimism; + (isLedgerHardware && chainId === CHAINS.Optimism) || + chainId === CHAINS.OptimismSepolia; if (isLedgerLiveOptimism || isLedgerHardwareOptimism) { const error = `Optimism is currently not supported in ${isLedgerLiveOptimism ? 'Ledger Live' : 'Ledger Hardware'}.`; diff --git a/features/wsteth/shared/wrap-faq/wrap-faq.tsx b/features/wsteth/shared/wrap-faq/wrap-faq.tsx index c2409724f..d9752e793 100644 --- a/features/wsteth/shared/wrap-faq/wrap-faq.tsx +++ b/features/wsteth/shared/wrap-faq/wrap-faq.tsx @@ -1,24 +1,28 @@ import React from 'react'; + +import { CHAINS } from 'consts/chains'; +import { useDappStatus } from 'modules/web3'; import { Section } from 'shared/components'; import { useMatomoEventHandle } from 'shared/hooks'; -import { useDappStatus, DAPP_CHAIN_TYPE } from 'modules/web3'; import { EthereumFAQ } from './ethereum-faq/faq'; import { OptimismFAQ } from './optimism-faq/faq'; export const faqComponentsMap = new Map([ - [DAPP_CHAIN_TYPE.Ethereum, EthereumFAQ], - [DAPP_CHAIN_TYPE.Optimism, OptimismFAQ], - // FAQ for other networks + [CHAINS.Mainnet, EthereumFAQ], + [CHAINS.Sepolia, EthereumFAQ], + [CHAINS.Holesky, EthereumFAQ], + [CHAINS.Optimism, OptimismFAQ], + [CHAINS.OptimismSepolia, OptimismFAQ], ]); export const WrapFaq = () => { - const { isWalletConnected, chainType } = useDappStatus(); + const { isWalletConnected, chainId } = useDappStatus(); const onClickHandler = useMatomoEventHandle(); const FAQ = !isWalletConnected ? EthereumFAQ - : faqComponentsMap.get(chainType) || EthereumFAQ; + : faqComponentsMap.get(chainId) || EthereumFAQ; return (
diff --git a/features/wsteth/unwrap/hooks/use-unwrap-tx-on-l2-approve.ts b/features/wsteth/unwrap/hooks/use-unwrap-tx-on-l2-approve.ts index 3341d2402..71a9b9fdd 100644 --- a/features/wsteth/unwrap/hooks/use-unwrap-tx-on-l2-approve.ts +++ b/features/wsteth/unwrap/hooks/use-unwrap-tx-on-l2-approve.ts @@ -13,7 +13,7 @@ type UseUnwrapTxApproveArgs = { }; export const useUnwrapTxOnL2Approve = ({ amount }: UseUnwrapTxApproveArgs) => { - const { isDappActiveOnL2, isChainTypeOnL2, address } = useDappStatus(); + const { isDappActiveOnL2, isChainIdOnL2, address } = useDappStatus(); const { l2, core } = useLidoSDKL2(); const { txModalStages } = useTxModalWrap(); @@ -77,8 +77,8 @@ export const useUnwrapTxOnL2Approve = ({ amount }: UseUnwrapTxApproveArgs) => { isAllowanceLoading, // There are 2 cases when we show the allowance on the unwrap page: // 1. wallet chain is any Optimism supported chain and chain switcher is Optimism (isDappActiveOnL2) - // 2. or wallet chain is any ETH supported chain, but chain switcher is Optimism (isChainTypeOnL2) - isShowAllowance: isDappActiveOnL2 || isChainTypeOnL2, + // 2. or wallet chain is any ETH supported chain, but chain switcher is Optimism (isChainIdOnL2) + isShowAllowance: isDappActiveOnL2 || isChainIdOnL2, }), [ processApproveTx, @@ -87,7 +87,7 @@ export const useUnwrapTxOnL2Approve = ({ amount }: UseUnwrapTxApproveArgs) => { isApprovalNeededBeforeUnwrap, isAllowanceLoading, isDappActiveOnL2, - isChainTypeOnL2, + isChainIdOnL2, ], ); }; diff --git a/features/wsteth/unwrap/unwrap-form/unwrap-stats.tsx b/features/wsteth/unwrap/unwrap-form/unwrap-stats.tsx index 0ff8cf7fa..c956eeeac 100644 --- a/features/wsteth/unwrap/unwrap-form/unwrap-stats.tsx +++ b/features/wsteth/unwrap/unwrap-form/unwrap-stats.tsx @@ -17,25 +17,25 @@ import { useUnwrapFormData, UnwrapFormInputType } from '../unwrap-form-context'; import { TOKENS_TO_WRAP } from '../../shared/types'; export const UnwrapStats = () => { - const { isDappActiveOnL2, chainTypeChainId } = useDappStatus(); + const { isDappActiveOnL2, chainId } = useDappStatus(); const { allowance, isAllowanceLoading, isShowAllowance } = useUnwrapFormData(); const amount = useWatch({ name: 'amount' }); const unwrapGasLimit = useUnwrapGasLimit(); // The 'unwrapGasLimit' difference between the networks is insignificant - // and can be neglected in the '!isChainTypeMatched' case + // and can be neglected in the '!isChainIdMatched' case // - // Using the chainTypeChainId (chainId from the chain switcher) for TX calculation (and below for 'approveTxCostInUsd'), + // Using the chainId (chainId from the chain switcher) for TX calculation (and below for 'approveTxCostInUsd'), // because the statistics here are shown for the chain from the chain switcher const { txCostUsd: unwrapTxCostInUsd, isLoading: isUnwrapTxCostLoading } = - useTxCostInUsd(unwrapGasLimit, chainTypeChainId); + useTxCostInUsd(unwrapGasLimit, chainId); const approveGasLimit = useApproveGasLimit(); // The 'approveGasLimit' difference between the networks is insignificant - // and can be neglected in the '!isChainTypeMatched' case + // and can be neglected in the '!isChainIdMatched' case const { txCostUsd: approveTxCostInUsd, isLoading: isApproveCostLoading } = - useTxCostInUsd(approveGasLimit, chainTypeChainId); + useTxCostInUsd(approveGasLimit, chainId); const { data: willReceiveStETH, isLoading: isWillReceiveStETHLoading } = useDebouncedStethByWsteth(amount); diff --git a/features/wsteth/wrap/hooks/use-wrap-tx-on-l1-approve.ts b/features/wsteth/wrap/hooks/use-wrap-tx-on-l1-approve.ts index c7ca37d12..098f92e57 100644 --- a/features/wsteth/wrap/hooks/use-wrap-tx-on-l1-approve.ts +++ b/features/wsteth/wrap/hooks/use-wrap-tx-on-l1-approve.ts @@ -22,7 +22,7 @@ export const useWrapTxOnL1Approve = ({ amount, token, }: UseWrapTxApproveArgs) => { - const { address, isWalletConnected, isDappActiveOnL1, isChainTypeOnL2 } = + const { address, isWalletConnected, isDappActiveOnL1, isChainIdOnL2 } = useDappStatus(); const { wrap } = useLidoSDK(); const { txModalStages } = useTxModalWrap(); @@ -90,9 +90,8 @@ export const useWrapTxOnL1Approve = ({ // There are 3 cases when we show the allowance on the wrap page: // 1. is wallet not connected (!isWalletConnected) // 2. or wallet chain is any ETH supported chain and chain switcher is ETH (isDappActiveOnL1) - // 3. or wallet chain is any Optimism supported chain, but chain switcher is ETH (!isChainTypeOnL2) - isShowAllowance: - !isWalletConnected || isDappActiveOnL1 || !isChainTypeOnL2, + // 3. or wallet chain is any Optimism supported chain, but chain switcher is ETH (!isChainIdOnL2) + isShowAllowance: !isWalletConnected || isDappActiveOnL1 || !isChainIdOnL2, }), [ processApproveTx, @@ -103,7 +102,7 @@ export const useWrapTxOnL1Approve = ({ refetchAllowance, isWalletConnected, isDappActiveOnL1, - isChainTypeOnL2, + isChainIdOnL2, ], ); }; diff --git a/features/wsteth/wrap/wrap-form/wrap-stats.tsx b/features/wsteth/wrap/wrap-form/wrap-stats.tsx index 5dd1ccdca..3b60250d5 100644 --- a/features/wsteth/wrap/wrap-form/wrap-stats.tsx +++ b/features/wsteth/wrap/wrap-form/wrap-stats.tsx @@ -16,7 +16,7 @@ import { useApproveGasLimit } from '../hooks/use-approve-gas-limit'; import { useWrapFormData, WrapFormInputType } from '../wrap-form-context'; export const WrapFormStats = () => { - const { isDappActive, chainTypeChainId } = useDappStatus(); + const { isDappActive, chainId } = useDappStatus(); const { allowance, isShowAllowance, wrapGasLimit, isAllowanceLoading } = useWrapFormData(); @@ -32,18 +32,18 @@ export const WrapFormStats = () => { useWstethBySteth(ONE_stETH); // The 'approveGasLimit' difference between the networks is insignificant - // and can be neglected in the '!isChainTypeMatched' case + // and can be neglected in the '!isChainIdMatched' case // - // Using the chainTypeChainId (chainId from the chain switcher) for TX calculation (and below for 'wrapTxCostInUsd'), + // Using the chainId (chainId from the chain switcher) for TX calculation (and below for 'wrapTxCostInUsd'), // because the statistics here are shown for the chain from the chain switcher const approveGasLimit = useApproveGasLimit(); const { txCostUsd: approveTxCostInUsd, isLoading: isApproveCostLoading } = - useTxCostInUsd(approveGasLimit, chainTypeChainId); + useTxCostInUsd(approveGasLimit, chainId); // The 'wrapGasLimit' difference between the networks is insignificant - // and can be neglected in the '!isChainTypeMatched' case + // and can be neglected in the '!isChainIdMatched' case const { txCostUsd: wrapTxCostInUsd, isLoading: isWrapCostLoading } = - useTxCostInUsd(wrapGasLimit, chainTypeChainId); + useTxCostInUsd(wrapGasLimit, chainId); return ( diff --git a/modules/web3/hooks/use-dapp-status.ts b/modules/web3/hooks/use-dapp-status.ts index 742fbf03e..1b450645e 100644 --- a/modules/web3/hooks/use-dapp-status.ts +++ b/modules/web3/hooks/use-dapp-status.ts @@ -13,7 +13,7 @@ export const useDappStatus = () => { // this can change between pages based on their dapp-chain context(or lack of) const dappChain = useDappChain(); - const { isSupportedChain, isChainTypeMatched } = dappChain; + const { isSupportedChain, isChainIdMatched } = dappChain; const isAccountActive = walletChainId ? isWalletConnected && isSupportedChain @@ -21,7 +21,7 @@ export const useDappStatus = () => { const isL2 = isSDKSupportedL2Chain(walletChainId); - const isDappActive = isAccountActive && isChainTypeMatched; + const isDappActive = isAccountActive && isChainIdMatched; const isDappActiveOnL1 = isDappActive && !isL2; diff --git a/modules/web3/web3-provider/dapp-chain.tsx b/modules/web3/web3-provider/dapp-chain.tsx index b4079e9a8..2d4f5936f 100644 --- a/modules/web3/web3-provider/dapp-chain.tsx +++ b/modules/web3/web3-provider/dapp-chain.tsx @@ -3,12 +3,13 @@ import React, { useContext, useState, useMemo, + useCallback, useEffect, } from 'react'; import invariant from 'tiny-invariant'; import { CHAINS, isSDKSupportedL2Chain } from 'consts/chains'; -import { useAccount } from 'wagmi'; +import { useAccount, useSwitchChain } from 'wagmi'; import { config } from 'config'; import { ModalProvider } from 'providers/modal-provider'; @@ -21,26 +22,21 @@ export enum DAPP_CHAIN_TYPE { Optimism = 'Optimism', } -type DappChainContextValue = { - chainType: DAPP_CHAIN_TYPE; - setChainType: React.Dispatch>; - supportedChainIds: number[]; - isChainTypeMatched: boolean; - isChainTypeOnL2: boolean; -}; - export type SupportedChainLabels = { [key in DAPP_CHAIN_TYPE]: string; }; -type UseDappChainValue = { - // Current DApp chain ID (may not match with chainType) +type DappChainContextValue = { chainId: number; - // Chain ID by current chainType - chainTypeChainId: number; + setChainId: React.Dispatch; + supportedL2: boolean; + supportedChainIds: number[]; + isChainIdOnL2: boolean; +}; +type UseDappChainValue = { isSupportedChain: boolean; - supportedChainTypes: DAPP_CHAIN_TYPE[]; + isChainIdMatched: boolean; supportedChainLabels: SupportedChainLabels; } & DappChainContextValue; @@ -55,8 +51,9 @@ const ETHEREUM_CHAINS = new Set([ const OPTIMISM_CHAINS = new Set([CHAINS.Optimism, CHAINS.OptimismSepolia]); -const getChainTypeByChainId = (chainId?: number): DAPP_CHAIN_TYPE | null => { - if (!chainId) return null; +export const getChainTypeByChainId = ( + chainId: number, +): DAPP_CHAIN_TYPE | null => { if (ETHEREUM_CHAINS.has(chainId)) { return DAPP_CHAIN_TYPE.Ethereum; } else if (OPTIMISM_CHAINS.has(chainId)) { @@ -65,15 +62,6 @@ const getChainTypeByChainId = (chainId?: number): DAPP_CHAIN_TYPE | null => { return null; }; -// At the current stage of the widget we don't care what ID is returned: -// - 'chainTypeChainId' is only used for statistics; -// - on the prod environment, the 'function map' of 'chainType' to 'chainId' will be 1 to 1 (bijective mapping). -const getChainIdByChainType = ( - chainType: DAPP_CHAIN_TYPE, - supportedChainIds: number[], -): number | undefined => - supportedChainIds.find((id) => getChainTypeByChainId(id) === chainType); - export const useDappChain = (): UseDappChainValue => { const context = useContext(DappChainContext); invariant(context, 'useDappChain was used outside of DappChainProvider'); @@ -110,22 +98,13 @@ export const useDappChain = (): UseDappChainValue => { {}, ) as SupportedChainLabels; - const chainTypeChainId = - getChainIdByChainType(context.chainType, context.supportedChainIds) ?? - config.defaultChain; - return { ...context, - chainId: - walletChain && context.supportedChainIds.includes(walletChain) - ? walletChain - : config.defaultChain, - chainTypeChainId, + supportedChainLabels, isSupportedChain: walletChain ? context.supportedChainIds.includes(walletChain) : true, - supportedChainTypes, - supportedChainLabels, + isChainIdMatched: walletChain === context.chainId, }; }, [context, walletChain]); }; @@ -133,41 +112,41 @@ export const useDappChain = (): UseDappChainValue => { export const SupportL2Chains: React.FC = ({ children, }) => { - const { chainId: walletChainId, isConnected } = useAccount(); - const [chainType, setChainType] = useState( - DAPP_CHAIN_TYPE.Ethereum, - ); + const { chainId: walletChain, isConnected } = useAccount(); + const { switchChain } = useSwitchChain(); + const [chainId, setChainIdInternal] = useState(config.defaultChain); useEffect(() => { - if (!walletChainId || !config.supportedChains.includes(walletChainId)) { - // This code resets 'chainType' to ETH when the wallet is disconnected. - // It also works on the first rendering, but we don't care, because the 'chainType' by default is ETH. - // Don't use it if you need to do something strictly, only when the wallet is disconnected. - setChainType(DAPP_CHAIN_TYPE.Ethereum); - return; - } - if (isConnected) { - const newChainType = getChainTypeByChainId(walletChainId); - if (newChainType) setChainType(newChainType); + const chainId = + walletChain && config.supportedChains.includes(walletChain) + ? walletChain + : config.defaultChain; + + setChainIdInternal(chainId); } - }, [walletChainId, isConnected, setChainType]); + }, [walletChain, isConnected, setChainIdInternal]); + + const handleSetChainId = useCallback>( + (newChainId) => { + setChainIdInternal(newChainId); + switchChain({ chainId: newChainId }); + }, + [switchChain], + ); return ( ({ - chainType, - setChainType, + chainId, + setChainId: handleSetChainId, + supportedL2: true, supportedChainIds: config.supportedChains, - isChainTypeMatched: - chainType === getChainTypeByChainId(walletChainId), - // At the moment a simple check is enough for us, - // however in the future we will either rethink this flag - // or use an array or Set (for example with L2_DAPP_CHAINS_TYPE) - isChainTypeOnL2: chainType === DAPP_CHAIN_TYPE.Optimism, + isChainIdOnL2: + getChainTypeByChainId(chainId) === DAPP_CHAIN_TYPE.Optimism, }), - [chainType, walletChainId], + [chainId, handleSetChainId], )} > @@ -179,14 +158,13 @@ export const SupportL2Chains: React.FC = ({ }; const onlyL1ChainsValue = { - chainType: DAPP_CHAIN_TYPE.Ethereum, + setChainId: () => {}, // only L1 chains + supportedL2: false, supportedChainIds: config.supportedChains.filter( (chain) => !isSDKSupportedL2Chain(chain), ), - isChainTypeMatched: true, - isChainTypeOnL2: false, - setChainType: () => {}, + isChainIdOnL2: false, }; // Value of this context only allows L1 chains and no chain switch @@ -195,11 +173,27 @@ const onlyL1ChainsValue = { // in order to prevent accidental useDappChain/useDappStatus misusage in top-lvl components export const SupportL1Chains: React.FC = ({ children, -}) => ( - - - {/* Stub LidoSDKL2Provider for hooks that gives isL2:false. Will be overriden in SupportL2Chains */} - {children} - - -); +}) => { + const { chainId: walletChain } = useAccount(); + + return ( + ({ + ...onlyL1ChainsValue, + chainId: + walletChain && + onlyL1ChainsValue.supportedChainIds.includes(walletChain) + ? walletChain + : config.defaultChain, + }), + [walletChain], + )} + > + + {/* Stub LidoSDKL2Provider for hooks that gives isL2:false. Will be overriden in SupportL2Chains */} + {children} + + + ); +}; diff --git a/shared/components/layout/header/components/chain-switcher/chain-switcher.tsx b/shared/components/layout/header/components/chain-switcher/chain-switcher.tsx index 459fd5643..9a244fb74 100644 --- a/shared/components/layout/header/components/chain-switcher/chain-switcher.tsx +++ b/shared/components/layout/header/components/chain-switcher/chain-switcher.tsx @@ -1,9 +1,17 @@ -import { FC, useState, useRef, ReactNode } from 'react'; -import { DAPP_CHAIN_TYPE } from 'modules/web3'; +import { FC, useState, useRef } from 'react'; import { useDappStatus } from 'modules/web3'; +import { wagmiChainMap } from 'modules/web3/web3-provider/web3-provider'; +import { + getChainTypeByChainId, + DAPP_CHAIN_TYPE, +} from 'modules/web3/web3-provider/dapp-chain'; +import { config } from 'config'; import { useClickOutside } from './hooks/use-click-outside'; -import { ChainSwitcherOptions } from './components/chain-switcher-options/chain-switcher-options'; +import { + ChainSwitcherOptions, + ChainOption, +} from './components/chain-switcher-options/chain-switcher-options'; import { SelectIconTooltip } from './components/select-icon-tooltip/select-icon-tooltip'; import { ChainSwitcherWrapperStyled, @@ -15,18 +23,30 @@ import { import { ReactComponent as OptimismLogo } from 'assets/icons/chain-toggler/optimism.svg'; import { ReactComponent as EthereumMainnetLogo } from 'assets/icons/chain-toggler/mainnet.svg'; -const iconsMap: Record = { - [DAPP_CHAIN_TYPE.Ethereum]: , - [DAPP_CHAIN_TYPE.Optimism]: , -}; +type IconsMapType = Record; + +const iconsMap: IconsMapType = config.supportedChains.reduce( + (acc: IconsMapType, chainId: number) => { + acc[chainId] = { + name: wagmiChainMap[chainId].name, + iconComponent: + getChainTypeByChainId(chainId) === DAPP_CHAIN_TYPE.Optimism ? ( + + ) : ( + + ), + }; + return acc; + }, + {}, +); export const ChainSwitcher: FC = () => { - const { isDappActive, chainType, supportedChainTypes, setChainType } = - useDappStatus(); + const { isDappActive, chainId, setChainId, supportedL2 } = useDappStatus(); const [opened, setOpened] = useState(false); const selectRef = useRef(null); - const isChainTypeUnlocked = supportedChainTypes.length > 1; + const isChainSwitcherUnlocked = supportedL2; useClickOutside(selectRef, () => setOpened(false)); @@ -34,19 +54,19 @@ export const ChainSwitcher: FC = () => { setOpened((prev) => !prev)} > - {iconsMap[chainType]} - {isChainTypeUnlocked && } + {iconsMap[chainId].iconComponent} + {isChainSwitcherUnlocked && } - {isChainTypeUnlocked && ( + {isChainSwitcherUnlocked && ( <> { - setChainType(chainType); + currentChainId={chainId} + onSelect={(chainId) => { + setChainId(chainId); setOpened(false); }} opened={opened} diff --git a/shared/components/layout/header/components/chain-switcher/components/chain-switcher-options/chain-switcher-options.tsx b/shared/components/layout/header/components/chain-switcher/components/chain-switcher-options/chain-switcher-options.tsx index b2a546bfc..9e93ef16f 100644 --- a/shared/components/layout/header/components/chain-switcher/components/chain-switcher-options/chain-switcher-options.tsx +++ b/shared/components/layout/header/components/chain-switcher/components/chain-switcher-options/chain-switcher-options.tsx @@ -1,17 +1,18 @@ import { FC, ReactNode } from 'react'; -import { DAPP_CHAIN_TYPE } from 'modules/web3'; import { PopoverWrapperStyled, PopupStyled, OptionStyled } from './styles'; +export type ChainOption = { name: string; iconComponent: ReactNode }; + interface ChainSwitcherOptionsProps { - currentChainType: DAPP_CHAIN_TYPE; - onSelect: (chainType: DAPP_CHAIN_TYPE) => void; + currentChainId: number; + onSelect: (chainId: number) => void; opened: boolean; - options: Record; + options: Record; } export const ChainSwitcherOptions: FC = ({ - currentChainType, + currentChainId, onSelect, opened, options, @@ -21,13 +22,13 @@ export const ChainSwitcherOptions: FC = ({ <> - {Object.entries(options).map(([chainType, icon]) => ( + {Object.entries(options).map(([chainId, chainOption]) => ( onSelect(chainType as DAPP_CHAIN_TYPE)} - $active={chainType === currentChainType} + key={chainId} + onClick={() => onSelect(Number(chainId))} + $active={Number(chainId) === currentChainId} > - {icon} {chainType} + {chainOption.iconComponent} {chainOption.name} ))} diff --git a/shared/wallet/fallback/useErrorMessage.ts b/shared/wallet/fallback/useErrorMessage.ts index dbe734a12..210647406 100644 --- a/shared/wallet/fallback/useErrorMessage.ts +++ b/shared/wallet/fallback/useErrorMessage.ts @@ -1,25 +1,27 @@ -import { useDappStatus } from 'modules/web3'; +import { useConnect } from 'wagmi'; import { useConnectorInfo } from 'reef-knot/core-react'; // TODO: to remove the 'reef-knot/web3-react' after it will be deprecated import { helpers } from 'reef-knot/web3-react'; + +import { useDappStatus } from 'modules/web3'; +import { wagmiChainMap } from 'modules/web3/web3-provider/web3-provider'; import { joinWithOr } from 'utils/join-with-or'; -import { useConnect } from 'wagmi'; export const useErrorMessage = (): string | undefined => { const { isLedger } = useConnectorInfo(); const { isSupportedChain, - isChainTypeMatched, isAccountActive, - chainType, + chainId, + isChainIdMatched, supportedChainLabels, } = useDappStatus(); const { error } = useConnect(); // Errors from chain state - if (isAccountActive && !isChainTypeMatched) { - return `Wrong network. Please switch to ${supportedChainLabels[chainType]} in your wallet to wrap/unwrap.`; + if (isAccountActive && !isChainIdMatched) { + return `Wrong network. Please switch to ${wagmiChainMap[chainId].name} in your wallet to wrap/unwrap.`; } if (!isSupportedChain) { From 7bd5d758743e71e61ae3e1c0c4efc06da9457843 Mon Sep 17 00:00:00 2001 From: Anton Shalimov Date: Thu, 9 Jan 2025 16:00:26 +0300 Subject: [PATCH 02/39] feat: remove explicit call the setChainIdInternal --- modules/web3/web3-provider/dapp-chain.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/web3/web3-provider/dapp-chain.tsx b/modules/web3/web3-provider/dapp-chain.tsx index 2d4f5936f..71a47bdfe 100644 --- a/modules/web3/web3-provider/dapp-chain.tsx +++ b/modules/web3/web3-provider/dapp-chain.tsx @@ -129,7 +129,6 @@ export const SupportL2Chains: React.FC = ({ const handleSetChainId = useCallback>( (newChainId) => { - setChainIdInternal(newChainId); switchChain({ chainId: newChainId }); }, [switchChain], From 17085ec5ad118e07ee8ab6ef281c9c33a54dd010 Mon Sep 17 00:00:00 2001 From: Anton Shalimov Date: Fri, 10 Jan 2025 14:21:03 +0300 Subject: [PATCH 03/39] fix: a flushing when changing the chain id --- modules/web3/hooks/use-dapp-status.ts | 3 ++- modules/web3/web3-provider/dapp-chain.tsx | 12 +++++++++--- .../components/chain-switcher/chain-switcher.tsx | 5 +++-- shared/wallet/button/button.tsx | 4 ++-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/modules/web3/hooks/use-dapp-status.ts b/modules/web3/hooks/use-dapp-status.ts index 1b450645e..c2ac5adb2 100644 --- a/modules/web3/hooks/use-dapp-status.ts +++ b/modules/web3/hooks/use-dapp-status.ts @@ -13,7 +13,7 @@ export const useDappStatus = () => { // this can change between pages based on their dapp-chain context(or lack of) const dappChain = useDappChain(); - const { isSupportedChain, isChainIdMatched } = dappChain; + const { isSupportedChain, isChainIdMatched, isSwitchChainWait } = dappChain; const isAccountActive = walletChainId ? isWalletConnected && isSupportedChain @@ -36,6 +36,7 @@ export const useDappStatus = () => { isDappActiveOnL2, isDappActiveOnL1, isWalletConnected, + isSwitchChainWait, walletChainId, address, }; diff --git a/modules/web3/web3-provider/dapp-chain.tsx b/modules/web3/web3-provider/dapp-chain.tsx index 71a47bdfe..68743da55 100644 --- a/modules/web3/web3-provider/dapp-chain.tsx +++ b/modules/web3/web3-provider/dapp-chain.tsx @@ -32,6 +32,7 @@ type DappChainContextValue = { supportedL2: boolean; supportedChainIds: number[]; isChainIdOnL2: boolean; + isSwitchChainWait: boolean; }; type UseDappChainValue = { @@ -115,6 +116,7 @@ export const SupportL2Chains: React.FC = ({ const { chainId: walletChain, isConnected } = useAccount(); const { switchChain } = useSwitchChain(); const [chainId, setChainIdInternal] = useState(config.defaultChain); + const [isSwitchChainWait, setIsSwitchChainWait] = useState(false); useEffect(() => { if (isConnected) { @@ -124,14 +126,16 @@ export const SupportL2Chains: React.FC = ({ : config.defaultChain; setChainIdInternal(chainId); + setIsSwitchChainWait(false); } - }, [walletChain, isConnected, setChainIdInternal]); + }, [walletChain, isConnected, setChainIdInternal, setIsSwitchChainWait]); const handleSetChainId = useCallback>( (newChainId) => { + setIsSwitchChainWait(true); switchChain({ chainId: newChainId }); }, - [switchChain], + [switchChain, setIsSwitchChainWait], ); return ( @@ -144,8 +148,9 @@ export const SupportL2Chains: React.FC = ({ supportedChainIds: config.supportedChains, isChainIdOnL2: getChainTypeByChainId(chainId) === DAPP_CHAIN_TYPE.Optimism, + isSwitchChainWait, }), - [chainId, handleSetChainId], + [chainId, handleSetChainId, isSwitchChainWait], )} > @@ -164,6 +169,7 @@ const onlyL1ChainsValue = { (chain) => !isSDKSupportedL2Chain(chain), ), isChainIdOnL2: false, + isSwitchChainWait: false, }; // Value of this context only allows L1 chains and no chain switch diff --git a/shared/components/layout/header/components/chain-switcher/chain-switcher.tsx b/shared/components/layout/header/components/chain-switcher/chain-switcher.tsx index 9a244fb74..f81d10343 100644 --- a/shared/components/layout/header/components/chain-switcher/chain-switcher.tsx +++ b/shared/components/layout/header/components/chain-switcher/chain-switcher.tsx @@ -42,7 +42,8 @@ const iconsMap: IconsMapType = config.supportedChains.reduce( ); export const ChainSwitcher: FC = () => { - const { isDappActive, chainId, setChainId, supportedL2 } = useDappStatus(); + const { isDappActive, chainId, setChainId, supportedL2, isSwitchChainWait } = + useDappStatus(); const [opened, setOpened] = useState(false); const selectRef = useRef(null); @@ -72,7 +73,7 @@ export const ChainSwitcher: FC = () => { opened={opened} options={iconsMap} /> - {!isDappActive && ( + {!isDappActive && !isSwitchChainWait && ( This network doesn’t match your wallet’s network diff --git a/shared/wallet/button/button.tsx b/shared/wallet/button/button.tsx index a3d538e20..f369f0450 100644 --- a/shared/wallet/button/button.tsx +++ b/shared/wallet/button/button.tsx @@ -19,7 +19,7 @@ export const Button: FC = (props) => { const { onClick, ...rest } = props; const isMobile = useBreakpoint('md'); - const { isDappActive, address } = useDappStatus(); + const { isDappActive, isSwitchChainWait, address } = useDappStatus(); const { openModal } = useWalletModal(); const { data: balance, isLoading } = useEthereumBalance(); @@ -35,7 +35,7 @@ export const Button: FC = (props) => { > - {isLoading ? ( + {isLoading || isSwitchChainWait ? ( ) : ( isDappActive && ( From 390e46c5340c3d2fbb73c6506a450d0caa2edb90 Mon Sep 17 00:00:00 2001 From: Anton Shalimov Date: Mon, 13 Jan 2025 10:04:58 +0300 Subject: [PATCH 04/39] feat: use the mutation of useSwitchChain --- modules/web3/hooks/use-dapp-status.ts | 3 ++- modules/web3/web3-provider/dapp-chain.tsx | 16 ++++++++++------ shared/wallet/fallback/useErrorMessage.ts | 3 ++- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/modules/web3/hooks/use-dapp-status.ts b/modules/web3/hooks/use-dapp-status.ts index c2ac5adb2..e213d4aa6 100644 --- a/modules/web3/hooks/use-dapp-status.ts +++ b/modules/web3/hooks/use-dapp-status.ts @@ -21,7 +21,8 @@ export const useDappStatus = () => { const isL2 = isSDKSupportedL2Chain(walletChainId); - const isDappActive = isAccountActive && isChainIdMatched; + const isDappActive = + isAccountActive && isChainIdMatched && !isSwitchChainWait; const isDappActiveOnL1 = isDappActive && !isL2; diff --git a/modules/web3/web3-provider/dapp-chain.tsx b/modules/web3/web3-provider/dapp-chain.tsx index 68743da55..67015054a 100644 --- a/modules/web3/web3-provider/dapp-chain.tsx +++ b/modules/web3/web3-provider/dapp-chain.tsx @@ -113,11 +113,17 @@ export const useDappChain = (): UseDappChainValue => { export const SupportL2Chains: React.FC = ({ children, }) => { - const { chainId: walletChain, isConnected } = useAccount(); - const { switchChain } = useSwitchChain(); const [chainId, setChainIdInternal] = useState(config.defaultChain); const [isSwitchChainWait, setIsSwitchChainWait] = useState(false); + const { chainId: walletChain, isConnected } = useAccount(); + const { switchChain } = useSwitchChain({ + mutation: { + onMutate: () => setIsSwitchChainWait(true), + onSettled: () => setIsSwitchChainWait(false), + }, + }); + useEffect(() => { if (isConnected) { const chainId = @@ -126,16 +132,14 @@ export const SupportL2Chains: React.FC = ({ : config.defaultChain; setChainIdInternal(chainId); - setIsSwitchChainWait(false); } - }, [walletChain, isConnected, setChainIdInternal, setIsSwitchChainWait]); + }, [walletChain, isConnected, setChainIdInternal]); const handleSetChainId = useCallback>( (newChainId) => { - setIsSwitchChainWait(true); switchChain({ chainId: newChainId }); }, - [switchChain, setIsSwitchChainWait], + [switchChain], ); return ( diff --git a/shared/wallet/fallback/useErrorMessage.ts b/shared/wallet/fallback/useErrorMessage.ts index 210647406..236346af2 100644 --- a/shared/wallet/fallback/useErrorMessage.ts +++ b/shared/wallet/fallback/useErrorMessage.ts @@ -14,13 +14,14 @@ export const useErrorMessage = (): string | undefined => { isAccountActive, chainId, isChainIdMatched, + isSwitchChainWait, supportedChainLabels, } = useDappStatus(); const { error } = useConnect(); // Errors from chain state - if (isAccountActive && !isChainIdMatched) { + if (isAccountActive && !isChainIdMatched && !isSwitchChainWait) { return `Wrong network. Please switch to ${wagmiChainMap[chainId].name} in your wallet to wrap/unwrap.`; } From 9cda0f1073388125046310cf4c8afcdf980aa625 Mon Sep 17 00:00:00 2001 From: Anton Shalimov Date: Mon, 13 Jan 2025 10:06:54 +0300 Subject: [PATCH 05/39] feat: remove a style fix --- shared/wallet/button/button.tsx | 4 +--- shared/wallet/button/styles.tsx | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/shared/wallet/button/button.tsx b/shared/wallet/button/button.tsx index f369f0450..f9b65030d 100644 --- a/shared/wallet/button/button.tsx +++ b/shared/wallet/button/button.tsx @@ -1,6 +1,6 @@ import { FC } from 'react'; import type { Address } from 'viem'; -import { ButtonProps, useBreakpoint } from '@lidofinance/lido-ui'; +import { ButtonProps } from '@lidofinance/lido-ui'; import { FormatToken } from 'shared/formatters'; import { useDappStatus, useEthereumBalance } from 'modules/web3'; @@ -18,7 +18,6 @@ import { export const Button: FC = (props) => { const { onClick, ...rest } = props; - const isMobile = useBreakpoint('md'); const { isDappActive, isSwitchChainWait, address } = useDappStatus(); const { openModal } = useWalletModal(); @@ -30,7 +29,6 @@ export const Button: FC = (props) => { variant="text" color="secondary" onClick={() => openModal({})} - $isAddPaddingLeft={!isLoading && !isDappActive && !isMobile} {...rest} > diff --git a/shared/wallet/button/styles.tsx b/shared/wallet/button/styles.tsx index 7091eba93..daac15b14 100644 --- a/shared/wallet/button/styles.tsx +++ b/shared/wallet/button/styles.tsx @@ -6,8 +6,6 @@ export const WalledButtonStyle = styled((props) =>