Skip to content

Commit b8ffd79

Browse files
committed
use Metamask SDK when metamask not found in injected providers
1 parent b073150 commit b8ffd79

File tree

10 files changed

+767
-13
lines changed

10 files changed

+767
-13
lines changed

package-lock.json

Lines changed: 589 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/modal/src/modalManager.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,12 @@ export class Web3Auth extends Web3AuthNoModal implements IWeb3AuthModal {
287287
if (connector.status !== CONNECTOR_STATUS.NOT_READY) return;
288288

289289
// only initialize a external connectors here if it is a cached connector.
290-
if (this.cachedConnector !== connectorName && connectorName !== "metamask" && connector.type === CONNECTOR_CATEGORY.EXTERNAL) return;
290+
if (
291+
this.cachedConnector !== connectorName &&
292+
connectorName !== WALLET_CONNECTORS.METAMASK &&
293+
connector.type === CONNECTOR_CATEGORY.EXTERNAL
294+
)
295+
return;
291296

292297
// in-app wallets or cached wallet (being connected or already connected) are initialized first.
293298
// if connector is configured then only initialize in app or cached connector.

packages/modal/src/ui/components/ExternalWallets.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ export default function ExternalWallets(props: ExternalWalletsProps) {
107107
const generateWalletButtons = (wallets: Record<string, WalletRegistryItem>): ExternalButton[] => {
108108
return Object.keys(wallets).reduce((acc, wallet) => {
109109
if (adapterVisibilityMap[wallet] === false) return acc;
110-
if (wallet === "metamask") return acc;
110+
// Metamask is always visible in the main screen, no need to show it in the external wallets list
111+
if (wallet === WALLET_CONNECTORS.METAMASK) return acc;
111112

112113
const walletRegistryItem: WalletRegistryItem = wallets[wallet];
113114
let href = "";
@@ -147,6 +148,8 @@ export default function ExternalWallets(props: ExternalWalletsProps) {
147148

148149
// Generate custom adapter buttons
149150
const customAdapterButtons: ExternalButton[] = Object.keys(config).reduce((acc, adapter) => {
151+
// Metamask is always visible in the main screen, no need to show it in the external wallets list
152+
if (adapter === WALLET_CONNECTORS.METAMASK) return acc;
150153
if (![WALLET_CONNECTORS.WALLET_CONNECT_V2].includes(adapter) && !config[adapter].isInjected && adapterVisibilityMap[adapter]) {
151154
log.debug("custom adapter", adapter, config[adapter]);
152155
acc.push({

packages/modal/src/ui/components/Modal.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { LOGIN_PROVIDER, type SafeEventEmitter } from "@web3auth/auth";
2-
import { ChainNamespaceType, cloneDeep, CONNECTOR_NAMES, log, WalletRegistry } from "@web3auth/no-modal";
2+
import { ChainNamespaceType, cloneDeep, CONNECTOR_NAMES, log, WALLET_CONNECTORS, WalletRegistry } from "@web3auth/no-modal";
33
import deepmerge from "deepmerge";
44
import { useCallback, useEffect, useMemo, useState } from "react";
55
import { useTranslation } from "react-i18next";
@@ -179,7 +179,7 @@ export default function Modal(props: ModalProps) {
179179
className="w3a--w-full w3ajs-external-toggle__button"
180180
style={{ width: "100%" }}
181181
onClick={() => {
182-
handleExternalWalletClick({ connector: "metamask" });
182+
preHandleExternalWalletClick({ connector: WALLET_CONNECTORS.METAMASK });
183183
}}
184184
>
185185
<Image imageId="login-metamask" hoverImageId="login-metamask" fallbackImageId="wallet" height="24" width="24" isButton extension="svg" />

packages/no-modal/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
},
5151
"dependencies": {
5252
"@ethereumjs/util": "^9.1.0",
53+
"@metamask/sdk": "^0.32.1",
5354
"@solana/wallet-standard-features": "^1.3.0",
5455
"@solana/web3.js": "^1.98.0",
5556
"@toruslabs/base-controllers": "^7.2.1",

packages/no-modal/src/base/wallet/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const SOLANA_CONNECTORS = {
1010

1111
export const EVM_CONNECTORS = {
1212
COINBASE: "coinbase",
13+
METAMASK: "metamask",
1314
...MULTI_CHAIN_CONNECTORS,
1415
};
1516

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { metaMaskConnector } from "./metaMaskConnector";
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import { MetaMaskSDK, type MetaMaskSDKOptions, type SDKProvider } from "@metamask/sdk";
2+
3+
import {
4+
BaseConnectorSettings,
5+
CHAIN_NAMESPACES,
6+
ChainNamespaceType,
7+
CONNECTED_EVENT_DATA,
8+
CONNECTOR_CATEGORY,
9+
CONNECTOR_CATEGORY_TYPE,
10+
CONNECTOR_EVENTS,
11+
CONNECTOR_NAMESPACES,
12+
CONNECTOR_STATUS,
13+
CONNECTOR_STATUS_TYPE,
14+
ConnectorFn,
15+
ConnectorInitOptions,
16+
ConnectorNamespaceType,
17+
ConnectorParams,
18+
IProvider,
19+
UserInfo,
20+
WALLET_CONNECTORS,
21+
WalletLoginError,
22+
Web3AuthError,
23+
} from "@/core/base";
24+
25+
import { BaseEvmConnector } from "../base-evm-connector";
26+
27+
export interface MetaMaskConnectorOptions extends BaseConnectorSettings {
28+
connectorSettings?: MetaMaskSDKOptions;
29+
}
30+
31+
class MetaMaskConnector extends BaseEvmConnector<void> {
32+
readonly connectorNamespace: ConnectorNamespaceType = CONNECTOR_NAMESPACES.EIP155;
33+
34+
readonly currentChainNamespace: ChainNamespaceType = CHAIN_NAMESPACES.EIP155;
35+
36+
readonly type: CONNECTOR_CATEGORY_TYPE = CONNECTOR_CATEGORY.EXTERNAL;
37+
38+
readonly name: string = WALLET_CONNECTORS.METAMASK;
39+
40+
public status: CONNECTOR_STATUS_TYPE = CONNECTOR_STATUS.NOT_READY;
41+
42+
private metamaskProvider: SDKProvider | null = null;
43+
44+
private metamaskSDK: MetaMaskSDK | null = null;
45+
46+
private metamaskOptions: MetaMaskSDKOptions = { dappMetadata: { name: "Web3Auth" } };
47+
48+
constructor(connectorOptions: MetaMaskConnectorOptions) {
49+
super(connectorOptions);
50+
this.metamaskOptions = { ...this.metamaskOptions, ...connectorOptions.connectorSettings };
51+
}
52+
53+
get provider(): IProvider | null {
54+
if (this.status !== CONNECTOR_STATUS.NOT_READY && this.metamaskProvider) {
55+
return this.metamaskProvider as unknown as IProvider;
56+
}
57+
return null;
58+
}
59+
60+
set provider(_: IProvider | null) {
61+
throw new Error("Not implemented");
62+
}
63+
64+
async init(options: ConnectorInitOptions): Promise<void> {
65+
await super.init(options);
66+
const chainConfig = this.coreOptions.chains.find((x) => x.chainId === options.chainId);
67+
super.checkInitializationRequirements({ chainConfig });
68+
69+
this.metamaskSDK = new MetaMaskSDK(this.metamaskOptions);
70+
71+
this.status = CONNECTOR_STATUS.READY;
72+
this.emit(CONNECTOR_EVENTS.READY, WALLET_CONNECTORS.METAMASK);
73+
try {
74+
if (options.autoConnect) {
75+
this.rehydrated = true;
76+
await this.connect({ chainId: options.chainId });
77+
}
78+
} catch (error) {
79+
this.emit(CONNECTOR_EVENTS.ERRORED, error as Web3AuthError);
80+
}
81+
}
82+
83+
async connect({ chainId }: { chainId: string }): Promise<IProvider | null> {
84+
super.checkConnectionRequirements();
85+
if (!this.metamaskSDK) throw WalletLoginError.notConnectedError("Connector is not initialized");
86+
87+
this.status = CONNECTOR_STATUS.CONNECTING;
88+
this.emit(CONNECTOR_EVENTS.CONNECTING, { connector: WALLET_CONNECTORS.METAMASK });
89+
try {
90+
const chainConfig = this.coreOptions.chains.find((x) => x.chainId === chainId);
91+
if (!chainConfig) throw WalletLoginError.connectionError("Chain config is not available");
92+
93+
await this.metamaskSDK.connect();
94+
this.metamaskProvider = this.metamaskSDK.getProvider();
95+
96+
const currentChainId = (await this.metamaskProvider.request({ method: "eth_chainId" })) as string;
97+
if (currentChainId !== chainConfig.chainId) {
98+
await this.switchChain(chainConfig, true);
99+
}
100+
this.status = CONNECTOR_STATUS.CONNECTED;
101+
if (!this.provider) throw WalletLoginError.notConnectedError("Failed to connect with provider");
102+
this.provider.once("disconnect", () => {
103+
// ready to be connected again
104+
this.disconnect();
105+
});
106+
this.emit(CONNECTOR_EVENTS.CONNECTED, {
107+
connector: WALLET_CONNECTORS.METAMASK,
108+
reconnected: this.rehydrated,
109+
provider: this.provider,
110+
} as CONNECTED_EVENT_DATA);
111+
return this.provider;
112+
} catch (error) {
113+
// ready again to be connected
114+
this.status = CONNECTOR_STATUS.READY;
115+
this.rehydrated = false;
116+
this.emit(CONNECTOR_EVENTS.ERRORED, error as Web3AuthError);
117+
if (error instanceof Web3AuthError) throw error;
118+
throw WalletLoginError.connectionError("Failed to login with MetaMask wallet", error);
119+
}
120+
}
121+
122+
async disconnect(options: { cleanup: boolean } = { cleanup: false }): Promise<void> {
123+
await super.disconnectSession();
124+
this.provider?.removeAllListeners();
125+
if (options.cleanup) {
126+
this.status = CONNECTOR_STATUS.NOT_READY;
127+
this.metamaskProvider = null;
128+
} else {
129+
// ready to be connected again
130+
this.status = CONNECTOR_STATUS.READY;
131+
}
132+
await super.disconnect();
133+
}
134+
135+
async getUserInfo(): Promise<Partial<UserInfo>> {
136+
if (this.status !== CONNECTOR_STATUS.CONNECTED) throw WalletLoginError.notConnectedError("Not connected with wallet, Please login/connect first");
137+
return {};
138+
}
139+
140+
public async switchChain(params: { chainId: string }, init = false): Promise<void> {
141+
super.checkSwitchChainRequirements(params, init);
142+
await this.metamaskProvider.request({ method: "wallet_switchEthereumChain", params: [{ chainId: params.chainId }] });
143+
}
144+
145+
public async enableMFA(): Promise<void> {
146+
throw new Error("Method Not implemented");
147+
}
148+
149+
public async manageMFA(): Promise<void> {
150+
throw new Error("Method Not implemented");
151+
}
152+
}
153+
154+
export const metaMaskConnector = (params?: MetaMaskSDKOptions): ConnectorFn => {
155+
return ({ coreOptions }: ConnectorParams) => {
156+
return new MetaMaskConnector({ connectorSettings: params, coreOptions });
157+
};
158+
};

packages/no-modal/src/noModal.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
Web3AuthNoModalEvents,
3434
} from "@/core/base";
3535
import { CommonJRPCProvider } from "@/core/base-provider";
36+
import { metaMaskConnector } from "@/core/meta-mask-connector";
3637

3738
const CONNECTOR_CACHE_KEY = "Web3Auth-cachedConnector";
3839

@@ -309,6 +310,9 @@ export class Web3AuthNoModal extends SafeEventEmitter<Web3AuthNoModalEvents> imp
309310
}
310311
}
311312

313+
// it's safe to add it here as if there is a MetaMask injected provider, this won't override it
314+
connectorFns.push(metaMaskConnector());
315+
312316
// add WalletConnectV2 connector if enabled
313317
if (
314318
projectConfig.wallet_connect_enabled &&

packages/no-modal/webpack.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const config = generateWebpackConfig({
1212
'@/core/base-evm-connector': path.resolve(__dirname, 'src/connectors/base-evm-connector'),
1313
'@/core/base-solana-connector': path.resolve(__dirname, 'src/connectors/base-solana-connector'),
1414
'@/core/coinbase-connector': path.resolve(__dirname, 'src/connectors/coinbase-connector'),
15+
'@/core/meta-mask-connector': path.resolve(__dirname, 'src/connectors/meta-mask-connector'),
1516
'@/core/injected-evm-connector': path.resolve(__dirname, 'src/connectors/injected-evm-connector'),
1617
'@/core/injected-solana-connector': path.resolve(__dirname, 'src/connectors/injected-solana-connector'),
1718
'@/core/wallet-connect-v2-connector': path.resolve(__dirname, 'src/connectors/wallet-connect-v2-connector'),

0 commit comments

Comments
 (0)