Skip to content

Migrate to @metamask/connect-evm#2390

Open
0xFirekeeper wants to merge 2 commits intodevelopfrom
firekeeper/metamask-connect
Open

Migrate to @metamask/connect-evm#2390
0xFirekeeper wants to merge 2 commits intodevelopfrom
firekeeper/metamask-connect

Conversation

@0xFirekeeper
Copy link
Copy Markdown
Member

@0xFirekeeper 0xFirekeeper commented May 4, 2026

Swap out the legacy @metamask/sdk for the new @metamask/connect-evm client and update package metadata (version bump to 2.3.0). Code changes load connect-evm (handling ESM/CJS shapes), reuse a single client instance, attach a disconnect shim to the provider, and map legacy MetaMask SDK options to the new connect-evm option shape (including Infura/custom RPC merging via getInfuraRpcUrls). README updated to reflect the Connect EVM integration and backwards compatibility notes; package.json dependency updated accordingly.

Summary by CodeRabbit

  • New Features

    • Rebranded integration to MetaMask Connect EVM for improved wallet behavior and priority when a MetaMask browser wallet is present.
    • Added new configuration options: headless, infuraAPIKey, readonlyRPCMap, openDeeplink, useDeeplink.
    • dappMetadata is now optional and supports iconUrl for better dApp identification.
  • Documentation

    • Updated docs and usage example to reflect Connect EVM integration and new option mappings.

Swap out the legacy @metamask/sdk for the new @metamask/connect-evm client and update package metadata (version bump to 2.3.0). Code changes load connect-evm (handling ESM/CJS shapes), reuse a single client instance, attach a disconnect shim to the provider, and map legacy MetaMask SDK options to the new connect-evm option shape (including Infura/custom RPC merging via getInfuraRpcUrls). README updated to reflect the Connect EVM integration and backwards compatibility notes; package.json dependency updated accordingly.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 4, 2026

Walkthrough

Replaces @metamask/sdk with @metamask/connect-evm, adds lazy import and caching of Connect EVM, maps legacy MetaMask SDK options to the Connect EVM shape, adjusts disconnect behavior to call client.disconnect(), and bumps package metadata and README. Version updated to 2.4.0.

Changes

MetaMask Connect EVM Migration

Layer / File(s) Summary
Package Metadata
packages/metamask/package.json
Version bumped to 2.4.0; description changed to "MetaMask Connect EVM wallet module"; dependency swapped from @metamask/sdk@metamask/connect-evm@^1.0.0.
Public Type / Options
packages/metamask/src/index.ts, packages/metamask/README.md
MetaMaskSDKOptions exported/ documented with optional fields and additions: dappMetadata?: { name?, url?, iconUrl?, base64Icon? }, headless?, infuraAPIKey?, readonlyRPCMap?, openDeeplink?, useDeeplink?, extensionOnly?, showInstallModal?.
Dynamic Imports
packages/metamask/src/index.ts
loadImports() added to lazily import @metamask/connect-evm, extracting createEVMClient and getInfuraRpcUrls across ESM/CJS shapes and caching the import promise; throws if required exports missing.
Option Mapping / Data Shape
packages/metamask/src/index.ts
mapLegacyOptions() translates legacy SDK options into Connect EVM shape: merges Infura RPCs (when infuraAPIKey present), chain RPCs, and readonlyRPCMap into supportedNetworks (falls back to hardcoded mainnet RPC if empty); sets dapp.base64Icon and derives dapp.name/dapp.url; maps headless/extensionOnlyui and openDeeplink/useDeeplinkmobile; sets default showInstallModal when unspecified.
Client Creation & Caching
packages/metamask/src/index.ts
getInterface now lazily reuses a cached Connect EVM client+provider when available; otherwise awaits imports, computes appLogoUrl, calls mapLegacyOptions(), creates a MetamaskConnectEVM client and returns provider + instance.
Provider Wrapping & Disconnect
packages/metamask/src/index.ts
Added attachDisconnectShim() to set provider.disconnect() to call client.disconnect() instead of calling old sdk.terminate().
Docs / Usage
packages/metamask/README.md
README updated to reference Connect EVM, document new/optional fields and mappings, adjust extensionOnly semantics, and show formatted usage example; guidance updated to list this module before injected wallets.
sequenceDiagram
    participant Dapp as Dapp (web3-onboard)
    participant Module as metamask module
    participant Importer as `@metamask/connect-evm` (dynamic import)
    participant Client as ConnectEVM client
    participant Infura as Infura/RPC

    Dapp->>Module: init metamask({ options })
    Module->>Importer: loadImports() (dynamic)
    Importer-->>Module: createEVMClient, getInfuraRpcUrls
    Module->>Module: mapLegacyOptions(...) (merge Infura, chains, readonlyRPCMap)
    Module->>Client: createEVMClient(mappedOptions)
    Client-->>Module: client instance + provider
    Module->>Client: attachDisconnectShim(provider.disconnect -> client.disconnect)
    Module->>Infura: (if needed) resolve RPC URLs via getInfuraRpcUrls
    Module-->>Dapp: return provider + instance
