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

# Connect to EVM - React Native quickstart

Get started with MetaMask Connect EVM in your React Native or Expo dapp.

## 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 MyProject

```

```
npx create-expo-app MyProject --template

```

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

Install MetaMask Connect EVM and its required polyfill packages:

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

```

### 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

// Polyfill window object — React Native doesn't have one
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
}

// Polyfill Event if missing
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
}

// Polyfill CustomEvent if missing
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
}

```

tip

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

Create the empty module stub used by the Metro config:

src/empty-module.js

```
module.exports = {}

```

### 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-evm` before `react-native-get-random-values`, you will get `crypto.getRandomValues is not a function`.

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

Initialize the EVM client using [createEVMClient](/metamask-connect/evm/reference/methods/#createevmclient). `mobile.preferredOpenLink` is required; it tells MetaMask Connect how to open deeplinks to the MetaMask Mobile app. Use the client to connect, sign, and send transactions:

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

let clientPromise = null

function getClient() {
  if (!clientPromise) {
    clientPromise = createEVMClient({
      dapp: {
        name: 'My RN Dapp',
        url: 'https://mydapp.com',
      },
      api: {
        supportedNetworks: {
          '0x1': 'https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY',
          '0xaa36a7': 'https://sepolia.infura.io/v3/YOUR_INFURA_API_KEY',
        },
      },
      ui: {
        preferExtension: false,
      },
      mobile: {
        preferredOpenLink: deeplink => Linking.openURL(deeplink),
      },
    })
  }
  return clientPromise
}

export default function App() {
  const clientRef = useRef(null)
  const [accounts, setAccounts] = useState([])
  const [chainId, setChainId] = useState(null)
  const [connecting, setConnecting] = useState(false)

  useEffect(() => {
    let mounted = true

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

      const provider = client.getProvider()
      provider.on('accountsChanged', accs => {
        if (mounted) setAccounts(accs)
      })
      provider.on('chainChanged', id => {
        if (mounted) setChainId(id)
      })
      provider.on('disconnect', () => {
        if (mounted) {
          setAccounts([])
          setChainId(null)
        }
      })
    }

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

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

    setConnecting(true)
    try {
      const result = await client.connect({ chainIds: ['0x1'] })
      setAccounts(result.accounts)
      setChainId(result.chainId)
    } catch (err) {
      if (err.code === 4001) {
        Alert.alert('Rejected', 'Connection was rejected.')
        return
      }
      if (err.code === -32002) {
        Alert.alert('Pending', 'Check MetaMask to approve the request.')
        return
      }
      Alert.alert('Error', err.message ?? 'Connection failed')
    } finally {
      setConnecting(false)
    }
  }, [])

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

  const isConnected = accounts.length > 0

  return (
    <View style={styles.container}>
      {!isConnected ? (
        <TouchableOpacity style={styles.button} onPress={handleConnect} disabled={connecting}>
          <Text style={styles.buttonText}>{connecting ? 'Connecting...' : 'Connect MetaMask'}</Text>
        </TouchableOpacity>
      ) : (
        <View>
          <Text style={styles.label}>Account: {accounts[0]}</Text>
          <Text style={styles.label}>Chain: {chainId}</Text>
          <TouchableOpacity style={styles.button} onPress={handleDisconnect}>
            <Text style={styles.buttonText}>Disconnect</Text>
          </TouchableOpacity>
        </View>
      )}
    </View>
  )
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 },
  button: { backgroundColor: '#037DD6', padding: 14, borderRadius: 8, marginVertical: 8 },
  buttonText: { color: '#fff', fontSize: 16, textAlign: 'center' },
  label: { fontSize: 14, marginVertical: 4 },
})

```

### 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/MyProject/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

```

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

- [Manage user accounts](/metamask-connect/evm/guides/manage-user-accounts/)
- [Send transactions](/metamask-connect/evm/guides/send-transactions/)
- [Sign data](/metamask-connect/evm/guides/sign-data/)
- [Production readiness checklist](/metamask-connect/evm/guides/best-practices/production-readiness/)
- [Troubleshoot bundler polyfill issues](/metamask-connect/troubleshooting/metro-polyfill-issues/)
