diff --git a/packages/no-modal/src/connectors/metamask-connector/metamaskConnector.ts b/packages/no-modal/src/connectors/metamask-connector/metamaskConnector.ts index ec52743a2..0bd3b6e87 100644 --- a/packages/no-modal/src/connectors/metamask-connector/metamaskConnector.ts +++ b/packages/no-modal/src/connectors/metamask-connector/metamaskConnector.ts @@ -34,6 +34,7 @@ import { getSolanaChainByChainConfig, type IProvider, isUserRejectedError, + log, type UserInfo, WALLET_CONNECTOR_TYPE, WALLET_CONNECTORS, @@ -68,6 +69,12 @@ export interface MetaMaskConnectorOptions extends BaseConnectorSettings { connectorSettings?: MetaMaskConnectorSettings; } +// `@metamask/connect-evm` announces its SDK-backed provider over EIP-6963 with this +// RDNS value. Web3Auth uses the dedicated `metaMaskConnector` for MetaMask, so MIPD +// discovery should ignore this provider to avoid treating the QR/deeplink transport +// as a native injected wallet. +export const METAMASK_ERC_6963_PROVIDER_RDNS = "io.metamask.mmc"; + class MetaMaskConnector extends BaseConnector { readonly connectorNamespace: ConnectorNamespaceType = CONNECTOR_NAMESPACES.MULTICHAIN; @@ -398,10 +405,12 @@ class MetaMaskConnector extends BaseConnector { async disconnect(options: { cleanup: boolean } = { cleanup: false }): Promise { if (!this.multichainClient) throw WalletLoginError.connectionError("Multichain client is not available"); this.checkDisconnectionRequirements(); - await this.clearWalletSession(); + await this.clearMultichainWalletSessions(); // Disconnect using the multichain client - await this.multichainClient.disconnect(); + if (this.multichainClient.status === "connected") { + await this.multichainClient.disconnect(); + } if (options.cleanup) { this.status = CONNECTOR_STATUS.NOT_READY; @@ -601,6 +610,33 @@ class MetaMaskConnector extends BaseConnector { ? this.coreOptions.chains.find((x) => x.chainId === evmChainId) : undefined; } + + private async clearMultichainWalletSessions(): Promise { + const addresses = new Set(); + + if (this.evmProvider) { + const evmAccounts = await this.evmProvider.request({ method: EVM_METHOD_TYPES.GET_ACCOUNTS }); + evmAccounts.forEach((account) => addresses.add(account)); + } + + if (this.solanaProvider) { + this.solanaProvider.accounts.forEach((account) => addresses.add(account.address)); + } + + if (addresses.size === 0) { + await this.clearWalletSession(); + return; + } + + // MetaMask can authorize both EVM and Solana accounts in one session, but BaseConnector + // caches SIWW tokens under one address at a time. Clear every connected address on disconnect. + for (const address of addresses) { + this.initSessionManager(address); + await this.clearWalletSession().catch((error) => { + log.error("Failed to clear multichain wallet session", error); + }); + } + } } /** diff --git a/packages/no-modal/src/noModal.ts b/packages/no-modal/src/noModal.ts index 8899184c6..8bae0952f 100644 --- a/packages/no-modal/src/noModal.ts +++ b/packages/no-modal/src/noModal.ts @@ -94,7 +94,7 @@ import { type AuthConnectorType, isAuthConnector, } from "./connectors/auth-connector"; -import { metaMaskConnector } from "./connectors/metamask-connector"; +import { METAMASK_ERC_6963_PROVIDER_RDNS, metaMaskConnector } from "./connectors/metamask-connector"; import { walletServicesPlugin } from "./plugins/wallet-services-plugin"; import { type AccountAbstractionProvider } from "./providers/account-abstraction-provider"; import { CommonJRPCProvider } from "./providers/base-provider"; @@ -1024,12 +1024,20 @@ export class Web3AuthNoModal extends SafeEventEmitter imp if (chainNamespaces.has(CHAIN_NAMESPACES.EIP155)) { const { createMipd, injectedEvmConnector } = await import("./connectors/injected-evm-connector"); const evmMipd = createMipd(); + // `@metamask/connect-evm` SDK announces its own EIP-6963 provider (`io.metamask.mmc`) so + // it can be discovered by generic wallet pickers. We already register MetaMask via + // `metaMaskConnector`, so we must exclude the SDK-announced provider here; otherwise + // it is misclassified as an injected wallet and the modal shows MetaMask as installed, + // even when the user does not have the extension installed. + const isNonSdkAnnouncedProvider = (providerDetail: { info: { rdns: string } }) => + providerDetail.info.rdns !== METAMASK_ERC_6963_PROVIDER_RDNS; // subscribe to new injected connectors evmMipd.subscribe((providerDetails) => { - const newConnectors = providerDetails.map((providerDetail) => injectedEvmConnector(providerDetail)(config)); + const filteredProviderDetails = providerDetails.filter(isNonSdkAnnouncedProvider); + const newConnectors = filteredProviderDetails.map((providerDetail) => injectedEvmConnector(providerDetail)(config)); this.setConnectors(newConnectors); }); - connectorFns.push(...evmMipd.getProviders().map(injectedEvmConnector)); + connectorFns.push(...evmMipd.getProviders().filter(isNonSdkAnnouncedProvider).map(injectedEvmConnector)); } }