Loading

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is missing most required checklist items from the template (version increment verification, local testing, yarn check-all, CI confirmation, documentation updates), making it largely incomplete relative to the template requirements. Add explicit confirmation that all checklist items have been completed, including version bump verification, local testing results, check-all and CI status, and README/docs updates.
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Migrate to @metamask/connect-evm' directly reflects the main change: replacing the legacy @metamask/sdk with @metamask/connect-evm throughout the MetaMask wallet module.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch firekeeper/metamask-connect

Review rate limit: 4/5 reviews remaining, refill in 12 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/metamask/src/index.ts`:
- Around line 72-74: The cached importPromise created via importPromise =
importPromise ?? loadImports().catch(error => { throw error }) can become
permanently rejected and poison future getInterface calls; update the catch
handler on loadImports() so that on rejection it clears importPromise (e.g., set
importPromise = undefined) before rethrowing the error, ensuring subsequent
calls to getInterface will retry loadImports().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bbd073ca-0a0b-4209-8b25-f09792507d9d

📥 Commits

Reviewing files that changed from the base of the PR and between e3b8623 and 70c2d0e.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (3)
  • packages/metamask/README.md
  • packages/metamask/package.json
  • packages/metamask/src/index.ts

Comment thread packages/metamask/src/index.ts Outdated
Bump @web3-onboard/metamask to v2.4.0 and enhance SDK initialization.

- Add a fallback Mainnet RPC and ensure api.supportedNetworks is always non-empty by deriving RPCs in priority order: Infura, readonlyRPCMap, web3-onboard chains, then public fallback.
- Accept and normalize web3-onboard chains (toHexChainId + chainsToRpcMap) and thread them into getInterface/mapLegacyOptions so the client can use configured chains.
- Improve dynamic import caching with retry semantics to avoid permanently failing on transient import errors.
- Add showInstallModal option (defaults to false) and update legacy options handling (extensionOnly only maps to preferExtension when true; headless preserved).
- Minor README formatting and example tweaks.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/metamask/README.md`:
- Around line 37-47: Add documentation for the missing showInstallModal option
to the options interface in the README: declare the property showInstallModal?:
boolean and document it as mapped to mobile.showInstallModal (or the equivalent
runtime option name) so the README matches the implementation that supports
showInstallModal; update the options block where headless, infuraAPIKey,
readonlyRPCMap, openDeeplink, and useDeeplink are documented to include this new
entry.
- Around line 57-60: The README example sets extensionOnly: false which is a
no-op because the code only maps extensionOnly when true; either remove
extensionOnly from the example or set it to extensionOnly: true to demonstrate
the behavior — update the metamaskSDK(...) call (the
metamaskSDKWallet/options/dappMetadata block) to reflect one of these changes so
the example matches actual mapping logic.

