chore(web3): wallet connect v2 (#3324)

This commit is contained in:
Art 2023-04-20 17:20:59 +02:00 committed by GitHub
parent 9e054c7c85
commit 360624282d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1995 additions and 300 deletions

View File

@ -15,6 +15,7 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
#Test configuration variables
CYPRESS_FAIRGROUND=false

View File

@ -68,8 +68,10 @@ context(
it('should have connector list visible', function () {
const connectList = [
'Unknown',
'MetaMask, Brave or other injected web wallet',
'MetaMask',
'Coinbase',
'WalletConnect',
'WalletConnect Legacy',
];
cy.get(connectorList).within(() => {
cy.get('button').each(($btn, i) => {

View File

@ -14,6 +14,7 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
#Test configuration variables
CYPRESS_FAIRGROUND=false

View File

@ -26,6 +26,7 @@ import {
import { Loader } from '@vegaprotocol/ui-toolkit';
import colors from 'tailwindcss/colors';
import { useBalances } from '../../lib/balances/balances-store';
import { useWeb3Disconnect } from '@vegaprotocol/web3';
const removeLeadingAddressSymbol = (key: string) => {
if (key && key.length > 2 && key.slice(0, 2) === '0x') {
@ -184,6 +185,7 @@ export const EthWallet = () => {
const { appDispatch } = useAppState();
const { account, connector } = useWeb3React();
const pendingTxs = usePendingTransactions();
const disconnect = useWeb3Disconnect(connector);
return (
<WalletCard>
@ -239,7 +241,7 @@ export const EthWallet = () => {
<div className="flex justify-end">
<button
className="underline"
onClick={() => connector.deactivate()}
onClick={() => disconnect()}
data-testid="disconnect-from-eth-wallet-button"
>
{t('disconnect')}

View File

@ -1,5 +1,10 @@
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
import { getChainName, Web3ConnectDialog } from '@vegaprotocol/web3';
import {
getChainName,
useWeb3ConnectStore,
useWeb3Disconnect,
Web3ConnectDialog,
} from '@vegaprotocol/web3';
import { useWeb3React } from '@web3-react/core';
import type { ReactElement } from 'react';
import { useCallback, useEffect } from 'react';
@ -51,7 +56,9 @@ interface Web3ContentProps {
}
export const Web3Content = ({ children, appChainId }: Web3ContentProps) => {
const { error, connector, chainId } = useWeb3React();
const { connector, chainId } = useWeb3React();
const error = useWeb3ConnectStore((store) => store.error);
const disconnect = useWeb3Disconnect(connector);
useEffect(() => {
if (connector?.connectEagerly) {
@ -67,7 +74,7 @@ export const Web3Content = ({ children, appChainId }: Web3ContentProps) => {
<Splash>
<div className="flex flex-col items-center gap-12">
<p className="text-white">Something went wrong: {error.message}</p>
<Button onClick={() => connector.deactivate()}>Disconnect</Button>
<Button onClick={() => disconnect()}>Disconnect</Button>
</div>
</Splash>
);
@ -80,7 +87,7 @@ export const Web3Content = ({ children, appChainId }: Web3ContentProps) => {
<p className="text-white">
This app only works on {getChainName(appChainId)}
</p>
<Button onClick={() => connector.deactivate()}>Disconnect</Button>
<Button onClick={() => disconnect()}>Disconnect</Button>
</div>
</Splash>
);

View File

@ -1,20 +1,17 @@
import { ethers } from 'ethers';
import type { Web3ReactHooks } from '@web3-react/core';
import { initializeConnector } from '@web3-react/core';
import { MetaMask } from '@web3-react/metamask';
import { WalletConnect } from '@web3-react/walletconnect';
import type { Connector } from '@web3-react/types';
import { ENV } from '../config/env';
import { UrlConnector } from '@vegaprotocol/web3';
import {
initializeMetaMaskConnector,
initializeWalletConnector,
initializeWalletConnectLegacyConnector,
initializeCoinbaseConnector,
WALLETCONNECT_PROJECT_ID,
initializeUrlConnector,
} from '@vegaprotocol/web3';
const [metamask, metamaskHooks] = initializeConnector<MetaMask>(
(actions) => new MetaMask(actions)
);
const [urlConnector, urlHooks] = initializeConnector<UrlConnector>(
(actions) =>
new UrlConnector(actions, ENV.localProviderUrl, ENV.ethWalletMnemonic)
);
initializeUrlConnector(ENV.localProviderUrl, ENV.ethWalletMnemonic);
export const createDefaultProvider = (providerUrl: string, chainId: number) => {
return new ethers.providers.JsonRpcProvider(providerUrl, chainId);
@ -24,19 +21,14 @@ export const createConnectors = (providerUrl: string, chainId: number) => {
if (isNaN(chainId)) {
throw new Error('Invalid Ethereum chain ID for environment');
}
const [walletconnect, walletconnectHooks] =
initializeConnector<WalletConnect>(
(actions) =>
new WalletConnect(actions, {
rpc: {
[chainId]: providerUrl,
},
}),
[chainId]
);
return [
ENV.urlConnect ? [urlConnector, urlHooks] : null,
[metamask, metamaskHooks],
[walletconnect, walletconnectHooks],
].filter(Boolean) as [Connector, Web3ReactHooks][];
ENV.urlConnect
? initializeUrlConnector(ENV.localProviderUrl, ENV.ethWalletMnemonic)
: null,
initializeMetaMaskConnector(),
initializeCoinbaseConnector(providerUrl),
initializeWalletConnector(WALLETCONNECT_PROJECT_ID, chainId, providerUrl),
initializeWalletConnectLegacyConnector(chainId, providerUrl),
].filter(Boolean) as unknown as [Connector, Web3ReactHooks][];
};

View File

@ -2,4 +2,4 @@ NX_VEGA_URL=https://api.stagnet3.vega.xyz/graphql
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet3/vegawallet-stagnet3.toml
NX_VEGA_NETWORKS='{"TESTNET":"https://multisig-signer.fairground.wtf","MAINNET":"https://multisig-signer.vega.xyz"}'
NX_VEGA_ENV=STAGNET3
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72

View File

@ -9,6 +9,7 @@ import {
} from '@vegaprotocol/environment';
import { AsyncRenderer, Button, Lozenge } from '@vegaprotocol/ui-toolkit';
import type { EthereumConfig } from '@vegaprotocol/web3';
import { useWeb3Disconnect } from '@vegaprotocol/web3';
import { useEthereumConfig, Web3Provider } from '@vegaprotocol/web3';
import { t } from '@vegaprotocol/i18n';
import { ENV } from './config/env';
@ -34,6 +35,7 @@ const pageWrapperClasses = classnames(
const ConnectedApp = ({ config }: { config: EthereumConfig | null }) => {
const { account, connector } = useWeb3React();
const disconnect = useWeb3Disconnect(connector);
return (
<main className="w-full max-w-3xl px-5 justify-self-center">
<h1>{t('Multisig signer')}</h1>
@ -41,7 +43,7 @@ const ConnectedApp = ({ config }: { config: EthereumConfig | null }) => {
<p>
Connected to Eth wallet: <Lozenge>{account}</Lozenge>
</p>
<Button onClick={() => connector.deactivate()}>Disconnect</Button>
<Button onClick={() => disconnect()}>Disconnect</Button>
</div>
<ContractDetails config={config} />
<h2>{t('Add or remove signer')}</h2>

View File

@ -1,5 +1,10 @@
import { useEnvironment } from '@vegaprotocol/environment';
import { getChainName, useEthereumConfig } from '@vegaprotocol/web3';
import {
getChainName,
useEthereumConfig,
useWeb3ConnectStore,
useWeb3Disconnect,
} from '@vegaprotocol/web3';
import { Button, Splash, AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { Web3ConnectDialog } from '@vegaprotocol/web3';
import { useWeb3React } from '@web3-react/core';
@ -55,7 +60,9 @@ export const Web3Content = ({
appChainId,
setDialogOpen,
}: Web3ContentProps) => {
const { error, connector, chainId } = useWeb3React();
const { connector, chainId } = useWeb3React();
const error = useWeb3ConnectStore((store) => store.error);
const disconnect = useWeb3Disconnect(connector);
useEffect(() => {
if (connector?.connectEagerly) {
@ -71,7 +78,7 @@ export const Web3Content = ({
<Splash>
<div className="flex flex-col items-center gap-12">
<p className="text-white">Something went wrong: {error.message}</p>
<Button onClick={() => connector.deactivate()}>Disconnect</Button>
<Button onClick={() => disconnect()}>Disconnect</Button>
</div>
</Splash>
);
@ -84,7 +91,7 @@ export const Web3Content = ({
<p className="text-white">
This app only works on {getChainName(appChainId)}
</p>
<Button onClick={() => connector.deactivate()}>Disconnect</Button>
<Button onClick={() => disconnect()}>Disconnect</Button>
</div>
</Splash>
);

View File

@ -1,13 +1,13 @@
import { ethers } from 'ethers';
import type { Web3ReactHooks } from '@web3-react/core';
import { initializeConnector } from '@web3-react/core';
import { MetaMask } from '@web3-react/metamask';
import { WalletConnect } from '@web3-react/walletconnect';
import type { Connector } from '@web3-react/types';
const [metamask, metamaskHooks] = initializeConnector<MetaMask>(
(actions) => new MetaMask(actions)
);
import {
WALLETCONNECT_PROJECT_ID,
initializeCoinbaseConnector,
initializeMetaMaskConnector,
initializeWalletConnectLegacyConnector,
initializeWalletConnector,
} from '@vegaprotocol/web3';
export const createDefaultProvider = (providerUrl: string, chainId: number) => {
return new ethers.providers.JsonRpcProvider(providerUrl, chainId);
@ -17,18 +17,11 @@ export const createConnectors = (providerUrl: string, chainId: number) => {
if (isNaN(chainId)) {
throw new Error('Invalid Ethereum chain ID for environment');
}
const [walletconnect, walletconnectHooks] =
initializeConnector<WalletConnect>(
(actions) =>
new WalletConnect(actions, {
rpc: {
[chainId]: providerUrl,
},
}),
[chainId]
);
return [
[metamask, metamaskHooks],
[walletconnect, walletconnectHooks],
] as [Connector, Web3ReactHooks][];
initializeMetaMaskConnector(),
initializeCoinbaseConnector(providerUrl),
initializeWalletConnector(WALLETCONNECT_PROJECT_ID, chainId, providerUrl),
initializeWalletConnectLegacyConnector(chainId, providerUrl),
].filter(Boolean) as unknown as [Connector, Web3ReactHooks][];
};

View File

@ -12,6 +12,7 @@ NX_VEGA_URL=http://localhost:3028/query
NX_VEGA_WALLET_URL=http://localhost:1789
NX_ETH_LOCAL_PROVIDER_URL=http://localhost:8545/
NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supply green clown session"
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
# Expose some env vars to cypress environment for market setup
CYPRESS_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session

View File

@ -27,7 +27,7 @@ describe('ethereum wallet', { tags: '@smoke', testIsolation: true }, () => {
cy.getByTestId('tab-deposits').should('not.be.empty');
});
it('should see an option to cancel the attempted connection', () => {
it('should see QR code modal for WalletConnect', () => {
// 0004-EWAL-003
cy.wait('@NetworkParams');
@ -36,8 +36,8 @@ describe('ethereum wallet', { tags: '@smoke', testIsolation: true }, () => {
cy.getByTestId('connect-eth-wallet-btn').click();
cy.getByTestId('web3-connector-list').should('exist');
cy.getByTestId('web3-connector-WalletConnect').click();
cy.get('#walletconnect-qrcode-text').should('exist');
cy.get('#walletconnect-qrcode-close').click();
// testing if exists rather than visible because of the long loading time
cy.get('w3m-modal').should('exist');
});
it('able to disconnect eth wallet', () => {

View File

@ -11,4 +11,5 @@ NX_VEGA_TOKEN_URL=https://token.fairground.wtf
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72

View File

@ -14,10 +14,9 @@ export const Web3Provider = ({ children }: { children: ReactNode }) => {
const { config, loading, error } = useEthereumConfig();
const { ETHEREUM_PROVIDER_URL, ETH_LOCAL_PROVIDER_URL, ETH_WALLET_MNEMONIC } =
useEnvironment();
const [connectors, initializeConnectors] = useWeb3ConnectStore((store) => [
store.connectors,
store.initialize,
]);
const connectors = useWeb3ConnectStore((store) => store.connectors);
const initializeConnectors = useWeb3ConnectStore((store) => store.initialize);
useEffect(() => {
if (config?.chain_id) {

View File

@ -33,6 +33,7 @@ import { ViewingBanner } from '../components/viewing-banner';
import { Banner } from '../components/banner';
import { AppLoader, DynamicLoader } from '../components/app-loader';
import { Navbar } from '../components/navbar';
import { ENV } from '../lib/config';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import { activeOrdersProvider } from '@vegaprotocol/orders';
@ -144,7 +145,7 @@ const PartyData = () => {
const MaybeConnectEagerly = () => {
useVegaEagerConnect(Connectors);
useEthereumEagerConnect();
useEthereumEagerConnect(ENV.dsn);
const { pubKey, connect } = useVegaWallet();
const [searchParams] = useSearchParams();

View File

@ -19,6 +19,12 @@ const mockSocketServer = Cypress.env('VEGA_URL')
? new Server(Cypress.env('VEGA_URL').replace('http', 'ws'))
: null;
// DO NOT REMOVE: PASSTHROUGH for walletconnect
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const relayServer = new Server('wss://relay.walletconnect.com', {
mock: false,
});
export function addMockSubscription() {
Cypress.Commands.add(
'mockSubscription',

View File

@ -1,6 +1,7 @@
import type { StateCreator } from 'zustand';
import { act } from 'react-dom/test-utils';
const { create: actualCreate } = jest.requireActual('zustand'); // if using jest
const { create: actualCreate, useStore: actualUseStore } =
jest.requireActual('zustand'); // if using jest
// a variable to hold reset functions for all stores declared in the app
const storeResetFns = new Set<() => void>();
@ -15,7 +16,13 @@ export const create =
return store;
};
export const createStore = create;
export const useStore = actualUseStore;
// Reset all stores after each test run
beforeEach(() => {
act(() => storeResetFns.forEach((resetFn) => resetFn()));
});
// also export default as web3-react internals import and use zustand as the default import
export default create;

View File

@ -35,6 +35,7 @@ import {
ETHEREUM_EAGER_CONNECT,
useWeb3ConnectStore,
getChainName,
useWeb3Disconnect,
} from '@vegaprotocol/web3';
import type { DepositBalances } from './use-deposit-balances';
import { FaucetNotification } from './faucet-notification';
@ -448,11 +449,12 @@ const DisconnectEthereumButton = ({
}) => {
const { connector } = useWeb3React();
const [, , removeEagerConnector] = useLocalStorage(ETHEREUM_EAGER_CONNECT);
const disconnect = useWeb3Disconnect(connector);
return (
<ButtonLink
onClick={() => {
connector.deactivate();
disconnect();
removeEagerConnector();
onDisconnect();
}}

View File

@ -12,6 +12,7 @@ interface DialogProps {
open: boolean;
onChange?: (isOpen: boolean) => void;
onCloseAutoFocus?: (e: Event) => void;
onInteractOutside?: (e: Event) => void;
title?: string;
icon?: ReactNode;
intent?: Intent;
@ -24,6 +25,7 @@ export function Dialog({
open,
onChange,
onCloseAutoFocus,
onInteractOutside,
title,
icon,
intent,
@ -66,6 +68,7 @@ export function Dialog({
<DialogPrimitives.Content
className={contentClasses}
onCloseAutoFocus={onCloseAutoFocus}
onInteractOutside={onInteractOutside}
data-testid={dataTestId}
>
<div className={wrapperClasses}>

View File

@ -1,6 +1,7 @@
import type { StateCreator } from 'zustand';
import { act } from 'react-dom/test-utils';
const { create: actualCreate } = jest.requireActual('zustand'); // if using jest
const { create: actualCreate, useStore: actualUseStore } =
jest.requireActual('zustand'); // if using jest
// a variable to hold reset functions for all stores declared in the app
const storeResetFns = new Set<() => void>();
@ -15,6 +16,9 @@ export const create =
return store;
};
export const createStore = create;
export const useStore = actualUseStore;
// Reset all stores after each test run
beforeEach(() => {
act(() => storeResetFns.forEach((resetFn) => resetFn()));

View File

@ -1,23 +1,24 @@
export * from './lib/constants';
export * from './lib/eip-1193-custom-bridge';
export * from './lib/ethereum-error';
export * from './lib/ethereum-transaction-dialog';
export * from './lib/url-connector';
export * from './lib/use-bridge-contract';
export * from './lib/use-eager-connect';
export * from './lib/use-token-contract';
export * from './lib/use-token-decimals';
export * from './lib/use-ethereum-config';
export * from './lib/use-ethereum-read-contract';
export * from './lib/use-ethereum-transaction';
export * from './lib/use-ethereum-transaction-updater';
export * from './lib/use-ethereum-transaction-store';
export * from './lib/use-ethereum-transaction-manager';
export * from './lib/use-ethereum-transaction-store';
export * from './lib/use-ethereum-transaction-updater';
export * from './lib/use-ethereum-transaction';
export * from './lib/use-ethereum-withdraw-approvals-manager';
export * from './lib/use-ethereum-withdraw-approvals-store';
export * from './lib/use-get-withdraw-delay';
export * from './lib/use-get-withdraw-threshold';
export * from './lib/ethereum-transaction-dialog';
export * from './lib/web3-provider';
export * from './lib/web3-connectors';
export * from './lib/use-token-contract';
export * from './lib/use-token-decimals';
export * from './lib/use-web3-disconnect';
export * from './lib/web3-connect-dialog';
export * from './lib/web3-connect-store';
export * from './lib/url-connector';
export * from './lib/eip-1193-custom-bridge';
export * from './lib/web3-connectors';
export * from './lib/web3-provider';

View File

@ -9,3 +9,10 @@ export const getChainName = (chainId: number | null | undefined) => {
const name = chainId ? ChainIdMap[chainId] : undefined;
return name || 'Unknown';
};
/**
* WalletConnect project id
* https://cloud.walletconnect.com/sign-in
*/
export const WALLETCONNECT_PROJECT_ID =
process.env['NX_WALLETCONNECT_PROJECT_ID'] || '';

View File

@ -69,7 +69,7 @@ export class UrlConnector extends Connector {
const chainId = await this.provider!.request({ method: 'eth_chainId' });
this.actions.update({ chainId: Number(chainId) });
} catch (error) {
this.actions.reportError(error as Error);
this.onError?.(error as Error);
}
}
}

View File

@ -1,30 +1,40 @@
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import { useLocalStorage, useLogger } from '@vegaprotocol/react-helpers';
import type { Web3ReactHooks } from '@web3-react/core';
import { MetaMask } from '@web3-react/metamask';
import type { Connector } from '@web3-react/types';
import { WalletConnect } from '@web3-react/walletconnect';
import { WalletConnect } from '@web3-react/walletconnect-v2';
import { useEffect, useRef } from 'react';
import { useWeb3ConnectStore } from './web3-connect-store';
export const ETHEREUM_EAGER_CONNECT = 'ethereum-eager-connect';
export const useEagerConnect = () => {
export const useEagerConnect = (sentryDsn?: string) => {
const connectors = useWeb3ConnectStore((store) => store.connectors);
const [eagerConnector] = useLocalStorage(ETHEREUM_EAGER_CONNECT);
const attemptedRef = useRef(false);
const logger = useLogger({ dsn: sentryDsn });
useEffect(() => {
if (attemptedRef.current || 'Cypress' in window) return;
const stored = getConnector(connectors, eagerConnector);
// found a valid connection option
if (stored && stored[0].connectEagerly) {
stored[0].connectEagerly();
}
const tryConnectEagerly = async () => {
// found a valid connection option
if (stored && stored[0].connectEagerly) {
try {
await stored[0].connectEagerly();
} catch (err) {
// NOOP - no active session
logger.error('ERR_WEB3_EAGER_CONNECT', (err as Error).message);
}
}
};
tryConnectEagerly();
attemptedRef.current = true;
}, [eagerConnector, connectors]);
}, [eagerConnector, connectors, logger]);
};
const getConnector = (

View File

@ -0,0 +1,13 @@
import { useWeb3ConnectStore } from './web3-connect-store';
import type { Connector } from '@web3-react/types';
export const useWeb3Disconnect = (connector: Connector) => {
const clearError = useWeb3ConnectStore((store) => store.clearError);
return () => {
if (connector.deactivate) {
connector.deactivate();
}
connector.resetState();
clearError();
};
};

View File

@ -6,7 +6,7 @@ import { MetaMask } from '@web3-react/metamask';
import type { Connector } from '@web3-react/types';
const [foo, fooHooks] = initializeConnector((actions) => {
return new MetaMask(actions);
return new MetaMask({ actions });
});
const connectors: [Connector, Web3ReactHooks][] = [[foo, fooHooks]];

View File

@ -2,11 +2,15 @@ import { t } from '@vegaprotocol/i18n';
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
import { MetaMask } from '@web3-react/metamask';
import { WalletConnect } from '@web3-react/walletconnect';
import { WalletConnect } from '@web3-react/walletconnect-v2';
import { WalletConnect as WalletConnectLegacy } from '@web3-react/walletconnect';
import { CoinbaseWallet } from '@web3-react/coinbase-wallet';
import type { Connector } from '@web3-react/types';
import { ETHEREUM_EAGER_CONNECT } from './use-eager-connect';
import type { Web3ReactHooks } from '@web3-react/core';
import { useWeb3ConnectStore } from './web3-connect-store';
import { theme } from '@vegaprotocol/tailwindcss-config';
import classNames from 'classnames';
interface Web3ConnectDialogProps {
dialogOpen: boolean;
@ -20,37 +24,79 @@ export const Web3ConnectDialog = ({
setDialogOpen,
connectors,
desiredChainId,
}: Web3ConnectDialogProps) => {
const [, setEagerConnector] = useLocalStorage(ETHEREUM_EAGER_CONNECT);
}: Web3ConnectDialogProps) => (
<Dialog
open={dialogOpen}
onChange={setDialogOpen}
onInteractOutside={(e) => {
// do not close dialog when clicked outside (wallet connect modal)
e.preventDefault();
}}
intent={Intent.None}
title={t('Connect to your Ethereum wallet')}
size="small"
>
<ul className="grid grid-cols-2 gap-2" data-testid="web3-connector-list">
{connectors.map((connector, i) => (
<li key={i} className="mb-2 last:mb-0">
<ConnectButton
connector={connector}
desiredChainId={desiredChainId}
onClick={() => {
setDialogOpen(false);
}}
/>
</li>
))}
</ul>
</Dialog>
);
const ConnectButton = ({
connector,
desiredChainId,
onClick,
}: {
connector: [Connector, Web3ReactHooks];
desiredChainId?: number;
onClick?: () => void;
}) => {
const [connectorInstance, { useIsActivating }] = connector;
const isActivating = useIsActivating();
const info = getConnectorInfo(connectorInstance);
const [, setEagerConnector] = useLocalStorage(ETHEREUM_EAGER_CONNECT);
return (
<Dialog
open={dialogOpen}
onChange={setDialogOpen}
intent={Intent.None}
title={t('Connect to your Ethereum wallet')}
<button
className={classNames(
'flex items-center gap-2 p-2 rounded ',
'hover:bg-vega-light-100 hover:dark:bg-vega-dark-100',
{
'!bg-vega-yellow text-black hover:active:!bg-vega-yellow':
isActivating,
}
)}
data-testid={`web3-connector-${info.name}`}
title={info.alt}
onClick={async () => {
// remove orphaned wallet connect modals
const modals = document.querySelectorAll('w3m-modal');
if (modals.length > 0) {
modals.forEach((m) => m.remove());
}
try {
await connectorInstance.activate(desiredChainId);
setEagerConnector(info.name);
onClick?.();
} catch (err) {
console.log('could not connect to the wallet', info.name, err);
// NOOP - cancelled wallet connector
}
}}
>
<ul data-testid="web3-connector-list">
{connectors.map(([connector], i) => {
const info = getConnectorInfo(connector);
return (
<li key={i} className="mb-2 last:mb-0">
<button
className="hover:text-vega-pink dark:hover:text-vega-yellow underline"
data-testid={`web3-connector-${info.name}`}
onClick={async () => {
await connector.activate(desiredChainId);
setEagerConnector(info.name);
setDialogOpen(false);
}}
>
{info.text}
</button>
</li>
);
})}
</ul>
</Dialog>
{info.icon}
<span>{info.text}</span>
</button>
);
};
@ -72,15 +118,193 @@ export const Web3ConnectUncontrolledDialog = () => {
function getConnectorInfo(connector: Connector) {
if (connector instanceof MetaMask) {
return {
icon: <MetaMaskIcon width={40} />,
name: 'MetaMask',
text: t('MetaMask, Brave or other injected web wallet'),
text: t('MetaMask'),
alt: t('MetaMask, Brave or other injected web wallet'),
};
}
if (connector instanceof CoinbaseWallet) {
return {
icon: <CoinbaseWalletIcon width={40} />,
name: 'CoinbaseWallet',
text: t('Coinbase'),
};
}
if (connector instanceof WalletConnect) {
return {
icon: <WalletConnectIcon width={40} />,
name: 'WalletConnect',
text: t('WalletConnect'),
alt: t('WalletConnect v2'),
};
}
if (connector instanceof WalletConnectLegacy) {
return {
icon: (
<WalletConnectIcon
width={40}
fillColor={theme.colors.vega.light[200]}
/>
),
name: 'WalletConnectLegacy',
text: t('WalletConnect Legacy'),
alt: t('WalletConnect v1'),
};
}
return { name: 'Unknown', text: t('Unknown') };
}
type IconProps = {
width?: number;
height?: number;
};
const MetaMaskIcon = ({ width, height }: IconProps) => (
<svg
viewBox="0 0 47 47"
fill="none"
xmlns="http://www.w3.org/2000/svg"
width={width}
height={height}
>
<g>
<path
d="m40.632 6.969-14.136 10.62 2.628-6.259L40.632 6.97Z"
fill="#E17726"
stroke="#E17726"
strokeWidth="0.223"
strokeLinecap="round"
strokeLinejoin="round"
></path>
<path
d="m8.024 6.969 14.01 10.72-2.502-6.359L8.024 6.97ZM35.542 31.594l-3.761 5.834 8.054 2.251 2.307-7.958-6.6-.127ZM6.528 31.721 8.82 39.68l8.04-2.251-3.747-5.834-6.586.127Z"
fill="#E27625"
stroke="#E27625"
strokeWidth="0.223"
strokeLinecap="round"
strokeLinejoin="round"
></path>
<path
d="m16.428 21.738-2.237 3.427 7.97.368-.266-8.709-5.467 4.914ZM32.229 21.738l-5.552-5.012-.181 8.807 7.97-.368-2.237-3.427ZM16.861 37.428l4.824-2.365-4.152-3.285-.672 5.65ZM26.971 35.063l4.81 2.365-.657-5.65-4.153 3.285Z"
fill="#E27625"
stroke="#E27625"
strokeWidth="0.223"
strokeLinecap="round"
strokeLinejoin="round"
></path>
<path
d="m31.78 37.428-4.81-2.365.392 3.172-.042 1.345 4.46-2.152ZM16.861 37.428l4.475 2.152-.028-1.345.377-3.172-4.824 2.365Z"
fill="#D5BFB2"
stroke="#D5BFB2"
strokeWidth="0.223"
strokeLinecap="round"
strokeLinejoin="round"
></path>
<path
d="m21.42 29.682-4-1.19 2.825-1.316 1.174 2.506ZM27.236 29.682l1.175-2.506 2.838 1.317-4.013 1.19Z"
fill="#233447"
stroke="#233447"
strokeWidth="0.223"
strokeLinecap="round"
strokeLinejoin="round"
></path>
<path
d="m16.861 37.427.7-5.834-4.447.128 3.747 5.706ZM31.096 31.593l.685 5.834 3.761-5.706-4.446-.128ZM34.465 25.165l-7.97.368.741 4.15 1.175-2.507 2.838 1.317 3.216-3.328ZM17.42 28.493l2.825-1.317 1.175 2.506.74-4.149-7.97-.368 3.23 3.328Z"
fill="#CC6228"
stroke="#CC6228"
strokeWidth="0.223"
strokeLinecap="round"
strokeLinejoin="round"
></path>
<path
d="m14.19 25.165 3.343 6.613-.112-3.285-3.23-3.328ZM31.25 28.493l-.126 3.285 3.342-6.613-3.216 3.328ZM22.161 25.533l-.741 4.149.937 4.9.21-6.458-.406-2.591ZM26.495 25.533l-.391 2.577.196 6.471.937-4.9-.741-4.148Z"
fill="#E27525"
stroke="#E27525"
strokeWidth="0.223"
strokeLinecap="round"
strokeLinejoin="round"
></path>
<path
d="m27.237 29.682-.937 4.9.671.481 4.153-3.285.126-3.285-4.013 1.19ZM17.42 28.493l.112 3.285 4.153 3.285.671-.481-.937-4.9-3.999-1.19Z"
fill="#F5841F"
stroke="#F5841F"
strokeWidth="0.223"
strokeLinecap="round"
strokeLinejoin="round"
></path>
<path
d="m27.32 39.58.042-1.345-.363-.312h-5.342l-.35.312.029 1.345-4.475-2.152 1.566 1.303 3.175 2.223h5.439l3.188-2.224 1.552-1.302-4.46 2.152Z"
fill="#C0AC9D"
stroke="#C0AC9D"
strokeWidth="0.223"
strokeLinecap="round"
strokeLinejoin="round"
></path>
<path
d="m26.97 35.063-.67-.482h-3.944l-.67.482-.378 3.172.35-.312h5.34l.364.312-.391-3.172Z"
fill="#161616"
stroke="#161616"
strokeWidth="0.223"
strokeLinecap="round"
strokeLinejoin="round"
></path>
<path
d="m41.234 18.283 1.188-5.863-1.79-5.451-13.66 10.266 5.257 4.503 7.425 2.195 1.636-1.94-.713-.524 1.132-1.048-.867-.68 1.133-.878-.741-.58ZM6.234 12.42l1.203 5.863-.77.58 1.147.878-.867.68L8.08 21.47l-.713.524 1.636 1.94 7.425-2.195 5.257-4.503L8.025 6.97l-1.79 5.452Z"
fill="#763E1A"
stroke="#763E1A"
strokeWidth="0.223"
strokeLinecap="round"
strokeLinejoin="round"
></path>
<path
d="m39.654 23.933-7.425-2.195 2.237 3.427-3.342 6.613 4.419-.057h6.6l-2.49-7.788ZM16.428 21.738l-7.425 2.195-2.475 7.788h6.586l4.418.056-3.342-6.612 2.238-3.427ZM26.495 25.533l.476-8.298 2.153-5.905h-9.592l2.153 5.905.476 8.298.181 2.605.014 6.443H26.3l.014-6.443.182-2.605Z"
fill="#F5841F"
stroke="#F5841F"
strokeWidth="0.223"
strokeLinecap="round"
strokeLinejoin="round"
></path>
</g>
</svg>
);
const CoinbaseWalletIcon = ({ width, height }: IconProps) => (
<svg
width={width}
height={height}
viewBox="0 0 1024 1024"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="1024" height="1024" fill="#0052FF" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M152 512C152 710.823 313.177 872 512 872C710.823 872 872 710.823 872 512C872 313.177 710.823 152 512 152C313.177 152 152 313.177 152 512ZM420 396C406.745 396 396 406.745 396 420V604C396 617.255 406.745 628 420 628H604C617.255 628 628 617.255 628 604V420C628 406.745 617.255 396 604 396H420Z"
fill="white"
/>
</svg>
);
const WalletConnectIcon = ({
width,
height,
fillColor = '#3396ff',
}: IconProps & { fillColor?: string }) => (
<svg
fill="none"
width={width}
height={height}
viewBox="0 0 400 400"
xmlns="http://www.w3.org/2000/svg"
>
<g>
<circle cx="200" cy="200" fill={fillColor} r="199.5" stroke="none" />
<path
d="m122.519 148.965c42.791-41.729 112.171-41.729 154.962 0l5.15 5.022c2.14 2.086 2.14 5.469 0 7.555l-17.617 17.18c-1.07 1.043-2.804 1.043-3.874 0l-7.087-6.911c-29.853-29.111-78.253-29.111-108.106 0l-7.59 7.401c-1.07 1.043-2.804 1.043-3.874 0l-17.617-17.18c-2.14-2.086-2.14-5.469 0-7.555zm191.397 35.529 15.679 15.29c2.14 2.086 2.14 5.469 0 7.555l-70.7 68.944c-2.139 2.087-5.608 2.087-7.748 0l-50.178-48.931c-.535-.522-1.402-.522-1.937 0l-50.178 48.931c-2.139 2.087-5.608 2.087-7.748 0l-70.7015-68.945c-2.1396-2.086-2.1396-5.469 0-7.555l15.6795-15.29c2.1396-2.086 5.6085-2.086 7.7481 0l50.1789 48.932c.535.522 1.402.522 1.937 0l50.177-48.932c2.139-2.087 5.608-2.087 7.748 0l50.179 48.932c.535.522 1.402.522 1.937 0l50.179-48.931c2.139-2.087 5.608-2.087 7.748 0z"
fill="#fff"
/>
</g>
</svg>
);

View File

@ -6,11 +6,13 @@ interface State {
isOpen: boolean;
connectors: [Connector, Web3ReactHooks][];
desiredChainId?: number;
error?: Error;
}
interface Actions {
initialize: (connectors: State['connectors'], desiredChainId: number) => void;
open: () => void;
close: () => void;
clearError: () => void;
}
export const useWeb3ConnectStore = create<State & Actions>()((set) => ({
@ -21,4 +23,5 @@ export const useWeb3ConnectStore = create<State & Actions>()((set) => ({
},
open: () => set(() => ({ isOpen: true })),
close: () => set(() => ({ isOpen: false })),
clearError: () => set(() => ({ error: undefined })),
}));

View File

@ -2,8 +2,102 @@ import type { Web3ReactHooks } from '@web3-react/core';
import { initializeConnector } from '@web3-react/core';
import type { Connector } from '@web3-react/types';
import { MetaMask } from '@web3-react/metamask';
import { WalletConnect } from '@web3-react/walletconnect';
import { WalletConnect } from '@web3-react/walletconnect-v2';
import { WalletConnect as WalletConnectLegacy } from '@web3-react/walletconnect';
import { CoinbaseWallet } from '@web3-react/coinbase-wallet';
import { initializeUrlConnector } from './url-connector';
import { WALLETCONNECT_PROJECT_ID } from './constants';
import { useWeb3ConnectStore } from './web3-connect-store';
import { theme } from '@vegaprotocol/tailwindcss-config';
export const initializeCoinbaseConnector = (providerUrl: string) =>
initializeConnector<CoinbaseWallet>(
(actions) =>
new CoinbaseWallet({
actions,
options: {
appName: 'Vega',
darkMode: true,
url: providerUrl,
},
onError: (error) => {
console.log('ERR_COINBASE_WALLET', error);
useWeb3ConnectStore.setState({ error });
},
})
);
export const initializeWalletConnectLegacyConnector = (
chainId: number,
providerUrl: string
) =>
initializeConnector<WalletConnectLegacy>(
(actions) =>
new WalletConnectLegacy({
actions,
options: {
rpc: {
[chainId]: providerUrl,
},
qrcode: true,
},
defaultChainId: chainId,
})
);
export const initializeWalletConnector = (
projectId: string,
chainId: number,
providerUrl: string
) =>
projectId && projectId.length > 0
? initializeConnector<WalletConnect>(
(actions) =>
new WalletConnect({
actions,
defaultChainId: chainId,
options: {
projectId: projectId,
chains: [chainId],
showQrModal: true,
rpcMap: {
[chainId]: providerUrl,
},
qrModalOptions: {
themeMode: 'dark',
themeVariables: {
'--w3m-z-index': '40',
'--w3m-accent-color': theme.colors.vega.yellow.DEFAULT,
'--w3m-background-color': theme.colors.vega.dark[100],
'--w3m-font-family': 'AlphaLyrae',
'--w3m-container-border-radius': '0.25rem',
'--w3m-background-border-radius': '0.25rem',
'--w3m-accent-fill-color': theme.colors.vega.yellow.DEFAULT,
},
},
},
onError: (error) => {
console.log('ERR_WALLET_CONNECT', error.message);
useWeb3ConnectStore.setState({ error });
},
})
)
: null;
export const initializeMetaMaskConnector = () =>
initializeConnector<MetaMask>(
(actions) =>
new MetaMask({
actions,
options: {
mustBeMetaMask: false,
},
onError: (error) => {
console.log('ERR_META_MASK', error.message);
useWeb3ConnectStore.setState({ error });
},
})
);
export const createConnectors = (
providerUrl: string,
@ -15,23 +109,11 @@ export const createConnectors = (
throw new Error('Invalid Ethereum chain ID for environment');
}
const [metamask, metamaskHooks] = initializeConnector<MetaMask>(
(actions) => new MetaMask(actions)
);
const [walletconnect, walletconnectHooks] =
initializeConnector<WalletConnect>(
(actions) =>
new WalletConnect(actions, {
rpc: {
[chainId]: providerUrl,
},
}),
[chainId]
);
return [
initializeUrlConnector(localProviderUrl, walletMnemonic),
[metamask, metamaskHooks],
[walletconnect, walletconnectHooks],
].filter(Boolean) as [Connector, Web3ReactHooks][];
initializeMetaMaskConnector(),
initializeCoinbaseConnector(providerUrl),
initializeWalletConnector(WALLETCONNECT_PROJECT_ID, chainId, providerUrl),
initializeWalletConnectLegacyConnector(chainId, providerUrl),
].filter(Boolean) as unknown as [Connector, Web3ReactHooks][];
};

View File

@ -4,7 +4,9 @@ import { initializeConnector } from '@web3-react/core';
import { MetaMask } from '@web3-react/metamask';
import { Web3Provider } from './web3-provider';
const [foo, fooHooks] = initializeConnector((actions) => new MetaMask(actions));
const [foo, fooHooks] = initializeConnector(
(actions) => new MetaMask({ actions })
);
const connectors: [MetaMask, Web3ReactHooks][] = [[foo, fooHooks]];

View File

@ -1,6 +1,7 @@
import type { Web3ReactHooks } from '@web3-react/core';
import { Web3ReactProvider } from '@web3-react/core';
import type { Connector } from '@web3-react/types';
import { useMemo } from 'react';
interface Web3ProviderProps {
children: JSX.Element | JSX.Element[];
@ -8,7 +9,19 @@ interface Web3ProviderProps {
}
export const Web3Provider = ({ children, connectors }: Web3ProviderProps) => {
/**
* The connectors prop passed to Web3ReactProvider must be referentially static.
* https://github.com/Uniswap/web3-react/blob/31742897f9fddb38e00e36c2516029d3df9a9c54/packages/core/src/provider.tsx#L66
*/
const key = useMemo(
() => `WEB3_PROVIDER_${Date.now().toString()}`,
// eslint-disable-next-line react-hooks/exhaustive-deps
[connectors]
);
return (
<Web3ReactProvider connectors={connectors}>{children}</Web3ReactProvider>
<Web3ReactProvider key={key} connectors={connectors}>
{children}
</Web3ReactProvider>
);
};

View File

@ -31,6 +31,7 @@ import { WithdrawLimits } from './withdraw-limits';
import {
ETHEREUM_EAGER_CONNECT,
useWeb3ConnectStore,
useWeb3Disconnect,
} from '@vegaprotocol/web3';
interface FormFields {
@ -298,6 +299,7 @@ const EthereumButton = ({ clearAddress }: { clearAddress: () => void }) => {
const openDialog = useWeb3ConnectStore((state) => state.open);
const { isActive, connector } = useWeb3React();
const [, , removeEagerConnector] = useLocalStorage(ETHEREUM_EAGER_CONNECT);
const disconnect = useWeb3Disconnect(connector);
if (!isActive) {
return (
@ -310,7 +312,7 @@ const EthereumButton = ({ clearAddress }: { clearAddress: () => void }) => {
return (
<UseButton
onClick={() => {
connector.deactivate();
disconnect();
clearAddress();
removeEagerConnector();
}}

View File

@ -21,6 +21,7 @@
"dependencies": {
"@apollo/client": "^3.5.8",
"@blueprintjs/icons": "^3.32.0",
"@coinbase/wallet-sdk": "^3.6.5",
"@radix-ui/react-accordion": "^1.1.0",
"@radix-ui/react-checkbox": "^1.0.1",
"@radix-ui/react-dialog": "^1.0.2",
@ -37,10 +38,12 @@
"@sentry/react": "^6.19.2",
"@sentry/tracing": "^6.19.2",
"@vegaprotocol/wallet-client": "0.1.9",
"@walletconnect/ethereum-provider": "^1.7.5",
"@web3-react/core": "8.0.20-beta.0",
"@web3-react/metamask": "8.0.16-beta.0",
"@web3-react/walletconnect": "^8.0.23-beta.0",
"@walletconnect/ethereum-provider": "^2.6.0",
"@web3-react/coinbase-wallet": "8.1.2-beta.0",
"@web3-react/core": "^8.1.2-beta.0",
"@web3-react/metamask": "^8.1.2-beta.0",
"@web3-react/walletconnect": "8.1.3-beta.0",
"@web3-react/walletconnect-v2": "^8.1.3-beta.0",
"ag-grid-community": "^27.0.1",
"ag-grid-react": "^27.0.1",
"allotment": "1.18.1",

1614
yarn.lock

File diff suppressed because it is too large Load Diff