From 8d42481130994e70b5555a761e2d29c9de8bede0 Mon Sep 17 00:00:00 2001 From: Sam Keen Date: Tue, 2 May 2023 13:31:16 +0100 Subject: [PATCH] feat(governance): eth chain should not inhibit usage (#3568) --- .../disconnected-notice.spec.tsx | 39 ++++++++++++ .../disconnected-notice.tsx | 33 ++++++++++ .../components/disconnected-notice/index.tsx | 1 + .../src/components/eth-wallet/eth-wallet.tsx | 11 +++- .../web3-connector/web3-connector.tsx | 60 ++++++++++++------- .../contexts/app-state/app-state-context.ts | 10 ++++ .../contexts/app-state/app-state-provider.tsx | 9 ++- .../governance/src/i18n/translations/dev.json | 3 +- .../components/list-asset/list-asset.spec.tsx | 1 + .../hooks/use-vote-information.spec.ts | 1 + 10 files changed, 144 insertions(+), 24 deletions(-) create mode 100644 apps/governance/src/components/disconnected-notice/disconnected-notice.spec.tsx create mode 100644 apps/governance/src/components/disconnected-notice/disconnected-notice.tsx create mode 100644 apps/governance/src/components/disconnected-notice/index.tsx diff --git a/apps/governance/src/components/disconnected-notice/disconnected-notice.spec.tsx b/apps/governance/src/components/disconnected-notice/disconnected-notice.spec.tsx new file mode 100644 index 000000000..f22924342 --- /dev/null +++ b/apps/governance/src/components/disconnected-notice/disconnected-notice.spec.tsx @@ -0,0 +1,39 @@ +import { render, screen } from '@testing-library/react'; +import { DisconnectedNotice } from './disconnected-notice'; + +describe('DisconnectedNotice', () => { + it('renders Notification when isDisconnected is true and correctNetworkChainId is valid', () => { + render( + + ); + const disconnectedNotice = screen.getByTestId('disconnected-notice'); + expect(disconnectedNotice).toBeInTheDocument(); + }); + + it("doesn't render Notification when isDisconnected is false", () => { + render( + + ); + const disconnectedNotice = screen.queryByTestId('disconnected-notice'); + expect(disconnectedNotice).not.toBeInTheDocument(); + }); + + it("doesn't render Notification when correctNetworkChainId is undefined", () => { + render( + + ); + const disconnectedNotice = screen.queryByTestId('disconnected-notice'); + expect(disconnectedNotice).not.toBeInTheDocument(); + }); + + it("doesn't render Notification when correctNetworkChainId is null", () => { + render( + + ); + const disconnectedNotice = screen.queryByTestId('disconnected-notice'); + expect(disconnectedNotice).not.toBeInTheDocument(); + }); +}); diff --git a/apps/governance/src/components/disconnected-notice/disconnected-notice.tsx b/apps/governance/src/components/disconnected-notice/disconnected-notice.tsx new file mode 100644 index 000000000..efd63ceb1 --- /dev/null +++ b/apps/governance/src/components/disconnected-notice/disconnected-notice.tsx @@ -0,0 +1,33 @@ +import { useTranslation } from 'react-i18next'; +import { Intent, Notification } from '@vegaprotocol/ui-toolkit'; + +interface DisconnectedNoticeProps { + isDisconnected: boolean; + correctNetworkChainId?: string | null; +} + +export const DisconnectedNotice = ({ + isDisconnected, + correctNetworkChainId, +}: DisconnectedNoticeProps) => { + const { t } = useTranslation(); + + if ( + !isDisconnected || + correctNetworkChainId === undefined || + correctNetworkChainId === null + ) { + return null; + } + + return ( +
+ +
+ ); +}; diff --git a/apps/governance/src/components/disconnected-notice/index.tsx b/apps/governance/src/components/disconnected-notice/index.tsx new file mode 100644 index 000000000..943ebd09b --- /dev/null +++ b/apps/governance/src/components/disconnected-notice/index.tsx @@ -0,0 +1 @@ +export * from './disconnected-notice'; diff --git a/apps/governance/src/components/eth-wallet/eth-wallet.tsx b/apps/governance/src/components/eth-wallet/eth-wallet.tsx index f095ed20a..ca334a43d 100644 --- a/apps/governance/src/components/eth-wallet/eth-wallet.tsx +++ b/apps/governance/src/components/eth-wallet/eth-wallet.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; import { Button } from '@vegaprotocol/ui-toolkit'; +import { DisconnectedNotice } from '../disconnected-notice'; import { AppStateActionType, @@ -26,7 +27,8 @@ import { import { Loader } from '@vegaprotocol/ui-toolkit'; import colors from 'tailwindcss/colors'; import { useBalances } from '../../lib/balances/balances-store'; -import { useWeb3Disconnect } from '@vegaprotocol/web3'; +import { useEthereumConfig, useWeb3Disconnect } from '@vegaprotocol/web3'; +import { getChainName } from '@vegaprotocol/web3'; const removeLeadingAddressSymbol = (key: string) => { if (key && key.length > 2 && key.slice(0, 2) === '0x') { @@ -182,16 +184,21 @@ const ConnectedKey = () => { export const EthWallet = () => { const { t } = useTranslation(); - const { appDispatch } = useAppState(); + const { appDispatch, appState } = useAppState(); const { account, connector } = useWeb3React(); const pendingTxs = usePendingTransactions(); const disconnect = useWeb3Disconnect(connector); + const { config } = useEthereumConfig(); return (

{t('ethereumKey')}

+ {account && (
- - {children} - + {children} void; } export const Web3Content = ({ children, appChainId }: Web3ContentProps) => { + const { appState, appDispatch } = useAppState(); const { connector, chainId } = useWeb3React(); + const [previousChainId, setPreviousChainId] = useState(chainId); const error = useWeb3ConnectStore((store) => store.error); const disconnect = useWeb3Disconnect(connector); + const showDisconnectNotice = useCallback( + (isVisible: boolean) => + appDispatch({ + type: AppStateActionType.SET_DISCONNECT_NOTICE, + isVisible, + }), + [appDispatch] + ); + useEffect(() => { if (connector?.connectEagerly) { connector.connectEagerly(); } - // wallet connect doesnt handle connectEagerly being called when connector is also in the + // wallet connect doesn't handle connectEagerly being called when connector is also in the // deps array. // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + useEffect(() => { + if (chainId !== undefined) { + // We use this to detect when the user switches networks. + setPreviousChainId(chainId); + + if (chainId !== appChainId) { + disconnect(); + + // If the user was previously connected, show the disconnect explanation notice. + if (previousChainId !== undefined && !appState.disconnectNotice) { + showDisconnectNotice(true); + } + } else if (appState.disconnectNotice) { + showDisconnectNotice(false); + } + } + }, [ + appChainId, + appDispatch, + appState.disconnectNotice, + chainId, + disconnect, + previousChainId, + showDisconnectNotice, + ]); + if (error) { return ( @@ -80,18 +113,5 @@ export const Web3Content = ({ children, appChainId }: Web3ContentProps) => { ); } - if (chainId !== undefined && chainId !== appChainId) { - return ( - -
-

- This app only works on {getChainName(appChainId)} -

- -
-
- ); - } - return children; }; diff --git a/apps/governance/src/contexts/app-state/app-state-context.ts b/apps/governance/src/contexts/app-state/app-state-context.ts index c2b8c3dce..054899d50 100644 --- a/apps/governance/src/contexts/app-state/app-state-context.ts +++ b/apps/governance/src/contexts/app-state/app-state-context.ts @@ -43,6 +43,11 @@ export interface AppState { * Message to display in a banner at the top of the screen, currently always shown as a warning/error */ bannerMessage: string; + /** + * Displays a notice to the user that they have been disconnected because they've changed their + * ethereum network to an incompatible one. + */ + disconnectNotice: boolean; } export enum AppStateActionType { @@ -58,6 +63,7 @@ export enum AppStateActionType { SET_ASSOCIATION_BREAKDOWN, SET_TRANSACTION_OVERLAY, SET_BANNER_MESSAGE, + SET_DISCONNECT_NOTICE, } export type AppStateAction = @@ -90,6 +96,10 @@ export type AppStateAction = | { type: AppStateActionType.SET_BANNER_MESSAGE; message: string; + } + | { + type: AppStateActionType.SET_DISCONNECT_NOTICE; + isVisible: boolean; }; type AppStateContextShape = { diff --git a/apps/governance/src/contexts/app-state/app-state-provider.tsx b/apps/governance/src/contexts/app-state/app-state-provider.tsx index 2b65344a9..182a9aee1 100644 --- a/apps/governance/src/contexts/app-state/app-state-provider.tsx +++ b/apps/governance/src/contexts/app-state/app-state-provider.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { BigNumber } from '../../lib/bignumber'; -import { AppStateActionType, AppStateContext } from './app-state-context'; import type { AppState, AppStateAction } from './app-state-context'; +import { AppStateActionType, AppStateContext } from './app-state-context'; interface AppStateProviderProps { children: React.ReactNode; @@ -19,6 +19,7 @@ const initialAppState: AppState = { ethConnectOverlay: false, transactionOverlay: false, bannerMessage: '', + disconnectNotice: false, }; function appStateReducer(state: AppState, action: AppStateAction): AppState { @@ -68,6 +69,12 @@ function appStateReducer(state: AppState, action: AppStateAction): AppState { bannerMessage: action.message, }; } + case AppStateActionType.SET_DISCONNECT_NOTICE: { + return { + ...state, + disconnectNotice: action.isVisible, + }; + } } } diff --git a/apps/governance/src/i18n/translations/dev.json b/apps/governance/src/i18n/translations/dev.json index 597361a04..e18e6107d 100644 --- a/apps/governance/src/i18n/translations/dev.json +++ b/apps/governance/src/i18n/translations/dev.json @@ -790,5 +790,6 @@ "approval (% validator voting power)": "approval (% validator voting power)", "67% voting power required": "67% voting power required", "Token": "Token", - "associateVegaNow": "Associate $VEGA now" + "associateVegaNow": "Associate $VEGA now", + "disconnectedNotice": "You have been disconnected. Connect your ETH wallet to the {{correctNetwork}} network to use this app." } diff --git a/apps/governance/src/routes/proposals/components/list-asset/list-asset.spec.tsx b/apps/governance/src/routes/proposals/components/list-asset/list-asset.spec.tsx index fa1026151..5cf4dfce3 100644 --- a/apps/governance/src/routes/proposals/components/list-asset/list-asset.spec.tsx +++ b/apps/governance/src/routes/proposals/components/list-asset/list-asset.spec.tsx @@ -56,6 +56,7 @@ const mockAppState: AppState = { ethConnectOverlay: false, transactionOverlay: false, bannerMessage: '', + disconnectNotice: false, }; jest.mock('../../../contexts/app-state/app-state-context', () => ({ diff --git a/apps/governance/src/routes/proposals/hooks/use-vote-information.spec.ts b/apps/governance/src/routes/proposals/hooks/use-vote-information.spec.ts index 7725d3626..c79bc73fc 100644 --- a/apps/governance/src/routes/proposals/hooks/use-vote-information.spec.ts +++ b/apps/governance/src/routes/proposals/hooks/use-vote-information.spec.ts @@ -19,6 +19,7 @@ const mockAppState: AppState = { ethConnectOverlay: false, transactionOverlay: false, bannerMessage: '', + disconnectNotice: false, }; jest.mock('../../../contexts/app-state/app-state-context', () => ({