| title | Node.js Quickstart - MetaMask Connect Multichain | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| description | Set up MetaMask Connect Multichain in a Node.js application to connect to EVM and Solana simultaneously using createMultichainClient, CAIP-25 scopes, and invokeMethod. | ||||||||||||
| sidebar_label | Node.js | ||||||||||||
| keywords |
|
Get started with MetaMask Connect Multichain in a Node.js application. Connect to EVM and Solana networks simultaneously through a single session. The SDK displays a QR code in the terminal that you scan with the MetaMask mobile app.
:::info No polyfills required
Node.js has native support for Buffer, crypto, stream, and other modules that require
polyfilling in browser or React Native environments.
:::
- Node.js version 20 or later installed.
- A package manager installed, such as npm, Yarn, or pnpm.
- The MetaMask mobile app installed on your phone.
- An Infura API key from the Infura dashboard.
Install the multichain client in an existing Node.js project:
npm install @metamask/connect-multichainCreate a file (index.mjs) and initialize the client using createMultichainClient.
In Node.js, there is no window.location, so you must set dapp.url explicitly.
Use getInfuraRpcUrls to generate RPC URLs for all Infura-supported chains:
import { createMultichainClient, getInfuraRpcUrls } from '@metamask/connect-multichain'
const client = await createMultichainClient({
dapp: {
name: 'My Node.js Multichain App',
url: 'https://myapp.com',
},
api: {
supportedNetworks: getInfuraRpcUrls({
infuraApiKey: 'YOUR_INFURA_API_KEY',
}),
},
}):::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.
:::
Register a wallet_sessionChanged listener using the on method to capture session data, then connect with both EVM and Solana scopes in a single call.
A QR code appears in the terminal. Scan it with the MetaMask mobile app:
let session
client.on('wallet_sessionChanged', s => {
session = s
})
await client.connect(['eip155:1', 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'], [])
const ethAddress = session?.sessionScopes?.['eip155:1']?.accounts?.[0]?.split(':').pop()
const solAddress = session?.sessionScopes?.[
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'
]?.accounts?.[0]
?.split(':')
.pop()
console.log('ETH:', ethAddress)
console.log('SOL:', solAddress)The user sees a single approval prompt for all requested chains.
Use invokeMethod with an EVM scope to make JSON-RPC requests.
Read methods route through the RPC node; signing methods route through the wallet:
// Read: get balance via RPC node
const balance = await client.invokeMethod({
scope: 'eip155:1',
request: {
method: 'eth_getBalance',
params: [ethAddress, 'latest'],
},
})
console.log('ETH balance:', balance)
// Sign: personal_sign via wallet
const ethMsg = '0x' + Buffer.from('Hello Ethereum!', 'utf8').toString('hex')
const ethSig = await client.invokeMethod({
scope: 'eip155:1',
request: {
method: 'personal_sign',
params: [ethMsg, ethAddress],
},
})
console.log('ETH signature:', ethSig)Use invokeMethod with a Solana scope. All Solana methods route through the wallet:
const solMsg = Buffer.from('Hello Solana!', 'utf8').toString('base64')
const solSig = await client.invokeMethod({
scope: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
request: {
method: 'signMessage',
params: {
account: { address: solAddress },
message: solMsg,
},
},
})
console.log('SOL signature:', solSig)Use disconnect to disconnect all scopes and end the session.
// Disconnect all scopes
await client.disconnect()
console.log('Disconnected')
// Or disconnect specific scopes only
// await client.disconnect(['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'])Step 3 captures the session with a minimal wallet_sessionChanged listener. For production use, expand the handler to track all scope and account changes throughout the session lifecycle:
client.on('wallet_sessionChanged', session => {
if (session?.sessionScopes) {
const scopes = Object.keys(session.sessionScopes)
console.log('Active scopes:', scopes)
for (const [scope, data] of Object.entries(session.sessionScopes)) {
console.log(` ${scope}:`, data.accounts)
}
} else {
console.log('Session ended')
}
})| Method | Description |
|---|---|
connect(scopes, caipAccountIds) |
Connects to MetaMask with multichain scopes. |
getSession |
Returns the current session with approved accounts. |
invokeMethod({ scope, request }) |
Calls an RPC method on a specific chain using a scope. |
disconnect |
Disconnects all scopes and ends the session. |
disconnect(scopes) |
Disconnects specific scopes without ending the session. |
on(event, handler) |
Registers an event handler. |
getInfuraRpcUrls({ infuraApiKey }) |
Generates Infura RPC URLs keyed by CAIP-2 chain ID. |
import { createMultichainClient, getInfuraRpcUrls } from '@metamask/connect-multichain'
const ETH_MAINNET = 'eip155:1'
const SOLANA_MAINNET = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'
const client = await createMultichainClient({
dapp: {
name: 'My Node.js Multichain App',
url: 'https://myapp.com',
},
api: {
supportedNetworks: getInfuraRpcUrls({
infuraApiKey: 'YOUR_INFURA_API_KEY',
}),
},
})
// Capture session data via event before connecting
let session
client.on('wallet_sessionChanged', s => {
session = s
})
// Connect — scan the QR code with the MetaMask mobile app
await client.connect([ETH_MAINNET, SOLANA_MAINNET], [])
const ethAddress = session?.sessionScopes?.[ETH_MAINNET]?.accounts?.[0]?.split(':').pop()
const solAddress = session?.sessionScopes?.[SOLANA_MAINNET]?.accounts?.[0]?.split(':').pop()
console.log('ETH:', ethAddress)
console.log('SOL:', solAddress)
// Get ETH balance
const balance = await client.invokeMethod({
scope: ETH_MAINNET,
request: {
method: 'eth_getBalance',
params: [ethAddress, 'latest'],
},
})
console.log('ETH balance:', balance)
// Sign an Ethereum message
const ethMsg = '0x' + Buffer.from('Hello Ethereum!', 'utf8').toString('hex')
const ethSig = await client.invokeMethod({
scope: ETH_MAINNET,
request: {
method: 'personal_sign',
params: [ethMsg, ethAddress],
},
})
console.log('ETH signature:', ethSig)
// Sign a Solana message
const solMsg = Buffer.from('Hello Solana!', 'utf8').toString('base64')
const solSig = await client.invokeMethod({
scope: SOLANA_MAINNET,
request: {
method: 'signMessage',
params: {
account: { address: solAddress },
message: solMsg,
},
},
})
console.log('SOL signature:', solSig)
// Disconnect
await client.disconnect()
console.log('Disconnected')Run it with:
node index.mjs- Understand scopes, accounts, and sessions for CAIP-2 chain identifiers, CAIP-10 account IDs, and CAIP-25 sessions.
- Sign multichain transactions using
invokeMethod. - Send multichain transactions from a single session.
- See Create a multichain dapp for a full step-by-step tutorial with React.