> ## Documentation Index
> Fetch the complete documentation index at: https://docs.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
> [Building UI](https://docs.dynamic.xyz/docs/javascript/building-ui/overview.md) for details.

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

> [tab] Agent-friendly
>
> > [!TIP]
> >
> > Add the Dynamic docs MCP to your AI editor first — your agent can then query the docs directly.
>
> > [tab] Cursor
> >
> > [Click here to add the MCP to Cursor.](https://cursor.com/en/install-mcp?name=dynamic\&config=eyJ1cmwiOiJodHRwczovL3d3dy5keW5hbWljLnh5ei9kb2NzL21jcCJ9)
>
> > [tab] Claude Code
> >
> > ```bash theme={"system"}
> > claude mcp add --transport http dynamic https://www.dynamic.xyz/docs/mcp
> > ```
>
> > [tab] Codex
> >
> > ```bash theme={"system"}
> > codex mcp add dynamic --url https://www.dynamic.xyz/docs/mcp
> > ```
>
> > [tab] 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.
>
> Copy this prompt into your LLM (e.g. Claude, ChatGPT, Cursor) to implement Dynamic in your React Native app via the JS SDK.
>
> ````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/legacy-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/legacy-client` or `@dynamic-labs/legacy-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://console.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 viem
> ```
>
> `@tanstack/react-query` and `viem` are required peer dependencies of `@dynamic-labs-sdk/react-hooks` and `@dynamic-labs-sdk/evm`, respectively.
>
> ### 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) {
>   global.crypto.getRandomValues = getRandomValues;
> }
> if (!global.crypto.randomUUID) {
>   global.crypto.randomUUID = randomUUID;
> }
>
> global.Buffer = Buffer;
>
> type MinimalLocation = { origin: string };
>
> const globalWithLocation = globalThis as typeof globalThis & {
>   location?: MinimalLocation;
> };
>
> // The embedded wallet client reads window.location.origin; use metadata.universalLink
> if (!globalWithLocation.location) {
>   // @ts-expect-error — partial Location implementation
>   globalWithLocation.location = { origin: 'https://example.com' };
> }
> ```
>
> ### 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.
> // The client argument is optional: omit it unless you run multiple clients.
> 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: otpVerification } = useSendEmailOTP();
>   const { mutate: verifyOTP } = useVerifyOTP();
>
>   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)
>
> **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, Aleo, Stellar; 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` (plus its required peer `viem`)
> - 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` (plus required peer dependencies `@ton/core`, `@ton/crypto`, `@ton/ton`, `@tonconnect/sdk`, `@tonconnect/ui`)
> - 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 and a `globalThis.location` shim whose origin equals `metadata.universalLink` (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`, `addSuiExtension`, `addTronExtension`, `addBitcoinExtension`) bundle external wallet discovery + embedded (WaaS) support
> - **TON extensions:** TON has no combined default extension. Use `addWaasTonExtension` from `@dynamic-labs-sdk/ton/waas` for embedded wallets and `addTonConnectExtension` from `@dynamic-labs-sdk/ton/ton-connect` for external TON wallets
> - **Standalone embedded-only extensions** (`addWaasEvmExtension`, `addWaasSolanaExtension`, `addWaasTonExtension`, `addWaasSuiExtension`, `addWaasTronExtension`, `addWaasBitcoinExtension`, `addWaasAleoExtension`) produce a smaller bundle. Use them when the user only needs embedded wallets. They live on the chain package's `/waas` subpath, for example `import { addWaasEvmExtension } from '@dynamic-labs-sdk/evm/waas';`
> - Every extension and SDK function takes an **optional trailing client argument** that defaults to the client created by `createDynamicClient()`. Omit it unless the app runs multiple clients
> - 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://console.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/legacy-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(myClient)` when there is one client | The client argument is optional and defaults to the client you created |
> | `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 [Dynamic Console > Developers > API & SDK Keys](https://console.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: Console 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 Console at https://console.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
> ````

> [tab] Manual
>
> [https://documentation-quickstarts.vercel.app/javascript-react-native?color-scheme=dark](https://documentation-quickstarts.vercel.app/javascript-react-native?color-scheme=dark)
>
> [https://documentation-quickstarts.vercel.app/javascript-react-native?color-scheme=light](https://documentation-quickstarts.vercel.app/javascript-react-native?color-scheme=light)
>
> > [!TIP]
> >
> > **After the embed**
> >
> > * Platform setup: [React Native Setup](https://docs.dynamic.xyz/docs/javascript/react-native/setup.md), [Expo](https://docs.dynamic.xyz/docs/javascript/react-native/expo.md), [Bare React Native](https://docs.dynamic.xyz/docs/javascript/react-native/bare-react-native.md).
> > * Full client setup: [Creating a Dynamic Client](https://docs.dynamic.xyz/docs/javascript/reference/client/create-dynamic-client.md), [Initializing the Dynamic Client](https://docs.dynamic.xyz/docs/javascript/reference/client/initialize-dynamic-client.md).
> > * Add chains with [EVM](https://docs.dynamic.xyz/docs/javascript/reference/evm/adding-evm-extensions.md), [Solana](https://docs.dynamic.xyz/docs/javascript/reference/solana/adding-solana-extensions.md), or [all extension types](https://docs.dynamic.xyz/docs/javascript/reference/adding-extensions.md).
> > * Reactive patterns: [React Hooks](https://docs.dynamic.xyz/docs/javascript/reference/react-hooks.md).
> > * Something wrong? See [Troubleshooting](https://docs.dynamic.xyz/docs/get-started/troubleshooting/general.md).
>
> ## 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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.