From 47dba51053d64b7423ad854f284f7be3caef5243 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Tue, 20 Jan 2026 13:45:26 -0800 Subject: [PATCH 1/8] Add chainId request interceptor --- packages/connect-evm/src/connect.ts | 5 +++++ packages/connect-evm/src/constants.ts | 3 +++ packages/connect-evm/src/utils/type-guards.ts | 12 ++++++++++++ 3 files changed, 20 insertions(+) diff --git a/packages/connect-evm/src/connect.ts b/packages/connect-evm/src/connect.ts index c8d0e114..5adae89a 100644 --- a/packages/connect-evm/src/connect.ts +++ b/packages/connect-evm/src/connect.ts @@ -35,6 +35,7 @@ import { getPermittedEthChainIds } from './utils/caip'; import { isAccountsRequest, isAddChainRequest, + isChainIdRequest, isConnectRequest, isSwitchChainRequest, validSupportedChainsUrls, @@ -624,6 +625,10 @@ export class MetamaskConnectEVM { return this.#provider.accounts; } + if (isChainIdRequest(request)) { + return this.#provider.selectedChainId; + } + logger('Request not intercepted, forwarding to default handler', request); return Promise.resolve(); } diff --git a/packages/connect-evm/src/constants.ts b/packages/connect-evm/src/constants.ts index 01f35055..523eba57 100644 --- a/packages/connect-evm/src/constants.ts +++ b/packages/connect-evm/src/constants.ts @@ -14,10 +14,13 @@ export const CONNECT_METHODS = [ export const ACCOUNTS_METHODS = ['eth_accounts', 'eth_coinbase']; +export const CHAIN_METHODS = ['eth_chainId']; + export const INTERCEPTABLE_METHODS = [ ...ACCOUNTS_METHODS, ...IGNORED_METHODS, ...CONNECT_METHODS, + ...CHAIN_METHODS, // These have bespoke handlers 'wallet_revokePermissions', 'wallet_switchEthereumChain', diff --git a/packages/connect-evm/src/utils/type-guards.ts b/packages/connect-evm/src/utils/type-guards.ts index 72a6aa09..e321a444 100644 --- a/packages/connect-evm/src/utils/type-guards.ts +++ b/packages/connect-evm/src/utils/type-guards.ts @@ -61,6 +61,18 @@ export function isAccountsRequest( return req.method === 'eth_accounts' || req.method === 'eth_coinbase'; } +/** + * Type guard for generic eth_chainId request. + * + * @param req - The request object to check + * @returns True if the request is a eth_chainId request, false otherwise + */ +export function isChainIdRequest( + req: ProviderRequest, +): req is Extract { + return req.method === 'eth_chainId'; +} + /** * Validates that all values in a Record are valid URLs. * From 979477ec0fb8b2562e464ee4f2123972ae88fc70 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Tue, 20 Jan 2026 13:45:47 -0800 Subject: [PATCH 2/8] expose ConnectMultichain.state in ConnectEvm --- packages/connect-evm/src/connect.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/connect-evm/src/connect.ts b/packages/connect-evm/src/connect.ts index 5adae89a..78f8684b 100644 --- a/packages/connect-evm/src/connect.ts +++ b/packages/connect-evm/src/connect.ts @@ -4,6 +4,7 @@ import type { MultichainCore, MultichainOptions, Scope, + SDKState, SessionData, } from '@metamask/connect-multichain'; import { @@ -900,6 +901,10 @@ export class MetamaskConnectEVM { get selectedChainId(): Hex | undefined { return this.#provider.selectedChainId; } + + get state(): SDKState { + return this.#core.state; + } } /** From 5aa59643a6a86d08b8b9b9f6ff36acb6e4ff35a4 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 21 Jan 2026 14:47:23 -0800 Subject: [PATCH 3/8] WIP displayUri --- .../src/domain/multichain/types.ts | 1 + .../src/multichain/index.ts | 101 ++++++++++++++++-- 2 files changed, 93 insertions(+), 9 deletions(-) diff --git a/packages/connect-multichain/src/domain/multichain/types.ts b/packages/connect-multichain/src/domain/multichain/types.ts index bd933f27..220363d8 100644 --- a/packages/connect-multichain/src/domain/multichain/types.ts +++ b/packages/connect-multichain/src/domain/multichain/types.ts @@ -60,6 +60,7 @@ export type MultichainOptions = { headless?: boolean; preferExtension?: boolean; showInstallModal?: boolean; + displayUri?: (uri: string) => void; }; mobile?: { preferredOpenLink?: (deeplink: string, target?: string) => void; diff --git a/packages/connect-multichain/src/multichain/index.ts b/packages/connect-multichain/src/multichain/index.ts index 48dc0c9d..b279b5cf 100644 --- a/packages/connect-multichain/src/multichain/index.ts +++ b/packages/connect-multichain/src/multichain/index.ts @@ -58,7 +58,7 @@ import { RequestRouter } from './rpc/requestRouter'; import { DefaultTransport } from './transports/default'; import { MWPTransport } from './transports/mwp'; import { keymanager } from './transports/mwp/KeyManager'; -import { getDappId, openDeeplink, setupDappMetadata } from './utils'; +import { compressString, getDappId, openDeeplink, setupDappMetadata } from './utils'; import { MultichainApiClientWrapperTransport } from './transports/multichainApiClientWrapper'; export { getInfuraRpcUrls } from '../domain/multichain/api/infura'; @@ -340,6 +340,84 @@ export class MultichainSDK extends MultichainCore { }; } + async #getQrCodeLink( + scopes: Scope[], + caipAccountIds: CaipAccountId[], + sessionProperties?: SessionProperties, + ): Promise { + return new Promise(async (resolve, reject) => { + if ( + this.dappClient.state === 'CONNECTED' || + this.dappClient.state === 'CONNECTING' + ) { + await this.dappClient.disconnect(); + } + const connectionRequest = new Promise((_resolve) => { + this.dappClient.on( + 'session_request', + (sessionRequest: SessionRequest) => { + _resolve({ + sessionRequest, + metadata: { + dapp: this.options.dapp, + sdk: { + version: getVersion(), + platform: getPlatformType(), + }, + }, + }); + }, + ); + + (async (): Promise => { + try { + await this.transport.connect({ scopes, caipAccountIds, sessionProperties }); + await this.storage.setTransport(TransportType.MWP); + this.status = 'connected'; + await this.storage.setTransport(TransportType.MWP); + } catch (error) { + if (error instanceof ProtocolError) { + // Ignore Request expired errors to allow modal to regenerate expired qr codes + if (error.code !== ErrorCode.REQUEST_EXPIRED) { + this.status = 'disconnected'; + reject(error); + } + // If request is expires, the QRCode will automatically be regenerated we can ignore this case + } else { + this.status = 'disconnected'; + reject( + error instanceof Error ? error : new Error(String(error)), + ); + } + } + })().catch(() => { + // Error already handled in the async function + }); + }); + + // TODO: DRY THIS + const json = JSON.stringify(connectionRequest); + const compressed = compressString(json); + const urlEncoded = encodeURIComponent(compressed); + const qrCodeLink = `${METAMASK_DEEPLINK_BASE}/mwp?p=${urlEncoded}&c=1`; + + return resolve(qrCodeLink); + + // async (error?: Error) => { + // if (error) { + // await this.storage.removeTransport(); + // reject(error); + // } else { + // await this.storage.setTransport(TransportType.MWP); + // resolve(); + // } + // }, + // .catch((error) => { + // reject(error instanceof Error ? error : new Error(String(error))); + // }); + }); + } + async #renderInstallModalAsync( desktopPreferred: boolean, scopes: Scope[], @@ -378,7 +456,7 @@ export class MultichainSDK extends MultichainCore { (async (): Promise => { try { await this.transport.connect({ scopes, caipAccountIds, sessionProperties }); - await this.options.ui.factory.unload(); + await this.options.ui.factory.unload(); // this causes the successCallback below to fire this.options.ui.factory.modal?.unmount(); this.status = 'connected'; await this.storage.setTransport(TransportType.MWP); @@ -425,13 +503,18 @@ export class MultichainSDK extends MultichainCore { sessionProperties?: SessionProperties, ): Promise { // create the listener only once to avoid memory leaks - this.#beforeUnloadListener ??= this.#createBeforeUnloadListener(); - await this.#renderInstallModalAsync( - desktopPreferred, - scopes, - caipAccountIds, - sessionProperties, - ); + if (this.options.ui.displayUri) { + const qrCodeLink = await this.#getQrCodeLink(scopes, caipAccountIds, sessionProperties); + this.options.ui.displayUri(qrCodeLink); + } else { + this.#beforeUnloadListener ??= this.#createBeforeUnloadListener(); + await this.#renderInstallModalAsync( + desktopPreferred, + scopes, + caipAccountIds, + sessionProperties, + ); + } } async #setupDefaultTransport(): Promise { From fff9b77aff68dfa7b8f4849d694ee5ea1f8fe31e Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Thu, 22 Jan 2026 10:07:40 -0800 Subject: [PATCH 4/8] Fix ConnectEvm.status --- packages/connect-evm/src/connect.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/connect-evm/src/connect.ts b/packages/connect-evm/src/connect.ts index 707c97ca..46982b25 100644 --- a/packages/connect-evm/src/connect.ts +++ b/packages/connect-evm/src/connect.ts @@ -4,7 +4,7 @@ import type { MultichainCore, MultichainOptions, Scope, - SDKState, + ConnectionStatus, SessionData, } from '@metamask/connect-multichain'; import { @@ -899,8 +899,13 @@ export class MetamaskConnectEVM { return this.#provider.selectedChainId; } - get state(): SDKState { - return this.#core.state; + /** + * Gets the current connection status + * + * @returns The current connection status + */ + get status(): ConnectionStatus { + return this.#core.status; } } From 854a8a333400dd2cdbbee695a035e51631960e15 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Thu, 22 Jan 2026 10:08:19 -0800 Subject: [PATCH 5/8] Revert "WIP displayUri" This reverts commit 5aa59643a6a86d08b8b9b9f6ff36acb6e4ff35a4. --- .../src/domain/multichain/types.ts | 1 - .../src/multichain/index.ts | 101 ++---------------- 2 files changed, 9 insertions(+), 93 deletions(-) diff --git a/packages/connect-multichain/src/domain/multichain/types.ts b/packages/connect-multichain/src/domain/multichain/types.ts index 220363d8..bd933f27 100644 --- a/packages/connect-multichain/src/domain/multichain/types.ts +++ b/packages/connect-multichain/src/domain/multichain/types.ts @@ -60,7 +60,6 @@ export type MultichainOptions = { headless?: boolean; preferExtension?: boolean; showInstallModal?: boolean; - displayUri?: (uri: string) => void; }; mobile?: { preferredOpenLink?: (deeplink: string, target?: string) => void; diff --git a/packages/connect-multichain/src/multichain/index.ts b/packages/connect-multichain/src/multichain/index.ts index b279b5cf..48dc0c9d 100644 --- a/packages/connect-multichain/src/multichain/index.ts +++ b/packages/connect-multichain/src/multichain/index.ts @@ -58,7 +58,7 @@ import { RequestRouter } from './rpc/requestRouter'; import { DefaultTransport } from './transports/default'; import { MWPTransport } from './transports/mwp'; import { keymanager } from './transports/mwp/KeyManager'; -import { compressString, getDappId, openDeeplink, setupDappMetadata } from './utils'; +import { getDappId, openDeeplink, setupDappMetadata } from './utils'; import { MultichainApiClientWrapperTransport } from './transports/multichainApiClientWrapper'; export { getInfuraRpcUrls } from '../domain/multichain/api/infura'; @@ -340,84 +340,6 @@ export class MultichainSDK extends MultichainCore { }; } - async #getQrCodeLink( - scopes: Scope[], - caipAccountIds: CaipAccountId[], - sessionProperties?: SessionProperties, - ): Promise { - return new Promise(async (resolve, reject) => { - if ( - this.dappClient.state === 'CONNECTED' || - this.dappClient.state === 'CONNECTING' - ) { - await this.dappClient.disconnect(); - } - const connectionRequest = new Promise((_resolve) => { - this.dappClient.on( - 'session_request', - (sessionRequest: SessionRequest) => { - _resolve({ - sessionRequest, - metadata: { - dapp: this.options.dapp, - sdk: { - version: getVersion(), - platform: getPlatformType(), - }, - }, - }); - }, - ); - - (async (): Promise => { - try { - await this.transport.connect({ scopes, caipAccountIds, sessionProperties }); - await this.storage.setTransport(TransportType.MWP); - this.status = 'connected'; - await this.storage.setTransport(TransportType.MWP); - } catch (error) { - if (error instanceof ProtocolError) { - // Ignore Request expired errors to allow modal to regenerate expired qr codes - if (error.code !== ErrorCode.REQUEST_EXPIRED) { - this.status = 'disconnected'; - reject(error); - } - // If request is expires, the QRCode will automatically be regenerated we can ignore this case - } else { - this.status = 'disconnected'; - reject( - error instanceof Error ? error : new Error(String(error)), - ); - } - } - })().catch(() => { - // Error already handled in the async function - }); - }); - - // TODO: DRY THIS - const json = JSON.stringify(connectionRequest); - const compressed = compressString(json); - const urlEncoded = encodeURIComponent(compressed); - const qrCodeLink = `${METAMASK_DEEPLINK_BASE}/mwp?p=${urlEncoded}&c=1`; - - return resolve(qrCodeLink); - - // async (error?: Error) => { - // if (error) { - // await this.storage.removeTransport(); - // reject(error); - // } else { - // await this.storage.setTransport(TransportType.MWP); - // resolve(); - // } - // }, - // .catch((error) => { - // reject(error instanceof Error ? error : new Error(String(error))); - // }); - }); - } - async #renderInstallModalAsync( desktopPreferred: boolean, scopes: Scope[], @@ -456,7 +378,7 @@ export class MultichainSDK extends MultichainCore { (async (): Promise => { try { await this.transport.connect({ scopes, caipAccountIds, sessionProperties }); - await this.options.ui.factory.unload(); // this causes the successCallback below to fire + await this.options.ui.factory.unload(); this.options.ui.factory.modal?.unmount(); this.status = 'connected'; await this.storage.setTransport(TransportType.MWP); @@ -503,18 +425,13 @@ export class MultichainSDK extends MultichainCore { sessionProperties?: SessionProperties, ): Promise { // create the listener only once to avoid memory leaks - if (this.options.ui.displayUri) { - const qrCodeLink = await this.#getQrCodeLink(scopes, caipAccountIds, sessionProperties); - this.options.ui.displayUri(qrCodeLink); - } else { - this.#beforeUnloadListener ??= this.#createBeforeUnloadListener(); - await this.#renderInstallModalAsync( - desktopPreferred, - scopes, - caipAccountIds, - sessionProperties, - ); - } + this.#beforeUnloadListener ??= this.#createBeforeUnloadListener(); + await this.#renderInstallModalAsync( + desktopPreferred, + scopes, + caipAccountIds, + sessionProperties, + ); } async #setupDefaultTransport(): Promise { From d7871819254a7161db436bb5106ada97d453304e Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Thu, 22 Jan 2026 10:20:57 -0800 Subject: [PATCH 6/8] changelog --- packages/connect-evm/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/connect-evm/CHANGELOG.md b/packages/connect-evm/CHANGELOG.md index 4c2c37f5..ee608b62 100644 --- a/packages/connect-evm/CHANGELOG.md +++ b/packages/connect-evm/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Fix `eth_chainId` requests not being resolved from local cached state when using the EIP-1193 Provider `request()` method over the `MwpTransport` ([#124](https://github.com/MetaMask/connect-monorepo/pull/124)) + ### Added - Add `mobile.preferredOpenLink` option support for React Native deeplink handling in wagmi connector ([#118](https://github.com/MetaMask/connect-monorepo/pull/118)) From c329abd8fe0e7fa314f900b502c43efe5e6feffa Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Thu, 22 Jan 2026 10:26:49 -0800 Subject: [PATCH 7/8] lint --- packages/connect-evm/src/connect.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/connect-evm/src/connect.ts b/packages/connect-evm/src/connect.ts index 3354b9df..404a4a60 100644 --- a/packages/connect-evm/src/connect.ts +++ b/packages/connect-evm/src/connect.ts @@ -5,7 +5,6 @@ import type { MultichainCore, MultichainOptions, Scope, - ConnectionStatus, SessionData, } from '@metamask/connect-multichain'; import { From 46aca15bc8affbd980368e5f82dbbbffd00b9836 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Thu, 22 Jan 2026 10:30:14 -0800 Subject: [PATCH 8/8] lint --- packages/connect-evm/CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/connect-evm/CHANGELOG.md b/packages/connect-evm/CHANGELOG.md index fd1c20b4..2fa48ac0 100644 --- a/packages/connect-evm/CHANGELOG.md +++ b/packages/connect-evm/CHANGELOG.md @@ -7,14 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -### Fixed - -- Fix `eth_chainId` requests not being resolved from local cached state when using the EIP-1193 Provider `request()` method over the `MwpTransport` ([#124](https://github.com/MetaMask/connect-monorepo/pull/124)) - ### Added - Add `ConnectEvm.status` property which exposes the current `ConnectionStatus` ([#136](https://github.com/MetaMask/connect-monorepo/pull/136)) +### Fixed + +- Fix `eth_chainId` requests not being resolved from local cached state when using the EIP-1193 Provider `request()` method over the `MwpTransport` ([#124](https://github.com/MetaMask/connect-monorepo/pull/124)) + ## [0.2.0] ### Added