> For the complete documentation index, see [llms.txt](/llms.txt).

# Multichain React Native quickstart

Get started with MetaMask Connect Multichain in your React Native or Expo dapp. Connect to EVM and Solana networks simultaneously through a single session.

## Steps[​](#steps "Direct link to Steps")

### 1\. Create a new project[​](#1-create-a-new-project "Direct link to 1. Create a new project")

Create a new React Native or Expo project:

- React Native
- Expo

```
npx react-native@latest init MyMultichainProject

```

```
npx create-expo-app MyMultichainProject --template

```

### 2\. Install dependencies[​](#2-install-dependencies "Direct link to 2. Install dependencies")

Install MetaMask Connect Multichain and required polyfill packages:

```
npm install @metamask/connect-multichain react-native-get-random-values buffer @react-native-async-storage/async-storage readable-stream

```

For Solana transaction building (optional):

```
npm install @solana/web3.js

```

### 3\. Create polyfills[​](#3-create-polyfills "Direct link to 3. Create polyfills")

Create `polyfills.ts` (at the project root or in `src/`) with all required global shims. This file must be imported before any SDK code:

polyfills.ts

```
import { Buffer } from 'buffer'

global.Buffer = Buffer

let windowObj: any
if (typeof global !== 'undefined' && global.window) {
  windowObj = global.window
} else if (typeof window !== 'undefined') {
  windowObj = window
} else {
  windowObj = {}
}

if (!windowObj.location) {
  windowObj.location = {
    hostname: 'mydapp.com',
    href: 'https://mydapp.com',
  }
}
if (typeof windowObj.addEventListener !== 'function') {
  windowObj.addEventListener = () => {}
}
if (typeof windowObj.removeEventListener !== 'function') {
  windowObj.removeEventListener = () => {}
}
if (typeof windowObj.dispatchEvent !== 'function') {
  windowObj.dispatchEvent = () => true
}

if (typeof global !== 'undefined') {
  global.window = windowObj
}

if (typeof global.Event === 'undefined') {
  class EventPolyfill {
    type: string
    bubbles: boolean
    cancelable: boolean
    defaultPrevented = false
    constructor(type: string, options?: EventInit) {
      this.type = type
      this.bubbles = options?.bubbles ?? false
      this.cancelable = options?.cancelable ?? false
    }
    preventDefault() {
      this.defaultPrevented = true
    }
    stopPropagation() {}
    stopImmediatePropagation() {}
  }
  global.Event = EventPolyfill as any
  windowObj.Event = EventPolyfill as any
}

if (typeof global.CustomEvent === 'undefined') {
  const EventClass =
    global.Event ||
    class {
      type: string
      constructor(type: string) {
        this.type = type
      }
    }
  class CustomEventPolyfill extends (EventClass as any) {
    detail: any
    constructor(type: string, options?: CustomEventInit) {
      super(type, options)
      this.detail = options?.detail ?? null
    }
  }
  global.CustomEvent = CustomEventPolyfill as any
  windowObj.CustomEvent = CustomEventPolyfill as any
}

```

Create the empty module stub used by the Metro config:

src/empty-module.js

```
module.exports = {}

```

tip

For detailed troubleshooting of polyfill issues, see [React Native Metro polyfill issues](/metamask-connect/troubleshooting/metro-polyfill-issues/).

### 4\. Configure Metro[​](#4-configure-metro "Direct link to 4. Configure Metro")

Metro cannot resolve Node.js built-in modules. Map them to React Native-compatible shims or the empty module stub:

- React Native
- Expo

metro.config.js

```
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config')
const path = require('path')

const emptyModule = path.resolve(__dirname, 'src/empty-module.js')

const config = {
  resolver: {
    extraNodeModules: {
      stream: require.resolve('readable-stream'),
      crypto: emptyModule,
      http: emptyModule,
      https: emptyModule,
      net: emptyModule,
      tls: emptyModule,
      zlib: emptyModule,
      os: emptyModule,
      dns: emptyModule,
      assert: emptyModule,
      url: emptyModule,
      path: emptyModule,
      fs: emptyModule,
    },
  },
}

module.exports = mergeConfig(getDefaultConfig(__dirname), config)

```

Run `npx expo customize metro.config.js` to create a default config, then update it:

metro.config.js

```
const { getDefaultConfig } = require('expo/metro-config')
const path = require('path')

const config = getDefaultConfig(__dirname)
const emptyModule = path.resolve(__dirname, 'src/empty-module.js')

config.resolver.extraNodeModules = {
  stream: require.resolve('readable-stream'),
  crypto: emptyModule,
  http: emptyModule,
  https: emptyModule,
  net: emptyModule,
  tls: emptyModule,
  zlib: emptyModule,
  os: emptyModule,
  dns: emptyModule,
  assert: emptyModule,
  url: emptyModule,
  path: emptyModule,
  fs: emptyModule,
}

module.exports = config

```

