Migrate to @metamask/connect-evm#2390
Conversation
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.
WalkthroughReplaces ChangesMetaMask Connect EVM Migration
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
🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Review rate limit: 4/5 reviews remaining, refill in 12 minutes. Comment |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (3)
packages/metamask/README.mdpackages/metamask/package.jsonpackages/metamask/src/index.ts
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.
There was a problem hiding this comment.
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
📒 Files selected for processing (3)
packages/metamask/README.mdpackages/metamask/package.jsonpackages/metamask/src/index.ts
| /** 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 | ||
| } |
There was a problem hiding this comment.
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.
| const metamaskSDKWallet = metamaskSDK({ | ||
| options: { | ||
| extensionOnly: false, | ||
| dappMetadata: { |
There was a problem hiding this comment.
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.
| 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.
| type EvmClientOptions = Parameters<typeof CreateEVMClientFn>[0] | ||
|
|
||
| type ConnectEvmImports = { | ||
| createEVMClient: typeof CreateEVMClientFn | ||
| getInfuraRpcUrls: typeof GetInfuraRpcUrlsFn | ||
| } |
There was a problem hiding this comment.
🧩 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.tsRepository: thirdweb-dev/web3-onboard
Length of output: 282
🏁 Script executed:
head -n 30 packages/metamask/src/index.tsRepository: 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.
| const supportedNetworks: Record<string, string> = { | ||
| ...fromInfura, | ||
| ...fromChains, | ||
| ...(options.readonlyRPCMap ?? {}) | ||
| } |
There was a problem hiding this comment.
🧩 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:
- 1: https://docs.metamask.io/metamask-connect/evm/guides/manage-networks/
- 2: https://www.npmjs.com/package/@metamask/connect-evm
- 3: https://docs.metamask.io/metamask-connect/evm/quickstart/javascript/
- 4: https://docs.metamask.io/metamask-connect/evm/reference/methods/
🏁 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 javascriptRepository: thirdweb-dev/web3-onboard
Length of output: 104
🏁 Script executed:
# Search for readonlyRPCMap usage
rg "readonlyRPCMap" -t typescript -t javascript -B 3 -A 3Repository: 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=20Repository: 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 3Repository: 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=30Repository: thirdweb-dev/web3-onboard
Length of output: 1791
🏁 Script executed:
# Read the actual file to understand context
wc -l packages/metamask/src/index.tsRepository: thirdweb-dev/web3-onboard
Length of output: 104
🏁 Script executed:
# Read the toHexChainId function implementation
rg "function toHexChainId" -A 10 packages/metamask/src/index.tsRepository: 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.tsRepository: thirdweb-dev/web3-onboard
Length of output: 1162
🏁 Script executed:
# Search for fromInfura and fromChains definitions
sed -n '190,220p' packages/metamask/src/index.tsRepository: thirdweb-dev/web3-onboard
Length of output: 1065
🏁 Script executed:
# Find chainsToRpcMap function
rg "chainsToRpcMap" -g "*.ts" -g "*.js" -B 2 -A 8Repository: 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.
| 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.
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
Documentation