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

# Integrate SocketFi Wallets into Your React Native App

> Step-by-step guide to embedding passkey-powered smart wallets in your Expo/React Native app using deep linking and SecureStore session storage.

SocketFi brings embedded, passkey-secured Stellar/Soroban wallets to mobile — no seed phrases, no wallet apps, no browser extensions required. Authentication runs through a hosted SocketFi flow that opens in a secure in-app browser, then deep-links back to your app with a ready-to-use session. This guide walks you through every step from SDK installation to production-ready transaction handling.

## Prerequisites

Before you begin, make sure you have:

* Expo SDK 53 or later
* React Native (managed or bare workflow)
* TypeScript (recommended)
* A SocketFi application and **Client ID** from the SocketFi Dashboard

<Warning>
  Deep linking **must** be configured in `app.json` before authentication will work. The SocketFi hosted flow redirects back to your app via a custom URL scheme. Without it, the session cannot be delivered and authentication will hang.
</Warning>

<Steps>
  ### Install the SDK and Expo dependencies

  Install `@socketfi/react-native` and the two Expo libraries it depends on for in-app browsing and deep-link handling.

  <CodeGroup>
    ```bash npm theme={null}
    npm install @socketfi/react-native
    npx expo install expo-web-browser expo-linking
    ```

    ```bash yarn theme={null}
    yarn add @socketfi/react-native
    npx expo install expo-web-browser expo-linking
    ```

    ```bash pnpm theme={null}
    pnpm add @socketfi/react-native
    npx expo install expo-web-browser expo-linking
    ```
  </CodeGroup>

  `expo-web-browser` opens the SocketFi-hosted authentication flow in a secure browser sheet. `expo-linking` handles the deep-link callback that returns the session to your app.

  ### Configure deep linking in app.json

  Add a custom URL scheme to your Expo configuration. This is the scheme SocketFi uses to redirect users back to your application after authentication completes.

  ```json app.json theme={null}
  {
    "expo": {
      "name": "My App",
      "slug": "my-app",
      "scheme": "myapp",
      "ios": {
        "bundleIdentifier": "com.example.myapp"
      },
      "android": {
        "package": "com.example.myapp"
      }
    }
  }
  ```

  If you prefer a TypeScript config file:

  ```typescript app.config.ts theme={null}
  export default {
    expo: {
      name: "My App",
      slug: "my-app",
      scheme: "myapp",
      ios: { bundleIdentifier: "com.example.myapp" },
      android: { package: "com.example.myapp" },
    },
  };
  ```

  The scheme value (`myapp` above) must be lowercase, contain no spaces, and match the `returnTo` URL you configure in the next step.

  ### Set up the Expo linking listener

  Register a deep-link listener early in your app's lifecycle so the SDK can receive the authentication callback as soon as it arrives.

  ```typescript src/utils/linking.ts theme={null}
  import * as Linking from "expo-linking";
  import { useEffect } from "react";
  import { socketfi } from "../socketfi/client";

  export function useDeepLinkHandler() {
    useEffect(() => {
      // Handle links when the app is already open
      const subscription = Linking.addEventListener("url", ({ url }) => {
        socketfi.handleRedirect(url);
      });

      // Handle the link that launched the app from a cold start
      Linking.getInitialURL().then((url) => {
        if (url) socketfi.handleRedirect(url);
      });

      return () => subscription.remove();
    }, []);
  }
  ```

  Call this hook at your app's root component so it runs before any authentication attempt:

  ```typescript App.tsx theme={null}
  import { useDeepLinkHandler } from "./src/utils/linking";
  import { AuthProvider } from "./src/auth/provider";
  import { RootNavigator } from "./src/navigation/RootNavigator";

  export default function App() {
    useDeepLinkHandler();

    return (
      <AuthProvider>
        <RootNavigator />
      </AuthProvider>
    );
  }
  ```

  ### Create the SocketFi client

  Create a shared client instance. Store your Client ID in an environment variable — never hard-code it.

  Add it to your environment:

  ```bash .env theme={null}
  EXPO_PUBLIC_SOCKETFI_CLIENT_ID=your_client_id_here
  ```

  Then create `src/socketfi/client.ts`:

  ```typescript src/socketfi/client.ts theme={null}
  import { SocketFi } from "@socketfi/react-native";

  export const socketfi = new SocketFi({
    clientId: process.env.EXPO_PUBLIC_SOCKETFI_CLIENT_ID!,
    network: "TESTNET", // switch to "MAINNET" for production
    returnTo: "myapp://auth", // must match your app.json scheme
    brand: {
      appName: "My App",
      primaryColor: "#4F46E5",
    },
    onError(error) {
      console.error("[SocketFi]", error);
    },
  });
  ```

  <Note>
    `EXPO_PUBLIC_` prefixed variables are automatically inlined by the Expo build pipeline. Variables without this prefix are **not** available in the client bundle.
  </Note>

  ### Build the AuthProvider with SecureStore

  Use `expo-secure-store` to persist session tokens in the device's encrypted keychain rather than unencrypted `AsyncStorage`.

  ```bash theme={null}
  npx expo install expo-secure-store
  ```

  ```typescript src/auth/context.ts theme={null}
  import { createContext, useContext } from "react";

  export type Session = {
    socketfiAccessToken: string;
    userProfile: {
      id: string;
      wallet: string;
    };
  };

  export type AuthState = {
    loading: boolean;
    authenticated: boolean;
    session: Session | null;
    login: () => Promise<void>;
    logout: () => Promise<void>;
  };

  export const AuthContext = createContext<AuthState | null>(null);

  export function useAuth(): AuthState {
    const ctx = useContext(AuthContext);
    if (!ctx) throw new Error("useAuth must be used inside AuthProvider");
    return ctx;
  }
  ```

  ```typescript src/auth/provider.tsx theme={null}
  import {
    useState,
    useEffect,
    useCallback,
    ReactNode,
  } from "react";
  import * as SecureStore from "expo-secure-store";
  import { socketfi } from "../socketfi/client";
  import { AuthContext, Session } from "./context";

  const SESSION_KEY = "socketfi_session";

  export function AuthProvider({ children }: { children: ReactNode }) {
    const [loading, setLoading] = useState(true);
    const [session, setSession] = useState<Session | null>(null);

    // Restore persisted session on mount
    useEffect(() => {
      SecureStore.getItemAsync(SESSION_KEY).then((stored) => {
        if (stored) {
          try {
            setSession(JSON.parse(stored));
          } catch {
            SecureStore.deleteItemAsync(SESSION_KEY);
          }
        }
        setLoading(false);
      });
    }, []);

    const login = useCallback(async () => {
      setLoading(true);
      try {
        const result = await socketfi.authenticate();
        const newSession = result as Session;
        setSession(newSession);
        await SecureStore.setItemAsync(SESSION_KEY, JSON.stringify(newSession));
      } finally {
        setLoading(false);
      }
    }, []);

    const logout = useCallback(async () => {
      setSession(null);
      await SecureStore.deleteItemAsync(SESSION_KEY);
    }, []);

    return (
      <AuthContext.Provider
        value={{ loading, authenticated: !!session, session, login, logout }}
      >
        {children}
      </AuthContext.Provider>
    );
  }
  ```

  <Warning>
    Do **not** use `AsyncStorage` to store `socketfiAccessToken`. It is unencrypted and readable by other processes on a rooted device. Always use `expo-secure-store` for sensitive data in production.
  </Warning>

  ### Authenticate users

  Build a login screen that calls `login()` from the auth context. The SDK opens the SocketFi hosted flow in a browser sheet; once the user completes passkey authentication, the sheet dismisses and your `AuthProvider` receives the session automatically.

  ```typescript src/screens/LoginScreen.tsx theme={null}
  import { View, Text, TouchableOpacity, ActivityIndicator, StyleSheet } from "react-native";
  import { useAuth } from "../auth/context";

  export function LoginScreen() {
    const { loading, login } = useAuth();

    return (
      <View style={styles.container}>
        <Text style={styles.title}>Welcome to My App</Text>
        <Text style={styles.subtitle}>
          Sign in with your passkey — no password needed.
        </Text>

        <TouchableOpacity
          style={[styles.button, loading && styles.buttonDisabled]}
          onPress={login}
          disabled={loading}
        >
          {loading ? (
            <ActivityIndicator color="#fff" />
          ) : (
            <Text style={styles.buttonText}>Sign In with Passkey</Text>
          )}
        </TouchableOpacity>
      </View>
    );
  }

  const styles = StyleSheet.create({
    container: { flex: 1, alignItems: "center", justifyContent: "center", padding: 24 },
    title: { fontSize: 24, fontWeight: "700", marginBottom: 8 },
    subtitle: { fontSize: 16, color: "#6B7280", marginBottom: 32, textAlign: "center" },
    button: { backgroundColor: "#4F46E5", paddingVertical: 14, paddingHorizontal: 32, borderRadius: 8 },
    buttonDisabled: { opacity: 0.6 },
    buttonText: { color: "#fff", fontWeight: "600", fontSize: 16 },
  });
  ```

  Display the wallet address after sign-in:

  ```typescript src/screens/HomeScreen.tsx theme={null}
  import { View, Text, StyleSheet } from "react-native";
  import { useAuth } from "../auth/context";

  export function HomeScreen() {
    const { session, logout } = useAuth();

    return (
      <View style={styles.container}>
        <Text style={styles.label}>Your Stellar Wallet</Text>
        <Text style={styles.address}>{session?.userProfile.wallet}</Text>

        <Text onPress={logout} style={styles.signOut}>
          Sign out
        </Text>
      </View>
    );
  }

  const styles = StyleSheet.create({
    container: { flex: 1, padding: 24 },
    label: { fontSize: 12, color: "#6B7280", marginBottom: 4 },
    address: { fontSize: 14, fontFamily: "monospace", marginBottom: 32 },
    signOut: { color: "#EF4444" },
  });
  ```

  ### Request transactions

  Call `socketfi.requestTransaction()` for any state-changing Soroban contract operation. The SDK opens the SocketFi approval screen so the user can review and sign, then returns the result to your app.

  ```typescript src/services/token.ts theme={null}
  import { socketfi } from "../socketfi/client";

  export async function transferTokens(
    from: string,
    to: string,
    amount: bigint
  ): Promise<string> {
    const result = await socketfi.requestTransaction({
      contractId: process.env.EXPO_PUBLIC_TOKEN_CONTRACT_ID!,
      method: "transfer",
      args: [from, to, amount],
    });

    if (!result.success) {
      throw new Error("Transaction failed");
    }

    return result.transactionHash!;
  }
  ```

  Handle loading and error state in the screen:

  ```typescript src/screens/SendScreen.tsx theme={null}
  import { useState } from "react";
  import { View, Text, TouchableOpacity, ActivityIndicator, Alert, StyleSheet } from "react-native";
  import { transferTokens } from "../services/token";
  import { useAuth } from "../auth/context";

  export function SendScreen() {
    const { session } = useAuth();
    const [loading, setLoading] = useState(false);

    const handleSend = async () => {
      if (!session) return;
      setLoading(true);
      try {
        const hash = await transferTokens(
          session.userProfile.wallet,
          "RECIPIENT_ADDRESS",
          100n
        );
        Alert.alert("Success", `Transaction submitted.\n\n${hash}`);
      } catch (err: unknown) {
        const message = err instanceof Error ? err.message : "Transaction failed";
        Alert.alert("Error", message);
      } finally {
        setLoading(false);
      }
    };

    return (
      <View style={styles.container}>
        <TouchableOpacity
          style={[styles.button, loading && styles.buttonDisabled]}
          onPress={handleSend}
          disabled={loading}
        >
          {loading ? (
            <ActivityIndicator color="#fff" />
          ) : (
            <Text style={styles.buttonText}>Send 100 Tokens</Text>
          )}
        </TouchableOpacity>
      </View>
    );
  }

  const styles = StyleSheet.create({
    container: { flex: 1, padding: 24, justifyContent: "center" },
    button: { backgroundColor: "#4F46E5", paddingVertical: 14, borderRadius: 8, alignItems: "center" },
    buttonDisabled: { opacity: 0.6 },
    buttonText: { color: "#fff", fontWeight: "600", fontSize: 16 },
  });
  ```
