Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions skills/metamask-connect/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
---
name: metamask-connect
description: Build dApps that integrate MetaMask via the MetaMask Connect SDK — EVM (@metamask/connect-evm), Solana (@metamask/connect-solana), and multichain (@metamask/connect-multichain), plus the wagmi metaMask() connector. Covers client setup across browser/React/React Native, connecting, signing messages, sending transactions, multichain invokeMethod across CAIP-2 scopes, migrating from @metamask/sdk, and troubleshooting connection/polyfill issues.
---

# MetaMask Connect SDK

## When to use

- You want to set up a dApp's MetaMask integration — EVM, Solana, or both (multichain) — in vanilla browser JS/TS, React, or React Native
- You want a headless integration — a Node.js CLI, server, or bot that connects to MetaMask Mobile via a terminal QR code
- You want to connect/disconnect, manage the provider and session state, or switch chains
- You want to sign messages (`personal_sign`, `eth_signTypedData_v4`, Solana `signMessage`) — e.g. Sign-In With Ethereum or nonce auth
- You want to send transactions (`eth_sendTransaction`, Solana `sendTransaction` / `signAndSendTransaction`)
- You want to operate across chains through the multichain client's `invokeMethod`
- You want to use or migrate to the wagmi `metaMask()` connector
- You want to migrate an existing `@metamask/sdk` integration to the Connect SDK
- You need to diagnose connection failures, React Native polyfill errors, or QR/deeplink issues

## Installation

Pick the client for your integration:

| You need | Package | Factory |
| --------------------------------- | ---------------------------------------------------------------------- | ------------------------ |
| EVM only | `@metamask/connect-evm` | `createEVMClient` |
| Solana only | `@metamask/connect-solana` | `createSolanaClient` |
| EVM **and** Solana in one session | `@metamask/connect-multichain` | `createMultichainClient` |
| You already use wagmi | wagmi `metaMask()` connector (needs `@metamask/connect-evm` as a peer) | — |

## Always-on conventions

Before writing or reviewing **any** MetaMask Connect code, read [references/conventions.md](references/conventions.md) — the always-on core guardrails (import paths, required config, `supportedNetworks`, singleton behavior, error handling, connection state) plus a topic index into focused references. Then load the focused reference(s) for your task: [evm.md](references/evm.md) (chain IDs / `switchChain`), [events.md](references/events.md), [multichain.md](references/multichain.md), [solana.md](references/solana.md), [react-native.md](references/react-native.md) (polyfills / Metro), [csp.md](references/csp.md), [testing.md](references/testing.md). Each topic has a single canonical home, so apply the relevant reference alongside every workflow below.

## Set up (choose your stack)

| Building | Workflow |
| --------------------------------- | -------------------------------------------------------------------------------- |
| EVM dApp — vanilla browser JS/TS | [workflows/setup-evm-browser.md](workflows/setup-evm-browser.md) |
| EVM dApp — React | [workflows/setup-evm-react.md](workflows/setup-evm-react.md) |
| EVM dApp — React Native | [workflows/setup-evm-react-native.md](workflows/setup-evm-react-native.md) |
| Solana dApp — vanilla browser | [workflows/setup-solana-browser.md](workflows/setup-solana-browser.md) |
| Solana dApp — React | [workflows/setup-solana-react.md](workflows/setup-solana-react.md) |
| Solana dApp — React Native | [workflows/setup-solana-react-native.md](workflows/setup-solana-react-native.md) |
| EVM + Solana (multichain) | [workflows/setup-multichain.md](workflows/setup-multichain.md) |
| Node.js CLI / server (headless) | [workflows/setup-node.md](workflows/setup-node.md) |
| wagmi app | [workflows/setup-wagmi.md](workflows/setup-wagmi.md) |
| wagmi + the connect-evm connector | [workflows/setup-wagmi-connector.md](workflows/setup-wagmi-connector.md) |

## Sign & send (single-chain clients)

Use these with a directly-created EVM or Solana client. If you set up the **multichain** client, sign/send via `invokeMethod` instead — see the multichain workflows below.

