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

# React Native Quickstart (JS SDK)

Use the JavaScript SDK inside a React Native app (Expo or bare). State is wired via the `@dynamic-labs-sdk/react-hooks` package — the same headless client and hooks as on web, plus native polyfills and deep links.

<Warning>
  The JavaScript SDK is headless — there is no built-in modal, wallet picker, or
  login form. You build all UI yourself. See the [JavaScript SDK
  Overview](/docs/javascript/overview) for details.
</Warning>

<Note>
  Before you start: a React Native app (Expo development build or bare) and a Dynamic environment ID from the [Dynamic dashboard](https://app.dynamic.xyz). Expo Go is not supported.
</Note>

<Tabs>
  <Tab title="Agent-friendly">
    <Tip>
      Add the Dynamic docs MCP to your AI editor first — your agent can then query the docs directly.
    </Tip>

    <Tabs>
      <Tab title="Cursor">
        [Click here to add the MCP to Cursor.](https://cursor.com/en/install-mcp?name=dynamic\&config=eyJ1cmwiOiJodHRwczovL3d3dy5keW5hbWljLnh5ei9kb2NzL21jcCJ9)
      </Tab>

      <Tab title="Claude Code">
        ```bash theme={"system"}
        claude mcp add --transport http dynamic https://www.dynamic.xyz/docs/mcp
        ```
      </Tab>

      <Tab title="Codex">
        ```bash theme={"system"}
        codex mcp add dynamic --url https://www.dynamic.xyz/docs/mcp
        ```
      </Tab>

      <Tab title="Other">
        If your editor or agent supports remote HTTP MCP, add a server that points at `https://www.dynamic.xyz/docs/mcp`. The exact steps depend on the product; use its MCP documentation for URL-based or HTTP transport setup.
      </Tab>
    </Tabs>

    Copy this prompt into your LLM (e.g. Claude, ChatGPT, Cursor) to implement Dynamic in your React Native app via the JS SDK.

    <div data-llm-quickstart>
      ````markdown theme={"system"}
      # React Native Quickstart (JS SDK)

      ## Default for React Native apps

      **This is the recommended quickstart for React Native.** Use this document—not the Legacy React Native SDK quickstart (`/react-native/reference/quickstart`, agent prompt id: `react-native`)—when integrating Dynamic into a new or existing React Native app unless the user already uses `@dynamic-labs/client` + `ReactNativeExtension` or explicitly requests the legacy SDK.

      ## Purpose and Usage

      This document provides structured quickstart information for using the Dynamic JavaScript SDK inside a React Native app (Expo or bare). It is designed to be consumed by LLMs to generate accurate installation commands and code examples based on user requirements.

      Key facts:
      - Same headless `@dynamic-labs-sdk/client` as web
      - State flows through `@dynamic-labs-sdk/react-hooks` (same as React web)
      - React Native adds polyfills, native modules, and deep-link metadata only
      - Do **not** install `@dynamic-labs/client` or `@dynamic-labs/react-native-extension` (legacy)

      **How to use this document:**

      Start by asking the user which path they want:

      > "Would you like a **Quick start** (Expo + email login + EVM + embedded wallet, zero configuration — recommended if you're just getting started) or a **Custom setup** (choose Expo vs bare, chains, auth method, and wallet type)?"

      - If the user chooses **Quick start**: follow the Quick Start Path section exactly. Do not ask any configuration questions — execute the happy path as written.
      - If the user chooses **Custom setup**: follow the Custom Setup Path section and ask all configuration questions before proceeding.

      ---

      ## Quick Start Path

      > Use this path when the user chooses Quick start. Execute every step in order. Do not skip steps. Do not ask configuration questions.

      **Stack:** Expo (development build) + npm + EVM only + email OTP + embedded WaaS wallet + `@dynamic-labs-sdk/react-hooks`

      ### Step 1 — Prerequisites

      Tell the user:
      - Node.js 18+ is required
      - An Expo app is required (e.g. `npx create-expo-app@latest`)
      - **Expo Go is not supported** — the SDK needs native modules linked via a development build or EAS Build
      - A Dynamic environment ID is required — get one at https://app.dynamic.xyz/dashboard/developer/api

      Ask the user for their environment ID before proceeding.

      ### Step 2 — Install

      ```
      npx expo install @dynamic-labs-sdk/client @dynamic-labs-sdk/evm @dynamic-labs-sdk/react-hooks @tanstack/react-query @react-native-async-storage/async-storage react-native-keychain expo-linking expo-crypto buffer
      ```

      `@tanstack/react-query` is a required peer dependency of `@dynamic-labs-sdk/react-hooks`.

      ### Step 3 — Create polyfills

      Create `polyfills.ts` at the project root:

      ```typescript
      import { Buffer } from 'buffer';
      import { getRandomValues, randomUUID } from 'expo-crypto';

      if (!global.crypto) {
        // @ts-expect-error — partial Crypto implementation
        global.crypto = {};
      }
      if (!global.crypto.getRandomValues) {
        // @ts-expect-error — partial Crypto implementation
        global.crypto.getRandomValues = getRandomValues;
      }
      if (!global.crypto.randomUUID) {
        // @ts-expect-error — partial Crypto implementation
        global.crypto.randomUUID = randomUUID;
      }

      global.Buffer = Buffer;
      ```

      ### Step 4 — Load polyfills first

      Create `index.ts` at the project root and point `package.json` `"main"` at it:

      ```typescript
      // Polyfills must run before any other import
      import './polyfills';
      import 'expo-router/entry';
      ```

      ```json
      {
        "main": "./index.ts"
      }
      ```

      If the app does not use Expo Router, import `./polyfills` as the first line of the existing entry file instead.

      ### Step 5 — Create the Dynamic client module

      Create `src/dynamicClient.ts` (or project-root `dynamicClient.ts`). Importing this file at app root registers extensions before any component renders.

      ```typescript
      import { createDynamicClient, initializeClient } from "@dynamic-labs-sdk/client";
      import { addEvmExtension } from "@dynamic-labs-sdk/evm";

      export const dynamicClient = createDynamicClient({
        autoInitialize: false,
        environmentId: "YOUR_ENVIRONMENT_ID",
        metadata: {
          name: "My App",
          // Deep link scheme registered in the native app (Info.plist / AndroidManifest / app.json)
          nativeLink: "myapp://",
          // App origin — required for passkeys; use your real HTTPS origin in production
          universalLink: "https://example.com",
        },
      });

      // Register extensions immediately, before initialization completes.
      // Extension functions take NO arguments — do not pass the client instance.
      addEvmExtension();

      void initializeClient();
      ```

      ### Step 6 — Wrap the app in `QueryClientProvider` and `DynamicProvider`

      At the root layout (e.g. `app/_layout.tsx` for Expo Router), import the client module and wrap the tree. Mount `QueryClientProvider` outside `DynamicProvider`:

      ```tsx
      import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
      import { DynamicProvider } from "@dynamic-labs-sdk/react-hooks";
      import { dynamicClient } from "./dynamicClient";

      const queryClient = new QueryClient();

      export default function RootLayout({ children }: { children: React.ReactNode }) {
        return (
          <QueryClientProvider client={queryClient}>
            <DynamicProvider client={dynamicClient}>
              {children}
            </DynamicProvider>
          </QueryClientProvider>
        );
      }
      ```

      ### Step 7 — Email OTP login component

      Build a minimal login form. The JS SDK is headless — there is no built-in modal.

      ```tsx
      import { useState } from "react";
      import { View, TextInput, Button } from "react-native";
      import { useSendEmailOTP, useVerifyOTP } from "@dynamic-labs-sdk/react-hooks";

      export function Login() {
        const [email, setEmail] = useState("");
        const [code, setCode] = useState("");

        const { mutate: sendEmailOTP, data: sendResult } = useSendEmailOTP();
        const { mutate: verifyOTP } = useVerifyOTP();

        const otpVerification = sendResult?.otpVerification;

        if (!otpVerification) {
          return (
            <View>
              <TextInput value={email} onChangeText={setEmail} placeholder="email" autoCapitalize="none" />
              <Button title="Send code" onPress={() => sendEmailOTP({ email })} />
            </View>
          );
        }

        return (
          <View>
            <TextInput value={code} onChangeText={setCode} placeholder="123456" keyboardType="number-pad" />
            <Button
              title="Verify"
              onPress={() => verifyOTP({ otpVerification, verificationToken: code })}
            />
          </View>
        );
      }
      ```

      ### Step 8 — Create the WaaS wallet on auth success (required — not automatic)

      **After `verifyOTP` succeeds, the wallet does not exist yet.** Subscribe once at app mount with `useOnEvent` and call `createWaasWalletAccounts()` on `userChanged`. Do not guard this with an `accounts.length === 0` check — the SDK may return a stale non-zero list immediately after auth, causing the creation step to be silently skipped.

      ```tsx
      import { useOnEvent } from "@dynamic-labs-sdk/react-hooks";
      import { createWaasWalletAccounts, getChainsMissingWaasWalletAccounts } from "@dynamic-labs-sdk/client/waas";

      export function WaasBootstrap() {
        useOnEvent({
          event: "userChanged",
          listener: async (user) => {
            if (!user) return;
            const missingChains = getChainsMissingWaasWalletAccounts();
            if (missingChains.length === 0) return;
            await createWaasWalletAccounts({ chains: missingChains });
          },
        });
        return null;
      }
      ```

      Mount `<WaasBootstrap />` once inside `DynamicProvider`.

      ### Step 9 — Display wallet state with hooks

      Hooks from `@dynamic-labs-sdk/react-hooks` subscribe to client events and re-render automatically. **Use `wallet.address`, not `wallet.accountAddress`.**

      ```tsx
      import { Text, View } from "react-native";
      import { useUser, useGetWalletAccounts, useInitStatus } from "@dynamic-labs-sdk/react-hooks";

      export function Dashboard() {
        const { data: initStatus } = useInitStatus();
        const { data: user } = useUser();
        const { data: accounts = [] } = useGetWalletAccounts();

        if (initStatus !== "finished") return <Text>Loading…</Text>;
        if (!user) return <Text>Not signed in</Text>;

        const address = accounts[0]?.address;
        return (
          <View>
            <Text>Signed in as {user.email}</Text>
            <Text>Wallet: {address}</Text>
          </View>
        );
      }
      ```

      ### Step 10 — Rebuild the native app

      After installing native modules, rebuild so they are linked:

      ```
      npx expo prebuild --clean
      npx expo run:ios
      # or: npx expo run:android
      ```

      ---

      ## Custom Setup Path

      > Use this path when the user chooses Custom setup. Ask ALL questions below before generating any code.

      **Questions to ask the user:**

      1. Are you using **Expo** or **bare React Native** (`@react-native-community/cli`)?
      2. Which package manager do you prefer? (npm, yarn, pnpm, bun — use `npx expo install` when on Expo)
      3. Which chains do you want to support? (EVM, Solana, Sui, Aptos, Bitcoin, Tron, Starknet, TON, Cosmos — one or more)
      4. If EVM or Solana: do you need only embedded wallets (smaller bundle) or the full extension including external wallet discovery?
      5. If EVM or Solana: do you need WalletConnect for external wallet connections?
      6. Which auth method? (email OTP, SMS OTP, social, external wallet, or a combination)

      **Only after receiving answers**, use the sections below to generate the correct setup. Always include `@dynamic-labs-sdk/react-hooks` regardless of chain selection.

      ### Package Manager Commands
      - Expo: `npx expo install`
      - `npm`: `npm i` / `npm install`
      - `yarn`: `yarn add`
      - `pnpm`: `pnpm add`
      - `bun`: `bun add`

      ### Package Mapping
      - Core (always required): `@dynamic-labs-sdk/client`
      - React state hooks (always required): `@dynamic-labs-sdk/react-hooks` and `@tanstack/react-query`
      - Storage / keychain (always required on RN): `@react-native-async-storage/async-storage`, `react-native-keychain`
      - EVM: `@dynamic-labs-sdk/evm`
      - Solana: `@dynamic-labs-sdk/solana`
      - Sui: `@dynamic-labs-sdk/sui`
      - Aptos: `@dynamic-labs-sdk/aptos`
      - Bitcoin: `@dynamic-labs-sdk/bitcoin`
      - Tron: `@dynamic-labs-sdk/tron`
      - Starknet: `@dynamic-labs-sdk/starknet`
      - TON: `@dynamic-labs-sdk/ton`
      - Cosmos: `@dynamic-labs-sdk/cosmos`
      - Social / OAuth (optional): `react-native-inappbrowser-reborn`
      - Passkeys (optional): `react-native-passkey` — see https://docs.dynamic.xyz/javascript/react-native/passkeys
      - WalletConnect (optional): `@walletconnect/react-native-compat` (+ `@react-native-community/netinfo` on bare)
      - Export WaaS private key (optional): `@dynamic-labs-sdk/react-native-embedded-wallet-export`, `react-native-webview`

      ### Expo-specific setup
      - Polyfills: `expo-crypto`, `buffer` — create `polyfills.ts` with crypto + Buffer shims (see Quick Start Path)
      - Entry: custom `index.ts` that imports `./polyfills` first, then `expo-router/entry` (or your entry); set `"main": "./index.ts"`
      - Add `expo-linking` when you need deep links / social return
      - Rebuild: `npx expo prebuild --clean` then `npx expo run:ios` / `npx expo run:android`
      - Full guide: https://docs.dynamic.xyz/javascript/react-native/expo

      ### Bare React Native–specific setup
      - Polyfills: `react-native-get-random-values`, `buffer`
      - Babel: add `@babel/plugin-transform-export-namespace-from` and `@babel/plugin-transform-class-static-block`, then restart Metro with `--reset-cache`
      - Create `polyfills.ts` with random-values import first, Buffer, `crypto.randomUUID` shim, and a `window.location` / `globalThis.location` shim whose origin equals `metadata.universalLink`
      - Import `./polyfills` as the **first** line of `index.js`
      - Register deep link scheme in `Info.plist` and `AndroidManifest.xml`; add Android `<queries>` for `https`/`wc` when using WalletConnect; add JitPack for WalletConnect
      - Rebuild: `cd ios && bundle exec pod install && cd ..` then `npx react-native run-ios` / `run-android`
      - Full guide: https://docs.dynamic.xyz/javascript/react-native/bare-react-native

      ### Extension Notes
      - **Default extensions** (`addEvmExtension`, `addSolanaExtension`, `addTonExtension`) bundle external wallet discovery + embedded (WaaS) support
      - **Standalone embedded-only extensions** (`addWaasEvmExtension`, `addWaasSolanaExtension`, `addWaasTonExtension`) produce a smaller bundle — use when the user only needs embedded wallets
      - Extension functions take **NO arguments** — do not pass the client instance
      - Register extensions immediately after `createDynamicClient()`, before initialization completes

      ### React Native wiring (apply to all custom setups)

      1. Create polyfills and load them before any other app code.
      2. Create the client and register extensions in a single module (`dynamicClient.ts`). Set `metadata.nativeLink` (deep link scheme) and `metadata.universalLink` (HTTPS origin). Import this module at app root.
      3. Install `@tanstack/react-query` alongside `@dynamic-labs-sdk/react-hooks`.
      4. Wrap the tree in `<QueryClientProvider>` (outer) and `<DynamicProvider client={dynamicClient}>` (inner).
      5. Read state with hooks from `@dynamic-labs-sdk/react-hooks`. For one-off listeners, use `useOnEvent` — do not call `onEvent` inside component bodies.
      6. Rebuild the native app after adding native modules.

      ### WalletConnect on React Native
      Same extensions as web, but open wallets with `Linking.openURL` (from `react-native`, or `expo-linking`) instead of `window.open`, and skip the QR code — mobile deep links straight to the wallet app. Import `@walletconnect/react-native-compat` as the **first** line of `polyfills.ts`. See https://docs.dynamic.xyz/javascript/reference/wallets/walletconnect-integration.

      ### Post-Auth Patterns (embedded wallets)

      - **WaaS wallet creation** — not automatic. Trigger from `useOnEvent` on `userChanged` with `getChainsMissingWaasWalletAccounts()` / `createWaasWalletAccounts()`.
      - **State hooks return the react-query result** — read the value off `data`, e.g. `const { data: accounts = [] } = useGetWalletAccounts()` then `accounts[0]?.address`
      - **OTP verification** — parameter is `verificationToken`, not `otp`
      - **Client metadata** — use `universalLink` and `nativeLink`, not `url`

      ### Documentation
      All docs for this SDK: https://docs.dynamic.xyz (paths starting with `/javascript/`)
      Environment ID: https://app.dynamic.xyz/dashboard/developer/api
      Expo setup: https://docs.dynamic.xyz/javascript/react-native/expo
      Bare setup: https://docs.dynamic.xyz/javascript/react-native/bare-react-native

      ---

      ## Critical API Reference (apply to both paths)

      | Correct | Incorrect | Notes |
      |---|---|---|
      | `@dynamic-labs-sdk/client` | `@dynamic-labs/client` | Legacy RN SDK — different API |
      | `metadata: { nativeLink, universalLink }` | `metadata: { url }` | RN needs both links |
      | `<QueryClientProvider>` + `<DynamicProvider>` | Hooks without providers | Required for all hooks |
      | `useOnEvent({ event, listener })` | `onEvent` inside components | Prevents subscription leaks |
      | `addEvmExtension()` | `addEvmExtension(client)` | Extension functions take no arguments |
      | `verifyOTP({ otpVerification, verificationToken })` | `verificationToken` misspelled as `otp` | Parameter is `verificationToken` |
      | `wallet.address` | `wallet.accountAddress` | Use `address` from `useGetWalletAccounts()` |
      | Development build / `expo prebuild` | Expo Go | Native modules are not linked in Expo Go |

      ---

      ## Building a headless integration?

      The JavaScript SDK is always headless. See the [Authentication screens](/javascript/authentication-methods/headless-authentication) for a full list of screens your app needs to handle.

      ---

      ## Step-Up Authentication

      **Required before accepting the `2026_04_01` API version** — verify your
      minimum API version in [Dashboard > Developers > API & SDK Keys](https://app.dynamic.xyz/dashboard/developer/api).

      The JavaScript SDK is always headless — there is no built-in step-up UI.
      **You must always handle step-up authentication manually.**

      See [Step-up authentication](/javascript/authentication-methods/step-up-auth/overview) for the full implementation guide.

      ---

      ## Device Registration

      **Required before accepting the `2026_04_01` API version.**

      The JavaScript SDK is always headless — handle device registration manually.

      Full guide: https://docs.dynamic.xyz/javascript/authentication-methods/device-registration

      ---

      ## Troubleshooting — Dashboard Configuration

      If the app builds successfully but login fails, wallets don't appear, or you see network/auth errors, ask the user to verify each of the following in their Dynamic dashboard at https://app.dynamic.xyz:

      ### 1 — Chains not enabled
      The EVM chain (or any other chain used in the quickstart) must be enabled under **Chains & Networks**.

      ### 2 — Login method not enabled
      Email OTP (or whichever login method the app uses) must be toggled on under **Sign-in Methods**.

      ### 3 — Embedded wallets not enabled
      If the app uses WaaS embedded wallets, enable **Embedded Wallets** under **Wallets**.

      ### 4 — Origins / deep links
      Add your app's HTTPS origin (`metadata.universalLink`) to **Allowed Origins** under **Security**. Ensure `metadata.nativeLink` matches the scheme registered in the native project.

      If the app is still not working, check Metro / device logs and refer to https://docs.dynamic.xyz/overview/troubleshooting/general. Platform setup details: https://docs.dynamic.xyz/javascript/react-native/setup

      ---

      ## React Native–Specific Pitfalls

      - **Expo Go will not work.** Use a development build or bare workflow.
      - **Polyfills must load first.** Importing them after other modules can break crypto and embedded wallets.
      - **Hooks return `null`/empty before init completes.** Gate UI on `useInitStatus().data === 'finished'`.
      - **`DynamicProvider` must wrap the entire tree that uses hooks.**
      - **`DynamicProvider` does not include `QueryClientProvider`.** Mount both; put `QueryClientProvider` outside `DynamicProvider`.
      - **Do not call `onEvent` inside component bodies.** Use `useOnEvent`.
      - **Don't recreate `dynamicClient` per render.** Module-level singleton only.
      - **Passkeys need extra native setup** beyond installing the package — see https://docs.dynamic.xyz/javascript/react-native/passkeys
      ````
    </div>
  </Tab>

  <Tab title="Manual">
    <iframe frameBorder="0" width="100%" height="600px" src="https://documentation-quickstarts.vercel.app/javascript-react-native?color-scheme=dark" allow="clipboard-write" className="rn-qs-dark">
      {' '}
    </iframe>

    <iframe frameBorder="0" width="100%" height="600px" src="https://documentation-quickstarts.vercel.app/javascript-react-native?color-scheme=light" allow="clipboard-write" className="rn-qs-light">
      {' '}
    </iframe>

    <Tip>
      **After the embed**

      * Platform setup: [React Native Setup](/docs/javascript/react-native/setup), [Expo](/docs/javascript/react-native/expo), [Bare React Native](/docs/javascript/react-native/bare-react-native).
      * Full client setup: [Creating a Dynamic Client](/docs/javascript/reference/client/create-dynamic-client), [Initializing the Dynamic Client](/docs/javascript/reference/client/initialize-dynamic-client).
      * Add chains with [EVM](/docs/javascript/reference/evm/adding-evm-extensions), [Solana](/docs/javascript/reference/solana/adding-solana-extensions), or [all extension types](/docs/javascript/reference/adding-extensions).
      * Reactive patterns: [React Hooks](/docs/javascript/reference/react-hooks).
      * Something wrong? See [Troubleshooting](/docs/overview/troubleshooting/general).
    </Tip>

    ## Key patterns

    After polyfills and the client are in place, React Native uses the same hooks as React on web:

    ```tsx theme={"system"}
    import { useGetWalletAccounts, useUser, useInitStatus } from '@dynamic-labs-sdk/react-hooks';

    function Dashboard() {
      const { data: walletAccounts = [] } = useGetWalletAccounts();
      const { data: user } = useUser();
      const { data: initStatus } = useInitStatus();
      // re-renders automatically when wallets, user, or init status change
    }
    ```

    <Note>
      Import your `dynamicClient.ts` module at the root of your app so extensions are registered before any component renders. Set `metadata.nativeLink` and `metadata.universalLink` when creating the client.
    </Note>
  </Tab>
</Tabs>
