feat(wallet): new browser wallet connection model (#5572)
Co-authored-by: Dexter Edwards <dexter.edwards93@gmail.com>
This commit is contained in:
parent
3dab5f3d9b
commit
39907f07db
@ -26,7 +26,7 @@ import {
|
|||||||
} from '@vegaprotocol/web3';
|
} from '@vegaprotocol/web3';
|
||||||
import { Web3Provider } from '@vegaprotocol/web3';
|
import { Web3Provider } from '@vegaprotocol/web3';
|
||||||
import { VegaWalletDialogs } from './components/vega-wallet-dialogs';
|
import { VegaWalletDialogs } from './components/vega-wallet-dialogs';
|
||||||
import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
import { VegaWalletProvider, useChainId } from '@vegaprotocol/wallet';
|
||||||
import {
|
import {
|
||||||
useVegaTransactionManager,
|
useVegaTransactionManager,
|
||||||
useVegaTransactionUpdater,
|
useVegaTransactionUpdater,
|
||||||
@ -96,7 +96,9 @@ const cache: InMemoryCacheConfig = {
|
|||||||
const Web3Container = ({
|
const Web3Container = ({
|
||||||
chainId,
|
chainId,
|
||||||
}: {
|
}: {
|
||||||
|
/** Ethereum chain id */
|
||||||
chainId: number;
|
chainId: number;
|
||||||
|
/** Ethereum provider url */
|
||||||
providerUrl: string;
|
providerUrl: string;
|
||||||
}) => {
|
}) => {
|
||||||
const InitializeHandlers = () => {
|
const InitializeHandlers = () => {
|
||||||
@ -123,6 +125,9 @@ const Web3Container = ({
|
|||||||
MOZILLA_EXTENSION_URL,
|
MOZILLA_EXTENSION_URL,
|
||||||
VEGA_WALLET_URL,
|
VEGA_WALLET_URL,
|
||||||
} = useEnvironment();
|
} = useEnvironment();
|
||||||
|
|
||||||
|
const vegaChainId = useChainId(VEGA_URL);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (chainId) {
|
if (chainId) {
|
||||||
return initializeConnectors(
|
return initializeConnectors(
|
||||||
@ -157,7 +162,8 @@ const Web3Container = ({
|
|||||||
!VEGA_EXPLORER_URL ||
|
!VEGA_EXPLORER_URL ||
|
||||||
!DocsLinks ||
|
!DocsLinks ||
|
||||||
!CHROME_EXTENSION_URL ||
|
!CHROME_EXTENSION_URL ||
|
||||||
!MOZILLA_EXTENSION_URL
|
!MOZILLA_EXTENSION_URL ||
|
||||||
|
!vegaChainId
|
||||||
) {
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -169,6 +175,7 @@ const Web3Container = ({
|
|||||||
config={{
|
config={{
|
||||||
network: VEGA_ENV,
|
network: VEGA_ENV,
|
||||||
vegaUrl: VEGA_URL,
|
vegaUrl: VEGA_URL,
|
||||||
|
chainId: vegaChainId,
|
||||||
vegaWalletServiceUrl: VEGA_WALLET_URL,
|
vegaWalletServiceUrl: VEGA_WALLET_URL,
|
||||||
links: {
|
links: {
|
||||||
explorer: VEGA_EXPLORER_URL,
|
explorer: VEGA_EXPLORER_URL,
|
||||||
|
@ -48,6 +48,7 @@ const vegaWalletConfig: VegaWalletConfig = {
|
|||||||
chromeExtensionUrl: 'chrome',
|
chromeExtensionUrl: 'chrome',
|
||||||
mozillaExtensionUrl: 'mozilla',
|
mozillaExtensionUrl: 'mozilla',
|
||||||
},
|
},
|
||||||
|
chainId: 'VEGA_CHAIN_ID',
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderComponent = (proposal: ProposalQuery['proposal']) => {
|
const renderComponent = (proposal: ProposalQuery['proposal']) => {
|
||||||
|
@ -1,14 +1,50 @@
|
|||||||
import { ToastsContainer, useToasts } from '@vegaprotocol/ui-toolkit';
|
import {
|
||||||
|
Intent,
|
||||||
|
ToastsContainer,
|
||||||
|
TradingButton,
|
||||||
|
VegaIcon,
|
||||||
|
VegaIconNames,
|
||||||
|
useToasts,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||||
import {
|
import {
|
||||||
useEthereumTransactionToasts,
|
useEthereumTransactionToasts,
|
||||||
useEthereumWithdrawApprovalsToasts,
|
useEthereumWithdrawApprovalsToasts,
|
||||||
useVegaTransactionToasts,
|
useVegaTransactionToasts,
|
||||||
|
useWalletDisconnectToastActions,
|
||||||
|
useWalletDisconnectedToasts,
|
||||||
} from '@vegaprotocol/web3';
|
} from '@vegaprotocol/web3';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const WalletDisconnectAdditionalContent = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { hideToast } = useWalletDisconnectToastActions();
|
||||||
|
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||||
|
(store) => store.openVegaWalletDialog
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<p className="mt-2">
|
||||||
|
<TradingButton
|
||||||
|
data-testid="connect-vega-wallet"
|
||||||
|
onClick={() => {
|
||||||
|
hideToast();
|
||||||
|
openVegaWalletDialog();
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
intent={Intent.Danger}
|
||||||
|
icon={<VegaIcon name={VegaIconNames.ARROW_RIGHT} size={14} />}
|
||||||
|
>
|
||||||
|
<span className="whitespace-nowrap uppercase">{t('Connect')}</span>
|
||||||
|
</TradingButton>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const ToastsManager = () => {
|
export const ToastsManager = () => {
|
||||||
useVegaTransactionToasts();
|
useVegaTransactionToasts();
|
||||||
useEthereumTransactionToasts();
|
useEthereumTransactionToasts();
|
||||||
useEthereumWithdrawApprovalsToasts();
|
useEthereumWithdrawApprovalsToasts();
|
||||||
|
useWalletDisconnectedToasts(<WalletDisconnectAdditionalContent />);
|
||||||
|
|
||||||
const toasts = useToasts((store) => store.toasts);
|
const toasts = useToasts((store) => store.toasts);
|
||||||
return <ToastsContainer order="desc" toasts={toasts} />;
|
return <ToastsContainer order="desc" toasts={toasts} />;
|
||||||
|
@ -13,6 +13,7 @@ import type { ReactNode } from 'react';
|
|||||||
import { Web3Provider } from './web3-provider';
|
import { Web3Provider } from './web3-provider';
|
||||||
import { useT } from '../../lib/use-t';
|
import { useT } from '../../lib/use-t';
|
||||||
import { DataLoader } from './data-loader';
|
import { DataLoader } from './data-loader';
|
||||||
|
import { useChainId } from '@vegaprotocol/wallet';
|
||||||
|
|
||||||
export const Bootstrapper = ({ children }: { children: ReactNode }) => {
|
export const Bootstrapper = ({ children }: { children: ReactNode }) => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
@ -26,13 +27,16 @@ export const Bootstrapper = ({ children }: { children: ReactNode }) => {
|
|||||||
CHROME_EXTENSION_URL,
|
CHROME_EXTENSION_URL,
|
||||||
} = useEnvironment();
|
} = useEnvironment();
|
||||||
|
|
||||||
|
const chainId = useChainId(VEGA_URL);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!VEGA_URL ||
|
!VEGA_URL ||
|
||||||
!VEGA_WALLET_URL ||
|
!VEGA_WALLET_URL ||
|
||||||
!VEGA_EXPLORER_URL ||
|
!VEGA_EXPLORER_URL ||
|
||||||
!CHROME_EXTENSION_URL ||
|
!CHROME_EXTENSION_URL ||
|
||||||
!MOZILLA_EXTENSION_URL ||
|
!MOZILLA_EXTENSION_URL ||
|
||||||
!DocsLinks
|
!DocsLinks ||
|
||||||
|
!chainId
|
||||||
) {
|
) {
|
||||||
return <AppLoader />;
|
return <AppLoader />;
|
||||||
}
|
}
|
||||||
@ -72,6 +76,7 @@ export const Bootstrapper = ({ children }: { children: ReactNode }) => {
|
|||||||
config={{
|
config={{
|
||||||
network: VEGA_ENV,
|
network: VEGA_ENV,
|
||||||
vegaUrl: VEGA_URL,
|
vegaUrl: VEGA_URL,
|
||||||
|
chainId,
|
||||||
vegaWalletServiceUrl: VEGA_WALLET_URL,
|
vegaWalletServiceUrl: VEGA_WALLET_URL,
|
||||||
links: {
|
links: {
|
||||||
explorer: VEGA_EXPLORER_URL,
|
explorer: VEGA_EXPLORER_URL,
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
import {
|
|
||||||
Intent,
|
|
||||||
useToasts,
|
|
||||||
ToastHeading,
|
|
||||||
CLOSE_AFTER,
|
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
|
||||||
import { useEffect, useMemo } from 'react';
|
|
||||||
import { useT } from '../use-t';
|
|
||||||
import { VegaWalletConnectButton } from '../../components/vega-wallet-connect-button';
|
|
||||||
|
|
||||||
const WALLET_DISCONNECTED_TOAST_ID = 'WALLET_DISCONNECTED_TOAST_ID';
|
|
||||||
|
|
||||||
export const useWalletDisconnectedToasts = () => {
|
|
||||||
const t = useT();
|
|
||||||
const [hasToast, setToast, updateToast] = useToasts((state) => [
|
|
||||||
state.hasToast,
|
|
||||||
state.setToast,
|
|
||||||
state.update,
|
|
||||||
]);
|
|
||||||
const { isAlive } = useVegaWallet();
|
|
||||||
|
|
||||||
const toast = useMemo(
|
|
||||||
() => ({
|
|
||||||
id: WALLET_DISCONNECTED_TOAST_ID,
|
|
||||||
intent: Intent.Danger,
|
|
||||||
content: (
|
|
||||||
<>
|
|
||||||
<ToastHeading>{t('Wallet connection lost')}</ToastHeading>
|
|
||||||
<p>{t('The connection to the Vega wallet has been lost.')}</p>
|
|
||||||
<p className="mt-2">
|
|
||||||
<VegaWalletConnectButton
|
|
||||||
intent={Intent.Danger}
|
|
||||||
onClick={() => {
|
|
||||||
updateToast(WALLET_DISCONNECTED_TOAST_ID, {
|
|
||||||
hidden: true,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
onClose: () => {
|
|
||||||
updateToast(WALLET_DISCONNECTED_TOAST_ID, {
|
|
||||||
hidden: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
closeAfter: CLOSE_AFTER,
|
|
||||||
}),
|
|
||||||
[t, updateToast]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isAlive === false) {
|
|
||||||
if (hasToast(WALLET_DISCONNECTED_TOAST_ID)) {
|
|
||||||
updateToast(WALLET_DISCONNECTED_TOAST_ID, { hidden: false });
|
|
||||||
} else {
|
|
||||||
setToast(toast);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [hasToast, isAlive, setToast, t, toast, updateToast]);
|
|
||||||
};
|
|
@ -1,4 +1,4 @@
|
|||||||
import { ToastsContainer, useToasts } from '@vegaprotocol/ui-toolkit';
|
import { Intent, ToastsContainer, useToasts } from '@vegaprotocol/ui-toolkit';
|
||||||
import { useProposalToasts } from '@vegaprotocol/proposals';
|
import { useProposalToasts } from '@vegaprotocol/proposals';
|
||||||
import { useVegaTransactionToasts } from '@vegaprotocol/web3';
|
import { useVegaTransactionToasts } from '@vegaprotocol/web3';
|
||||||
import { useEthereumTransactionToasts } from '@vegaprotocol/web3';
|
import { useEthereumTransactionToasts } from '@vegaprotocol/web3';
|
||||||
@ -6,7 +6,26 @@ import { useEthereumWithdrawApprovalsToasts } from '@vegaprotocol/web3';
|
|||||||
import { useReadyToWithdrawalToasts } from '@vegaprotocol/withdraws';
|
import { useReadyToWithdrawalToasts } from '@vegaprotocol/withdraws';
|
||||||
import { Links } from '../lib/links';
|
import { Links } from '../lib/links';
|
||||||
import { useReferralToasts } from '../client-pages/referrals/hooks/use-referral-toasts';
|
import { useReferralToasts } from '../client-pages/referrals/hooks/use-referral-toasts';
|
||||||
import { useWalletDisconnectedToasts } from '../lib/hooks/use-wallet-disconnected-toasts';
|
import {
|
||||||
|
useWalletDisconnectToastActions,
|
||||||
|
useWalletDisconnectedToasts,
|
||||||
|
} from '@vegaprotocol/web3';
|
||||||
|
import { VegaWalletConnectButton } from '../components/vega-wallet-connect-button';
|
||||||
|
|
||||||
|
const WalletDisconnectAdditionalContent = () => {
|
||||||
|
const { hideToast } = useWalletDisconnectToastActions();
|
||||||
|
return (
|
||||||
|
<p className="mt-2">
|
||||||
|
<VegaWalletConnectButton
|
||||||
|
intent={Intent.Danger}
|
||||||
|
onClick={() => {
|
||||||
|
// hide toast when clicked on `Connect`
|
||||||
|
hideToast();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const ToastsManager = () => {
|
export const ToastsManager = () => {
|
||||||
useProposalToasts();
|
useProposalToasts();
|
||||||
@ -17,7 +36,7 @@ export const ToastsManager = () => {
|
|||||||
withdrawalsLink: Links.PORTFOLIO(),
|
withdrawalsLink: Links.PORTFOLIO(),
|
||||||
});
|
});
|
||||||
useReferralToasts();
|
useReferralToasts();
|
||||||
useWalletDisconnectedToasts();
|
useWalletDisconnectedToasts(<WalletDisconnectAdditionalContent />);
|
||||||
|
|
||||||
const toasts = useToasts((store) => store.toasts);
|
const toasts = useToasts((store) => store.toasts);
|
||||||
return <ToastsContainer order="desc" toasts={toasts} />;
|
return <ToastsContainer order="desc" toasts={toasts} />;
|
||||||
|
@ -22,7 +22,7 @@ const mockUpdateDialogOpen = jest.fn();
|
|||||||
const mockCloseVegaDialog = jest.fn();
|
const mockCloseVegaDialog = jest.fn();
|
||||||
|
|
||||||
let mockIsDesktopRunning = true;
|
let mockIsDesktopRunning = true;
|
||||||
const mockChainId = 'chain-id';
|
const mockChainId = 'VEGA_CHAIN_ID';
|
||||||
|
|
||||||
jest.mock('../use-is-wallet-service-running', () => ({
|
jest.mock('../use-is-wallet-service-running', () => ({
|
||||||
useIsWalletServiceRunning: jest
|
useIsWalletServiceRunning: jest
|
||||||
@ -30,10 +30,6 @@ jest.mock('../use-is-wallet-service-running', () => ({
|
|||||||
.mockImplementation(() => mockIsDesktopRunning),
|
.mockImplementation(() => mockIsDesktopRunning),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('./use-chain-id', () => ({
|
|
||||||
useChainId: jest.fn().mockImplementation(() => mockChainId),
|
|
||||||
}));
|
|
||||||
|
|
||||||
let defaultProps: VegaConnectDialogProps;
|
let defaultProps: VegaConnectDialogProps;
|
||||||
|
|
||||||
const INITIAL_KEY = 'some-key';
|
const INITIAL_KEY = 'some-key';
|
||||||
@ -71,6 +67,7 @@ const defaultConfig: VegaWalletConfig = {
|
|||||||
chromeExtensionUrl: 'chrome-link',
|
chromeExtensionUrl: 'chrome-link',
|
||||||
mozillaExtensionUrl: 'mozilla-link',
|
mozillaExtensionUrl: 'mozilla-link',
|
||||||
},
|
},
|
||||||
|
chainId: 'VEGA_CHAIN_ID',
|
||||||
};
|
};
|
||||||
|
|
||||||
function generateJSX(
|
function generateJSX(
|
||||||
@ -214,7 +211,12 @@ describe('VegaConnectDialog', () => {
|
|||||||
.mockClear()
|
.mockClear()
|
||||||
.mockImplementation(() =>
|
.mockImplementation(() =>
|
||||||
delayedReject(
|
delayedReject(
|
||||||
new WalletError('User error', 3001, 'The user rejected the request')
|
new WalletError(
|
||||||
|
'User error',
|
||||||
|
3001,
|
||||||
|
'The user rejected the request'
|
||||||
|
),
|
||||||
|
delay
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -323,13 +325,6 @@ describe('VegaConnectDialog', () => {
|
|||||||
render(generateJSX());
|
render(generateJSX());
|
||||||
await selectInjected();
|
await selectInjected();
|
||||||
|
|
||||||
// Chain check
|
|
||||||
expect(screen.getByText('Verifying chain')).toBeInTheDocument();
|
|
||||||
expect(vegaWindow.getChainId).toHaveBeenCalled();
|
|
||||||
await act(async () => {
|
|
||||||
jest.advanceTimersByTime(delay);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Await user connect
|
// Await user connect
|
||||||
expect(screen.getByText('Connecting...')).toBeInTheDocument();
|
expect(screen.getByText('Connecting...')).toBeInTheDocument();
|
||||||
expect(vegaWindow.connectWallet).toHaveBeenCalled();
|
expect(vegaWindow.connectWallet).toHaveBeenCalled();
|
||||||
@ -350,43 +345,6 @@ describe('VegaConnectDialog', () => {
|
|||||||
expect(mockCloseVegaDialog).toHaveBeenCalledWith();
|
expect(mockCloseVegaDialog).toHaveBeenCalledWith();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles invalid chain', async () => {
|
|
||||||
const delay = 100;
|
|
||||||
const invalidChain = 'invalid chain';
|
|
||||||
const vegaWindow = {
|
|
||||||
getChainId: jest.fn(() =>
|
|
||||||
delayedResolve({ chainID: invalidChain }, delay)
|
|
||||||
),
|
|
||||||
connectWallet: jest.fn(() => delayedResolve(null, delay)),
|
|
||||||
disconnectWallet: jest.fn(() => delayedResolve(undefined, delay)),
|
|
||||||
listKeys: jest.fn(() =>
|
|
||||||
delayedResolve(
|
|
||||||
{
|
|
||||||
keys: [{ name: 'test key', publicKey: '0x123' }],
|
|
||||||
},
|
|
||||||
100
|
|
||||||
)
|
|
||||||
),
|
|
||||||
};
|
|
||||||
mockBrowserWallet(vegaWindow);
|
|
||||||
render(generateJSX());
|
|
||||||
await selectInjected();
|
|
||||||
|
|
||||||
// Chain check
|
|
||||||
expect(screen.getByText('Verifying chain')).toBeInTheDocument();
|
|
||||||
expect(vegaWindow.getChainId).toHaveBeenCalled();
|
|
||||||
await act(async () => {
|
|
||||||
jest.advanceTimersByTime(delay);
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(screen.getByText('Wrong network')).toBeInTheDocument();
|
|
||||||
expect(
|
|
||||||
screen.getByText(
|
|
||||||
new RegExp(`set your wallet network in your app to "${mockChainId}"`)
|
|
||||||
)
|
|
||||||
).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
async function selectInjected() {
|
async function selectInjected() {
|
||||||
expect(await screen.findByRole('dialog')).toBeInTheDocument();
|
expect(await screen.findByRole('dialog')).toBeInTheDocument();
|
||||||
fireEvent.click(await screen.findByTestId('connector-injected'));
|
fireEvent.click(await screen.findByTestId('connector-injected'));
|
||||||
|
@ -44,7 +44,6 @@ import { isBrowserWalletInstalled } from '../utils';
|
|||||||
import { useIsWalletServiceRunning } from '../use-is-wallet-service-running';
|
import { useIsWalletServiceRunning } from '../use-is-wallet-service-running';
|
||||||
import { SnapStatus, useSnapStatus } from '../use-snap-status';
|
import { SnapStatus, useSnapStatus } from '../use-snap-status';
|
||||||
import { useVegaWalletDialogStore } from './vega-wallet-dialog-store';
|
import { useVegaWalletDialogStore } from './vega-wallet-dialog-store';
|
||||||
import { useChainId } from './use-chain-id';
|
|
||||||
import { useT } from '../use-t';
|
import { useT } from '../use-t';
|
||||||
import { Trans } from 'react-i18next';
|
import { Trans } from 'react-i18next';
|
||||||
|
|
||||||
@ -65,7 +64,7 @@ export const VegaConnectDialog = ({
|
|||||||
contentOnly,
|
contentOnly,
|
||||||
onClose,
|
onClose,
|
||||||
}: VegaConnectDialogProps) => {
|
}: VegaConnectDialogProps) => {
|
||||||
const { disconnect, acknowledgeNeeded } = useVegaWallet();
|
const { chainId, disconnect, acknowledgeNeeded } = useVegaWallet();
|
||||||
const vegaWalletDialogOpen = useVegaWalletDialogStore(
|
const vegaWalletDialogOpen = useVegaWalletDialogStore(
|
||||||
(store) => store.vegaWalletDialogOpen
|
(store) => store.vegaWalletDialogOpen
|
||||||
);
|
);
|
||||||
@ -85,10 +84,6 @@ export const VegaConnectDialog = ({
|
|||||||
[updateVegaWalletDialog, acknowledgeNeeded, disconnect]
|
[updateVegaWalletDialog, acknowledgeNeeded, disconnect]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ensure we have a chain Id so we can compare with wallet chain id.
|
|
||||||
// This value will already be in the cache, if it failed the app wont render
|
|
||||||
const chainId = useChainId();
|
|
||||||
|
|
||||||
const content = chainId && (
|
const content = chainId && (
|
||||||
<ConnectDialogContainer
|
<ConnectDialogContainer
|
||||||
connectors={connectors}
|
connectors={connectors}
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
import { renderHook, waitFor } from '@testing-library/react';
|
|
||||||
import { useVegaWallet } from '../use-vega-wallet';
|
|
||||||
import { useChainId } from './use-chain-id';
|
|
||||||
|
|
||||||
global.fetch = jest.fn();
|
|
||||||
const mockFetch = global.fetch as jest.Mock;
|
|
||||||
mockFetch.mockImplementation((url: string) => {
|
|
||||||
return Promise.resolve({ ok: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.mock('../use-vega-wallet', () => {
|
|
||||||
const original = jest.requireActual('../use-vega-wallet');
|
|
||||||
return {
|
|
||||||
...original,
|
|
||||||
useVegaWallet: jest.fn(),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('useChainId', () => {
|
|
||||||
it('does not call fetch when statistics url could not be determined', async () => {
|
|
||||||
(useVegaWallet as jest.Mock).mockReturnValue({
|
|
||||||
vegaUrl: '',
|
|
||||||
});
|
|
||||||
renderHook(() => useChainId());
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(mockFetch).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls fetch with correct statistics url', async () => {
|
|
||||||
(useVegaWallet as jest.Mock).mockReturnValue({
|
|
||||||
vegaUrl: 'http://localhost:1234/graphql',
|
|
||||||
});
|
|
||||||
renderHook(() => useChainId());
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(mockFetch).toHaveBeenCalledWith(
|
|
||||||
'http://localhost:1234/statistics'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not return chain id when chain id is not present in response', async () => {
|
|
||||||
(useVegaWallet as jest.Mock).mockReturnValue({
|
|
||||||
vegaUrl: 'http://localhost:1234/graphql',
|
|
||||||
});
|
|
||||||
const { result } = renderHook(() => useChainId());
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(result.current).toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns chain id when chain id is present in response', async () => {
|
|
||||||
(useVegaWallet as jest.Mock).mockReturnValue({
|
|
||||||
vegaUrl: 'http://localhost:1234/graphql',
|
|
||||||
});
|
|
||||||
mockFetch.mockImplementation(() => {
|
|
||||||
return Promise.resolve({
|
|
||||||
ok: true,
|
|
||||||
json: () => ({
|
|
||||||
statistics: {
|
|
||||||
chainId: '1234',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const { result } = renderHook(() => useChainId());
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(result.current).not.toBeUndefined();
|
|
||||||
expect(result.current).toEqual('1234');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,10 +1,11 @@
|
|||||||
import { clearConfig, setConfig } from '../storage';
|
import { clearConfig, setConfig } from '../storage';
|
||||||
import type { Transaction, VegaConnector } from './vega-connector';
|
import type { Transaction, VegaConnector } from './vega-connector';
|
||||||
|
|
||||||
|
type VegaWalletEvent = 'client.disconnected';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Vega {
|
interface Vega {
|
||||||
getChainId: () => Promise<{ chainID: string }>;
|
connectWallet: (args: { chainId: string }) => Promise<null>;
|
||||||
connectWallet: () => Promise<null>;
|
|
||||||
disconnectWallet: () => Promise<void>;
|
disconnectWallet: () => Promise<void>;
|
||||||
listKeys: () => Promise<{
|
listKeys: () => Promise<{
|
||||||
keys: Array<{ name: string; publicKey: string }>;
|
keys: Array<{ name: string; publicKey: string }>;
|
||||||
@ -34,6 +35,9 @@ declare global {
|
|||||||
};
|
};
|
||||||
transactionHash: string;
|
transactionHash: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
on: (event: VegaWalletEvent, callback: () => void) => void;
|
||||||
|
isConnected?: () => Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Window {
|
interface Window {
|
||||||
@ -47,15 +51,60 @@ export const InjectedConnectorErrors = {
|
|||||||
INVALID_CHAIN: new Error('Invalid chain'),
|
INVALID_CHAIN: new Error('Invalid chain'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const wait = (ms: number) =>
|
||||||
|
new Promise<boolean>((_, reject) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
reject(false);
|
||||||
|
}, ms);
|
||||||
|
});
|
||||||
|
|
||||||
|
const INJECTED_CONNECTOR_TIMEOUT = 1000;
|
||||||
|
|
||||||
export class InjectedConnector implements VegaConnector {
|
export class InjectedConnector implements VegaConnector {
|
||||||
|
isConnected = false;
|
||||||
|
chainId: string | null = null;
|
||||||
description = 'Connects using the Vega wallet browser extension';
|
description = 'Connects using the Vega wallet browser extension';
|
||||||
|
alive: ReturnType<typeof setInterval> | undefined = undefined;
|
||||||
|
|
||||||
async getChainId() {
|
async connectWallet(chainId: string) {
|
||||||
return window.vega.getChainId();
|
this.chainId = chainId;
|
||||||
}
|
try {
|
||||||
|
await window.vega.connectWallet({ chainId });
|
||||||
|
this.isConnected = true;
|
||||||
|
window.vega.on('client.disconnected', () => {
|
||||||
|
this.isConnected = false;
|
||||||
|
});
|
||||||
|
|
||||||
connectWallet() {
|
this.alive = setInterval(async () => {
|
||||||
return window.vega.connectWallet();
|
try {
|
||||||
|
const connected = await Promise.race([
|
||||||
|
// FIXME: All of the `window.vega` initiated promises are `pending`
|
||||||
|
// while waiting for the user action when transaction is sent.
|
||||||
|
// (Probably due to the FIFO queue of the `PortServer`?)
|
||||||
|
// Because of that we cannot `wait` here as while waiting for the
|
||||||
|
// user action in wallet this will `reject`. It'd be cool if the
|
||||||
|
// `window.vega` was not blocking the api calls.
|
||||||
|
|
||||||
|
// wait(INJECTED_CONNECTOR_TIMEOUT),
|
||||||
|
|
||||||
|
// `isConnected` is only available in the newer versions
|
||||||
|
// of the browser wallet
|
||||||
|
'isConnected' in window.vega &&
|
||||||
|
typeof window.vega.isConnected === 'function'
|
||||||
|
? window.vega.isConnected()
|
||||||
|
: window.vega.listKeys(),
|
||||||
|
]);
|
||||||
|
this.isConnected = Boolean(connected);
|
||||||
|
} catch {
|
||||||
|
this.isConnected = false;
|
||||||
|
}
|
||||||
|
}, INJECTED_CONNECTOR_TIMEOUT * 2);
|
||||||
|
} catch {
|
||||||
|
throw new Error(
|
||||||
|
`could not connect to the vega wallet on chain: ${chainId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect() {
|
async connect() {
|
||||||
@ -69,19 +118,11 @@ export class InjectedConnector implements VegaConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async isAlive() {
|
async isAlive() {
|
||||||
try {
|
return this.isConnected;
|
||||||
const keys = await window.vega.listKeys();
|
|
||||||
if (keys.keys.length > 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnect() {
|
disconnect() {
|
||||||
|
clearInterval(this.alive);
|
||||||
clearConfig();
|
clearConfig();
|
||||||
return window.vega.disconnectWallet();
|
return window.vega.disconnectWallet();
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,9 @@ export interface VegaWalletContextShape {
|
|||||||
/** Url of current connected node */
|
/** Url of current connected node */
|
||||||
vegaUrl: string;
|
vegaUrl: string;
|
||||||
|
|
||||||
|
/** Vega chain id */
|
||||||
|
chainId: string;
|
||||||
|
|
||||||
/** Url of running wallet service */
|
/** Url of running wallet service */
|
||||||
vegaWalletServiceUrl: string;
|
vegaWalletServiceUrl: string;
|
||||||
|
|
||||||
|
@ -7,3 +7,4 @@ export * from './provider';
|
|||||||
export * from './connect-dialog';
|
export * from './connect-dialog';
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
export * from './storage';
|
export * from './storage';
|
||||||
|
export * from './use-chain-id';
|
||||||
|
@ -22,6 +22,7 @@ const defaultConfig: VegaWalletConfig = {
|
|||||||
chromeExtensionUrl: 'chrome-link',
|
chromeExtensionUrl: 'chrome-link',
|
||||||
mozillaExtensionUrl: 'mozilla-link',
|
mozillaExtensionUrl: 'mozilla-link',
|
||||||
},
|
},
|
||||||
|
chainId: 'VEGA_CHAIN_ID',
|
||||||
};
|
};
|
||||||
|
|
||||||
const setup = (config?: Partial<VegaWalletConfig>) => {
|
const setup = (config?: Partial<VegaWalletConfig>) => {
|
||||||
@ -68,6 +69,7 @@ describe('VegaWalletProvider', () => {
|
|||||||
expect(result.current).toEqual({
|
expect(result.current).toEqual({
|
||||||
network: defaultConfig.network,
|
network: defaultConfig.network,
|
||||||
vegaUrl: defaultConfig.vegaUrl,
|
vegaUrl: defaultConfig.vegaUrl,
|
||||||
|
chainId: defaultConfig.chainId,
|
||||||
vegaWalletServiceUrl: defaultConfig.vegaWalletServiceUrl,
|
vegaWalletServiceUrl: defaultConfig.vegaWalletServiceUrl,
|
||||||
acknowledgeNeeded: false,
|
acknowledgeNeeded: false,
|
||||||
pubKey: null,
|
pubKey: null,
|
||||||
|
@ -39,6 +39,7 @@ interface VegaWalletLinks {
|
|||||||
export interface VegaWalletConfig {
|
export interface VegaWalletConfig {
|
||||||
network: Networks;
|
network: Networks;
|
||||||
vegaUrl: string;
|
vegaUrl: string;
|
||||||
|
chainId: string;
|
||||||
vegaWalletServiceUrl: string;
|
vegaWalletServiceUrl: string;
|
||||||
links: VegaWalletLinks;
|
links: VegaWalletLinks;
|
||||||
keepAlive?: number;
|
keepAlive?: number;
|
||||||
@ -168,6 +169,7 @@ export const VegaWalletProvider = ({
|
|||||||
const contextValue = useMemo<VegaWalletContextShape>(() => {
|
const contextValue = useMemo<VegaWalletContextShape>(() => {
|
||||||
return {
|
return {
|
||||||
vegaUrl: config.vegaUrl,
|
vegaUrl: config.vegaUrl,
|
||||||
|
chainId: config.chainId,
|
||||||
vegaWalletServiceUrl: config.vegaWalletServiceUrl,
|
vegaWalletServiceUrl: config.vegaWalletServiceUrl,
|
||||||
network: config.network,
|
network: config.network,
|
||||||
links: {
|
links: {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
export function mockBrowserWallet(overrides?: Partial<Vega>) {
|
export function mockBrowserWallet(overrides?: Partial<Vega>) {
|
||||||
const vega: Vega = {
|
const vega: Vega = {
|
||||||
getChainId: jest.fn().mockReturnValue(Promise.resolve({ chainID: '1' })),
|
|
||||||
connectWallet: jest.fn().mockReturnValue(Promise.resolve(null)),
|
connectWallet: jest.fn().mockReturnValue(Promise.resolve(null)),
|
||||||
disconnectWallet: jest.fn().mockReturnValue(Promise.resolve()),
|
disconnectWallet: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
listKeys: jest
|
listKeys: jest
|
||||||
@ -14,6 +13,8 @@ export function mockBrowserWallet(overrides?: Partial<Vega>) {
|
|||||||
success: true,
|
success: true,
|
||||||
txHash: '0x123',
|
txHash: '0x123',
|
||||||
}),
|
}),
|
||||||
|
on: jest.fn(),
|
||||||
|
isConnected: jest.fn().mockRejectedValue(Promise.resolve(true)),
|
||||||
...overrides,
|
...overrides,
|
||||||
};
|
};
|
||||||
// @ts-ignore globalThis has no index signature
|
// @ts-ignore globalThis has no index signature
|
||||||
|
100
libs/wallet/src/use-chain-id.spec.ts
Normal file
100
libs/wallet/src/use-chain-id.spec.ts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import { renderHook, waitFor } from '@testing-library/react';
|
||||||
|
import { MAX_FETCH_ATTEMPTS, useChainId } from './use-chain-id';
|
||||||
|
|
||||||
|
global.fetch = jest.fn();
|
||||||
|
const mockFetch = global.fetch as jest.Mock;
|
||||||
|
mockFetch.mockImplementation((url: string) => {
|
||||||
|
return Promise.resolve({ ok: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('useChainId', () => {
|
||||||
|
it('does not call fetch when statistics url could not be determined', async () => {
|
||||||
|
renderHook(() => useChainId(''));
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockFetch).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls fetch with correct statistics url', async () => {
|
||||||
|
renderHook(() => useChainId('http://localhost:1234/graphql'));
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockFetch).toHaveBeenCalledWith(
|
||||||
|
'http://localhost:1234/statistics'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not return chain id when chain id is not present in response', async () => {
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useChainId('http://localhost:1234/graphql')
|
||||||
|
);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns chain id when chain id is present in response', async () => {
|
||||||
|
mockFetch.mockImplementation(() => {
|
||||||
|
return Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
json: () => ({
|
||||||
|
statistics: {
|
||||||
|
chainId: '1234',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useChainId('http://localhost:1234/graphql')
|
||||||
|
);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current).not.toBeUndefined();
|
||||||
|
expect(result.current).toEqual('1234');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns chain id when within max number of attempts', async () => {
|
||||||
|
mockFetch.mockImplementation(() => {
|
||||||
|
if (mockFetch.mock.calls.length < MAX_FETCH_ATTEMPTS) {
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
return Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
json: () => ({
|
||||||
|
statistics: {
|
||||||
|
chainId: '1234',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useChainId('http://localhost:1234/graphql')
|
||||||
|
);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current).not.toBeUndefined();
|
||||||
|
expect(result.current).toEqual('1234');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not return chain id when max number of attempts exceeded', async () => {
|
||||||
|
mockFetch.mockImplementation(() => {
|
||||||
|
if (mockFetch.mock.calls.length < MAX_FETCH_ATTEMPTS + 10) {
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
return Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
json: () => ({
|
||||||
|
statistics: {
|
||||||
|
chainId: '1234',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useChainId('http://localhost:5678/graphql')
|
||||||
|
);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useVegaWallet } from '../use-vega-wallet';
|
|
||||||
|
export const MAX_FETCH_ATTEMPTS = 3;
|
||||||
|
|
||||||
const cache: Record<string, string> = {};
|
const cache: Record<string, string> = {};
|
||||||
|
|
||||||
@ -18,16 +19,17 @@ const getNodeStatisticsUrl = (vegaUrl: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useChainId = () => {
|
export const useChainId = (vegaUrl: string | undefined) => {
|
||||||
const { vegaUrl } = useVegaWallet();
|
const [chainId, setChainId] = useState<undefined | string>(
|
||||||
const [chainId, setChainId] = useState<undefined | string>(cache[vegaUrl]);
|
vegaUrl ? cache[vegaUrl] : undefined
|
||||||
const [fetchAttempt, setFetchAttempt] = useState(1);
|
);
|
||||||
|
const [fetchAttempts, setFetchAttempts] = useState(1);
|
||||||
|
|
||||||
const statisticsUrl = getNodeStatisticsUrl(vegaUrl);
|
const statisticsUrl = vegaUrl ? getNodeStatisticsUrl(vegaUrl) : undefined;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// abort when `/statistics` URL could not be determined
|
// abort when `/statistics` URL could not be determined
|
||||||
if (!statisticsUrl) return;
|
if (!statisticsUrl || !vegaUrl) return;
|
||||||
let isCancelled = false;
|
let isCancelled = false;
|
||||||
if (cache[vegaUrl]) {
|
if (cache[vegaUrl]) {
|
||||||
setChainId(cache[vegaUrl]);
|
setChainId(cache[vegaUrl]);
|
||||||
@ -42,16 +44,19 @@ export const useChainId = () => {
|
|||||||
if (!response?.statistics?.chainId) {
|
if (!response?.statistics?.chainId) {
|
||||||
throw new Error('statistics.chainId not present in fetched response');
|
throw new Error('statistics.chainId not present in fetched response');
|
||||||
}
|
}
|
||||||
setChainId(response?.statistics?.chainId);
|
|
||||||
|
const chainId = response.statistics.chainId;
|
||||||
|
cache[vegaUrl] = chainId;
|
||||||
|
setChainId(chainId);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
if (fetchAttempt < 3) {
|
if (fetchAttempts < MAX_FETCH_ATTEMPTS) {
|
||||||
setFetchAttempt((value) => (value += 1));
|
setFetchAttempts((value) => (value += 1));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return () => {
|
return () => {
|
||||||
isCancelled = true;
|
isCancelled = true;
|
||||||
};
|
};
|
||||||
}, [fetchAttempt, statisticsUrl, vegaUrl]);
|
}, [fetchAttempts, statisticsUrl, vegaUrl]);
|
||||||
return chainId;
|
return chainId;
|
||||||
};
|
};
|
@ -7,7 +7,7 @@ import { useVegaWallet } from './use-vega-wallet';
|
|||||||
|
|
||||||
export function useEagerConnect(connectors: Connectors) {
|
export function useEagerConnect(connectors: Connectors) {
|
||||||
const [connecting, setConnecting] = useState(true);
|
const [connecting, setConnecting] = useState(true);
|
||||||
const { vegaUrl, connect, acknowledgeNeeded } = useVegaWallet();
|
const { vegaUrl, chainId, connect, acknowledgeNeeded } = useVegaWallet();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const attemptConnect = async () => {
|
const attemptConnect = async () => {
|
||||||
@ -33,7 +33,7 @@ export function useEagerConnect(connectors: Connectors) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (connector instanceof InjectedConnector) {
|
if (connector instanceof InjectedConnector) {
|
||||||
await connector.connectWallet();
|
await connector.connectWallet(chainId);
|
||||||
await connect(connector);
|
await connect(connector);
|
||||||
} else if (connector instanceof SnapConnector) {
|
} else if (connector instanceof SnapConnector) {
|
||||||
connector.nodeAddress = new URL(vegaUrl).origin;
|
connector.nodeAddress = new URL(vegaUrl).origin;
|
||||||
@ -51,7 +51,7 @@ export function useEagerConnect(connectors: Connectors) {
|
|||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
attemptConnect();
|
attemptConnect();
|
||||||
}
|
}
|
||||||
}, [connect, connectors, acknowledgeNeeded, vegaUrl]);
|
}, [connect, connectors, acknowledgeNeeded, vegaUrl, chainId]);
|
||||||
|
|
||||||
return connecting;
|
return connecting;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ const defaultConfig: VegaWalletConfig = {
|
|||||||
chromeExtensionUrl: 'chrome-link',
|
chromeExtensionUrl: 'chrome-link',
|
||||||
mozillaExtensionUrl: 'mozilla-link',
|
mozillaExtensionUrl: 'mozilla-link',
|
||||||
},
|
},
|
||||||
|
chainId: 'VEGA_CHAIN_ID',
|
||||||
};
|
};
|
||||||
|
|
||||||
const setup = (callback = jest.fn(), config?: Partial<VegaWalletConfig>) => {
|
const setup = (callback = jest.fn(), config?: Partial<VegaWalletConfig>) => {
|
||||||
@ -46,20 +47,10 @@ describe('useInjectedConnector', () => {
|
|||||||
expect(result.current.status).toBe(Status.Error);
|
expect(result.current.status).toBe(Status.Error);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('errors if chain ids dont match', async () => {
|
|
||||||
mockBrowserWallet();
|
|
||||||
const { result } = setup();
|
|
||||||
await act(async () => {
|
|
||||||
result.current.connect(injected, '2'); // default mock chainId is '1'
|
|
||||||
});
|
|
||||||
expect(result.current.error?.message).toBe('Invalid chain');
|
|
||||||
expect(result.current.status).toBe(Status.Error);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('errors if connection throws', async () => {
|
it('errors if connection throws', async () => {
|
||||||
const callback = jest.fn();
|
const callback = jest.fn();
|
||||||
mockBrowserWallet({
|
mockBrowserWallet({
|
||||||
getChainId: () => Promise.reject('failed'),
|
connectWallet: jest.fn().mockReturnValue(Promise.reject()),
|
||||||
});
|
});
|
||||||
const { result } = setup(callback);
|
const { result } = setup(callback);
|
||||||
|
|
||||||
@ -67,7 +58,9 @@ describe('useInjectedConnector', () => {
|
|||||||
result.current.connect(injected, '1'); // default mock chainId is '1'
|
result.current.connect(injected, '1'); // default mock chainId is '1'
|
||||||
});
|
});
|
||||||
expect(result.current.status).toBe(Status.Error);
|
expect(result.current.status).toBe(Status.Error);
|
||||||
expect(result.current.error?.message).toBe('injected connection failed');
|
expect(result.current.error?.message).toBe(
|
||||||
|
'could not connect to the vega wallet on chain: 1'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('connects', async () => {
|
it('connects', async () => {
|
||||||
@ -78,7 +71,6 @@ describe('useInjectedConnector', () => {
|
|||||||
act(() => {
|
act(() => {
|
||||||
result.current.connect(injected, '1'); // default mock chainId is '1'
|
result.current.connect(injected, '1'); // default mock chainId is '1'
|
||||||
});
|
});
|
||||||
expect(result.current.status).toBe(Status.GettingChainId);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(vega.connectWallet).toHaveBeenCalled();
|
expect(vega.connectWallet).toHaveBeenCalled();
|
||||||
|
@ -40,17 +40,19 @@ export const useInjectedConnector = (onConnect: () => void) => {
|
|||||||
connector.nodeAddress = new URL(vegaUrl).origin;
|
connector.nodeAddress = new URL(vegaUrl).origin;
|
||||||
}
|
}
|
||||||
|
|
||||||
setStatus(Status.GettingChainId);
|
// check the chain id for snap connector
|
||||||
|
if (connector instanceof SnapConnector) {
|
||||||
const { chainID } = await connector.getChainId();
|
setStatus(Status.GettingChainId);
|
||||||
if (chainID !== appChainId) {
|
const { chainID } = await connector.getChainId();
|
||||||
throw InjectedConnectorErrors.INVALID_CHAIN;
|
if (chainID !== appChainId) {
|
||||||
|
throw InjectedConnectorErrors.INVALID_CHAIN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setStatus(Status.Connecting);
|
setStatus(Status.Connecting);
|
||||||
if (connector instanceof InjectedConnector) {
|
if (connector instanceof InjectedConnector) {
|
||||||
// extra step for injected connector - authorize wallet
|
// extra step for injected connector - authorize wallet
|
||||||
await connector.connectWallet();
|
await connector.connectWallet(appChainId);
|
||||||
}
|
}
|
||||||
await connect(connector); // connect with keys
|
await connect(connector); // connect with keys
|
||||||
|
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
|
export * from './lib/__generated__/TransactionResult';
|
||||||
|
export * from './lib/__generated__/WithdrawalApproval';
|
||||||
export * from './lib/constants';
|
export * from './lib/constants';
|
||||||
|
export * from './lib/default-web3-provider';
|
||||||
export * from './lib/eip-1193-custom-bridge';
|
export * from './lib/eip-1193-custom-bridge';
|
||||||
export * from './lib/ethereum-error';
|
export * from './lib/ethereum-error';
|
||||||
export * from './lib/ethereum-transaction-dialog';
|
export * from './lib/ethereum-transaction-dialog';
|
||||||
|
export * from './lib/types';
|
||||||
export * from './lib/url-connector';
|
export * from './lib/url-connector';
|
||||||
export * from './lib/use-bridge-contract';
|
export * from './lib/use-bridge-contract';
|
||||||
export * from './lib/use-eager-connect';
|
export * from './lib/use-eager-connect';
|
||||||
@ -19,7 +23,12 @@ export * from './lib/use-get-withdraw-delay';
|
|||||||
export * from './lib/use-get-withdraw-threshold';
|
export * from './lib/use-get-withdraw-threshold';
|
||||||
export * from './lib/use-token-contract';
|
export * from './lib/use-token-contract';
|
||||||
export * from './lib/use-token-decimals';
|
export * from './lib/use-token-decimals';
|
||||||
|
export * from './lib/use-transaction-result';
|
||||||
|
export * from './lib/use-vega-transaction-manager';
|
||||||
|
export * from './lib/use-vega-transaction-store';
|
||||||
export * from './lib/use-vega-transaction-toasts';
|
export * from './lib/use-vega-transaction-toasts';
|
||||||
|
export * from './lib/use-vega-transaction-updater';
|
||||||
|
export * from './lib/use-wallet-disconnected-toasts';
|
||||||
export * from './lib/use-web3-disconnect';
|
export * from './lib/use-web3-disconnect';
|
||||||
export * from './lib/web3-connect-dialog';
|
export * from './lib/web3-connect-dialog';
|
||||||
export * from './lib/web3-connect-store';
|
export * from './lib/web3-connect-store';
|
||||||
@ -27,11 +36,3 @@ export * from './lib/web3-connectors';
|
|||||||
export * from './lib/web3-provider';
|
export * from './lib/web3-provider';
|
||||||
export * from './lib/withdrawal-approval-dialog';
|
export * from './lib/withdrawal-approval-dialog';
|
||||||
export * from './lib/withdrawal-approval-status';
|
export * from './lib/withdrawal-approval-status';
|
||||||
export * from './lib/default-web3-provider';
|
|
||||||
export * from './lib/use-vega-transaction-manager';
|
|
||||||
export * from './lib/use-vega-transaction-store';
|
|
||||||
export * from './lib/use-vega-transaction-updater';
|
|
||||||
export * from './lib/use-transaction-result';
|
|
||||||
export * from './lib/types';
|
|
||||||
export * from './lib/__generated__/TransactionResult';
|
|
||||||
export * from './lib/__generated__/WithdrawalApproval';
|
|
||||||
|
78
libs/web3/src/lib/use-wallet-disconnected-toasts.tsx
Normal file
78
libs/web3/src/lib/use-wallet-disconnected-toasts.tsx
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import {
|
||||||
|
Intent,
|
||||||
|
useToasts,
|
||||||
|
ToastHeading,
|
||||||
|
CLOSE_AFTER,
|
||||||
|
type Toast,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
|
import { useEffect, useMemo } from 'react';
|
||||||
|
import { useT } from './use-t';
|
||||||
|
import { usePrevious } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
|
export const WALLET_DISCONNECTED_TOAST_ID = 'WALLET_DISCONNECTED_TOAST_ID';
|
||||||
|
|
||||||
|
export const useWalletDisconnectToastActions = () => {
|
||||||
|
const [hasToast, updateToast] = useToasts((state) => [
|
||||||
|
state.hasToast,
|
||||||
|
state.update,
|
||||||
|
]);
|
||||||
|
const hideToast = () => {
|
||||||
|
if (!hasToast(WALLET_DISCONNECTED_TOAST_ID)) return;
|
||||||
|
updateToast(WALLET_DISCONNECTED_TOAST_ID, {
|
||||||
|
hidden: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const showToast = () => {
|
||||||
|
if (!hasToast(WALLET_DISCONNECTED_TOAST_ID)) return;
|
||||||
|
updateToast(WALLET_DISCONNECTED_TOAST_ID, {
|
||||||
|
hidden: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return { showToast, hideToast };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useWalletDisconnectedToasts = (
|
||||||
|
additionalContent?: JSX.Element
|
||||||
|
) => {
|
||||||
|
const t = useT();
|
||||||
|
const [hasToast, setToast] = useToasts((state) => [
|
||||||
|
state.hasToast,
|
||||||
|
state.setToast,
|
||||||
|
state.update,
|
||||||
|
]);
|
||||||
|
const { showToast, hideToast } = useWalletDisconnectToastActions();
|
||||||
|
|
||||||
|
const { isAlive } = useVegaWallet();
|
||||||
|
const wasAlive = usePrevious(isAlive);
|
||||||
|
const disconnected = wasAlive && !isAlive;
|
||||||
|
|
||||||
|
const toast: Toast = useMemo(
|
||||||
|
() => ({
|
||||||
|
id: WALLET_DISCONNECTED_TOAST_ID,
|
||||||
|
intent: Intent.Danger,
|
||||||
|
content: (
|
||||||
|
<>
|
||||||
|
<ToastHeading>{t('Wallet connection lost')}</ToastHeading>
|
||||||
|
<p>{t('The connection to the Vega wallet has been lost.')}</p>
|
||||||
|
{additionalContent}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
onClose: () => {
|
||||||
|
hideToast();
|
||||||
|
},
|
||||||
|
closeAfter: CLOSE_AFTER,
|
||||||
|
}),
|
||||||
|
[additionalContent, hideToast, t]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (disconnected) {
|
||||||
|
if (hasToast(WALLET_DISCONNECTED_TOAST_ID)) {
|
||||||
|
showToast();
|
||||||
|
} else {
|
||||||
|
setToast(toast);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [disconnected, hasToast, isAlive, setToast, showToast, t, toast]);
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user