Skip to main content

Multichain Node.js quickstart

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.

No polyfills required

Node.js has native support for Buffer, crypto, stream, and other modules that require polyfilling in browser or React Native environments.

Prerequisites

Steps

1. Install dependencies

npm install @metamask/connect-multichain

2. Initialize the multichain client

Create a file (for example, index.mjs) and initialize the client. 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:

index.mjs
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',
}),
},
})
Asynchronous 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.

3. Connect to MetaMask

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:

await client.connect(
['eip155:1', 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'],
[],
)

const session = await client.getSession()
const ethAccounts = session?.sessionScopes?.['eip155:1']?.accounts ?? []
const solAccounts =
session?.sessionScopes?.['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp']
?.accounts ?? []
console.log('ETH accounts:', ethAccounts)
console.log('SOL accounts:', solAccounts)

The user sees a single approval prompt for all requested chains.

4. Invoke EVM methods

Use invokeMethod with an EVM scope to make JSON-RPC requests. Read methods route through the RPC node; signing methods route through the wallet:

const ethAddress = ethAccounts[0]?.split(':').pop()

// 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 message = '0x' + Buffer.from('Hello from Node.js!', 'utf8').toString('hex')
const signature = await client.invokeMethod({
scope: 'eip155:1',
request: {
method: 'personal_sign',
params: [message, ethAddress],
},
})
console.log('ETH signature:', signature)

5. Invoke Solana methods

Use invokeMethod with a Solana scope. All Solana methods route through the wallet:

const solAddress = solAccounts[0]?.split(':').pop()
const solMessage = Buffer.from('Hello from Node.js!', 'utf8').toString('base64')

const solSignature = await client.invokeMethod({
scope: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
request: {
method: 'signMessage',
params: {
account: { address: solAddress },
message: solMessage,
},
},
})
console.log('SOL signature:', solSignature)

6. Disconnect

// Disconnect all scopes
await client.disconnect()
console.log('Disconnected')

// Or disconnect specific scopes only
// await client.disconnect(['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'])

Listen for session events

Register event listeners before calling connect() to track session changes:

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')
}
})

Multichain client methods at a glance

MethodDescription
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.

Full example

index.mjs
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',
}),
},
})

// Connect — scan the QR code with the MetaMask mobile app
await client.connect([ETH_MAINNET, SOLANA_MAINNET], [])

const session = await client.getSession()
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

Next steps