Skip to main content

Connect to multichain using Node.js

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 MetaMask Mobile.

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',
}),
},
})
createMultichainClient is async

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 MetaMask Mobile:

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(apiKey)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 MetaMask Mobile
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