In `@packages/metamask/src/index.ts`:
- Around line 57-62: The types use `typeof` on type-only imports
(CreateEVMClientFn, GetInfuraRpcUrlsFn) which is invalid; update the type
aliases so they reference the imported types directly (e.g., use
CreateEVMClientFn and GetInfuraRpcUrlsFn rather than typeof
CreateEVMClientFn/GetInfuraRpcUrlsFn) and adjust ConnectEvmImports and
EvmClientOptions to use those direct type names; ensure any other occurrences
(e.g., the similar case around line 199) are changed the same way so all
type-only imports are not wrapped with typeof.
- Around line 217-221: supportedNetworks currently merges readonlyRPCMap raw
while fromChains uses chainsToRpcMap/toHexChainId to normalize chain IDs to hex;
normalize readonlyRPCMap keys the same way before merging so
api.supportedNetworks only contains hex chain IDs. Update the merge to transform
options.readonlyRPCMap (if present) by mapping each key through toHexChainId
(and skip or log invalid conversions) producing a normalizedReadonlyRPCMap, then
use {...fromInfura, ...fromChains, ...normalizedReadonlyRPCMap} when building
supportedNetworks; reference supportedNetworks, readonlyRPCMap, chainsToRpcMap,
and toHexChainId to locate the code to change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8de51b8e-b670-45a0-a206-8b15a2d34cf8

📥 Commits

Reviewing files that changed from the base of the PR and between 70c2d0e and 734c77e.

📒 Files selected for processing (3)
  • packages/metamask/README.md
  • packages/metamask/package.json
  • packages/metamask/src/index.ts

Comment on lines +37 to 47
/** Mapped to `ui.headless`. */
headless?: boolean
/** Used to populate `api.supportedNetworks` via `getInfuraRpcUrls`. */
infuraAPIKey?: string
/** Merged into `api.supportedNetworks`. */
readonlyRPCMap?: Record<string, string>
/** Mapped to `mobile.preferredOpenLink`. */
openDeeplink?: (deeplink: string) => void
/** Mapped to `mobile.useDeeplink`. */
useDeeplink?: boolean
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Document showInstallModal in the options interface.

The implementation supports showInstallModal, but the README options type omits it.

Proposed fix
   /** Mapped to `ui.headless`. */
   headless?: boolean
+  /** Mapped to `ui.showInstallModal` (defaults to `false`). */
+  showInstallModal?: boolean
   /** Used to populate `api.supportedNetworks` via `getInfuraRpcUrls`. */
   infuraAPIKey?: string
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/metamask/README.md` around lines 37 - 47, Add documentation for the
missing showInstallModal option to the options interface in the README: declare
the property showInstallModal?: boolean and document it as mapped to
mobile.showInstallModal (or the equivalent runtime option name) so the README
matches the implementation that supports showInstallModal; update the options
block where headless, infuraAPIKey, readonlyRPCMap, openDeeplink, and
useDeeplink are documented to include this new entry.

Comment on lines +57 to +60
const metamaskSDKWallet = metamaskSDK({
options: {
extensionOnly: false,
dappMetadata: {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update example to avoid a no-op extensionOnly: false.

Current mapping only applies extensionOnly when it is true; using false in the example implies behavior that does not change anything.

Proposed fix
 const metamaskSDKWallet = metamaskSDK({
   options: {
-    extensionOnly: false,
     dappMetadata: {
       name: 'Demo Web3Onboard'
     }
   }
 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const metamaskSDKWallet = metamaskSDK({
options: {
extensionOnly: false,
dappMetadata: {
const metamaskSDKWallet = metamaskSDK({
options: {
dappMetadata: {
name: 'Demo Web3Onboard'
}
}
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/metamask/README.md` around lines 57 - 60, The README example sets
extensionOnly: false which is a no-op because the code only maps extensionOnly
when true; either remove extensionOnly from the example or set it to
extensionOnly: true to demonstrate the behavior — update the metamaskSDK(...)
call (the metamaskSDKWallet/options/dappMetadata block) to reflect one of these
changes so the example matches actual mapping logic.