| Task | Workflow |
| ---------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| Sign — EVM (`personal_sign`, `eth_signTypedData_v4`, `connectAndSign`) | [workflows/sign-evm-message.md](workflows/sign-evm-message.md) |
| Sign — Solana (wallet-standard `signMessage`) | [workflows/sign-solana-message.md](workflows/sign-solana-message.md) |
| Send — EVM (`eth_sendTransaction`, gas, receipts, `connectWith`) | [workflows/send-evm-transaction.md](workflows/send-evm-transaction.md) |
| Send — Solana (`sendTransaction` / `signAndSendTransaction`) | [workflows/send-solana-transaction.md](workflows/send-solana-transaction.md) |

## Multichain operations (`invokeMethod` across CAIP-2 scopes)

Use these after `createMultichainClient` to sign or send across CAIP-2 scopes.

| Ecosystem | Workflow |
| --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| EVM scopes (`eth_sendTransaction`, `personal_sign`, `eth_signTypedData_v4`) | [workflows/multichain-evm-operations.md](workflows/multichain-evm-operations.md) |
| Solana scopes (`signTransaction`, `signAndSendTransaction`, `signMessage`) | [workflows/multichain-solana-operations.md](workflows/multichain-solana-operations.md) |

## Migrate

| Migrating from | Workflow |
| ----------------------------------------------------- | ---------------------------------------------------------------------------- |
| `@metamask/sdk` → `@metamask/connect-*` | [workflows/migrate-from-sdk.md](workflows/migrate-from-sdk.md) |
| wagmi app → the new `@metamask/connect-evm` connector | [workflows/migrate-wagmi-connector.md](workflows/migrate-wagmi-connector.md) |

## Troubleshooting

When a connection hangs/fails, a React Native app crashes on a missing polyfill, QR codes or deeplinks don't work, the Solana wallet adapter doesn't detect MetaMask, or a session is lost after reload — see [references/troubleshooting.md](references/troubleshooting.md) for a symptom → cause → fix index and a diagnostic checklist.

## Important notes

These are the highest-value guardrails; [references/conventions.md](references/conventions.md) has the full, source-verified set.

- EVM chain IDs are **hex strings** (`'0x1'`, not `1` or `'1'`); CAIP-2 scopes use **decimal** (`eip155:1`).
- Every chain the dApp touches must be in `api.supportedNetworks` with a reachable RPC URL — the check runs in the provider's `request()` path, not in `connect()`.
- The multichain core is a **singleton** — create clients once at startup, never inside a React render.
- Handle EIP-1193 code `4001` (user rejected) and `-32002` (extension request pending) in `catch` blocks; multichain `invokeMethod` errors arrive wrapped in `RPCInvokeMethodErr` (original code on `rpcCode`).
- React Native needs polyfills (a `window` shim always; `Event`/`CustomEvent` only when also using wagmi; `react-native-get-random-values` as the first import) plus metro `extraNodeModules` shims (`stream` → `readable-stream`, the rest → empty stubs).

## Resources

- NPM: `@metamask/connect-evm`, `@metamask/connect-solana`, `@metamask/connect-multichain`
- Source plugin: https://github.com/MetaMask/metamask-connect-cursor-plugin
- Provenance: generated from that plugin's `skills/` and always-on `rules/`, source-verified against the published `@metamask/connect-*` packages.
92 changes: 92 additions & 0 deletions skills/metamask-connect/references/conventions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# MetaMask Connect — Conventions & Guardrails