</Steps>

## Recommended project structure

```text theme={null}
src/
├── socketfi/
│   └── client.ts          # SocketFi instance
├── auth/
│   ├── context.ts          # Context type + useAuth hook
│   └── provider.tsx        # AuthProvider with SecureStore
├── screens/
│   ├── LoginScreen.tsx
│   ├── HomeScreen.tsx
│   └── SendScreen.tsx
├── services/
│   └── token.ts            # requestTransaction wrappers
├── navigation/
│   └── RootNavigator.tsx
└── utils/
    └── linking.ts          # Deep-link handler hook
```

## Production tips

* **Test on physical devices.** Passkey prompts and deep-link callbacks behave differently in simulators. Always validate your auth and transaction flows on real iOS and Android hardware before shipping.
* **Test on both iOS and Android.** Deep-link handling has platform-specific edge cases — cover both in your QA pass.
* **Handle offline gracefully.** Mobile devices lose connectivity frequently. Detect the network state and disable transaction requests when offline rather than letting them fail silently.
* **Verify tokens server-side.** Use `@socketfi/server`'s `verifyAuth()` to validate `socketfiAccessToken` in your API before granting access to protected resources.
* **Monitor authentication and transaction failures.** Pipe `onError` events to your crash/observability provider so you can detect regressions in the auth flow after updates.