### 5\. Set up the entry file[​](#5-set-up-the-entry-file "Direct link to 5. Set up the entry file")

The import order is critical. `react-native-get-random-values` **must** be the very first import, followed by the polyfills file, before any other code:

index.js or App.tsx (Bare RN) / app/_layout.tsx (Expo Router)

```
import 'react-native-get-random-values'
import './polyfills'

```

caution

If you import anything from `@metamask/connect-multichain` before `react-native-get-random-values`, you will get `crypto.getRandomValues is not a function`.

### 6\. Use MetaMask Connect Multichain[​](#6-use-metamask-connect-multichain "Direct link to 6. Use MetaMask Connect Multichain")

Initialize the multichain client using [createMultichainClient](/metamask-connect/multichain/reference/methods/#createmultichainclient). `mobile.preferredOpenLink` is required; it tells MetaMask Connect how to open deeplinks to the MetaMask Mobile app. Connect to both EVM and Solana networks in a single session using [connect](/metamask-connect/multichain/reference/methods/#connect):

```
import React, { useEffect, useRef, useState, useCallback } from 'react'
import { View, Text, TouchableOpacity, StyleSheet, Alert, Linking, ScrollView } from 'react-native'
import { createMultichainClient, getInfuraRpcUrls } from '@metamask/connect-multichain'

const ETH_MAINNET = 'eip155:1'
const POLYGON = 'eip155:137'
const SOLANA_MAINNET = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'

let clientPromise = null

function getClient() {
  if (!clientPromise) {
    clientPromise = createMultichainClient({
      dapp: {
        name: 'My Multichain RN Dapp',
        url: 'https://mydapp.com',
      },
      api: {
        supportedNetworks: getInfuraRpcUrls({
          infuraApiKey: 'YOUR_INFURA_API_KEY',
        }),
      },
      mobile: {
        preferredOpenLink: deeplink => Linking.openURL(deeplink),
      },
    })
  }
  return clientPromise
}

export default function App() {
  const clientRef = useRef(null)
  const [session, setSession] = useState(null)
  const [connecting, setConnecting] = useState(false)

  useEffect(() => {
    let mounted = true

    async function init() {
      const client = await getClient()
      if (!mounted) return
      clientRef.current = client

      client.on('wallet_sessionChanged', newSession => {
        if (mounted) setSession(newSession)
      })
    }

    init()
    return () => {
      mounted = false
    }
  }, [])

  const handleConnect = useCallback(async () => {
    const client = clientRef.current
    if (!client) return

    setConnecting(true)
    try {
      await client.connect([ETH_MAINNET, POLYGON, SOLANA_MAINNET], [])
      const newSession = await client.provider.getSession()
      setSession(newSession)
    } catch (err) {
      if (err.code === 4001) {
        Alert.alert('Rejected', 'Connection was rejected.')
        return
      }
      Alert.alert('Error', err.message ?? 'Connection failed')
    } finally {
      setConnecting(false)
    }
  }, [])

  const handleGetBalance = useCallback(async () => {
    const client = clientRef.current
    if (!client || !session) return

    const ethAccounts = session.sessionScopes?.[ETH_MAINNET]?.accounts ?? []
    if (ethAccounts.length === 0) return

    try {
      const address = ethAccounts[0].split(':').pop()
      const balance = await client.invokeMethod({
        scope: ETH_MAINNET,
        request: {
          method: 'eth_getBalance',
          params: [address, 'latest'],
        },
      })
      const ethBalance = (parseInt(balance, 16) / 1e18).toFixed(6)
      Alert.alert('ETH Balance', `${ethBalance} ETH`)
    } catch (err) {
      Alert.alert('Error', err.message)
    }
  }, [session])

  const handleSignSolana = useCallback(async () => {
    const client = clientRef.current
    if (!client || !session) return

    const solAccounts = session.sessionScopes?.[SOLANA_MAINNET]?.accounts ?? []
    if (solAccounts.length === 0) return

    try {
      const pubkey = solAccounts[0].split(':').pop()
      const message = Buffer.from('Hello from Multichain RN!').toString('base64')
      const result = await client.invokeMethod({
        scope: SOLANA_MAINNET,
        request: {
          method: 'solana_signMessage',
          params: { message, pubkey },
        },
      })
      Alert.alert('Signed', result.signature.slice(0, 40) + '...')
    } catch (err) {
      Alert.alert('Sign failed', err.message)
    }
  }, [session])

  const handleDisconnect = useCallback(async () => {
    const client = clientRef.current
    if (!client) return
    await client.disconnect()
    setSession(null)
  }, [])

  const scopes = Object.keys(session?.sessionScopes ?? {})
  const isConnected = scopes.length > 0

  return (
    <ScrollView contentContainerStyle={styles.container}>
      {!isConnected ? (
        <TouchableOpacity style={styles.button} onPress={handleConnect} disabled={connecting}>
          <Text style={styles.buttonText}>
            {connecting ? 'Connecting...' : 'Connect (EVM + Solana)'}
          </Text>
        </TouchableOpacity>
      ) : (
        <View>
          <Text style={styles.heading}>Connected Scopes</Text>
          {scopes.map(scope => {
            const accs = session.sessionScopes[scope]?.accounts ?? []
            return (
              <View key={scope} style={styles.scopeCard}>
                <Text style={styles.scopeLabel}>{scope}</Text>
                {accs.map(acc => (
                  <Text key={acc} style={styles.label}>
                    {acc.split(':').pop()}
                  </Text>
                ))}
              </View>
            )
          })}
          <TouchableOpacity style={styles.button} onPress={handleGetBalance}>
            <Text style={styles.buttonText}>Get ETH Balance</Text>
          </TouchableOpacity>
          <TouchableOpacity style={styles.button} onPress={handleSignSolana}>
            <Text style={styles.buttonText}>Sign Solana Message</Text>
          </TouchableOpacity>
          <TouchableOpacity style={styles.button} onPress={handleDisconnect}>
            <Text style={styles.buttonText}>Disconnect All</Text>
          </TouchableOpacity>
        </View>
      )}
    </ScrollView>
  )
}

const styles = StyleSheet.create({
  container: { flexGrow: 1, justifyContent: 'center', alignItems: 'center', padding: 20 },
  heading: { fontSize: 18, fontWeight: 'bold', marginBottom: 12 },
  scopeCard: {
    backgroundColor: '#f5f5f5',
    padding: 12,
    borderRadius: 8,
    marginVertical: 6,
    width: '100%',
  },
  scopeLabel: { fontSize: 14, fontWeight: '600', marginBottom: 4 },
  button: {
    backgroundColor: '#037DD6',
    padding: 14,
    borderRadius: 8,
    marginVertical: 8,
    width: '100%',
  },
  buttonText: { color: '#fff', fontSize: 16, textAlign: 'center' },
  label: { fontSize: 12, color: '#555' },
})

```

### 7\. iOS configuration[​](#7-ios-configuration "Direct link to 7. iOS configuration")

Add the `metamask` URL scheme to your `Info.plist` so the app can open the MetaMask mobile app:

ios/MyMultichainProject/Info.plist

```
<key>LSApplicationQueriesSchemes</key>
<array>
  <string>metamask</string>
</array>

```

### 8\. Build and run[​](#8-build-and-run "Direct link to 8. Build and run")

- React Native
- Expo

```
npx react-native run-android
npx react-native run-ios

```

```
npx expo prebuild
npx expo run:android
npx expo run:ios

```

## Multichain client methods at a glance[​](#multichain-client-methods-at-a-glance "Direct link to Multichain client methods at a glance")

| Method                                                                                           | Description                                                                                   |
| ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- |
| [connect(scopes, caipAccountIds)](/metamask-connect/multichain/reference/methods/#connect)       | Connects to MetaMask with multichain [scopes](/metamask-connect/multichain/concepts/scopes/). |
| [getSession](/metamask-connect/multichain/reference/methods/#getsession)                         | Returns the current session with approved accounts.                                           |
| [invokeMethod({ scope, request })](/metamask-connect/multichain/reference/methods/#invokemethod) | Calls an RPC method on a specific chain.                                                      |
| [disconnect](/metamask-connect/multichain/reference/methods/#disconnect)                         | Disconnects all scopes and ends the session.                                                  |
| [disconnect(scopes)](/metamask-connect/multichain/reference/methods/#disconnect)                 | Disconnects specific scopes without ending the session.                                       |
| [on(event, handler)](/metamask-connect/multichain/reference/methods/#on)                         | Registers an event handler.                                                                   |

## Next steps[​](#next-steps "Direct link to Next steps")

- Understand [scopes](/metamask-connect/multichain/concepts/scopes/), [accounts](/metamask-connect/multichain/concepts/accounts/), and [sessions](/metamask-connect/multichain/concepts/sessions/).
- [Sign multichain transactions.](/metamask-connect/multichain/guides/sign-transactions/)
- [Send multichain transactions.](/metamask-connect/multichain/guides/send-transactions/)
- Follow the [Create a multichain dapp tutorial](/metamask-connect/multichain/tutorials/create-multichain-dapp/).
- [Troubleshoot bundler polyfill issues.](/metamask-connect/troubleshooting/metro-polyfill-issues/)
