From f937a6d1ae90d7fd9ad91d32759ec1217397e6b8 Mon Sep 17 00:00:00 2001 From: viet-nv Date: Mon, 26 Aug 2024 16:30:45 +0700 Subject: [PATCH 01/10] feat: migrate from web3-react to wagmi --- package.json | 5 +- src/components/AddToMetamask/index.tsx | 8 +- .../components/DraggableNetworkButton.tsx | 6 +- src/components/Header/web3/SelectWallet.tsx | 20 +- .../Header/web3/WalletModal/Option.tsx | 33 +- .../Header/web3/WalletModal/PendingView.tsx | 112 -- .../Header/web3/WalletModal/index.tsx | 47 +- .../web3/WalletModal/useConnections.tsx | 108 +- src/components/LiveChart/index.tsx | 2 +- src/components/Pagination/index.tsx | 4 +- .../TransactionConfirmationModal/index.tsx | 7 +- src/components/WalletPopup/WalletView.tsx | 5 +- src/components/Web3Provider/index.tsx | 132 ++ src/connection/WalletConnectV2.ts | 61 - src/connection/activate.ts | 80 - src/connection/eagerlyConnect.ts | 74 - src/connection/eip6963/index.ts | 180 --- src/connection/eip6963/providers.ts | 66 - src/connection/eip6963/types.ts | 23 - src/connection/eip6963/utils.ts | 39 - src/connection/index.ts | 288 ---- src/connection/index.tsx | 44 + src/connection/meta.ts | 29 - src/connection/types.ts | 37 - src/connection/utils.ts | 94 -- src/connection/web3reactShim.ts | 19 + src/hooks/index.ts | 144 +- src/hooks/useAccount.ts | 27 + src/hooks/useEthersProvider.ts | 54 + src/hooks/useSwapCallbackV3.ts | 3 +- src/hooks/useSwapV2Callback.ts | 6 +- src/hooks/web3/useChangeNetwork.ts | 195 +-- src/hooks/web3/useDisconnectWallet.ts | 23 +- src/hooks/web3/useWalletSupportedChains.ts | 36 +- src/index.tsx | 27 +- src/state/index.ts | 1 - src/state/user/actions.ts | 4 - src/state/user/reducer.ts | 23 - vite.config.ts | 2 + yarn.lock | 1420 ++++++++++++++++- 40 files changed, 1942 insertions(+), 1546 deletions(-) delete mode 100644 src/components/Header/web3/WalletModal/PendingView.tsx create mode 100644 src/components/Web3Provider/index.tsx delete mode 100644 src/connection/WalletConnectV2.ts delete mode 100644 src/connection/activate.ts delete mode 100644 src/connection/eagerlyConnect.ts delete mode 100644 src/connection/eip6963/index.ts delete mode 100644 src/connection/eip6963/providers.ts delete mode 100644 src/connection/eip6963/types.ts delete mode 100644 src/connection/eip6963/utils.ts delete mode 100644 src/connection/index.ts create mode 100644 src/connection/index.tsx delete mode 100644 src/connection/meta.ts delete mode 100644 src/connection/types.ts delete mode 100644 src/connection/utils.ts create mode 100644 src/connection/web3reactShim.ts create mode 100644 src/hooks/useAccount.ts create mode 100644 src/hooks/useEthersProvider.ts diff --git a/package.json b/package.json index 5f0e0218b6..c04c472cd6 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "@reduxjs/toolkit": "1.9.3", "@sentry/react": "^7.18.0", "@sentry/tracing": "^7.18.0", + "@tanstack/react-query": "^5.52.1", "@use-gesture/react": "^10.2.27", "@web3-react/coinbase-wallet": "8.2.0", "@web3-react/core": "8.2.0", @@ -130,7 +131,9 @@ "swiper": "^8.4.4", "ua-parser-js": "^1.0.33", "util": "^0.12.5", + "viem": "^2.20.0", "vite-plugin-env-compatible": "^1.1.1", + "wagmi": "^2.12.7", "walktour": "^5.2.0", "wcag-contrast": "^3.0.0", "workbox-core": "^6.5.4", @@ -202,7 +205,7 @@ "prom-client": "^14.2.0", "storybook": "^7.6.2", "ts-node": "^10.9.1", - "typescript": "4.8.4", + "typescript": "5.3.3", "vite": "^4.3.9", "vite-plugin-checker": "^0.5.6", "vite-plugin-svgr": "^2.4.0", diff --git a/src/components/AddToMetamask/index.tsx b/src/components/AddToMetamask/index.tsx index da2b5efd60..df95a3e4ae 100644 --- a/src/components/AddToMetamask/index.tsx +++ b/src/components/AddToMetamask/index.tsx @@ -1,5 +1,4 @@ import { Token } from '@kyberswap/ks-sdk-core' -import { getConnection } from 'connection' import styled from 'styled-components' import { ButtonEmpty } from 'components/Button' @@ -15,7 +14,6 @@ const StyledLogo = styled.img` export default function AddTokenToMetaMask({ token }: { token: Token }) { const { chainId, walletKey } = useActiveWeb3React() const { connector } = useWeb3React() - const connection = getConnection(connector) async function addToMetaMask() { const tokenAddress = token.address @@ -40,14 +38,14 @@ export default function AddTokenToMetaMask({ token }: { token: Token }) { console.error(error) } } - const { icon } = connection.getProviderInfo() + const { icon } = connector || {} if (!walletKey || !icon) return null - if (walletKey === 'WALLET_CONNECT') return null + if (walletKey === 'WalletConnect') return null if (walletKey === 'COINBASE') return null // Coinbase wallet no need to add since it automatically track token return ( - + ) diff --git a/src/components/Header/web3/NetworkModal/components/DraggableNetworkButton.tsx b/src/components/Header/web3/NetworkModal/components/DraggableNetworkButton.tsx index b340c410b6..50be5ffc30 100644 --- a/src/components/Header/web3/NetworkModal/components/DraggableNetworkButton.tsx +++ b/src/components/Header/web3/NetworkModal/components/DraggableNetworkButton.tsx @@ -1,6 +1,5 @@ import { ChainId } from '@kyberswap/ks-sdk-core' import { Trans, t } from '@lingui/macro' -import { getConnection } from 'connection' import { motion, useAnimationControls, useDragControls } from 'framer-motion' import { rgba } from 'polished' import { stringify } from 'querystring' @@ -136,7 +135,6 @@ const DraggableNetworkButton = ({ const { isWrongNetwork, walletKey: walletName, chainId: walletChainId } = useActiveWeb3React() const { changeNetwork } = useChangeNetwork() const { connector, account } = useWeb3React() - const connection = getConnection(connector) const [dragging, setDragging] = useState(false) const ref = useRef(null) const dragControls = useDragControls() @@ -290,9 +288,9 @@ const DraggableNetworkButton = ({ )} {selected && !walletKey && } - {account && walletKey && connection.getProviderInfo().icon && ( + {account && walletKey && connector?.icon && ( - + )} diff --git a/src/components/Header/web3/SelectWallet.tsx b/src/components/Header/web3/SelectWallet.tsx index 1cbd84e0e4..f428d33350 100644 --- a/src/components/Header/web3/SelectWallet.tsx +++ b/src/components/Header/web3/SelectWallet.tsx @@ -1,7 +1,6 @@ import { Trans } from '@lingui/macro' -import { getConnection } from 'connection' import { darken, lighten } from 'polished' -import { useEffect, useMemo } from 'react' +import { useMemo } from 'react' import { Activity } from 'react-feather' import { useMedia } from 'react-use' import styled from 'styled-components' @@ -20,11 +19,9 @@ import useLogin from 'hooks/useLogin' import useMixpanel, { MIXPANEL_TYPE } from 'hooks/useMixpanel' import useTheme from 'hooks/useTheme' import { useNetworkModalToggle, useWalletModalToggle } from 'state/application/hooks' -import { useAppDispatch } from 'state/hooks' import { useSignedAccountInfo } from 'state/profile/hooks' import { isTransactionRecent, newTransactionsFirst, useAllTransactions } from 'state/transactions/hooks' import { TransactionDetails } from 'state/transactions/type' -import { updateRecentConnectionMeta } from 'state/user/actions' import { MEDIA_WIDTHS } from 'theme' import { shortenAddress } from 'utils' @@ -106,14 +103,12 @@ const AccountElement = styled.div` function Web3StatusInner() { const { chainId, account, walletKey, isWrongNetwork } = useActiveWeb3React() + const { connector } = useWeb3React() const { mixpanelHandler } = useMixpanel() const uptoMedium = useMedia(`(max-width: ${MEDIA_WIDTHS.upToMedium}px)`) const { signIn } = useLogin() const { ENSName } = useENSName(account ?? undefined) const theme = useTheme() - const { connector } = useWeb3React() - const connection = getConnection(connector) - const dispatch = useAppDispatch() const allTransactions = useAllTransactions() @@ -126,15 +121,6 @@ function Web3StatusInner() { const pendingLength = sortedRecentTransactions.filter(tx => !tx.receipt).length - useEffect(() => { - if (account || ENSName) { - const { rdns } = connection.getProviderInfo() - dispatch( - updateRecentConnectionMeta({ type: connection.type, address: account, ENSName: ENSName ?? undefined, rdns }), - ) - } - }, [ENSName, account, connection, dispatch]) - const hasPendingTransactions = !!pendingLength const toggleWalletModal = useWalletModalToggle() const toggleNetworkModal = useNetworkModalToggle() @@ -196,7 +182,7 @@ function Web3StatusInner() { ) : ( walletKey && ( - + ) )} diff --git a/src/components/Header/web3/WalletModal/Option.tsx b/src/components/Header/web3/WalletModal/Option.tsx index 6874d51113..7ae4d85e78 100644 --- a/src/components/Header/web3/WalletModal/Option.tsx +++ b/src/components/Header/web3/WalletModal/Option.tsx @@ -1,8 +1,7 @@ -import { ActivationStatus, useActivationState } from 'connection/activate' -import { Connection } from 'connection/types' import { darken } from 'polished' import React from 'react' import styled, { css } from 'styled-components' +import { Connector, useConnect } from 'wagmi' import { useActiveWeb3React } from 'hooks' import { useCloseModal } from 'state/application/hooks' @@ -98,33 +97,29 @@ const OptionCardLeft = styled.div` // } // ` -const Option = ({ connection }: { connection: Connection }) => { - const { activationState, tryActivation } = useActivationState() +const Option = ({ connector }: { connector: Connector }) => { const [isAcceptedTerm] = useIsAcceptedTerm() const { chainId } = useActiveWeb3React() - const { name, icon } = connection.getProviderInfo() - - const isSomeOptionPending = activationState.status === ActivationStatus.PENDING - const isCurrentOptionPending = isSomeOptionPending && activationState.connection === connection + const { name, icon } = connector const closeWalletModal = useCloseModal(ApplicationModal.WALLET) + const { variables, isPending: isSomeOptionPending } = useConnect({ + mutation: { + onSuccess: () => { + closeWalletModal() + }, + }, + }) + + const isCurrentOptionPending = isSomeOptionPending && variables.connector === connector const content = ( - isAcceptedTerm && - tryActivation( - connection, - () => { - closeWalletModal() - }, - chainId, - ) - } + id={`connect-${name}`} + onClick={() => isAcceptedTerm && connector.connect({ chainId })} connected={isCurrentOptionPending} isDisabled={!isAcceptedTerm} > diff --git a/src/components/Header/web3/WalletModal/PendingView.tsx b/src/components/Header/web3/WalletModal/PendingView.tsx deleted file mode 100644 index b9b73f7d80..0000000000 --- a/src/components/Header/web3/WalletModal/PendingView.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { Trans } from '@lingui/macro' -import { ActivationStatus, useActivationState } from 'connection/activate' -import { darken } from 'polished' -import styled from 'styled-components' - -import Loader from 'components/Loader' -import { useActiveWeb3React } from 'hooks' -import { useCloseModal } from 'state/application/hooks' -import { ApplicationModal } from 'state/application/types' - -const PendingSection = styled.div` - ${({ theme }) => theme.flexColumnNoWrap}; - align-items: center; - justify-content: center; - width: 100%; - & > * { - width: 100%; - } -` - -const StyledLoader = styled(Loader)` - margin-right: 1rem; -` - -const LoadingMessage = styled.div<{ hasError?: boolean }>` - ${({ theme }) => theme.flexRowNoWrap}; - align-items: center; - justify-content: flex-start; - border-radius: 16px; - color: ${({ theme, hasError }) => (hasError ? theme.red1 : 'inherit')}; - border: 1px solid ${({ theme, hasError }) => (hasError ? theme.red1 : theme.text4)}; - width: 100%; - & > * { - padding: 1rem; - } -` - -const ErrorGroup = styled.div` - ${({ theme }) => theme.flexRowNoWrap}; - align-items: center; - justify-content: space-between; - display: flex; - width: 100%; - ${({ theme }) => theme.mediaWidth.upToExtraSmall` - font-size:14px; - `} -` - -const ErrorButton = styled.div` - border-radius: 8px; - font-size: 12px; - color: ${({ theme }) => theme.primary}; - background-color: rgba(49, 203, 158, 0.2); - padding: 0.5rem; - font-weight: 600; - user-select: none; - min-width: 70px; - &:hover { - cursor: pointer; - background-color: ${({ theme }) => darken(0.1, theme.bg5)}; - } -` - -const LoadingWrapper = styled.div` - ${({ theme }) => theme.flexRowNoWrap}; - align-items: center; - width: 100%; -` - -export default function PendingView() { - const { activationState, tryActivation } = useActivationState() - - const { chainId } = useActiveWeb3React() - const closeWalletModal = useCloseModal(ApplicationModal.WALLET) - if (activationState.status === ActivationStatus.IDLE) return null - - const { name } = activationState.connection.getProviderInfo() - - return ( - - - - {activationState.status === ActivationStatus.ERROR ? ( - -
- Error connecting to {name}. -
- - tryActivation( - activationState.connection, - () => { - closeWalletModal() - }, - chainId, - ) - } - > - Try Again - -
- ) : ( - <> - - Initializing with {name}... - - )} -
-
-
- ) -} diff --git a/src/components/Header/web3/WalletModal/index.tsx b/src/components/Header/web3/WalletModal/index.tsx index 667003c03a..13436dcc5f 100644 --- a/src/components/Header/web3/WalletModal/index.tsx +++ b/src/components/Header/web3/WalletModal/index.tsx @@ -1,12 +1,11 @@ import { Trans } from '@lingui/macro' -import { ActivationStatus, useActivationState } from 'connection/activate' -import { ConnectionType } from 'connection/types' import dayjs from 'dayjs' import { rgba } from 'polished' import { useEffect, useState } from 'react' import { ChevronLeft } from 'react-feather' import { Text } from 'rebass' import styled from 'styled-components' +import { useConnect } from 'wagmi' import { ReactComponent as Close } from 'assets/images/x.svg' import Modal from 'components/Modal' @@ -24,13 +23,11 @@ import { useOpenNetworkModal, useWalletModalToggle, } from 'state/application/hooks' -import { useAppDispatch } from 'state/hooks' -import { clearRecentConnectionMeta } from 'state/user/actions' import { useIsAcceptedTerm } from 'state/user/hooks' import { ExternalLink } from 'theme' -import PendingView from './PendingView' -import { useConnections } from './useConnections' +import Option from './Option' +import { useOrderedConnections } from './useConnections' const CloseIcon = styled.div` height: 24px; @@ -108,10 +105,7 @@ const HoverText = styled.div` export default function WalletModal() { const { isWrongNetwork, account } = useActiveWeb3React() - const { activationState, cancelActivation } = useActivationState() - const theme = useTheme() - const dispatch = useAppDispatch() const walletModalOpen = useModalOpen(ApplicationModal.WALLET) const toggleWalletModal = useWalletModalToggle() @@ -119,8 +113,9 @@ export default function WalletModal() { const openWalletModal = useOpenModal(ApplicationModal.WALLET) const openNetworkModal = useOpenNetworkModal() + const { isPending: isSomeOptionPending, isIdle, isError, reset } = useConnect() const onDismiss = () => { - cancelActivation() + reset() closeWalletModal() } @@ -134,20 +129,18 @@ export default function WalletModal() { } }, [isWrongNetwork, openNetworkModal]) - const { orderedConnections } = useConnections() + const connectors = useOrderedConnections() const [isPinnedPopupWallet, setPinnedPopupWallet] = useState(false) - const isSomeOptionPending = activationState.status === ActivationStatus.PENDING - function getModalContent() { return ( - {(isSomeOptionPending || activationState.status === ActivationStatus.ERROR) && ( + {(isSomeOptionPending || isError) && ( { - cancelActivation() + reset() }} style={{ marginRight: '1rem', flex: 1 }} > @@ -159,20 +152,18 @@ export default function WalletModal() { { - cancelActivation() + reset() toggleWalletModal() }} > - {activationState.status === ActivationStatus.IDLE && ( + {isIdle && ( { if (!isAcceptedTerm) { mixpanelHandler(MIXPANEL_TYPE.WALLET_CONNECT_ACCEPT_TERM_CLICK) - } else { - dispatch(clearRecentConnectionMeta()) } setIsAcceptedTerm(!isAcceptedTerm) }} @@ -200,11 +191,11 @@ export default function WalletModal() { )} - {activationState.status !== ActivationStatus.IDLE ? ( - - ) : ( - {orderedConnections} - )} + + {connectors.map(c => ( + ) @@ -229,9 +220,13 @@ export default function WalletModal() { minHeight={false} maxHeight={90} maxWidth={600} - bypassScrollLock={isSomeOptionPending && activationState.connection.type === ConnectionType.WALLET_CONNECT_V2} + bypassScrollLock={ + isSomeOptionPending + //&& activationState.connection.type === ConnectionType.WALLET_CONNECT_V2 + } bypassFocusLock={ - isSomeOptionPending && activationState.connection.type === ConnectionType.WALLET_CONNECT_V2 + isSomeOptionPending + //&& activationState.connection.type === ConnectionType.WALLET_CONNECT_V2 // walletView === WALLET_VIEWS.PENDING && ['WALLET_CONNECT', 'KRYSTAL_WC', 'BLOCTO'].includes(pendingWalletKey) } > diff --git a/src/components/Header/web3/WalletModal/useConnections.tsx b/src/components/Header/web3/WalletModal/useConnections.tsx index 8e5e3c5263..aa3ffbcc43 100644 --- a/src/components/Header/web3/WalletModal/useConnections.tsx +++ b/src/components/Header/web3/WalletModal/useConnections.tsx @@ -1,69 +1,67 @@ -import { connections, eip6963Connection } from 'connection' -import { useInjectedProviderDetails } from 'connection/eip6963/providers' -import { Connection, ConnectionType, RecentConnectionMeta } from 'connection/types' -import { shouldUseDeprecatedInjector } from 'connection/utils' +import { CONNECTION, getConnectorWithId } from 'connection' import { useMemo } from 'react' +import { isMobile } from 'react-device-detect' +import { Connector, useConnect } from 'wagmi' + +function getInjectedConnectors(connectors: readonly Connector[]) { + let isCoinbaseWalletBrowser = false + const injectedConnectors = connectors.filter(c => { + // Special-case: Ignore coinbase eip6963-injected connector; coinbase connection is handled via the SDK connector. + if (c.id === CONNECTION.COINBASE_RDNS) { + if (isMobile) { + isCoinbaseWalletBrowser = true + } + return false + } + + return c.type === CONNECTION.INJECTED_CONNECTOR_TYPE && c.id !== CONNECTION.INJECTED_CONNECTOR_ID + }) + + // Special-case: Return deprecated window.ethereum connector when no eip6963 injectors are present. + const fallbackInjector = getConnectorWithId(connectors, CONNECTION.INJECTED_CONNECTOR_ID, { shouldThrow: true }) + if (!injectedConnectors.length && Boolean(window.ethereum)) { + return { injectedConnectors: [fallbackInjector], isCoinbaseWalletBrowser } + } -import { useAppSelector } from 'state/hooks' - -import Option from './Option' - -function useEIP6963Connections() { - const eip6963Injectors = useInjectedProviderDetails() - const eip6963Enabled = true - - return useMemo(() => { - if (!eip6963Enabled) return { eip6963Connections: [], showDeprecatedMessage: false } - - const eip6963Connections = eip6963Injectors.flatMap(injector => eip6963Connection.wrap(injector.info) ?? []) - - // Displays ui to activate window.ethereum for edge-case where we detect window.ethereum !== one of the eip6963 providers - const showDeprecatedMessage = eip6963Connections.length > 0 && shouldUseDeprecatedInjector(eip6963Injectors) - - return { eip6963Connections, showDeprecatedMessage } - }, [eip6963Injectors, eip6963Enabled]) + return { injectedConnectors, isCoinbaseWalletBrowser } } -function mergeConnections(connections: Connection[], eip6963Connections: Connection[]) { - const hasEip6963Connections = eip6963Connections.length > 0 - const displayedConnections = connections.filter(c => c.shouldDisplay()) - - if (!hasEip6963Connections) return displayedConnections +type InjectableConnector = Connector & { isInjected?: boolean } +export function useOrderedConnections(): InjectableConnector[] { + const { connectors } = useConnect() - const allConnections = [...displayedConnections.filter(c => c.type !== ConnectionType.INJECTED)] - // By default, injected options should appear second in the list (below Uniswap wallet) - allConnections.splice(1, 0, ...eip6963Connections) + return useMemo(() => { + const { injectedConnectors: injectedConnectorsBase, isCoinbaseWalletBrowser } = getInjectedConnectors(connectors) + const injectedConnectors = injectedConnectorsBase.map(c => ({ ...c, isInjected: true })) - return allConnections -} + const coinbaseSdkConnector = getConnectorWithId(connectors, CONNECTION.COINBASE_SDK_CONNECTOR_ID) + const walletConnectConnector = getConnectorWithId(connectors, CONNECTION.WALLET_CONNECT_CONNECTOR_ID) -// TODO(WEB-3244) Improve ordering logic to make less brittle, as it is spread across connections/index.ts and here -/** Returns an array of all connection Options that should be displayed, where the recent connection is first in the array */ -function getOrderedConnections(connections: Connection[], recentConnection: RecentConnectionMeta | undefined) { - const list: JSX.Element[] = [] - for (const connection of connections) { - if (!connection.shouldDisplay()) continue - const { name, rdns } = connection.getProviderInfo() + if (!coinbaseSdkConnector || !walletConnectConnector) { + throw new Error('Expected connector(s) missing from wagmi context.') + } - // For eip6963 injectors, we need to check rdns in addition to connection type to ensure it's the recent connection - const isRecent = connection.type === recentConnection?.type && (!rdns || rdns === recentConnection.rdns) + // Special-case: Only display the injected connector for in-wallet browsers. + if (isMobile && injectedConnectors.length === 1) { + return injectedConnectors + } - const option =