# React Native Quickstart (/wallets/react-native/quickstart)

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



ZeroDev Wallet runs on React Native. The same Wagmi-based API you use [on the web](/wallets/quickstart) works in native apps — the main difference is that on React Native the storages and stampers must be configured explicitly, since there are no platform defaults.

This guide walks you through setting up the SDK in an [Expo](https://expo.dev/) app and signing in with email OTP. Once connected, sending transactions and signing messages is plain Wagmi.

## Prerequisites [#prerequisites]

1. Create a new project on the [ZeroDev Dashboard](https://dashboard.zerodev.app/).
2. Enable **Sepolia** and **Arbitrum Sepolia**.

<div className="fd-steps">
  <div className="fd-step">
    ## Set up the project [#set-up-the-project]

    Create a new Expo app:

    <Tabs items="[&#x22;npm&#x22;,&#x22;yarn&#x22;,&#x22;pnpm&#x22;,&#x22;bun&#x22;]">
      <Tab value="npm">
        ```bash
        npx create-expo-app@latest <app_name>
        ```
      </Tab>

      <Tab value="yarn">
        ```bash
        yarn create expo-app <app_name>
        ```
      </Tab>

      <Tab value="pnpm">
        ```bash
        pnpm create expo-app@latest <app_name>
        ```
      </Tab>

      <Tab value="bun">
        ```bash
        bun create expo-app@latest <app_name>
        ```
      </Tab>
    </Tabs>

    Copy the project ID from the [prerequisites](#prerequisites) into a `.env` file in the project root:

    ```sh
    EXPO_PUBLIC_ZERODEV_PROJECT_ID=<your project id>
    ```

    Install the SDK:

    <Tabs items="[&#x22;npm&#x22;,&#x22;yarn&#x22;,&#x22;pnpm&#x22;,&#x22;bun&#x22;]">
      <Tab value="npm">
        ```bash
        npm i @zerodev/wallet-core@0.0.1 @zerodev/wallet-react@0.0.1
        ```
      </Tab>

      <Tab value="yarn">
        ```bash
        yarn add @zerodev/wallet-core@0.0.1 @zerodev/wallet-react@0.0.1
        ```
      </Tab>

      <Tab value="pnpm">
        ```bash
        pnpm add @zerodev/wallet-core@0.0.1 @zerodev/wallet-react@0.0.1
        ```
      </Tab>

      <Tab value="bun">
        ```bash
        bun add @zerodev/wallet-core@0.0.1 @zerodev/wallet-react@0.0.1
        ```
      </Tab>
    </Tabs>

    Install Wagmi (per the [Wagmi getting started guide](https://wagmi.sh/react/getting-started)):

    <Tabs items="[&#x22;npm&#x22;,&#x22;yarn&#x22;,&#x22;pnpm&#x22;,&#x22;bun&#x22;]">
      <Tab value="npm">
        ```bash
        npm i wagmi viem@2.x @tanstack/react-query
        ```
      </Tab>

      <Tab value="yarn">
        ```bash
        yarn add wagmi viem@2.x @tanstack/react-query
        ```
      </Tab>

      <Tab value="pnpm">
        ```bash
        pnpm add wagmi viem@2.x @tanstack/react-query
        ```
      </Tab>

      <Tab value="bun">
        ```bash
        bun add wagmi viem@2.x @tanstack/react-query
        ```
      </Tab>
    </Tabs>

    ZeroDev Wallet works with both Wagmi v2 and v3. Wagmi v3 still requires `viem@2.x`, so the pinned viem version above applies to either.

    Install the polyfills (`events` is a shim for a Node module that React Native doesn't ship):

    <Tabs items="[&#x22;npm&#x22;,&#x22;yarn&#x22;,&#x22;pnpm&#x22;,&#x22;bun&#x22;]">
      <Tab value="npm">
        ```bash
        npx expo install react-native-get-random-values events
        ```
      </Tab>

      <Tab value="yarn">
        ```bash
        yarn expo install react-native-get-random-values events
        ```
      </Tab>

      <Tab value="pnpm">
        ```bash
        pnpm expo install react-native-get-random-values events
        ```
      </Tab>

      <Tab value="bun">
        ```bash
        bunx expo install react-native-get-random-values events
        ```
      </Tab>
    </Tabs>

    Add the polyfill **at the very top** of your root `_layout.tsx`:

    ```tsx
    import "react-native-get-random-values"; // [!code ++]

    import { DarkTheme, DefaultTheme, ThemeProvider } from "expo-router";
    import { useColorScheme } from "react-native";

    import { AnimatedSplashOverlay } from "@/components/animated-icon";
    import AppTabs from "@/components/app-tabs";

    export default function TabLayout() {
      const colorScheme = useColorScheme();
      return (
        <ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
          <AnimatedSplashOverlay />
          <AppTabs />
        </ThemeProvider>
      );
    }
    ```
  </div>

  <div className="fd-step">
    ## Configure the SDK [#configure-the-sdk]

    Install the SDK's remaining peer deps — the storage adapter and the secure store backing the API-key stamper:

    <Tabs items="[&#x22;npm&#x22;,&#x22;yarn&#x22;,&#x22;pnpm&#x22;,&#x22;bun&#x22;]">
      <Tab value="npm">
        ```bash
        npx expo install @react-native-async-storage/async-storage expo-secure-store
        ```
      </Tab>

      <Tab value="yarn">
        ```bash
        yarn expo install @react-native-async-storage/async-storage expo-secure-store
        ```
      </Tab>

      <Tab value="pnpm">
        ```bash
        pnpm expo install @react-native-async-storage/async-storage expo-secure-store
        ```
      </Tab>

      <Tab value="bun">
        ```bash
        bunx expo install @react-native-async-storage/async-storage expo-secure-store
        ```
      </Tab>
    </Tabs>

    Create `wagmi.config.ts` with the `zeroDevWallet` connector. This replaces the plain config from Wagmi's getting-started guide:

    ```ts
    import { createSecureStoreStamper } from "@zerodev/wallet-core/react-native/stampers/secure-store";
    import { asyncStorageAdapter } from "@zerodev/wallet-core/react-native/storage/async-storage";
    import { zeroDevWallet } from "@zerodev/wallet-react";
    import { createConfig, createStorage, http } from "wagmi";
    import { arbitrumSepolia, sepolia } from "wagmi/chains";

    const ZERODEV_PROJECT_ID = process.env.EXPO_PUBLIC_ZERODEV_PROJECT_ID ?? "";
    // A domain — see the note below.
    export const RP_ID = "example.com";

    const chains = [sepolia, arbitrumSepolia] as const;

    export const wagmiConfig = createConfig({
      chains,
      connectors: [
        zeroDevWallet({
          projectId: ZERODEV_PROJECT_ID,
          chains,
          rpId: RP_ID,
          apiKeyStamper: createSecureStoreStamper(),
          sessionStorage: asyncStorageAdapter,
          persistStorage: asyncStorageAdapter,
        }),
      ],
      transports: {
        [sepolia.id]: http(),
        [arbitrumSepolia.id]: http(),
      },
      storage: createStorage({ storage: asyncStorageAdapter }),
      multiInjectedProviderDiscovery: false,
    });

    declare module "wagmi" {
      interface Register {
        config: typeof wagmiConfig;
      }
    }
    ```

    <Callout type="info" title="About RP_ID">
      `RP_ID` is a **domain**, as defined by [the WebAuthn standard](https://web.dev/articles/webauthn-rp-id). As long as you don't use [passkeys](/wallets/react-native/passkeys) or App Links for [OAuth](/wallets/react-native/google-oauth) / [magic-link](/wallets/react-native/magic-link) redirects, it doesn't have to be a valid domain you own. If you do use those features, the [domain association](/wallets/react-native/domain-association) must be set up and served from this same domain. Also, if you specify an Access Control List of whitelisted Origins on the [ZeroDev Dashboard](https://dashboard.zerodev.app/), the same domain needs to be on the allowlist as `https://${RP_ID}/`.
    </Callout>

    See [Configuration](/wallets/react-native/configuration) for what each option does and which ones are required on React Native.

    Then wrap the app in the Wagmi and React Query providers in `_layout.tsx`:

    ```tsx
    import "react-native-get-random-values";

    import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; // [!code ++]
    import { DarkTheme, DefaultTheme, ThemeProvider } from "expo-router";
    import { useColorScheme } from "react-native";
    import { WagmiProvider } from "wagmi"; // [!code ++]

    import { AnimatedSplashOverlay } from "@/components/animated-icon";
    import AppTabs from "@/components/app-tabs";
    import { wagmiConfig } from "@/wagmi.config"; // [!code ++]

    const queryClient = new QueryClient(); // [!code ++]

    export default function TabLayout() {
      const colorScheme = useColorScheme();
      return (
        <WagmiProvider config={wagmiConfig}> {/* [!code ++] */}
          <QueryClientProvider client={queryClient}> {/* [!code ++] */}
            <ThemeProvider
              value={colorScheme === "dark" ? DarkTheme : DefaultTheme}
            >
              <AnimatedSplashOverlay />
              <AppTabs />
            </ThemeProvider>
          </QueryClientProvider> {/* [!code ++] */}
        </WagmiProvider> // [!code ++]
      );
    }
    ```
  </div>

  <div className="fd-step">
    ## Add OTP email auth [#add-otp-email-auth]

    Email OTP is a two-step flow: send a code, then verify it. Verification connects the wallet (Wagmi's `useAccount().status` flips to `"connected"`).

    ```tsx
    import { useSendOTP, useVerifyOTP } from "@zerodev/wallet-react"; // [!code focus]
    import { useState } from "react";
    import { Button, Text, TextInput } from "react-native";

    const input = { borderWidth: 1, padding: 8, borderRadius: 4 };

    export function OtpEmailFlow() {
      const [email, setEmail] = useState("");
      const [code, setCode] = useState("");
      const [otp, setOtp] = useState<{
        otpId: string;
        otpEncryptionTargetBundle: string;
      } | null>(null);

      const sendOTP = useSendOTP(); // [!code focus]
      const verifyOTP = useVerifyOTP(); // [!code focus]

      // Step 1: email -> send a code
      if (otp === null) {
        return (
          <>
            <TextInput
              value={email}
              onChangeText={setEmail}
              placeholder="you@example.com"
              autoCapitalize="none"
              keyboardType="email-address"
              style={input}
            />
            <Button
              title={sendOTP.isPending ? "Sending…" : "Send code"}
              disabled={sendOTP.isPending || !email}
              onPress={() => sendOTP.mutate({ email }, { onSuccess: setOtp })} // [!code focus]
            />
            {sendOTP.error ? (
              <Text style={{ color: "red" }}>{sendOTP.error.message}</Text>
            ) : null}
          </>
        );
      }

      // Step 2: code -> verify and connect the wallet
      return (
        <>
          <TextInput
            value={code}
            onChangeText={setCode}
            placeholder="123456"
            keyboardType="number-pad"
            style={input}
          />
          <Button
            title={verifyOTP.isPending ? "Verifying…" : "Verify"}
            disabled={verifyOTP.isPending || !code}
            onPress={() => verifyOTP.mutate({ code, ...otp })} // [!code focus]
          />
          {verifyOTP.error ? (
            <Text style={{ color: "red" }}>{verifyOTP.error.message}</Text>
          ) : null}
        </>
      );
    }
    ```

    The `useSendOTP` and `useVerifyOTP` hooks work identically to the web — see [Email OTP](/wallets/auth/email-otp) for the full flow.
  </div>

  <div className="fd-step">
    ## Send a transaction [#send-a-transaction]

    Sending a transaction is plain Wagmi (`useSendTransaction`, `useBalance`, `useAccount`, …) — no ZeroDev-specific code needed:

    ```tsx
    import { parseEther } from "viem";
    import { Button, Text } from "react-native";
    import { useAccount, useSendTransaction } from "wagmi";

    export function SendTransaction() {
      const { status } = useAccount();
      const { sendTransaction, isPending, data: hash, error } = useSendTransaction();

      if (status !== "connected") return null;

      return (
        <>
          <Button
            title={isPending ? "Sending…" : "Send 0.001 ETH"}
            disabled={isPending}
            onPress={() =>
              sendTransaction({
                to: "0xd2135CfB216b74109775236E36d4b433F1DF507B",
                value: parseEther("0.001"),
              })
            }
          />
          {hash ? <Text>Transaction hash: {hash}</Text> : null}
          {error ? <Text style={{ color: "red" }}>{error.message}</Text> : null}
        </>
      );
    }
    ```

    Run the app — you now have a fully working wallet app: sign in with the email OTP flow to connect the wallet, then send a transaction.
  </div>

  <div className="fd-step">
    ## Switch to an Expo development build [#switch-to-an-expo-development-build]

    The features in the next guides (OAuth deep links, WebView export, passkeys) rely on native modules that Expo Go doesn't ship, so switch to a [development build](https://docs.expo.dev/develop/development-builds/expo-go-to-dev-build/):

    <Tabs items="[&#x22;npm&#x22;,&#x22;yarn&#x22;,&#x22;pnpm&#x22;,&#x22;bun&#x22;]">
      <Tab value="npm">
        ```bash
        npx expo install expo-dev-client
        ```
      </Tab>

      <Tab value="yarn">
        ```bash
        yarn expo install expo-dev-client
        ```
      </Tab>

      <Tab value="pnpm">
        ```bash
        pnpm expo install expo-dev-client
        ```
      </Tab>

      <Tab value="bun">
        ```bash
        bunx expo install expo-dev-client
        ```
      </Tab>
    </Tabs>

    Build and run on Android ([environment setup](https://docs.expo.dev/get-started/set-up-your-environment/?mode=development-build\&buildEnv=local\&platform=android\&device=simulated)):

    <Tabs items="[&#x22;npm&#x22;,&#x22;yarn&#x22;,&#x22;pnpm&#x22;,&#x22;bun&#x22;]">
      <Tab value="npm">
        ```bash
        npx expo run:android
        ```
      </Tab>

      <Tab value="yarn">
        ```bash
        yarn expo run:android
        ```
      </Tab>

      <Tab value="pnpm">
        ```bash
        pnpm expo run:android
        ```
      </Tab>

      <Tab value="bun">
        ```bash
        bunx expo run:android
        ```
      </Tab>
    </Tabs>

    Or on iOS — requires a Mac with Xcode ([environment setup](https://docs.expo.dev/get-started/set-up-your-environment/?mode=development-build\&buildEnv=local\&platform=ios\&device=simulated)):

    <Tabs items="[&#x22;npm&#x22;,&#x22;yarn&#x22;,&#x22;pnpm&#x22;,&#x22;bun&#x22;]">
      <Tab value="npm">
        ```bash
        npx expo run:ios
        ```
      </Tab>

      <Tab value="yarn">
        ```bash
        yarn expo run:ios
        ```
      </Tab>

      <Tab value="pnpm">
        ```bash
        pnpm expo run:ios
        ```
      </Tab>

      <Tab value="bun">
        ```bash
        bunx expo run:ios
        ```
      </Tab>
    </Tabs>

    The first run generates the native project (`expo prebuild`), builds it, installs the dev client on the emulator / Simulator, and starts the bundler. If `app.json` doesn't define `android.package` / `ios.bundleIdentifier` yet, the CLI prompts for one.

    <Callout type="info" title="What runs where">
      Email OTP, [Google OAuth](/wallets/react-native/google-oauth), and [wallet export](/wallets/react-native/export-wallet) work on both platforms with no further setup — on iOS they run in the Simulator with a free Apple account. [Passkeys](/wallets/react-native/passkeys) and verified `https` links ([Magic Link](/wallets/react-native/magic-link)) additionally require the [domain association](/wallets/react-native/domain-association) setup: free on Android, while the iOS half (the Associated Domains entitlement) needs a paid [Apple Developer](https://developer.apple.com/programs/) membership and is best tested on a physical device.
    </Callout>
  </div>
</div>

## Next Steps [#next-steps]

* [Configuration](/wallets/react-native/configuration) — Stampers, storage adapters, and connector options
* [Google OAuth](/wallets/react-native/google-oauth) — Social login with Expo WebBrowser
* [Domain Association](/wallets/react-native/domain-association) — Link your app to a domain for passkeys and App Links
* [Magic Link](/wallets/react-native/magic-link) — Sign in with a link sent by email
* [Passkeys](/wallets/react-native/passkeys) — Native WebAuthn
* [Export Wallet](/wallets/react-native/export-wallet) — Reveal the seed phrase or private key via WebView
* [React Native Web](/wallets/react-native/web) — Run the same app on the web with react-native-web
