Skip to main content

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
dappMetadatadappSame shape: { name, url, iconUrl }
dappMetadata.namedapp.nameRequired
dappMetadata.urldapp.urlAuto-set in browsers; required in Node.js and React Native
infuraAPIKeyapi.supportedNetworks via getInfuraRpcUrls(key)Helper generates RPC URLs for all Infura-supported chains
readonlyRPCMapapi.supportedNetworksMerge into the same object
headlessui.headlessSame behavior
extensionOnlyui.preferExtensiontrue prefers extension (default); not the same as "only"
openDeeplinkmobile.preferredOpenLinkSame signature: (deeplink: string) => void
useDeeplinkmobile.useDeeplinkSame behavior
timerRemovedNo longer configurable
enableAnalyticsRemovedNo longer available
communicationServerUrlRemovedManaged internally
storageRemovedManaged 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 createEVMClient resolves, even before connect().
  • Read-only calls (like eth_blockNumber) work immediately against supportedNetworks RPCs. Account-dependent calls require connect() first.
  • client.getProvider() never returns undefined.

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:

CapabilityDescription
Multichain clientcreateMultichainClient from @metamask/connect-multichain supports CAIP-25 scopes across EVM and Solana
invokeMethodCall RPC methods on specific CAIP-2 scopes without switching chains
Solana supportcreateSolanaClient from @metamask/connect-solana with wallet-standard adapter
connectAndSignConnect and sign a message in a single user approval
connectWithConnect and execute any RPC method in a single user approval
Partial disconnectdisconnect(scopes) revokes specific CAIP scopes while keeping others active
Singleton clientSubsequent create*Client calls merge options into the existing instance
wallet_sessionChangedEvent 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 neededInit 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
dappMetadatadappRenamed
infuraAPIKeygetInfuraRpcUrls(key) in api.supportedNetworksHelper function
readonlyRPCMapapi.supportedNetworksMerged with Infura URLs
headlessui.headlessMoved to ui namespace
extensionOnlyui.preferExtensionRenamed, slightly different semantics
openDeeplinkmobile.preferredOpenLinkMoved to mobile namespace
useDeeplinkmobile.useDeeplinkMoved to mobile namespace
SDKProviderEIP1193ProviderStandard provider interface
timerRemoved--
enableAnalyticsRemoved--
communicationServerUrlRemoved--
storageRemoved--
Important notes
  • createEVMClient is async: Unlike new MetaMaskSDK(), it returns a promise. Always await it before accessing the client.
  • The client is a singleton: Calling createEVMClient or createMultichainClient multiple 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' not 1 or '1' in chainIds and supportedNetworks keys.
  • Provider exists before connection: client.getProvider() never returns undefined. Read-only RPC calls work immediately; account-dependent calls require connect() first.
  • @metamask/sdk-react has no direct replacement: if you were using MetaMaskProvider and useSDK(), 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.