Migrate from the legacy SDK
This guide walks you through migrating from @metamask/sdk or @metamask/sdk-react to
@metamask/connect-evm.
MetaMask Connect is a rewrite of the legacy SDK built on the CAIP-25 Multichain API with async initialization, a singleton client, and built-in support for EVM, Solana, and multichain sessions.
1. Replace packages
Remove the old packages and install the new ones:
# Remove old
npm uninstall @metamask/sdk @metamask/sdk-react
# Install new (EVM only)
npm install @metamask/connect-evm
# If you also need Solana support
npm install @metamask/connect-solana
2. Update imports
Old: (remove these imports)
- import { MetaMaskSDK } from '@metamask/sdk'
- import { MetaMaskProvider, useSDK } from '@metamask/sdk-react'
New (EVM):
+ import { createEVMClient, getInfuraRpcUrls } from '@metamask/connect-evm'
New (Multichain):
+ import { createMultichainClient } from '@metamask/connect-multichain'
New (Solana):
+ import { createSolanaClient } from '@metamask/connect-solana'
3. Update initialization
Old:
- const sdk = new MetaMaskSDK({
- dappMetadata: {
- name: 'My DApp',
- url: window.location.href,
- },
- infuraAPIKey: 'YOUR_INFURA_KEY',
- readonlyRPCMap: {
- '0x89': 'https://polygon-rpc.com',
- },
- headless: true,
- extensionOnly: false,
- openDeeplink: link => window.open(link, '_blank'),
- })
- await sdk.init()
New:
+ const client = await createEVMClient({
+ dapp: {
+ name: 'My DApp',
+ url: window.location.href,
+ },
+ api: {
+ supportedNetworks: {
+ ...getInfuraRpcUrls('YOUR_INFURA_KEY'),
+ '0x89': 'https://polygon-rpc.com',
+ },
+ },
+ ui: {
+ headless: true,
+ preferExtension: false,
+ },
+ mobile: {
+ preferredOpenLink: (link: string) => window.open(link, '_blank'),
+ },
+ })
Option mapping
Use the following table to map MetaMaskSDK configuration options to their equivalents in createEVMClient.
The table includes renamed options, options that moved into grouped objects (for example, ui and mobile), and
options that MetaMask Connect no longer exposes.
Old (MetaMaskSDK) | New (createEVMClient) | Notes |
|---|---|---|
dappMetadata | dapp | Same shape: { name, url, iconUrl } |
dappMetadata.name | dapp.name | Required |
dappMetadata.url | dapp.url | Auto-set in browsers; required in Node.js and React Native |
infuraAPIKey | api.supportedNetworks via getInfuraRpcUrls(key) | Helper generates RPC URLs for all Infura-supported chains |
readonlyRPCMap | api.supportedNetworks | Merge into the same object |
headless | ui.headless | Same behavior |
extensionOnly | ui.preferExtension | true prefers extension (default); not the same as "only" |
openDeeplink | mobile.preferredOpenLink | Same signature: (deeplink: string) => void |
useDeeplink | mobile.useDeeplink | Same behavior |
timer | Removed | No longer configurable |
enableAnalytics | Removed | No longer available |
communicationServerUrl | Removed | Managed internally |
storage | Removed | Managed internally |
4. Update connection flow
In MetaMask Connect, you request chain permissions during connect() and receive the connected accounts
and selected chain ID in a single response. This replaces the previous flow where you connected first
and then made a separate JSON-RPC request for eth_chainId.
Old:
- const accounts = await sdk.connect()
- const chainId = await sdk.getProvider().request({ method: 'eth_chainId' })
New:
+ const { accounts, chainId } = await client.connect({
+ chainIds: ['0x1'],
+ })
connect() now returns an object with both accounts and chainId in a single call.
The chainIds parameter specifies which chains to request permission for (hex strings).
Ethereum Mainnet (0x1) is always included regardless of what you pass.
Connect-and-sign shortcut
Use connectAndSign to connect and sign a personal_sign message in one user approval:
const { accounts, chainId, signature } = await client.connectAndSign({
message: 'Sign in to My DApp',
chainIds: ['0x1'],
})
Connect-and-execute shortcut
Connect and execute any JSON-RPC method in a single user approval:
const { accounts, chainId, result } = await client.connectWith({
method: 'eth_sendTransaction',
params: [{ from: '0x...', to: '0x...', value: '0x0' }],
chainIds: ['0x1'],
})
5. Update provider access
In MetaMask Connect, client.getProvider() returns an EIP-1193 provider. You no longer use the
SDKProvider returned by sdk.getProvider().
Old:
- const provider = sdk.getProvider() // SDKProvider (may be undefined)
- await provider.request({ method: 'eth_chainId' })
New:
+ const provider = client.getProvider() // EIP-1193 provider (always exists)
+ await provider.request({ method: 'eth_chainId' })
Key differences:
- The provider is a standard EIP-1193 provider, not the custom
SDKProvider. - The provider is available immediately after
createEVMClientresolves, even beforeconnect(). - Read-only calls (like
eth_blockNumber) work immediately againstsupportedNetworksRPCs. Account-dependent calls requireconnect()first. client.getProvider()never returnsundefined.
6. Update event handling
EIP-1193 provider events work exactly the same way:
const provider = client.getProvider()
provider.on('chainChanged', chainId => {
/* hex string */
})
provider.on('accountsChanged', accounts => {
/* address array */
})
provider.on('disconnect', () => {
/* ... */
})
MetaMask Connect also exposes SDK-level events you can register during initialization:
const client = await createEVMClient({
dapp: { name: 'My DApp' },
eventHandlers: {
display_uri: uri => {
/* render QR code for mobile connection */
},
wallet_sessionChanged: session => {
/* session restored on page reload */
},
},
})
Or subscribe after creation:
client.on('display_uri', uri => {
/* ... */
})
client.on('wallet_sessionChanged', session => {
/* ... */
})
7. Adopt new capabilities
MetaMask Connect introduces features that are not available in @metamask/sdk:
| Capability | Description |
|---|---|
| Multichain client | createMultichainClient from @metamask/connect-multichain supports CAIP-25 scopes across EVM and Solana |
invokeMethod | Call RPC methods on specific CAIP-2 scopes without switching chains |
| Solana support | createSolanaClient from @metamask/connect-solana with wallet-standard adapter |
connectAndSign | Connect and sign a message in a single user approval |
connectWith | Connect and execute any RPC method in a single user approval |
| Partial disconnect | disconnect(scopes) revokes specific CAIP scopes while keeping others active |
| Singleton client | Subsequent create*Client calls merge options into the existing instance |
wallet_sessionChanged | Event fired when a session is restored on page load |
Full option mapping
Old (@metamask/sdk) | New (@metamask/connect-evm) | Status |
|---|---|---|
new MetaMaskSDK(opts) | await createEVMClient(opts) | Renamed, async |
sdk.init() | Not needed | Init happens in createEVMClient |
sdk.connect() | client.connect({ chainIds }) | Returns { accounts, chainId } |
sdk.getProvider() | client.getProvider() | Returns EIP-1193 provider |
sdk.disconnect() | client.disconnect() | Same, plus partial disconnect support |
dappMetadata | dapp | Renamed |
infuraAPIKey | getInfuraRpcUrls(key) in api.supportedNetworks | Helper function |
readonlyRPCMap | api.supportedNetworks | Merged with Infura URLs |
headless | ui.headless | Moved to ui namespace |
extensionOnly | ui.preferExtension | Renamed, slightly different semantics |
openDeeplink | mobile.preferredOpenLink | Moved to mobile namespace |
useDeeplink | mobile.useDeeplink | Moved to mobile namespace |
SDKProvider | EIP1193Provider | Standard provider interface |
timer | Removed | -- |
enableAnalytics | Removed | -- |
communicationServerUrl | Removed | -- |
storage | Removed | -- |
createEVMClientis async: Unlikenew MetaMaskSDK(), it returns a promise. Alwaysawaitit before accessing the client.- The client is a singleton: Calling
createEVMClientorcreateMultichainClientmultiple times merges options into the same instance. Do not recreate it on every render. connect()returns an object: Destructure{ accounts, chainId }instead of treating the return value as an accounts array.- Chain IDs must be hex strings: Use
'0x1'not1or'1'inchainIdsandsupportedNetworkskeys. - Provider exists before connection:
client.getProvider()never returnsundefined. Read-only RPC calls work immediately; account-dependent calls requireconnect()first. @metamask/sdk-reacthas no direct replacement: if you were usingMetaMaskProvideranduseSDK(), migrate to either wagmi hooks or manage the client instance in your own React context.- Test on both extension and mobile: the transport layer has changed, and behavior differences may surface in one environment but not the other.