> ## Documentation Index
> Fetch the complete documentation index at: https://docs.monad.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# How to use Mera with React Native

> Create and reuse passkey-derived Monad accounts in a React Native app.

[Mera](https://mera.category.xyz/) v0.2.0 adds React Native support through a native WebAuthn client. A user can create a passkey on the web, sign in with the synced passkey on iOS or Android, and derive the same Monad account in each app.

The web app and mobile app must use the same relying party ID (`rpId`). The passkey provider must also sync the passkey to the mobile device and support the WebAuthn PRF extension.

<Note>
  The [React Native demo](https://github.com/category-labs/mera/tree/main/demos/mobile) shows the complete flow, including passkey sign-in, biometric-gated device storage, account recovery, and transaction signing.
</Note>

## Requirements

* Node.js 24 or later
* An initialized Expo project with `expo` installed
* `@category-labs/mera` v0.2.0 or later
* An HTTPS host that you control for the relying party ID and platform association files
* iOS 18 or later, or Android 9 or later with a passkey provider that supports PRF
* Xcode for iOS, or Android Studio and a JDK with `keytool` on your `PATH` for Android

The Expo examples below use a development build because `react-native-passkey` calls native platform APIs.

## Install

Run the following commands from the root of an existing Expo project. To create a new project instead:

```bash theme={null}
npx create-expo-app@latest my-monad-app --template blank-typescript --yes
cd my-monad-app
```

The project creation command installs `expo` and creates the app entry point used in this guide. You do not need to clone the Mera demo.

Install Mera, the [`react-native-passkey`](https://github.com/f-23/react-native-passkey) client, and the other dependencies:

```bash theme={null}
npm install @category-labs/mera react-native-passkey viem @scure/bip32 @scure/bip39
npx expo install expo-crypto
```

## Add the Hermes crypto polyfill

Mera needs `crypto.getRandomValues`, which Hermes does not provide. Create a polyfill using `expo-crypto`:

```ts title="src/polyfills.ts" theme={null}
import { getRandomValues } from "expo-crypto";

if (typeof globalThis.crypto?.getRandomValues !== "function") {
  Object.defineProperty(globalThis, "crypto", {
    configurable: true,
    value: { ...globalThis.crypto, getRandomValues },
  });
}
```

Import the polyfill from the app entry point before importing code that uses Mera:

```ts title="index.ts" theme={null}
import "./src/polyfills";

import { registerRootComponent } from "expo";
import App from "./App";

registerRootComponent(App);
```

## Link the app to the passkey domain

Choose a host such as `accounts.example.com` for the relying party ID. Use only the host name, without `https://` or a path.

To reuse a passkey created by a web app, the web app must create it with this same `rpId`. Each mobile platform also requires a public association file that authorizes the native app to use credentials for the host.

### iOS

Serve the following JSON from `https://accounts.example.com/.well-known/apple-app-site-association`:

```json theme={null}
{
  "webcredentials": {
    "apps": ["TEAM_ID.com.example.app"]
  }
}
```

Replace `TEAM_ID` with your Apple team ID and `com.example.app` with the app's bundle identifier. Add the associated domain to the Expo configuration:

```ts title="app.config.ts" theme={null}
import type { ExpoConfig } from "expo/config";

const rpId = "accounts.example.com";

const config: ExpoConfig = {
  name: "My Monad App",
  slug: "my-monad-app",
  ios: {
    bundleIdentifier: "com.example.app",
    associatedDomains: [`webcredentials:${rpId}`],
  },
  android: {
    package: "com.example.app",
  },
  extra: { rpId },
};

export default config;
```

### Android

Serve the following JSON from `https://accounts.example.com/.well-known/assetlinks.json`:

```json theme={null}
[
  {
    "relation": ["delegate_permission/common.get_login_creds"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.example.app",
      "sha256_cert_fingerprints": ["SHA256_FINGERPRINT"]
    }
  }
]
```

Replace the package name and fingerprint with those of the Android app. Include the fingerprint for every certificate used to sign the app. The next section shows how to print the fingerprint for the default Expo debug certificate.

## Build the native app

Generate the `ios/` and `android/` projects after configuring the app. Expo Prebuild creates these directories and links `react-native-passkey` with the native projects:

```bash theme={null}
npx expo prebuild
```

For an Android development build, print the fingerprint of the generated debug certificate and replace `SHA256_FINGERPRINT` in `assetlinks.json` with the result:

```bash theme={null}
keytool -list -v \
  -keystore android/app/debug.keystore \
  -alias androiddebugkey \
  -storepass android \
  -keypass android
```

Update and deploy `assetlinks.json` with this fingerprint before testing passkeys. Both platform association files must be publicly available over HTTPS, return JSON, and respond without a redirect.

Then compile and run the development build. For iOS:

```bash theme={null}
npx expo run:ios
```

For Android:

```bash theme={null}
npx expo run:android
```

Expo Go cannot run this app because it does not include the native passkey module.

## Create or reuse a passkey

React Native does not expose the browser WebAuthn APIs. Pass Mera's `reactNativeWebAuthnClient` to every function that runs a passkey ceremony:

```ts title="src/passkey.ts" theme={null}
import {
  createPasskeyWithPrfOutput,
  getPasskeyPrfOutput,
} from "@category-labs/mera";
import { reactNativeWebAuthnClient } from "@category-labs/mera/react-native-webauthn-client";

const rpId = "accounts.example.com";

export function createAccount() {
  return createPasskeyWithPrfOutput({
    rp: { id: rpId, name: "My Monad App" },
    user: { name: "player@example.com", displayName: "Player One" },
    webAuthnClient: reactNativeWebAuthnClient,
  });
}

export function signIn() {
  return getPasskeyPrfOutput({
    rpId,
    webAuthnClient: reactNativeWebAuthnClient,
  });
}
```

Calling `signIn` without a credential ID lets the platform offer any passkey available for the relying party. If the passkey created on the web has synced to the device, selecting it returns the same PRF output and therefore derives the same account.

## Derive a Monad account

Derive the standard EVM BIP-44 key from the returned PRF output:

```ts title="src/wallet.ts" theme={null}
import { HDKey } from "@scure/bip32";
import { entropyToMnemonic, mnemonicToSeedSync } from "@scure/bip39";
import { wordlist } from "@scure/bip39/wordlists/english.js";

export function deriveEvmKey(
  prfOutput: Uint8Array,
  index = 0,
): Uint8Array {
  const mnemonic = entropyToMnemonic(prfOutput, wordlist);
  const seed = mnemonicToSeedSync(mnemonic);
  try {
    const node = HDKey.fromMasterSeed(seed).derive(`m/44'/60'/0'/0/${index}`);

    if (node.privateKey === null) {
      throw new Error("Derivation produced no private key");
    }

    return node.privateKey;
  } finally {
    seed.fill(0);
  }
}
```

The derivation path must match the web app. Increment `index` only when you intend to use another account from the same passkey.

## Send a transaction

Create a signing session, adapt it to a viem account, and send a transaction on Monad:

```ts title="src/send-transaction.ts" theme={null}
import { createSecp256k1SigningSession } from "@category-labs/mera";
import { toViemAccount } from "@category-labs/mera/viem";
import { createWalletClient, http, parseEther } from "viem";
import { monadTestnet } from "viem/chains";
import { signIn } from "./passkey";
import { deriveEvmKey } from "./wallet";

export async function sendTransaction() {
  const { prfOutput } = await signIn();
  const privateKey = deriveEvmKey(prfOutput);
  const session = createSecp256k1SigningSession({ privateKey });

  try {
    const client = createWalletClient({
      account: toViemAccount(session),
      chain: monadTestnet,
      transport: http(),
    });

    const hash = await client.sendTransaction({
      to: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
      value: parseEther("0.01"),
      gas: 21_000n,
    });

    return hash;
  } finally {
    privateKey.fill(0);
    prfOutput.fill(0);
    session.end();
  }
}
```

Use `monad` instead of `monadTestnet` for mainnet. See [Gas pricing](/developer-essentials/gas-pricing) before using other transaction types.

## Store and unlock the account

The credential ID is metadata and does not contain key material. The PRF output, however, can derive the account's private keys and must be treated as secret key material.

For a smoother mobile experience, an app can store the PRF output in platform secure storage protected by biometrics or the device credential. This lets the app unlock an existing account without showing the passkey picker for every transaction.

* Do not store the PRF output in `AsyncStorage` or application logs.
* End the signing session when the user locks the wallet, signs out, or the app's session expires.
* Clear temporary PRF, seed, and private-key buffers after deriving the session.
* Request the passkey again before displaying a recovery phrase or performing another sensitive export.

The [mobile demo storage implementation](https://github.com/category-labs/mera/blob/main/demos/mobile/src/storage.ts) shows this pattern with Expo SecureStore.

## Troubleshooting

* **`PRF_UNAVAILABLE`**: The selected passkey provider or operating system did not return the PRF extension. Check [Mera's authenticator support](https://mera.category.xyz/authenticator-support/).
* **`PASSKEY_OPERATION_FAILED` on the first request**: Confirm that the association file is reachable without a redirect and contains the installed app's exact bundle or package identifier and signing certificate.
* **The mobile address differs from the web address**: Confirm that both apps use the same `rpId`, the same passkey, and the same BIP-44 account index.

## Further resources

* [Create passkey accounts with Mera on the web](/guides/mera)
* [Official React Native integration recipe](https://mera.category.xyz/recipes/use-mera-with-react-native/)
* [React Native demo](https://github.com/category-labs/mera/tree/main/demos/mobile)
* [Mera WebAuthn client reference](https://mera.category.xyz/reference/web-authn-client/)