Comment on lines +57 to +62
type EvmClientOptions = Parameters<typeof CreateEVMClientFn>[0]

type ConnectEvmImports = {
createEVMClient: typeof CreateEVMClientFn
getInfuraRpcUrls: typeof GetInfuraRpcUrlsFn
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify problematic type patterns in this file.
rg -n "Parameters<typeof CreateEVMClientFn>|typeof CreateEVMClientFn|typeof GetInfuraRpcUrlsFn" packages/metamask/src/index.ts

Repository: thirdweb-dev/web3-onboard

Length of output: 282


🏁 Script executed:

head -n 30 packages/metamask/src/index.ts

Repository: thirdweb-dev/web3-onboard

Length of output: 1087


Remove typeof from type-only imports—they are already types.

CreateEVMClientFn and GetInfuraRpcUrlsFn are imported with import type, which means they cannot be used as runtime values. Using typeof on them violates TypeScript's type semantics and causes a compilation error.

Proposed fix
-type EvmClientOptions = Parameters<typeof CreateEVMClientFn>[0]
+type EvmClientOptions = Parameters<CreateEVMClientFn>[0]

 type ConnectEvmImports = {
-  createEVMClient: typeof CreateEVMClientFn
-  getInfuraRpcUrls: typeof GetInfuraRpcUrlsFn
+  createEVMClient: CreateEVMClientFn
+  getInfuraRpcUrls: GetInfuraRpcUrlsFn
 }

Also applies to: 199

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/metamask/src/index.ts` around lines 57 - 62, The types use `typeof`
on type-only imports (CreateEVMClientFn, GetInfuraRpcUrlsFn) which is invalid;
update the type aliases so they reference the imported types directly (e.g., use
CreateEVMClientFn and GetInfuraRpcUrlsFn rather than typeof
CreateEVMClientFn/GetInfuraRpcUrlsFn) and adjust ConnectEvmImports and
EvmClientOptions to use those direct type names; ensure any other occurrences
(e.g., the similar case around line 199) are changed the same way so all
type-only imports are not wrapped with typeof.

Comment on lines +217 to +221
const supportedNetworks: Record<string, string> = {
...fromInfura,
...fromChains,
...(options.readonlyRPCMap ?? {})
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In @metamask/connect-evm createEVMClient options, does api.supportedNetworks require chain IDs in 0x-hex format, and are decimal string keys unsupported?

💡 Result:

Yes, in @metamask/connect-evm's createEVMClient options, api.supportedNetworks requires chain IDs in 0x-hex format (e.g., '0x1', '0x89'). Decimal string keys (e.g., '1', '137') are unsupported, as the option is explicitly documented as a "Record<string, string>" or map where keys are hex chain IDs, with all official examples using 0x-prefixed hex strings. The getInfuraRpcUrls helper also returns hex-keyed maps for EVM.

Citations:


🏁 Script executed:

# First, let's find the file and examine the surrounding code
fd -t f "index.ts" | grep -E "packages/metamask.*index.ts"

Repository: thirdweb-dev/web3-onboard

Length of output: 100


🏁 Script executed:

# Search for toHexChainId function in the codebase
rg "toHexChainId" -t typescript -t javascript

Repository: thirdweb-dev/web3-onboard

Length of output: 104


🏁 Script executed:

# Search for readonlyRPCMap usage
rg "readonlyRPCMap" -t typescript -t javascript -B 3 -A 3

Repository: thirdweb-dev/web3-onboard

Length of output: 104


🏁 Script executed:

# Look for chain ID normalization utilities
rg "toHex|normalizeChainId|chainIdToHex" -t typescript -t javascript --max-count=20

Repository: thirdweb-dev/web3-onboard

Length of output: 104


🏁 Script executed:

# Let me search with correct file type flags
rg "toHexChainId" -g "*.ts" -g "*.js"

Repository: thirdweb-dev/web3-onboard

Length of output: 232


🏁 Script executed:

# Search for readonlyRPCMap
rg "readonlyRPCMap" -g "*.ts" -g "*.js" -B 3 -A 3

Repository: thirdweb-dev/web3-onboard

Length of output: 1612


🏁 Script executed:

# Look for chain ID normalization utilities
rg "toHex|normalizeChainId|chainIdToHex" -g "*.ts" -g "*.js" --max-count=30

Repository: thirdweb-dev/web3-onboard

Length of output: 1791


🏁 Script executed:

# Read the actual file to understand context
wc -l packages/metamask/src/index.ts

Repository: thirdweb-dev/web3-onboard

Length of output: 104


🏁 Script executed:

# Read the toHexChainId function implementation
rg "function toHexChainId" -A 10 packages/metamask/src/index.ts

Repository: thirdweb-dev/web3-onboard

Length of output: 415


🏁 Script executed:

# Read the context around lines 217-221
sed -n '200,230p' packages/metamask/src/index.ts

Repository: thirdweb-dev/web3-onboard

Length of output: 1162


🏁 Script executed:

# Search for fromInfura and fromChains definitions
sed -n '190,220p' packages/metamask/src/index.ts

Repository: thirdweb-dev/web3-onboard

Length of output: 1065


🏁 Script executed:

# Find chainsToRpcMap function
rg "chainsToRpcMap" -g "*.ts" -g "*.js" -B 2 -A 8

Repository: thirdweb-dev/web3-onboard

Length of output: 1325


Normalize readonlyRPCMap keys before building supportedNetworks.

The code requires api.supportedNetworks to use hex chain IDs (as stated in the comment on line 205-207 and confirmed by @metamask/connect-evm documentation). However, readonlyRPCMap is merged raw without normalization, while fromChains (which uses chainsToRpcMap) already normalizes IDs via toHexChainId. This inconsistency allows decimal or invalid chain IDs from readonlyRPCMap to be passed through, creating invalid network configurations.

Apply the same normalization pattern already used in chainsToRpcMap:

Proposed fix
+  const normalizedReadonlyRPCMap: Record<string, string> = {}
+  for (const [id, rpcUrl] of Object.entries(options.readonlyRPCMap ?? {})) {
+    const hexId = toHexChainId(id)
+    if (hexId) normalizedReadonlyRPCMap[hexId] = rpcUrl
+  }
+
   const supportedNetworks: Record<string, string> = {
     ...fromInfura,
     ...fromChains,
-    ...(options.readonlyRPCMap ?? {})
+    ...normalizedReadonlyRPCMap
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const supportedNetworks: Record<string, string> = {
...fromInfura,
...fromChains,
...(options.readonlyRPCMap ?? {})
}
const normalizedReadonlyRPCMap: Record<string, string> = {}
for (const [id, rpcUrl] of Object.entries(options.readonlyRPCMap ?? {})) {
const hexId = toHexChainId(id)
if (hexId) normalizedReadonlyRPCMap[hexId] = rpcUrl
}
const supportedNetworks: Record<string, string> = {
...fromInfura,
...fromChains,
...normalizedReadonlyRPCMap
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/metamask/src/index.ts` around lines 217 - 221, supportedNetworks
currently merges readonlyRPCMap raw while fromChains uses
chainsToRpcMap/toHexChainId to normalize chain IDs to hex; normalize
readonlyRPCMap keys the same way before merging so api.supportedNetworks only
contains hex chain IDs. Update the merge to transform options.readonlyRPCMap (if
present) by mapping each key through toHexChainId (and skip or log invalid
conversions) producing a normalizedReadonlyRPCMap, then use {...fromInfura,
...fromChains, ...normalizedReadonlyRPCMap} when building supportedNetworks;
reference supportedNetworks, readonlyRPCMap, chainsToRpcMap, and toHexChainId to
locate the code to change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant