feat(governance): eth chain should not inhibit usage (#3568)
This commit is contained in:
parent
8a59722f09
commit
8d42481130
@ -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(
|
||||||
|
<DisconnectedNotice isDisconnected={true} correctNetworkChainId={'1'} />
|
||||||
|
);
|
||||||
|
const disconnectedNotice = screen.getByTestId('disconnected-notice');
|
||||||
|
expect(disconnectedNotice).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't render Notification when isDisconnected is false", () => {
|
||||||
|
render(
|
||||||
|
<DisconnectedNotice isDisconnected={false} correctNetworkChainId={'1'} />
|
||||||
|
);
|
||||||
|
const disconnectedNotice = screen.queryByTestId('disconnected-notice');
|
||||||
|
expect(disconnectedNotice).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't render Notification when correctNetworkChainId is undefined", () => {
|
||||||
|
render(
|
||||||
|
<DisconnectedNotice
|
||||||
|
isDisconnected={true}
|
||||||
|
correctNetworkChainId={undefined}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
const disconnectedNotice = screen.queryByTestId('disconnected-notice');
|
||||||
|
expect(disconnectedNotice).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't render Notification when correctNetworkChainId is null", () => {
|
||||||
|
render(
|
||||||
|
<DisconnectedNotice isDisconnected={true} correctNetworkChainId={null} />
|
||||||
|
);
|
||||||
|
const disconnectedNotice = screen.queryByTestId('disconnected-notice');
|
||||||
|
expect(disconnectedNotice).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
@ -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 (
|
||||||
|
<div className="col-span-full" data-testid="disconnected-notice">
|
||||||
|
<Notification
|
||||||
|
message={t('disconnectedNotice', {
|
||||||
|
correctNetwork: correctNetworkChainId,
|
||||||
|
})}
|
||||||
|
intent={Intent.Danger}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
export * from './disconnected-notice';
|
@ -3,6 +3,7 @@ import React from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { DisconnectedNotice } from '../disconnected-notice';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AppStateActionType,
|
AppStateActionType,
|
||||||
@ -26,7 +27,8 @@ import {
|
|||||||
import { Loader } from '@vegaprotocol/ui-toolkit';
|
import { Loader } from '@vegaprotocol/ui-toolkit';
|
||||||
import colors from 'tailwindcss/colors';
|
import colors from 'tailwindcss/colors';
|
||||||
import { useBalances } from '../../lib/balances/balances-store';
|
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) => {
|
const removeLeadingAddressSymbol = (key: string) => {
|
||||||
if (key && key.length > 2 && key.slice(0, 2) === '0x') {
|
if (key && key.length > 2 && key.slice(0, 2) === '0x') {
|
||||||
@ -182,16 +184,21 @@ const ConnectedKey = () => {
|
|||||||
|
|
||||||
export const EthWallet = () => {
|
export const EthWallet = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { appDispatch } = useAppState();
|
const { appDispatch, appState } = useAppState();
|
||||||
const { account, connector } = useWeb3React();
|
const { account, connector } = useWeb3React();
|
||||||
const pendingTxs = usePendingTransactions();
|
const pendingTxs = usePendingTransactions();
|
||||||
const disconnect = useWeb3Disconnect(connector);
|
const disconnect = useWeb3Disconnect(connector);
|
||||||
|
const { config } = useEthereumConfig();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WalletCard>
|
<WalletCard>
|
||||||
<section data-testid="ethereum-wallet">
|
<section data-testid="ethereum-wallet">
|
||||||
<WalletCardHeader>
|
<WalletCardHeader>
|
||||||
<h1 className="m-0 uppercase">{t('ethereumKey')}</h1>
|
<h1 className="m-0 uppercase">{t('ethereumKey')}</h1>
|
||||||
|
<DisconnectedNotice
|
||||||
|
isDisconnected={appState.disconnectNotice}
|
||||||
|
correctNetworkChainId={getChainName(Number(config?.chain_id))}
|
||||||
|
/>
|
||||||
{account && (
|
{account && (
|
||||||
<div className="place-self-end font-mono">
|
<div className="place-self-end font-mono">
|
||||||
<div
|
<div
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
|
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
import {
|
import {
|
||||||
getChainName,
|
|
||||||
useWeb3ConnectStore,
|
useWeb3ConnectStore,
|
||||||
useWeb3Disconnect,
|
useWeb3Disconnect,
|
||||||
Web3ConnectDialog,
|
Web3ConnectDialog,
|
||||||
} from '@vegaprotocol/web3';
|
} from '@vegaprotocol/web3';
|
||||||
import { useWeb3React } from '@web3-react/core';
|
import { useWeb3React } from '@web3-react/core';
|
||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
AppStateActionType,
|
AppStateActionType,
|
||||||
useAppState,
|
useAppState,
|
||||||
@ -36,9 +35,7 @@ export function Web3Connector({
|
|||||||
const appChainId = Number(chainId);
|
const appChainId = Number(chainId);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Web3Content appChainId={appChainId} setDialogOpen={setDialogOpen}>
|
<Web3Content appChainId={appChainId}>{children}</Web3Content>
|
||||||
{children}
|
|
||||||
</Web3Content>
|
|
||||||
<Web3ConnectDialog
|
<Web3ConnectDialog
|
||||||
connectors={connectors}
|
connectors={connectors}
|
||||||
dialogOpen={appState.ethConnectOverlay}
|
dialogOpen={appState.ethConnectOverlay}
|
||||||
@ -52,23 +49,59 @@ export function Web3Connector({
|
|||||||
interface Web3ContentProps {
|
interface Web3ContentProps {
|
||||||
children: ReactElement;
|
children: ReactElement;
|
||||||
appChainId: number;
|
appChainId: number;
|
||||||
setDialogOpen: (isOpen: boolean) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Web3Content = ({ children, appChainId }: Web3ContentProps) => {
|
export const Web3Content = ({ children, appChainId }: Web3ContentProps) => {
|
||||||
|
const { appState, appDispatch } = useAppState();
|
||||||
const { connector, chainId } = useWeb3React();
|
const { connector, chainId } = useWeb3React();
|
||||||
|
const [previousChainId, setPreviousChainId] = useState(chainId);
|
||||||
const error = useWeb3ConnectStore((store) => store.error);
|
const error = useWeb3ConnectStore((store) => store.error);
|
||||||
const disconnect = useWeb3Disconnect(connector);
|
const disconnect = useWeb3Disconnect(connector);
|
||||||
|
|
||||||
|
const showDisconnectNotice = useCallback(
|
||||||
|
(isVisible: boolean) =>
|
||||||
|
appDispatch({
|
||||||
|
type: AppStateActionType.SET_DISCONNECT_NOTICE,
|
||||||
|
isVisible,
|
||||||
|
}),
|
||||||
|
[appDispatch]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (connector?.connectEagerly) {
|
if (connector?.connectEagerly) {
|
||||||
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.
|
// deps array.
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// 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) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<Splash>
|
<Splash>
|
||||||
@ -80,18 +113,5 @@ export const Web3Content = ({ children, appChainId }: Web3ContentProps) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chainId !== undefined && chainId !== appChainId) {
|
|
||||||
return (
|
|
||||||
<Splash>
|
|
||||||
<div className="flex flex-col items-center gap-12">
|
|
||||||
<p className="text-white">
|
|
||||||
This app only works on {getChainName(appChainId)}
|
|
||||||
</p>
|
|
||||||
<Button onClick={() => disconnect()}>Disconnect</Button>
|
|
||||||
</div>
|
|
||||||
</Splash>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return children;
|
return children;
|
||||||
};
|
};
|
||||||
|
@ -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
|
* Message to display in a banner at the top of the screen, currently always shown as a warning/error
|
||||||
*/
|
*/
|
||||||
bannerMessage: string;
|
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 {
|
export enum AppStateActionType {
|
||||||
@ -58,6 +63,7 @@ export enum AppStateActionType {
|
|||||||
SET_ASSOCIATION_BREAKDOWN,
|
SET_ASSOCIATION_BREAKDOWN,
|
||||||
SET_TRANSACTION_OVERLAY,
|
SET_TRANSACTION_OVERLAY,
|
||||||
SET_BANNER_MESSAGE,
|
SET_BANNER_MESSAGE,
|
||||||
|
SET_DISCONNECT_NOTICE,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AppStateAction =
|
export type AppStateAction =
|
||||||
@ -90,6 +96,10 @@ export type AppStateAction =
|
|||||||
| {
|
| {
|
||||||
type: AppStateActionType.SET_BANNER_MESSAGE;
|
type: AppStateActionType.SET_BANNER_MESSAGE;
|
||||||
message: string;
|
message: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: AppStateActionType.SET_DISCONNECT_NOTICE;
|
||||||
|
isVisible: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type AppStateContextShape = {
|
type AppStateContextShape = {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { BigNumber } from '../../lib/bignumber';
|
import { BigNumber } from '../../lib/bignumber';
|
||||||
import { AppStateActionType, AppStateContext } from './app-state-context';
|
|
||||||
import type { AppState, AppStateAction } from './app-state-context';
|
import type { AppState, AppStateAction } from './app-state-context';
|
||||||
|
import { AppStateActionType, AppStateContext } from './app-state-context';
|
||||||
|
|
||||||
interface AppStateProviderProps {
|
interface AppStateProviderProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@ -19,6 +19,7 @@ const initialAppState: AppState = {
|
|||||||
ethConnectOverlay: false,
|
ethConnectOverlay: false,
|
||||||
transactionOverlay: false,
|
transactionOverlay: false,
|
||||||
bannerMessage: '',
|
bannerMessage: '',
|
||||||
|
disconnectNotice: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
function appStateReducer(state: AppState, action: AppStateAction): AppState {
|
function appStateReducer(state: AppState, action: AppStateAction): AppState {
|
||||||
@ -68,6 +69,12 @@ function appStateReducer(state: AppState, action: AppStateAction): AppState {
|
|||||||
bannerMessage: action.message,
|
bannerMessage: action.message,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case AppStateActionType.SET_DISCONNECT_NOTICE: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
disconnectNotice: action.isVisible,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -790,5 +790,6 @@
|
|||||||
"approval (% validator voting power)": "approval (% validator voting power)",
|
"approval (% validator voting power)": "approval (% validator voting power)",
|
||||||
"67% voting power required": "67% voting power required",
|
"67% voting power required": "67% voting power required",
|
||||||
"Token": "Token",
|
"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."
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,7 @@ const mockAppState: AppState = {
|
|||||||
ethConnectOverlay: false,
|
ethConnectOverlay: false,
|
||||||
transactionOverlay: false,
|
transactionOverlay: false,
|
||||||
bannerMessage: '',
|
bannerMessage: '',
|
||||||
|
disconnectNotice: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
jest.mock('../../../contexts/app-state/app-state-context', () => ({
|
jest.mock('../../../contexts/app-state/app-state-context', () => ({
|
||||||
|
@ -19,6 +19,7 @@ const mockAppState: AppState = {
|
|||||||
ethConnectOverlay: false,
|
ethConnectOverlay: false,
|
||||||
transactionOverlay: false,
|
transactionOverlay: false,
|
||||||
bannerMessage: '',
|
bannerMessage: '',
|
||||||
|
disconnectNotice: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
jest.mock('../../../contexts/app-state/app-state-context', () => ({
|
jest.mock('../../../contexts/app-state/app-state-context', () => ({
|
||||||
|
Loading…
Reference in New Issue
Block a user