diff --git a/.cursor/rules/terminology.mdc b/.cursor/rules/terminology.mdc
index 013da003e45..f541c028c58 100644
--- a/.cursor/rules/terminology.mdc
+++ b/.cursor/rules/terminology.mdc
@@ -22,6 +22,7 @@ listed on the right unless they appear in code, URLs, or direct quotations from
| sign-in / sign-out (noun or adjective) | log-in, log-out, login, logout (as nouns/adj) |
| onchain / offchain | on-chain, off-chain |
| Wallet Standard, Wallet Adapter | wallet-standard, wallet-adapter (except in URLs) |
+| Wagmi | wagmi (except in code, package names, imports, URLs, and file paths) |
| MetaMask Agent Wallet | Agentic CLI, Agentic Wallet (except in code/URLs where `agentic` appears) |
| Agent Wallet (short form) | agent wallet (lowercase product name) |
| Guard Mode | guard mode (capitalize Mode) |
diff --git a/metamask-connect/evm/guides/migrate-from-sdk.md b/metamask-connect/evm/guides/migrate-from-sdk.md
index 5843c90f677..59c47944dec 100644
--- a/metamask-connect/evm/guides/migrate-from-sdk.md
+++ b/metamask-connect/evm/guides/migrate-from-sdk.md
@@ -82,8 +82,8 @@ initialization in a single async step.
:::caution
`createEVMClient` is async, so always `await` it before accessing the client. The client is also a
-singleton. Calling `createEVMClient` multiple times merges options into the same instance. Do not
-recreate it on every render.
+singleton. Calling `createEVMClient` multiple times merges new options into the same instance, but
+the `dapp` object from the first call is never overwritten. Do not recreate it on every render.
:::
**Old:**
@@ -150,7 +150,7 @@ options that MetaMask Connect EVM no longer exposes.
| `openDeeplink` | `mobile.preferredOpenLink` | Same signature: `(deeplink: string) => void` |
| `useDeeplink` | `mobile.useDeeplink` | Same behavior |
| `timer` | Removed | No longer configurable |
-| `enableAnalytics` | Removed | No longer available |
+| `enableAnalytics` | `analytics.enabled` | Replaced by `analytics: { enabled: false }` to opt out |
| `communicationServerUrl` | Removed | Managed internally |
| `storage` | Removed | Managed internally |
@@ -229,8 +229,9 @@ so read `.result` from the returned object to get the RPC response value.
:::tip React Native polyfills
Browser-based setups (Vite, Webpack) work without polyfills. If you are migrating a React Native
-app and encounter errors referencing `Buffer`, `crypto`, `stream`, or `Event is not defined`, see
-[React Native Metro polyfill issues](../../troubleshooting/metro-polyfill-issues.md).
+app and encounter errors referencing `Buffer`, `crypto`, or `stream`, see
+[React Native Metro polyfill issues](../../troubleshooting/metro-polyfill-issues.md). The
+`Event is not defined` error only occurs when you also use Wagmi, which dispatches DOM events.
:::
### 5. Update provider access
@@ -378,25 +379,26 @@ See the [multichain quickstart](../../multichain/quickstart/javascript.md) for a
-| 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({ infuraApiKey })`](../reference/methods.md#getinfurarpcurls) 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 | — |
+| 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 for EVM; `disconnect(scopes)` enables partial disconnect |
+| `sdk.terminate` | `client.disconnect` | `terminate` removed; `disconnect()` with no arguments revokes all scopes and ends the session |
+| `dappMetadata` | `dapp` | Renamed |
+| `infuraAPIKey` | [`getInfuraRpcUrls({ infuraApiKey })`](../reference/methods.md#getinfurarpcurls) 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` | `analytics.enabled` | Replaced by `analytics: { enabled: false }` to opt out |
+| `communicationServerUrl` | Removed | — |
+| `storage` | Removed | — |
diff --git a/metamask-connect/evm/quickstart/react-native.md b/metamask-connect/evm/quickstart/react-native.md
index ee675946e42..9154dc584f8 100644
--- a/metamask-connect/evm/quickstart/react-native.md
+++ b/metamask-connect/evm/quickstart/react-native.md
@@ -142,6 +142,10 @@ if (typeof global.CustomEvent === 'undefined') {
}
```
+:::note
+The `Event` and `CustomEvent` polyfills above are only required if you also use Wagmi, which dispatches DOM events. The `@metamask/connect-*` packages use `eventemitter3` internally and don't need them.
+:::
+
:::tip
For detailed troubleshooting of polyfill issues, see [React Native Metro polyfill issues](../../troubleshooting/metro-polyfill-issues.md).
:::
diff --git a/metamask-connect/evm/quickstart/wagmi.md b/metamask-connect/evm/quickstart/wagmi.md
index c8277209e62..53ec5212017 100644
--- a/metamask-connect/evm/quickstart/wagmi.md
+++ b/metamask-connect/evm/quickstart/wagmi.md
@@ -27,7 +27,7 @@ Get started with MetaMask Connect EVM in a React and Wagmi dapp.
[Download the quickstart template](#set-up-using-a-template) or [manually set up MetaMask Connect EVM](#set-up-manually) in an existing dapp.
:::tip Migrating from `@metamask/sdk`?
-If you are upgrading an existing wagmi project that used `@metamask/sdk`, see the
+If you are upgrading an existing Wagmi project that used `@metamask/sdk`, see the
[Wagmi connector migration reference](#migrate-from-metamasksdk) at the bottom of this page
for a parameter mapping table.
:::
@@ -97,11 +97,11 @@ TOD0: Update with new screenshot and link
Install MetaMask Connect EVM along with its peer dependencies to an existing React project:
:::note Version requirements
-This quickstart requires `wagmi@^3.6.0` and `wagmi/connectors@^8.0.0`
+This quickstart requires `wagmi@^3.6.0`. The `metaMask` connector is imported from the `wagmi/connectors` subpath (part of the `wagmi` package) and requires `@metamask/connect-evm@^2.1.0`.
:::
```bash npm2yarn
-npm install @metamask/connect-evm wagmi@^3.6.0 wagmi/connectors@^8.0.0 viem@2.x @tanstack/react-query
+npm install @metamask/connect-evm@^2.1.0 wagmi@^3.6.0 viem@2.x @tanstack/react-query
```
### 2. Import required dependencies
@@ -320,12 +320,12 @@ If you previously used `@metamask/sdk` with Wagmi, the MetaMask connector now us
```bash npm2yarn
npm uninstall @metamask/sdk
- npm install @metamask/connect-evm wagmi@^3.6.0 wagmi/connectors@^8.0.0
+ npm install @metamask/connect-evm@^2.1.0 wagmi@^3.6.0
```
-2. Update hook usage for wagmi v3:
+2. Update hook usage for Wagmi v3:
- | Old (wagmi v2) | New (wagmi v3) | Notes |
+ | Old (Wagmi v2) | New (Wagmi v3) | Notes |
| --------------------------------- | -------------------------------------- | ------------------------------------------- |
| `useAccount` | `useConnection` | Returns `address`, `isConnected`, `chainId` |
| `useConnect` returns `connectors` | `useConnectors` hook | Connectors are a separate hook |
diff --git a/metamask-connect/evm/reference/json-rpc-api/eth_coinbase.mdx b/metamask-connect/evm/reference/json-rpc-api/eth_coinbase.mdx
index 0a8fea64ca0..13158ec780b 100644
--- a/metamask-connect/evm/reference/json-rpc-api/eth_coinbase.mdx
+++ b/metamask-connect/evm/reference/json-rpc-api/eth_coinbase.mdx
@@ -11,7 +11,7 @@ import SimplifiedApiReference from '@site/src/components/SimplifiedApiReference'
diff --git a/metamask-connect/evm/reference/methods.md b/metamask-connect/evm/reference/methods.md
index a6c386e3288..855aedfbd2c 100644
--- a/metamask-connect/evm/reference/methods.md
+++ b/metamask-connect/evm/reference/methods.md
@@ -47,7 +47,11 @@ Creates a new EVM client instance.
| `ui.preferExtension` | `boolean` | No | Directly connects through the MetaMask extension when it's installed. The default is `true`. |
| `mobile.preferredOpenLink` | `(deeplink: string) => void` | No | A function that's called to open a deeplink to the MetaMask Mobile App. Required in React Native. |
| `mobile.useDeeplink` | `boolean` | No | Controls use of deeplinks for mobile connection flows. |
+| `ui.showInstallModal` | `boolean` | No | Shows the MetaMask install modal when the extension isn't detected. |
| `eventHandlers` | `object` | No | Optional callbacks for connection lifecycle and [provider events](provider-api.md#events). |
+| `debug` | `boolean` | No | Enables verbose SDK logging. The default is `false`. |
+| `skipAutoAnnounce` | `boolean` | No | Opts out of automatic EIP-6963 provider announcement (default since `@metamask/connect-evm` 2.0.0). Call `client.announceProvider()` to announce manually. |
+| `analytics.enabled` | `boolean` | No | Set to `false` to opt out of analytics. |
### Returns
@@ -274,6 +278,13 @@ Switches the active chain on the EVM client.
If the chain is not already added to the user's MetaMask wallet, the optional `chainConfiguration`
parameter triggers a `wallet_addEthereumChain` request as a fallback.
+:::note
+Since `@metamask/connect-evm` 1.2.0, calling `switchChain` without `chainConfiguration` for an
+unrecognized chain rejects with the wallet's raw `4902` error (not a "No chain configuration found."
+message). Handle `4902` in a `catch` block by retrying with `chainConfiguration` or calling
+`wallet_addEthereumChain`.
+:::
+
### Parameters
| Name | Type | Required | Description |
@@ -363,6 +374,11 @@ The EVM client exposes the following read-only properties:
| `selectedChainId` | `Hex \| undefined` | Currently selected chain ID as a hex string. |
| `status` | `ConnectEvmStatus` | Connection status: `'loaded'`, `'pending'`, `'connecting'`, `'connected'`, or `'disconnected'`. |
+:::note
+On the EVM client, `status` is a `ConnectEvmStatus`. Since `@metamask/connect-evm` 0.11.0 it
+reflects the EVM client's own status and no longer proxies `MultichainClient.status`.
+:::
+
### Example
```javascript
diff --git a/metamask-connect/evm/reference/provider-api.md b/metamask-connect/evm/reference/provider-api.md
index 141ff80d261..5bf69f5bfde 100644
--- a/metamask-connect/evm/reference/provider-api.md
+++ b/metamask-connect/evm/reference/provider-api.md
@@ -36,6 +36,10 @@ Throughout this documentation, we refer to the selected provider using `provider
When using MetaMask Connect EVM, you get the same EIP-1193 provider via `client.getProvider`.
The provider returned by MetaMask Connect EVM is available immediately after `createEVMClient` resolves
and supports the same methods, properties, and events documented below.
+
+Since `@metamask/connect-evm` 2.0.0, MetaMask Connect EVM announces its provider through EIP-6963 by
+default when the native MetaMask extension hasn't already announced one. Pass `skipAutoAnnounce: true`
+to `createEVMClient` to opt out, and call `client.announceProvider` to announce manually.
:::
## Properties
@@ -203,6 +207,7 @@ provider // Or window.ethereum if you don't support EIP-6963.
```
The provider emits this event when the currently connected chain changes.
+The `chainId` is a hex string (for example, `0x1`), not a decimal number.
Listen to this event to [detect a user's network](../guides/manage-networks.md).
### `connect`
@@ -329,6 +334,8 @@ You can use the error `code` property to determine why the request failed.
Common codes and their meaning include:
- `4001` - The request is rejected by the user.
+- `-32002` - A request is already pending. Don't send another request; wait for the user to respond in MetaMask.
+- `4902` - The chain isn't recognized by the wallet. Add it with `wallet_addEthereumChain`, or pass `chainConfiguration` to [`switchChain`](methods.md#switchchain).
- `-32602` - The parameters are invalid.
- `-32603` - Internal error.
@@ -340,6 +347,14 @@ The [`eth-rpc-errors`](https://npmjs.com/package/eth-rpc-errors) package impleme
returned by the MetaMask provider, and can help you identify their meaning.
:::
+:::note Typed errors
+When you use the multichain client, the core (`@metamask/connect-multichain`) exports typed error
+classes for `instanceof` checks: `RPCInvokeMethodErr` (wraps wallet `invokeMethod` errors; read the
+wallet's original code from `err.rpcCode`), `RPCHttpErr`, `RPCReadonlyResponseErr`, and
+`RPCReadonlyRequestErr`. The EVM provider itself rejects with a standard EIP-1193 `ProviderRpcError`
+(use `err.code`).
+:::
+
## Next steps
- [JSON-RPC API reference](./json-rpc-api/index.md)
diff --git a/metamask-connect/index.md b/metamask-connect/index.md
index d973a379b83..7ce9e74476e 100644
--- a/metamask-connect/index.md
+++ b/metamask-connect/index.md
@@ -80,7 +80,7 @@ MetaMask Connect is a complete rewrite of the legacy SDK.
Key differences include:
- Async initialization with `createEVMClient`.
-- A singleton client pattern.
+- A singleton client pattern built on the shared `createMultichainClient` core (the EVM and Solana clients wrap it).
- Built-in multichain support for both EVM and Solana.
- CAIP-25 session management.
- Improved relay server infrastructure.
diff --git a/metamask-connect/integration-options.md b/metamask-connect/integration-options.md
index 83a09c4d3d8..1dd2bca968f 100644
--- a/metamask-connect/integration-options.md
+++ b/metamask-connect/integration-options.md
@@ -34,6 +34,7 @@ all ecosystems.
If your dapp targets a single chain or you prefer per-chain provider interfaces, you can use the single-ecosystem or multi-ecosystem option.
All options share the same underlying transport and session infrastructure, so you can start with the option that fits your dapp today and migrate later.
+The EVM and Solana clients wrap the same `createMultichainClient` singleton core.
## Multichain (recommended)
@@ -97,7 +98,7 @@ Choose **multi-ecosystem** (both single-ecosystem packages) if you want per-chai
Yes. All three options share the same underlying transport and session infrastructure, so you can start with a single-ecosystem client and migrate to multichain later without changing your backend or connection logic.
The migration involves updating your client initialization code and adopting scope-based RPC routing.
-### Does MetaMask Connect work with wagmi, ethers.js, and viem?
+### Does MetaMask Connect work with Wagmi, ethers.js, and viem?
Yes. The EVM client (`@metamask/connect-evm`) provides an EIP-1193 compatible provider that works directly with viem's `custom` transport, ethers.js `BrowserProvider`, and web3.js `Web3` constructor.
The Solana client provides a Wallet Standard compatible wallet that works with the Solana Wallet Adapter ecosystem.
diff --git a/metamask-connect/multichain/concepts/scopes.md b/metamask-connect/multichain/concepts/scopes.md
index ee15550288d..f7844ade6bf 100644
--- a/metamask-connect/multichain/concepts/scopes.md
+++ b/metamask-connect/multichain/concepts/scopes.md
@@ -62,10 +62,10 @@ const balance = await client.invokeMethod({
## Supported scopes
-| Ecosystem | Format | Examples |
-| --------- | ---------------------- | ------------------------------------------------------------------------------------------------------- |
-| EVM | `eip155:` | `eip155:1` (Ethereum), `eip155:59144` (Linea), `eip155:137` (Polygon) |
-| Solana | `solana:` | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` (Mainnet), `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` (Devnet) |
+| Ecosystem | Format | Examples |
+| --------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| EVM | `eip155:` | `eip155:1` (Ethereum), `eip155:59144` (Linea), `eip155:137` (Polygon) |
+| Solana | `solana:` | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` (Mainnet), `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` (Devnet), `solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z` (Testnet) |
## Common EVM scopes
diff --git a/metamask-connect/multichain/guides/headless-mode.md b/metamask-connect/multichain/guides/headless-mode.md
index 0254a941a21..2c6661ac245 100644
--- a/metamask-connect/multichain/guides/headless-mode.md
+++ b/metamask-connect/multichain/guides/headless-mode.md
@@ -65,6 +65,11 @@ You **must** register the listener before calling `connect`, or you may miss the
client.on('display_uri', uri => {
showCustomQrModal(uri)
})
+
+// Register a session listener too, so restored sessions are captured.
+client.on('wallet_sessionChanged', session => {
+ // Update your app state with the new session
+})
```
### 3. Connect and handle the result
@@ -79,6 +84,8 @@ try {
hideCustomQrModal()
if (err.code === 4001) {
// User rejected — show retry UI
+ } else if (err.code === -32002) {
+ // A request is already pending — wait for the user to respond, don't call connect again
} else {
console.error('Connection failed:', err)
}
diff --git a/metamask-connect/multichain/guides/send-transactions.md b/metamask-connect/multichain/guides/send-transactions.md
index 0f8ef4bf2d7..127a2642f36 100644
--- a/metamask-connect/multichain/guides/send-transactions.md
+++ b/metamask-connect/multichain/guides/send-transactions.md
@@ -61,10 +61,10 @@ await client.connect(
The multichain client routes EVM methods based on type:
-| Route | Methods | Transport |
-| ------------ | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
-| **RPC node** | `eth_call`, `eth_getBalance`, `eth_blockNumber`, `eth_getTransactionReceipt`, `eth_estimateGas`, `eth_getCode`, `eth_getLogs` | Infura / custom RPC URL from `supportedNetworks` |
-| **Wallet** | `eth_sendTransaction`, `personal_sign`, `eth_signTypedData_v4`, `wallet_switchEthereumChain`, `wallet_addEthereumChain` | MetaMask (extension or mobile) |
+| Route | Methods | Transport |
+| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
+| **RPC node** | `eth_call`, `eth_getBalance`, `eth_getTransactionCount`, `eth_blockNumber`, `eth_getTransactionReceipt`, `eth_estimateGas`, `eth_getCode`, `eth_getLogs` | Infura / custom RPC URL from `supportedNetworks` |
+| **Wallet** | `eth_sendTransaction`, `personal_sign`, `eth_signTypedData_v4`, `wallet_switchEthereumChain`, `wallet_addEthereumChain` | MetaMask (extension or mobile) |
All Solana methods route through the MetaMask wallet. There is no RPC node fallback for Solana.
@@ -133,7 +133,7 @@ console.log('Estimated gas:', gasEstimate)
## Build and send a Solana transaction
-Build a transaction with `@solana/web3.js`, serialize it to base64, then send it with `solana_signAndSendTransaction`.
+Build a transaction with `@solana/web3.js`, serialize it to base64, then send it with `signAndSendTransaction`.
This signs and broadcasts the transaction in one step:
```javascript
@@ -163,8 +163,9 @@ const base64Transaction = Buffer.from(serialized).toString('base64')
const result = await client.invokeMethod({
scope: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
request: {
- method: 'solana_signAndSendTransaction',
+ method: 'signAndSendTransaction',
params: {
+ account: { address: fromPubkey.toBase58() },
transaction: base64Transaction,
},
},
@@ -174,15 +175,16 @@ console.log('SOL tx signature:', result.signature)
## Sign a Solana transaction without sending
-Use `solana_signTransaction` to get the signed transaction back without broadcasting it.
+Use `signTransaction` to get the signed transaction back without broadcasting it.
This is useful when you need to inspect or modify the signed output before submitting:
```javascript
const signResult = await client.invokeMethod({
scope: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
request: {
- method: 'solana_signTransaction',
+ method: 'signTransaction',
params: {
+ account: { address: fromPubkey.toBase58() },
transaction: base64Transaction,
},
},
diff --git a/metamask-connect/multichain/guides/sign-transactions.md b/metamask-connect/multichain/guides/sign-transactions.md
index 3d1cd287f91..65c3bbd177d 100644
--- a/metamask-connect/multichain/guides/sign-transactions.md
+++ b/metamask-connect/multichain/guides/sign-transactions.md
@@ -128,9 +128,9 @@ The `EIP712Domain` type must be declared in `types` even though `primaryType` is
Chain IDs in the typed data `domain.chainId` are integers (for example, `1`), not hex strings.
:::
-## Sign a Solana message (`solana_signMessage`)
+## Sign a Solana message (`signMessage`)
-Use [`invokeMethod`](../reference/methods.md#invokemethod) with `solana_signMessage` to sign an arbitrary message on Solana.
+Use [`invokeMethod`](../reference/methods.md#invokemethod) with `signMessage` to sign an arbitrary message on Solana.
The message must be base64-encoded:
```javascript
@@ -139,10 +139,10 @@ const message = btoa('Hello from Solana!')
const result = await client.invokeMethod({
scope: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', // Solana Mainnet
request: {
- method: 'solana_signMessage',
+ method: 'signMessage',
params: {
+ account: { address: 'YourSolanaPublicKeyBase58' },
message,
- pubkey: 'YourSolanaPublicKeyBase58',
},
},
})
diff --git a/metamask-connect/multichain/index.mdx b/metamask-connect/multichain/index.mdx
index fba3bcab5f0..2d8a5a2cb52 100644
--- a/metamask-connect/multichain/index.mdx
+++ b/metamask-connect/multichain/index.mdx
@@ -104,7 +104,7 @@ If you're adding MetaMask Connect Multichain to an existing dapp and want minima
### What chains does the multichain client support?
-MetaMask Connect Multichain supports all EVM-compatible networks (Ethereum, Polygon, Arbitrum, Optimism, Linea, Base, and any chain with a [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md) scope) and Solana (mainnet and devnet).
+MetaMask Connect Multichain supports all EVM-compatible networks (Ethereum, Polygon, Arbitrum, Optimism, Linea, Base, and any chain with a [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md) scope) and Solana (mainnet, devnet, and testnet; devnet and testnet are supported only in the MetaMask browser extension).
Future ecosystems will be supported as they are added to MetaMask.
Specify which chains to connect to using CAIP-2 scopes (for example, `eip155:1` for Ethereum Mainnet or `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` for Solana Mainnet).
diff --git a/metamask-connect/multichain/quickstart/javascript.md b/metamask-connect/multichain/quickstart/javascript.md
index 7375863ea76..81f4ec30ae3 100644
--- a/metamask-connect/multichain/quickstart/javascript.md
+++ b/metamask-connect/multichain/quickstart/javascript.md
@@ -172,6 +172,12 @@ if (ethAccounts.length > 0) {
The user sees a single approval prompt for all requested chains.
Use [`invokeMethod`](../reference/methods.md#invokemethod) to call RPC methods on any chain in the session by specifying a [scope](../concepts/scopes.md).
+:::tip Restore sessions
+Register a [`wallet_sessionChanged`](../reference/methods.md#events) listener before calling `connect`,
+and skip `connect` when [`getSession`](../reference/methods.md#getsession) already returns a session
+(for example, after a page reload). See [sessions](../concepts/sessions.md).
+:::
+
## Multichain client methods at a glance
| Method | Description |
diff --git a/metamask-connect/multichain/quickstart/nodejs.md b/metamask-connect/multichain/quickstart/nodejs.md
index 9a4946d74ae..b4563804471 100644
--- a/metamask-connect/multichain/quickstart/nodejs.md
+++ b/metamask-connect/multichain/quickstart/nodejs.md
@@ -71,7 +71,8 @@ const client = await createMultichainClient({
:::info Async client
`createMultichainClient` returns a promise. Always `await` it before using the client.
-The client is a singleton; calling it again returns the same instance with merged options.
+The client is a singleton; calling it again returns the same instance with merged options, except the
+`dapp` object from the first call, which is never overwritten.
:::
### 3. Connect to MetaMask
@@ -144,7 +145,7 @@ const solSig = await client.invokeMethod({
},
},
})
-console.log('SOL signature:', solSig)
+console.log('SOL signature:', solSig.signature)
```
### 6. Disconnect
@@ -257,7 +258,7 @@ const solSig = await client.invokeMethod({
},
},
})
-console.log('SOL signature:', solSig)
+console.log('SOL signature:', solSig.signature)
// Disconnect
await client.disconnect()
diff --git a/metamask-connect/multichain/quickstart/react-native.md b/metamask-connect/multichain/quickstart/react-native.md
index 86847d4d3fc..977e9679753 100644
--- a/metamask-connect/multichain/quickstart/react-native.md
+++ b/metamask-connect/multichain/quickstart/react-native.md
@@ -147,6 +147,10 @@ if (typeof global.CustomEvent === 'undefined') {
}
```
+:::note
+The `Event` and `CustomEvent` polyfills above are only required if you also use Wagmi, which dispatches DOM events. The `@metamask/connect-*` packages use `eventemitter3` internally and don't need them.
+:::
+
Create the empty module stub used by the Metro config:
```javascript title="src/empty-module.js"
@@ -362,8 +366,8 @@ export default function App() {
const result = await client.invokeMethod({
scope: SOLANA_MAINNET,
request: {
- method: 'solana_signMessage',
- params: { message, pubkey },
+ method: 'signMessage',
+ params: { account: { address: pubkey }, message },
},
})
Alert.alert('Signed', result.signature.slice(0, 40) + '...')
diff --git a/metamask-connect/multichain/reference/methods.md b/metamask-connect/multichain/reference/methods.md
index a63c6f8e6ab..56da397dc8b 100644
--- a/metamask-connect/multichain/reference/methods.md
+++ b/metamask-connect/multichain/reference/methods.md
@@ -40,26 +40,29 @@ Use those standard methods if you're building your own client or need lower-leve
## `createMultichainClient`
-Creates a new multichain client instance.
+Creates the multichain client. The client is a singleton: the first call creates the instance, and
+subsequent calls return that same instance.
### Parameters
-| Name | Type | Required | Description |
-| -------------------------- | ---------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `dapp.name` | `string` | Yes | Name of your dapp. |
-| `dapp.url` | `string` | No | URL of your dapp. In browsers this is often set automatically; required in Node.js and React Native. |
-| `dapp.iconUrl` | `string` | No | URL of your dapp icon. |
-| `dapp.base64Icon` | `string` | No | Base64-encoded icon when a hosted URL is unavailable (for example, some React Native setups). |
-| `api.supportedNetworks` | `Record` | No | Map of [CAIP-2](https://chainagnostic.org/CAIPs/caip-2) chain IDs to RPC URLs. Use [`getInfuraRpcUrls`](#getinfurarpcurls) to generate Infura URLs, then merge custom endpoints. |
-| `ui.headless` | `boolean` | No | Enables or disables [headless mode](../guides/headless-mode.md). The default is `false`. |
-| `ui.preferExtension` | `boolean` | No | Directly connects through the MetaMask extension when it's installed. The default is `true`. |
-| `mobile.preferredOpenLink` | `(deeplink: string) => void` | No | A function that's called to open a deeplink to the MetaMask Mobile App. Required in React Native. |
-| `mobile.useDeeplink` | `boolean` | No | Controls use of deeplinks for mobile connection flows. |
+| Name | Type | Required | Description |
+| -------------------------- | ---------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `dapp.name` | `string` | Yes | Name of your dapp. |
+| `dapp.url` | `string` | Conditional | URL of your dapp. In browsers this is often set automatically; required in Node.js and React Native (no `window.location`). |
+| `dapp.iconUrl` | `string` | No | URL of your dapp icon. |
+| `dapp.base64Icon` | `string` | No | Base64-encoded icon when a hosted URL is unavailable (for example, some React Native setups). |
+| `api.supportedNetworks` | `Record` | No | Map of [CAIP-2](https://chainagnostic.org/CAIPs/caip-2) chain IDs to RPC URLs. Use [`getInfuraRpcUrls`](#getinfurarpcurls) to generate Infura URLs, then merge custom endpoints. |
+| `ui.headless` | `boolean` | No | Enables or disables [headless mode](../guides/headless-mode.md). The default is `false`. |
+| `ui.preferExtension` | `boolean` | No | Directly connects through the MetaMask extension when it's installed. The default is `true`. |
+| `mobile.preferredOpenLink` | `(deeplink: string) => void` | No | A function that's called to open a deeplink to the MetaMask Mobile App. Required in React Native. |
+| `mobile.useDeeplink` | `boolean` | No | Controls use of deeplinks for mobile connection flows. |
### Returns
-Returns a promise that resolves to a multichain client instance.
-The client is a singleton; calling `createMultichainClient` again returns the same instance.
+Returns a promise that resolves to the multichain client instance.
+The client is a singleton: calling `createMultichainClient` again returns the same instance and merges
+any new options, except the `dapp` object from the first call, which is never overwritten. The
+`api.supportedNetworks` map merges additively across calls.
### Example
@@ -105,6 +108,13 @@ A promise that resolves when the connection is established.
await client.connect(['eip155:1', 'eip155:137', 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'], [])
```
+To force a fresh connection prompt even when a session already exists, pass `forceRequest` as the
+fourth argument:
+
+```javascript
+await client.connect(['eip155:1'], [], undefined, true)
+```
+
## `getSession`
Returns the current multichain session, including the approved scopes and accounts.
@@ -135,11 +145,11 @@ Use this to interact with any chain the user has approved, without switching con
### Parameters
-| Name | Type | Required | Description |
-| ------------------------ | ----------- | -------- | ---------------------------------------------------------------------------------------------- |
-| `options.scope` | `Scope` | Yes | The [CAIP-2](https://chainagnostic.org/CAIPs/caip-2) chain identifier to invoke the method on. |
-| `options.request.method` | `string` | Yes | The RPC method name. |
-| `options.request.params` | `unknown[]` | No | The method parameters. |
+| Name | Type | Required | Description |
+| ------------------------ | --------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
+| `options.scope` | `Scope` | Yes | The [CAIP-2](https://chainagnostic.org/CAIPs/caip-2) chain identifier to invoke the method on. |
+| `options.request.method` | `string` | Yes | The RPC method name. |
+| `options.request.params` | `unknown[] \| object` | No | The method parameters. EVM methods take an array; Solana methods take an object (for example, `{ account: { address }, message }`). |
### Returns
@@ -361,11 +371,11 @@ const client = await createMultichainClient({
Register event handlers using [`on`](#on) and remove them with [`off`](#off).
-| Event | Payload | Description |
-| ----------------------- | ------------------ | -------------------------------------------------------------------- |
-| `wallet_sessionChanged` | `Session` | Fired when session scopes or accounts change. |
-| `display_uri` | `string` | Fired with a URI for custom QR code implementations (headless mode). |
-| `stateChanged` | `ConnectionStatus` | Fired when the connection status changes. |
+| Event | Payload | Description |
+| ----------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `wallet_sessionChanged` | `Session` | Fired when the session changes: on connect, when scopes or accounts are added or removed, and during asynchronous session restoration. Register it before calling `connect` so restored sessions are captured. |
+| `display_uri` | `string` | Fired with a URI for custom QR code implementations (headless mode). |
+| `stateChanged` | `ConnectionStatus` | Fired when the connection status changes. |
### Example
@@ -386,28 +396,31 @@ client.on('stateChanged', status => {
## Error classes
-`@metamask/connect-multichain` exports typed error classes for granular error handling:
+`@metamask/connect-multichain` exports typed error classes (all extending a common `BaseErr`) for granular error handling:
-| Class | Description |
-| --------------- | ------------------------------------------------------------- |
-| `RpcError` | JSON-RPC errors from the wallet (includes `code` and `data`). |
-| `ProtocolError` | Connection protocol failures (transport, pairing, handshake). |
-| `StorageError` | Session persistence issues (read/write failures). |
+| Class | Code | Description |
+| ------------------------ | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `RPCInvokeMethodErr` | 53 | A wallet `invokeMethod` call failed. Wraps the wallet's error: read `rpcCode`, `rpcMessage`, and `rpcData` for the original JSON-RPC / EIP-1193 details, and `reason` for the normalized message. |
+| `RPCHttpErr` | 50 | An HTTP-level failure calling a read-only RPC endpoint (exposes `rpcEndpoint`, `method`, and `httpStatus`). |
+| `RPCReadonlyResponseErr` | 51 | A read-only RPC endpoint returned an error response (exposes `reason`). |
+| `RPCReadonlyRequestErr` | 52 | A read-only RPC request couldn't be issued (exposes `reason`). |
### Example
```javascript
-import { ProtocolError, RpcError, StorageError } from '@metamask/connect-multichain'
+import { RPCInvokeMethodErr } from '@metamask/connect-multichain'
try {
- await client.connect(['eip155:1'], [])
+ await client.invokeMethod({
+ scope: 'eip155:1',
+ request: { method: 'personal_sign', params: [message, address] },
+ })
} catch (err) {
- if (err instanceof RpcError) {
- // Check err.code for specific RPC error codes (4001, -32002, etc.)
- } else if (err instanceof ProtocolError) {
- // Connection protocol failure
- } else if (err instanceof StorageError) {
- // Session persistence issue
+ if (err instanceof RPCInvokeMethodErr) {
+ // The wallet's original code is on err.rpcCode (for example, 4001 when the user rejects).
+ if (err.rpcCode === 4001) {
+ // User rejected — show retry UI
+ }
}
}
```
diff --git a/metamask-connect/multichain/tutorials/create-multichain-dapp.md b/metamask-connect/multichain/tutorials/create-multichain-dapp.md
index 096bc3d11ff..62bebe7822c 100644
--- a/metamask-connect/multichain/tutorials/create-multichain-dapp.md
+++ b/metamask-connect/multichain/tutorials/create-multichain-dapp.md
@@ -137,6 +137,12 @@ const client = await getClient()
await client.connect([SCOPES.ETHEREUM, SCOPES.LINEA, SCOPES.BASE, SCOPES.SOLANA], [])
```
+:::tip Restore existing sessions
+Register a [`wallet_sessionChanged`](../reference/methods.md#events) listener before calling `connect`,
+and skip `connect` when [`getSession`](../reference/methods.md#getsession) already returns a session
+(for example, after a page reload).
+:::
+
The second argument is an optional array of [CAIP-10](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-10.md)
account preferences.
Pass an empty array to let the user choose their own accounts.
@@ -261,19 +267,19 @@ const signature = await client.invokeMethod({
console.log('EVM signature:', signature)
```
-#### Solana (`solana_signMessage`)
+#### Solana (`signMessage`)
-Use [`invokeMethod`](../reference/methods.md#invokemethod) with `solana_signMessage` to sign a message on Solana:
+Use [`invokeMethod`](../reference/methods.md#invokemethod) with `signMessage` to sign a message on Solana:
```typescript
const solAddress = extractAddress(solAccounts[0])
const signature = await client.invokeMethod({
scope: SCOPES.SOLANA,
request: {
- method: 'solana_signMessage',
+ method: 'signMessage',
params: {
+ account: { address: solAddress },
message: btoa('Hello from my multichain dapp!'),
- pubkey: solAddress,
},
},
})
@@ -310,14 +316,16 @@ The same address format and RPC method works across all EVM chains.
#### Solana transaction
-Use [`invokeMethod`](../reference/methods.md#invokemethod) with `solana_signAndSendTransaction` to send a Solana base64-encoded transaction:
+Use [`invokeMethod`](../reference/methods.md#invokemethod) with `signAndSendTransaction` to send a Solana base64-encoded transaction:
```typescript
+const solAddress = extractAddress(solAccounts[0])
const result = await client.invokeMethod({
scope: SCOPES.SOLANA,
request: {
- method: 'solana_signAndSendTransaction',
+ method: 'signAndSendTransaction',
params: {
+ account: { address: solAddress },
transaction: '',
},
},
@@ -497,10 +505,10 @@ export default function App() {
const sig = await client.invokeMethod({
scope: SCOPES.SOLANA,
request: {
- method: 'solana_signMessage',
+ method: 'signMessage',
params: {
+ account: { address: solAddress },
message: btoa('Hello from my multichain dapp!'),
- pubkey: solAddress,
},
},
})
@@ -597,7 +605,8 @@ export default function App() {
- **Leverage session persistence.**
Sessions survive page reloads and new tabs.
- Check for an existing session on startup with `getSession` before prompting the user to connect
+ Register a `wallet_sessionChanged` listener before calling `connect` to capture restored sessions,
+ and check for an existing session on startup with `getSession` before prompting the user to connect
again.
- **Show chain context clearly.**
diff --git a/metamask-connect/solana/guides/send-transactions/batch.md b/metamask-connect/solana/guides/send-transactions/batch.md
index 26e4d5ed69f..02cba047c02 100644
--- a/metamask-connect/solana/guides/send-transactions/batch.md
+++ b/metamask-connect/solana/guides/send-transactions/batch.md
@@ -1,12 +1,12 @@
---
title: 'Send Batch Solana Transactions - MetaMask Connect'
sidebar_label: Send batch transactions
-description: Sign and send multiple Solana transactions in a single wallet interaction using the signAndSendAllTransactions Wallet Standard feature.
+description: Sign and send multiple Solana transactions by passing several inputs to the signAndSendTransaction Wallet Standard feature.
keywords:
[
solana,
batch transactions,
- signAndSendAllTransactions,
+ signAndSendTransaction,
wallet-standard,
MetaMask,
Connect,
@@ -16,11 +16,17 @@ keywords:
# Send batch transactions
-The `solana:signAndSendAllTransactions` Wallet Standard feature lets you sign and send multiple
-Solana transactions in a single wallet interaction.
+The [`solana:signAndSendTransaction`](../../reference/methods.md#supported-wallet-standard-features)
+Wallet Standard feature accepts multiple inputs: you can pass several transactions to a single call to
+sign and send them, and it resolves to one result per transaction.
This is useful for operations that span several transactions, such as initializing multiple accounts,
batch token transfers, or multi-step program interactions.
+:::note
+No separate `signAndSendAllTransactions` feature exists. Send a batch by passing several inputs to
+`signAndSendTransaction`.
+:::
+
## Prerequisites
Follow Step 1 of the [quickstart](../../quickstart/javascript.md) to install the Solana client.
@@ -80,29 +86,40 @@ const serializedTransactions = recipients.map(recipient => {
})
```
-### 3. Sign and send all transactions
+### 3. Sign and send the transactions
-Use the [`solana:signAndSendAllTransactions`](../../reference/methods.md#supported-wallet-standard-features) feature to submit the batch:
+Pass each serialized transaction as a separate input to
+[`signAndSendTransaction`](../../reference/methods.md#supported-wallet-standard-features).
+The feature accepts one or more inputs and resolves to an array with one result per transaction:
```javascript
-const batchFeature = wallet.features['solana:signAndSendAllTransactions']
+const sendFeature = wallet.features['solana:signAndSendTransaction']
-const results = await batchFeature.signAndSendAllTransactions({
- account,
- transactions: serializedTransactions,
- chain: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
-})
+const results = await sendFeature.signAndSendTransaction(
+ ...serializedTransactions.map(transaction => ({
+ account,
+ transaction,
+ chain: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
+ }))
+)
```
+Each result contains a `signature` as a `Uint8Array`, in the same order as the inputs.
+
### 4. Confirm each transaction
-Always confirm transactions before updating the UI:
+Always confirm transactions before updating the UI.
+The Wallet Standard returns each signature as bytes, so encode it to base58 for
+`confirmTransaction`:
```javascript
+import bs58 from 'bs58'
+
for (const { signature } of results) {
+ const signatureStr = bs58.encode(signature)
const confirmation = await connection.confirmTransaction(
{
- signature,
+ signature: signatureStr,
blockhash,
lastValidBlockHeight: (await connection.getLatestBlockhash()).lastValidBlockHeight,
},
@@ -112,26 +129,31 @@ for (const { signature } of results) {
if (confirmation.value.err) {
console.error('Transaction failed on-chain:', confirmation.value.err)
} else {
- console.log('Transaction confirmed:', signature)
+ console.log('Transaction confirmed:', signatureStr)
}
}
```
## Error handling
-When sending batch transactions, handle common errors:
+When sending batch transactions, handle common errors.
+Wallet calls reject with an `RPCInvokeMethodErr`, which exposes the wallet's original code on
+`rpcCode`:
```javascript
try {
- const results = await batchFeature.signAndSendAllTransactions({
- account,
- transactions: serializedTransactions,
- chain: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
- })
+ const results = await sendFeature.signAndSendTransaction(
+ ...serializedTransactions.map(transaction => ({
+ account,
+ transaction,
+ chain: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
+ }))
+ )
} catch (err) {
- if (err.code === 4001) {
- // User rejected the batch — show retry UI
- } else if (err.code === -32002) {
+ const walletCode = err.rpcCode ?? err.code
+ if (walletCode === 4001) {
+ // User rejected — show retry UI
+ } else if (walletCode === -32002) {
// Request already pending — ask user to check MetaMask
} else {
console.error('Batch transaction error:', err)
@@ -145,6 +167,8 @@ try {
Call `getLatestBlockhash` immediately before building the batch.
- **Transaction size:** Each individual transaction is limited to 1,232 bytes.
If a single transaction exceeds this limit, split it into smaller transactions.
+- **Sequential processing:** Inputs are processed in order, and the call resolves with one result per
+ transaction once all are submitted.
- **Confirmation:** A submitted transaction is not finalized until `confirmTransaction` returns.
Always confirm before reporting success to the user.
- **Devnet and testnet** are only supported in the MetaMask browser extension, not the mobile wallet.
diff --git a/metamask-connect/solana/guides/use-wallet-adapter.md b/metamask-connect/solana/guides/use-wallet-adapter.md
index 0f44d270e66..20ee17af663 100644
--- a/metamask-connect/solana/guides/use-wallet-adapter.md
+++ b/metamask-connect/solana/guides/use-wallet-adapter.md
@@ -43,10 +43,10 @@ Install MetaMask Connect Solana and the Wallet Adapter packages:
```bash
npm install @metamask/connect-solana \
- @solana/web3.js \
- @solana/wallet-adapter-base \
- @solana/wallet-adapter-react \
- @solana/wallet-adapter-react-ui \
+ @solana/web3.js \
+ @solana/wallet-adapter-base \
+ @solana/wallet-adapter-react \
+ @solana/wallet-adapter-react-ui \
@solana/wallet-adapter-wallets
```
@@ -58,7 +58,7 @@ providers:
```typescript title='components/SolanaProvider.tsx'
'use client';
-import React, { FC, ReactNode, useEffect, useMemo } from 'react';
+import React, { FC, ReactNode, useEffect, useMemo, useState } from 'react';
import { ConnectionProvider, WalletProvider } from '@solana/wallet-adapter-react';
import { WalletAdapterNetwork } from '@solana/wallet-adapter-base';
import { WalletModalProvider } from '@solana/wallet-adapter-react-ui';
@@ -74,6 +74,7 @@ interface SolanaProviderProps {
export const SolanaProvider: FC = ({ children }) => {
const network = WalletAdapterNetwork.Devnet;
const endpoint = useMemo(() => clusterApiUrl(network), [network]);
+ const [ready, setReady] = useState(false);
useEffect(() => {
createSolanaClient({
@@ -81,9 +82,15 @@ export const SolanaProvider: FC = ({ children }) => {
name: 'My Solana Dapp',
url: window.location.origin,
},
- });
+ }).then(() => setReady(true));
}, []);
+ // Wait for createSolanaClient to resolve before mounting WalletProvider, so MetaMask
+ // is registered with the Wallet Standard registry before the wallet list renders.
+ if (!ready) {
+ return null;
+ }
+
return (
@@ -99,12 +106,10 @@ the [Wallet Standard](https://github.com/wallet-standard/wallet-standard) regist
This displays MetaMask as a connection option in the wallet modal, even if the user doesn't have
MetaMask installed.
-:::tip Timing
-The `useEffect` pattern above works because `createSolanaClient` typically resolves before the user
-opens the wallet modal.
-If MetaMask does not appear in the wallet list, ensure `createSolanaClient` has resolved before the
-`WalletProvider` renders.
-One approach is to await the client in your app's entry point before calling `createRoot().render()`.
+:::warning Timing
+MetaMask only appears in the wallet modal if `createSolanaClient` has resolved before the
+`WalletProvider` mounts. The example above gates rendering on a `ready` flag to guarantee this.
+As an alternative, await the client in your app's entry point before calling `createRoot().render()`.
See [Troubleshooting: MetaMask wallet not appearing](../../troubleshooting/index.md#metamask-wallet-not-appearing-in-solana-wallet-adapter)
for details.
:::
diff --git a/metamask-connect/solana/index.mdx b/metamask-connect/solana/index.mdx
index 45bcbf84035..c08c0181645 100644
--- a/metamask-connect/solana/index.mdx
+++ b/metamask-connect/solana/index.mdx
@@ -93,4 +93,4 @@ Yes. When you call `createSolanaClient`, MetaMask is automatically registered wi
### Which Solana networks does MetaMask Connect support?
-MetaMask Connect Solana supports mainnet, devnet, and testnet. You can configure custom RPC URLs for each network using the `api.supportedNetworks` option in `createSolanaClient`. If your dapp also targets EVM networks, use [`@metamask/connect-multichain`](../multichain/index.mdx) to manage both ecosystems in a single session.
+MetaMask Connect Solana supports mainnet, devnet, and testnet. Devnet and testnet are supported only in the MetaMask browser extension; on mobile, only mainnet is supported. You can configure custom RPC URLs for each network using the `api.supportedNetworks` option in `createSolanaClient`. If your dapp also targets EVM networks, use [`@metamask/connect-multichain`](../multichain/index.mdx) to manage both ecosystems in a single session.
diff --git a/metamask-connect/solana/quickstart/javascript.md b/metamask-connect/solana/quickstart/javascript.md
index 7a102f14d76..374cffcce5f 100644
--- a/metamask-connect/solana/quickstart/javascript.md
+++ b/metamask-connect/solana/quickstart/javascript.md
@@ -155,6 +155,11 @@ console.log('Connected account:', accounts[0].address)
The client handles cross-platform connection (desktop and mobile), including deeplinking.
+:::note
+This example uses devnet. Devnet and testnet are supported only in the MetaMask browser extension.
+On mobile, only Solana mainnet is supported.
+:::
+
## Solana client methods at a glance
| Method | Description |
diff --git a/metamask-connect/solana/quickstart/nodejs.md b/metamask-connect/solana/quickstart/nodejs.md
index 39250f92c9c..28dfa75d9f0 100644
--- a/metamask-connect/solana/quickstart/nodejs.md
+++ b/metamask-connect/solana/quickstart/nodejs.md
@@ -114,12 +114,12 @@ const result = await solanaClient.core.invokeMethod({
},
},
})
-console.log('Signature:', result)
+console.log('Signature:', result.signature)
```
### 5. Disconnect
-Use [`disconnect`](../reference/methods.md#disconnect) to disconnect all scopes and end the session.
+Use [`disconnect`](../reference/methods.md#disconnect) to revoke the Solana scopes. Any EVM or other scopes in the same multichain session stay active.
```javascript
await solanaClient.disconnect()
@@ -147,10 +147,11 @@ solanaClient.core.on('wallet_sessionChanged', session => {
| ------- | ----------------------------------------- |
| Mainnet | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` |
| Devnet | `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` |
+| Testnet | `solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z` |
:::note
-Devnet and testnet require [MetaMask Flask](https://metamask.io/flask/).
-Production MetaMask only supports Solana mainnet.
+Devnet and testnet are supported only in the MetaMask browser extension. On mobile (including the
+QR code flow in this quickstart), only Solana mainnet is supported.
:::
## Full example
@@ -198,7 +199,7 @@ const result = await solanaClient.core.invokeMethod({
},
},
})
-console.log('Signature:', result)
+console.log('Signature:', result.signature)
// Disconnect
await solanaClient.disconnect()
diff --git a/metamask-connect/solana/quickstart/react-native.md b/metamask-connect/solana/quickstart/react-native.md
index 975665c99e8..2f84c3150e3 100644
--- a/metamask-connect/solana/quickstart/react-native.md
+++ b/metamask-connect/solana/quickstart/react-native.md
@@ -335,10 +335,10 @@ export default function App() {
const result = await client.invokeMethod({
scope: SOLANA_MAINNET,
request: {
- method: 'solana_signMessage',
+ method: 'signMessage',
params: {
+ account: { address: accounts[0] },
message,
- pubkey: accounts[0],
},
},
})
@@ -429,7 +429,7 @@ npx expo run:ios
| Devnet | `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` |
:::note
-Devnet and testnet require [MetaMask Flask](https://metamask.io/flask/). Production MetaMask only supports Solana mainnet.
+Devnet and testnet are supported only in the MetaMask browser extension. On mobile, only Solana mainnet is supported.
:::
## Next steps
diff --git a/metamask-connect/solana/reference/methods.md b/metamask-connect/solana/reference/methods.md
index 9422138eb15..9179cf21821 100644
--- a/metamask-connect/solana/reference/methods.md
+++ b/metamask-connect/solana/reference/methods.md
@@ -196,14 +196,14 @@ The wallet returned by [`getWallet`](#getwallet) implements the following
[Wallet Standard](https://github.com/wallet-standard/wallet-standard) features.
Access them via `wallet.features['']`.
-| Feature | Description |
-| ------------------------------- | ------------------------------------------------------ |
-| `standard:connect` | Connect to the wallet and receive the user's accounts. |
-| `standard:disconnect` | Disconnect from the wallet. |
-| `standard:events` | Subscribe to account and chain change events. |
-| `solana:signMessage` | Sign an arbitrary message (returns a signature). |
-| `solana:signTransaction` | Sign a transaction without broadcasting it. |
-| `solana:signAndSendTransaction` | Sign a transaction and broadcast it to the network. |
+| Feature | Description |
+| ------------------------------- | ------------------------------------------------------------------------------------------------------ |
+| `standard:connect` | Connect to the wallet and receive the user's accounts. |
+| `standard:disconnect` | Disconnect from the wallet. |
+| `standard:events` | Subscribe to account and chain change events. |
+| `solana:signMessage` | Sign an arbitrary message (returns a signature). |
+| `solana:signTransaction` | Sign one or more transactions without broadcasting them. |
+| `solana:signAndSendTransaction` | Sign one or more transactions and broadcast them to the network. Pass multiple inputs to send a batch. |
### Example
@@ -239,16 +239,17 @@ type SolanaSupportedNetworks = Partial>
Configuration options passed to [`createSolanaClient`](#createsolanaclient).
-| Field | Type | Required | Description |
-| ----------------------- | ------------------------- | -------- | -------------------------------------------------------------------------- |
-| `dapp` | `object` | Yes | Dapp identification and branding settings. |
-| `dapp.name` | `string` | Yes | Name of your dapp. |
-| `dapp.url` | `string` | No | URL of your dapp. |
-| `dapp.iconUrl` | `string` | No | URL of your dapp icon. |
-| `api` | `object` | No | Optional API configuration. |
-| `api.supportedNetworks` | `SolanaSupportedNetworks` | No | Map of network names (`mainnet`, `devnet`, `testnet`) to RPC URLs. |
-| `debug` | `boolean` | No | Reserved for future use; not currently forwarded to the underlying client. |
-| `skipAutoRegister` | `boolean` | No | Skips auto-registering the wallet during creation. The default is `false`. |
+| Field | Type | Required | Description |
+| --------------------------- | ------------------------- | -------- | -------------------------------------------------------------------------- |
+| `dapp` | `object` | Yes | Dapp identification and branding settings. |
+| `dapp.name` | `string` | Yes | Name of your dapp. |
+| `dapp.url` | `string` | No | URL of your dapp. |
+| `dapp.iconUrl` | `string` | No | URL of your dapp icon. |
+| `api` | `object` | No | Optional API configuration. |
+| `api.supportedNetworks` | `SolanaSupportedNetworks` | No | Map of network names (`mainnet`, `devnet`, `testnet`) to RPC URLs. |
+| `analytics.integrationType` | `string` | No | Identifies your integration in analytics events. |
+| `debug` | `boolean` | No | Reserved for future use; not currently forwarded to the underlying client. |
+| `skipAutoRegister` | `boolean` | No | Skips auto-registering the wallet during creation. The default is `false`. |
:::note
`createSolanaClient` does not accept `eventHandlers`.
diff --git a/metamask-connect/supported-platforms.md b/metamask-connect/supported-platforms.md
index 72c74d98660..c4a06c73e2d 100644
--- a/metamask-connect/supported-platforms.md
+++ b/metamask-connect/supported-platforms.md
@@ -79,7 +79,7 @@ Select a ✅ to jump to the relevant quickstart or guide.
| Browser (vanilla JS/TS) | [✅](/metamask-connect/evm/quickstart/javascript) | [✅](/metamask-connect/solana/quickstart/javascript) | [✅](/metamask-connect/multichain/quickstart/javascript) |
| React | [✅](/metamask-connect/evm/quickstart/javascript) | [✅](/metamask-connect/solana/guides/use-wallet-adapter) | [✅](/metamask-connect/multichain/quickstart/javascript) |
| React Native | [✅](/metamask-connect/evm/quickstart/react-native) | [✅](/metamask-connect/solana) | [✅](/metamask-connect/multichain/quickstart/javascript) |
-| wagmi | [✅](/metamask-connect/evm) | — | — |
+| Wagmi | [✅](/metamask-connect/evm) | — | — |
| Wallet Standard | — | [✅](/metamask-connect/solana/guides/use-wallet-adapter/) | — |
| Node.js | ✅ | — | ✅ |
@@ -87,9 +87,16 @@ Select a ✅ to jump to the relevant quickstart or guide.
:::note
Node.js support uses QR code connections via the MetaMask mobile app.
+In Node.js and React Native there's no `window.location`, so you must set `dapp.url` explicitly when
+creating the client.
See the [Node.js playground](https://github.com/MetaMask/connect-monorepo/tree/main/playground/node-playground) for a working example.
:::
+:::note
+On React Native and mobile, Solana supports mainnet only. Devnet and testnet are supported only in
+the MetaMask browser extension.
+:::
+
## Next steps
- [Explore integration options.](./integration-options.md)
diff --git a/metamask-connect/troubleshooting/index.md b/metamask-connect/troubleshooting/index.md
index 37c2e1afe1f..1f921094871 100644
--- a/metamask-connect/troubleshooting/index.md
+++ b/metamask-connect/troubleshooting/index.md
@@ -26,7 +26,9 @@ The SDK handles its transport and crypto needs internally using browser-native A
**React Native** is the exception.
The React Native runtime lacks certain Web and Node.js APIs (`Buffer`, `crypto.getRandomValues`,
-`stream`, `Event`, `CustomEvent`), so polyfills and Metro configuration are required.
+`stream`, `window`), so polyfills and Metro configuration are required.
+The `Event` and `CustomEvent` globals aren't needed by the `@metamask/connect-*` packages (which use
+`eventemitter3` internally); polyfill them only if you also use Wagmi.
See the [React Native Metro polyfill guide](metro-polyfill-issues.md) for step-by-step setup
instructions.
@@ -36,37 +38,42 @@ The following error codes appear in `err.code` on rejected promises from `connec
`invokeMethod`, and `provider.request` calls.
Always check `err.code` before `err.message` for reliable error categorization.
-| Code | Meaning | Recommended handling |
-| -------- | --------------------------------- | ------------------------------------------------------------------------------------------ |
-| `4001` | User rejected the request | Show a retry button. Do not log this to error-tracking services. |
-| `-32002` | Request already pending | Show "Check MetaMask to approve the pending request." Do **not** call `connect()` again. |
-| `-32602` | Invalid parameters | Verify that parameters match the expected types (for example, hex chain IDs, not decimal). |
-| `-32603` | Internal error | Unexpected server-side error. Retry with exponential backoff. |
-| `-32000` | Execution reverted / server error | Transaction would fail onchain. Check contract inputs and sender balance. |
-| `1013` | Internal transport disconnect | The SDK handles reconnection internally. Do not treat this as a user-facing disconnect. |
+| Code | Meaning | Recommended handling |
+| -------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `4001` | User rejected the request | Show a retry button. Do not log this to error-tracking services. |
+| `-32002` | Request already pending | Show "Check MetaMask to approve the pending request." Do **not** call `connect()` again. |
+| `-32602` | Invalid parameters | Verify that parameters match the expected types (for example, hex chain IDs, not decimal). |
+| `-32603` | Internal error | Unexpected server-side error. Retry with exponential backoff. |
+| `4902` | Unrecognized chain ID | The chain isn't added to the wallet. Add it with `wallet_addEthereumChain`, or pass `chainConfiguration` to `switchChain`. See [Chain not configured](#chain-not-configured-in-supportednetworks). |
+| `-32000` | Execution reverted / server error | Transaction would fail onchain. Check contract inputs and sender balance. |
For the complete list of provider errors, see
[EIP-1193](https://eips.ethereum.org/EIPS/eip-1193#provider-errors) and
[EIP-1474](https://eips.ethereum.org/EIPS/eip-1474#error-codes).
-MetaMask Connect Multichain also exports typed error classes for granular `instanceof` checks:
+MetaMask Connect Multichain also exports typed error classes for granular `instanceof` checks. The
+class most relevant to wallet calls is `RPCInvokeMethodErr`, which wraps the wallet's response and
+exposes the wallet's original error code on `err.rpcCode`:
```typescript
-import { ProtocolError, RpcError, StorageError } from '@metamask/connect-multichain'
+import { RPCInvokeMethodErr } from '@metamask/connect-multichain'
try {
- await client.connect(['eip155:1'], [])
+ await client.invokeMethod({
+ scope: 'eip155:1',
+ request: { method: 'personal_sign', params: [message, address] },
+ })
} catch (err) {
- if (err instanceof RpcError) {
- console.log('RPC error code:', err.code)
- } else if (err instanceof ProtocolError) {
- console.log('Protocol failure:', err.message)
- } else if (err instanceof StorageError) {
- console.log('Session persistence issue:', err.message)
+ if (err instanceof RPCInvokeMethodErr) {
+ // The wallet's original JSON-RPC / EIP-1193 code is on err.rpcCode (for example, 4001).
+ console.log('Wallet error code:', err.rpcCode)
}
}
```
+The core also exports `RPCHttpErr`, `RPCReadonlyResponseErr`, and `RPCReadonlyRequestErr` for
+read-only RPC failures.
+
## Common issues
### Connection hangs after `connect()`
@@ -121,6 +128,7 @@ Do **not** call `connect` again; the original promise resolves once the user act
The chain ID passed to `connect` or `wallet_switchEthereumChain` is not listed in the
`api.supportedNetworks` configuration.
+The wallet rejects the request with `err.code === 4902` (unrecognized chain ID).
Add every chain the dapp needs to `supportedNetworks` with a valid RPC URL:
@@ -307,7 +315,7 @@ When any MetaMask Connect integration is misbehaving, ensure the following are t
- Chain IDs are hex strings for EVM (`'0x1'`, not `1` or `'1'`).
- In React Native dapps:
- Polyfills are loaded: `react-native-get-random-values` is the first entry-file
- import; `window` shim is present; `Event`/`CustomEvent` shims are present **only if using wagmi**;
+ import; `window` shim is present; `Event`/`CustomEvent` shims are present **only if using Wagmi**;
`Buffer` is set as a safety net for peer dependencies.
- `preferredOpenLink` is set for deeplinks to open MetaMask Mobile (see [Deeplinks not opening MetaMask app](metro-polyfill-issues.md#deeplinks-not-opening-metamask-app)).
- Import order is correct: polyfills before SDK imports; `react-native-get-random-values` is the
diff --git a/metamask-connect/troubleshooting/metro-polyfill-issues.md b/metamask-connect/troubleshooting/metro-polyfill-issues.md
index ccc9e844beb..842c2836681 100644
--- a/metamask-connect/troubleshooting/metro-polyfill-issues.md
+++ b/metamask-connect/troubleshooting/metro-polyfill-issues.md
@@ -29,7 +29,7 @@ import TabItem from "@theme/TabItem";
React Native uses the Metro bundler, which cannot resolve Node.js built-in modules.
MetaMask Connect packages and their dependencies reference modules like `stream`, `crypto`, `buffer`, and `http`.
Some code paths expect a browser-like `window` object, which React Native does not provide.
-MetaMask Connect uses `eventemitter3` internally and does not require DOM `Event` or `CustomEvent` globals; if you use **wagmi**, you may need to polyfill those separately.
+MetaMask Connect uses `eventemitter3` internally and does not require DOM `Event` or `CustomEvent` globals; if you use **Wagmi**, you may need to polyfill those.
This guide walks through the required polyfills, Metro configuration, and related React Native setup (including deeplinks to MetaMask Mobile).
@@ -48,6 +48,9 @@ npm install react-native-get-random-values buffer readable-stream @react-native-
```
`react-native-get-random-values` provides `crypto.getRandomValues`, which MetaMask Connect requires.
+React Native versions before 0.72 have no native `crypto.getRandomValues`; later versions may include
+one, but this polyfill remains the reliable cross-version default. It must be the first import, before
+any other module.
`readable-stream` provides a `stream` shim for Metro.
`buffer` provides the `Buffer` global.
`@react-native-async-storage/async-storage` is needed for session persistence.
@@ -171,9 +174,9 @@ if (typeof global !== 'undefined') {
}
```
-#### Optional wagmi polyfills for Event and CustomEvent
+#### Optional Wagmi polyfills for Event and CustomEvent
-If you use **wagmi**, add the following to `polyfills.ts` after the `window` shim (React Native does not provide DOM `Event` or `CustomEvent`, which wagmi-related code may expect):
+If you use **Wagmi**, add the following to `polyfills.ts` after the `window` shim (React Native does not provide DOM `Event` or `CustomEvent`, which Wagmi-related code may expect):
```typescript
// Polyfill Event if missing
@@ -291,9 +294,9 @@ Use the same `mobile.preferredOpenLink` pattern with [`createMultichainClient`](
### `Event is not defined` or `CustomEvent is not defined`
-**Cause**: React Native does not provide browser `Event` or `CustomEvent` classes. This typically appears when using **wagmi** (or another dependency that expects DOM events). MetaMask Connect uses `eventemitter3` internally and does not require these globals.
+**Cause**: React Native does not provide browser `Event` or `CustomEvent` classes. This typically appears when using **Wagmi** (or another dependency that expects DOM events). MetaMask Connect uses `eventemitter3` internally and does not require these globals.
-**Fix**: If you use wagmi, append the `Event` and `CustomEvent` polyfills from [Optional wagmi polyfills for Event and CustomEvent](#optional-wagmi-polyfills-for-event-and-customevent) to your `polyfills.ts` after the base `window` shim.
+**Fix**: If you use Wagmi, append the `Event` and `CustomEvent` polyfills from [Optional Wagmi polyfills for Event and CustomEvent](#optional-wagmi-polyfills-for-event-and-customevent) to your `polyfills.ts` after the base `window` shim.
### Expo Go not working