Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
getSolanaChainByChainConfig,
type IProvider,
isUserRejectedError,
log,
type UserInfo,
WALLET_CONNECTOR_TYPE,
WALLET_CONNECTORS,
Expand Down Expand Up @@ -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<void> {
readonly connectorNamespace: ConnectorNamespaceType = CONNECTOR_NAMESPACES.MULTICHAIN;

Expand Down Expand Up @@ -398,10 +405,12 @@ class MetaMaskConnector extends BaseConnector<void> {
async disconnect(options: { cleanup: boolean } = { cleanup: false }): Promise<void> {
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;
Expand Down Expand Up @@ -601,6 +610,33 @@ class MetaMaskConnector extends BaseConnector<void> {
? this.coreOptions.chains.find((x) => x.chainId === evmChainId)
: undefined;
}

private async clearMultichainWalletSessions(): Promise<void> {
const addresses = new Set<string>();

if (this.evmProvider) {
const evmAccounts = await this.evmProvider.request<never, string[]>({ 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);
});
}
}
}

/**
Expand Down
14 changes: 11 additions & 3 deletions packages/no-modal/src/noModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -1024,12 +1024,20 @@ export class Web3AuthNoModal extends SafeEventEmitter<Web3AuthNoModalEvents> 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));
}
}

Expand Down