Always-on guardrails for the MetaMask Connect SDK, distilled from the [MetaMask Connect Cursor plugin](https://github.com/MetaMask/metamask-connect-cursor-plugin) rules. Apply the core rules below whenever you generate or review MetaMask Connect (`@metamask/connect-evm` / `-multichain` / `-solana`) or wagmi `metaMask()` connector code. Deeper, domain-specific guidance lives in the focused reference files indexed below — read the one(s) relevant to your task.

## Topic index

Each topic has a single canonical home. Load the file for the area you're working in:

| Topic | Reference |
| ---------------------------------------------------------------------------- | ---------------------------------------- |
| EVM chain ID format (hex vs CAIP-2), `switchChain`, validation | [evm.md](evm.md) |
| Event handling — EIP-1193 events, `eventHandlers`, EIP-6963, status | [events.md](events.md) |
| Multichain session lifecycle — singleton, `wallet_sessionChanged`, timeouts | [multichain.md](multichain.md) |
| Solana constraints — wallet adapter, CAIP-2 genesis hashes, RPC routing | [solana.md](solana.md) |
| React Native — polyfills, import order, Metro `extraNodeModules`, `openLink` | [react-native.md](react-native.md) |
| Content Security Policy (browser) origins | [csp.md](csp.md) |
| Testing patterns — mocking, singleton cleanup, test networks | [testing.md](testing.md) |
| Symptom → cause → fix index for connection/polyfill/QR issues | [troubleshooting.md](troubleshooting.md) |

## Core (always-on)

These cross-cutting rules apply to every MetaMask Connect integration regardless of stack.

### Import Paths

- Import EVM client from `@metamask/connect-evm`
- Import multichain client from `@metamask/connect-multichain`
- Import Solana client from `@metamask/connect-solana`
- Never import from internal sub-packages like `@metamask/connect/dist/...` or `@metamask/connect-evm/src/...`
- For wagmi, import the `metaMask()` connector from `wagmi/connectors` (wagmi >= 3.6 / `@wagmi/connectors` >= 8) — it uses `@metamask/connect-evm` under the hood as an optional peer dependency. `@metamask/connect-evm` does **not** ship its own wagmi entrypoint (there is no `@metamask/connect-evm/wagmi`). See [setup-wagmi-connector.md](../workflows/setup-wagmi-connector.md)
- `@metamask/connect-multichain` is a **regular (transitive) dependency** of both `@metamask/connect-evm` and `@metamask/connect-solana` — it's installed automatically and you don't need to add it to your `package.json`. (If you import `createMultichainClient` directly, you can still rely on that transitively-installed copy; both clients `console.warn` at runtime if they detect a mismatched or duplicate `@metamask/connect-multichain` resolution.)

### Required Configuration

- `dapp.name` is always required — it appears in the MetaMask connection prompt
- `dapp.url` is required in Node.js and React Native (no `window.location`); in the browser it defaults to `window.location.href`, but passing it explicitly is safer
- `dapp.iconUrl` is optional — displayed in MetaMask connection UI
- `dapp.base64Icon` is an alternative to `iconUrl` — pass a base64-encoded icon string directly (useful when a hosted URL is unavailable, e.g., in React Native)

### Supported Networks

- Every chain the dApp interacts with must be in `api.supportedNetworks` with a reachable RPC URL
- Use `getInfuraRpcUrls({ infuraApiKey: 'API_KEY', chainIds?: Hex[] })` from `@metamask/connect-evm` to populate common EVM chains — it returns a hex-keyed map for `createEVMClient`
- Use `getInfuraRpcUrls({ infuraApiKey: 'API_KEY', caipChainIds?: string[] })` from `@metamask/connect-multichain` to populate CAIP-2 chains for `createMultichainClient`
- Use `getInfuraRpcUrls({ infuraApiKey: 'API_KEY', networks: SolanaNetwork[] })` from `@metamask/connect-solana` to populate a network-name-keyed map for `createSolanaClient` — `networks` is required
- Chain `0x1` (Ethereum mainnet) is auto-included in the EVM `connect()` permission request if not specified — but it is **not** auto-added to `supportedNetworks`, which must list every chain explicitly
- Making an RPC request whose active chain is missing from `supportedNetworks` throws "not configured in supportedNetworks" (the check runs in the provider's `request()` path, not in `connect()`). See [evm.md → Validation Error](evm.md#validation-error)

### Singleton Behavior

- `createMultichainClient` is the singleton shared core instance
- `createEVMClient` and `createSolanaClient` create chain-specific wrappers on top of that shared multichain core
- Repeated client creation still reuses the existing multichain session and merged core options, but EVM/Solana wrappers can attach fresh listeners
- The multichain core keeps the `dapp` object from the first call and does not overwrite it later
- Never call `create*Client` inside a React component render — call it once at app startup
- Do not wrap client creation in `useEffect` or other hooks that may re-run
- Full merge semantics: [multichain.md → Singleton Merging](multichain.md#singleton-merging)

### Error Handling

- Code `4001`: User rejected the request — show retry UI, do not log as application error. On the EVM provider it appears as `err.code`; on the multichain client it appears as `err.rpcCode` (see below)
- Code `-32002` ("request already pending") comes from the **extension transport only** — multichain MWP concurrent `connect()` instead throws a plain `Error` ("Existing connection is pending...") with no numeric code
- Wrap all `connect()`, `invokeMethod()`, and signing calls in try/catch
- Multichain `invokeMethod()` errors are wrapped in `RPCInvokeMethodErr` (its own `code` is `53`); the wallet's original code/message/data are preserved on `rpcCode` / `rpcMessage` / `rpcData`:

```typescript
import { RPCInvokeMethodErr } from '@metamask/connect-multichain';

try {
await client.invokeMethod({ scope, request });
} catch (err) {
if (err instanceof RPCInvokeMethodErr && err.rpcCode === 4001) {
// user rejection
}
}
```

- Other exported error classes: `RPCHttpErr` (code 50), `RPCReadonlyResponseErr` (51), `RPCReadonlyRequestErr` (52) — for RPC-node-routed read calls. (There are no `ProtocolError`/`StorageError`/`RpcError` exports.)

### Connection State

- Check connection state before making signing requests
- Listen for `wallet_sessionChanged` to track session state reactively
- Do not call `connect()` on page reload if a session already exists — listen for session restoration via events
- **Multichain client:** `disconnect()` with no arguments revokes all scopes and terminates the session; `disconnect(scopes)` revokes only those scopes
- **EVM client:** `disconnect()` revokes only the `eip155:*` scopes — Solana scopes on the same session survive; full teardown requires the multichain client
- **Solana client:** `disconnect()` revokes only the Solana scopes — EVM scopes on the same session survive; full teardown requires the multichain client

### Unsupported Methods

- The EVM client **rejects** these methods with `Method: <name> is not supported by Metamask Connect/EVM` (they are not silently ignored): `metamask_getProviderState`, `metamask_sendDomainMetadata`, `metamask_logWeb3ShimUsage`, `wallet_registerOnboarding`, `net_version`, `wallet_getPermissions`
- Since `@metamask/connect-evm` 2.0.0, `wallet_requestPermissions` resolves to a spec-shaped requested-permissions array — but `connect()` remains the canonical way to establish permissions
25 changes: 25 additions & 0 deletions skills/metamask-connect/references/csp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# MetaMask Connect — Content Security Policy (browser)

The canonical reference for CSP origins the SDK needs in the browser. Not relevant in Node.js or React Native. For always-on core guardrails see [conventions.md](conventions.md).

A host page with a strict CSP must allow MetaMask Connect's origins, or browser integrations fail in ways that look like other bugs — a blocked relay socket presents exactly like "connection hangs".

**Required:**

- `connect-src wss://mm-sdk-relay.api.cx.metamask.io` — the relay WebSocket used for remote (mobile / no-extension) connections. It cannot be proxied or deferred from within the library, so remote connections fail without it.
- `img-src data:` — the install/QR modal in `@metamask/multichain-ui` embeds the MetaMask fox as a `data:` URI inside the generated QR code (it sets `saveAsBlob: false`), so the QR will not render without it.

**Also consider:**

- `connect-src https://mm-sdk-analytics.api.cx.metamask.io` — the `@metamask/analytics` telemetry endpoint, used when analytics are enabled (the default). Unnecessary if you set `analytics: { enabled: false }`.
- `style-src 'unsafe-inline'` — `@metamask/multichain-ui` is built with Stencil, which injects component styles at runtime into Shadow DOM. Strict CSPs without `'unsafe-inline'` (or an equivalent nonce/hash strategy) may break modal styling.
- RPC endpoints you pass to `supportedNetworks` (e.g. `https://*.infura.io` or your own node provider) — add the matching `connect-src` entries for whatever you configure.
- `https://metamask.app.link` and `metamask://` — mobile deeplinks / universal links. These are top-level navigations and not normally subject to `connect-src`, but strict policies using `navigate-to` / `form-action` may need to allow them.

Minimal example (default analytics endpoint + Infura + install modal):

```
connect-src 'self' wss://mm-sdk-relay.api.cx.metamask.io https://mm-sdk-analytics.api.cx.metamask.io https://*.infura.io;
img-src 'self' data:;
style-src 'self' 'unsafe-inline';
```
Loading
Loading