> ## 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 Application

> Step-by-step guide to adding passkey-powered smart wallets to your React app — from SDK install through backend token verification.

SocketFi gives your React application embedded, passkey-secured smart wallets on Stellar/Soroban without browser extensions, seed phrases, or external wallet apps. This guide walks you through every integration step, from installing the SDK to verifying sessions on your backend, so you can ship a fully working wallet experience.

## Prerequisites

Before you begin, make sure you have:

* React 18 or later
* TypeScript (recommended)
* A SocketFi application and **Client ID** from the SocketFi Dashboard

<Steps>
  ### Install @socketfi/react

  Install the React SDK using your preferred package manager.

  <CodeGroup>
    ```bash npm theme={null}
    npm install @socketfi/react
    ```

    ```bash yarn theme={null}
    yarn add @socketfi/react
    ```

    ```bash pnpm theme={null}
    pnpm add @socketfi/react
    ```
  </CodeGroup>

  ### Create the SocketFi client

  Create a dedicated client file so the same instance is shared across your app. Store your Client ID in an environment variable — never hard-code it.

  Add the variable to your `.env` file:

  ```bash .env theme={null}
  VITE_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";

  export const socketfi = new SocketFi({
    clientId: import.meta.env.VITE_SOCKETFI_CLIENT_ID,
    network: "TESTNET", // switch to "MAINNET" for production
    brand: {
      appName: "My Application",
      primaryColor: "#4F46E5",
    },
    onError(error) {
      // surface errors to your monitoring provider here
      console.error("[SocketFi]", error);
    },
  });
  ```

  <Tip>
    The `brand` config is optional but recommended — your app name and accent color appear inside the SocketFi-hosted authentication flow so users know which app they're authenticating with.
  </Tip>

  ### Build the AuthProvider with React Context

  Wrap your application in an `AuthProvider` that exposes session state and helpers throughout your component tree.

  ```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: () => 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 { 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(() => {
      const stored = localStorage.getItem(SESSION_KEY);
      if (stored) {
        try {
          setSession(JSON.parse(stored));
        } catch {
          localStorage.removeItem(SESSION_KEY);
        }
      }
      setLoading(false);
    }, []);

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

    const logout = useCallback(() => {
      setSession(null);
      localStorage.removeItem(SESSION_KEY);
    }, []);

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

  Mount the provider at your application root:

  ```typescript src/main.tsx theme={null}
  import { StrictMode } from "react";
  import { createRoot } from "react-dom/client";
  import { AuthProvider } from "./auth/provider";
  import App from "./App";

  createRoot(document.getElementById("root")!).render(
    <StrictMode>
      <AuthProvider>
        <App />
      </AuthProvider>
    </StrictMode>
  );
  ```

  ### Add an authentication button

  Build a `LoginButton` component that calls `login()` from your context. The SocketFi hosted flow handles both new user registration (passkey creation + wallet setup) and returning user sign-in automatically.

  ```typescript src/components/LoginButton.tsx theme={null}
  import { useAuth } from "../auth/context";

  export function LoginButton() {
    const { loading, authenticated, login, logout } = useAuth();

    if (authenticated) {
      return (
        <button onClick={logout} disabled={loading}>
          Sign Out
        </button>
      );
    }

    return (
      <button onClick={login} disabled={loading}>
        {loading ? "Signing in…" : "Sign In with Passkey"}
      </button>
    );
  }
  ```

  ### Access wallet data in components

  Once authenticated, read the wallet address and access token anywhere in your tree via `useAuth()`.

  ```typescript src/components/WalletCard.tsx theme={null}
  import { useAuth } from "../auth/context";

  export function WalletCard() {
    const { session } = useAuth();

    if (!session) return null;

    return (
      <div>
        <p>
          <strong>Wallet:</strong> {session.userProfile.wallet}
        </p>
        <p>
          <strong>User ID:</strong> {session.userProfile.id}
        </p>
      </div>
    );
  }
  ```

  You can also create a custom hook to make component code cleaner:

  ```typescript src/auth/hooks.ts theme={null}
  import { useAuth } from "./context";

  export function useWalletAddress(): string | null {
    const { session } = useAuth();
    return session?.userProfile.wallet ?? null;
  }

  export function useAccessToken(): string | null {
    const { session } = useAuth();
    return session?.socketfiAccessToken ?? null;
  }
  ```

  ### Request transactions

  Call `socketfi.requestTransaction()` for any state-changing Soroban contract operation. The SDK opens the SocketFi approval UI so the user can review the contract, method, arguments, and fees before signing.

  ```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: import.meta.env.VITE_TOKEN_CONTRACT_ID,
      method: "transfer",
      args: [from, to, amount],
    });

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

    return result.transactionHash!;
  }
  ```

  Use it inside a component with proper loading and error state:

  ```typescript src/components/SendForm.tsx theme={null}
  import { useState } from "react";
  import { transferTokens } from "../services/token";
  import { useWalletAddress } from "../auth/hooks";

  export function SendForm() {
    const walletAddress = useWalletAddress();
    const [loading, setLoading] = useState(false);
    const [txHash, setTxHash] = useState<string | null>(null);
    const [error, setError] = useState<string | null>(null);

    const handleSend = async () => {
      if (!walletAddress) return;
      setLoading(true);
      setError(null);
      try {
        const hash = await transferTokens(walletAddress, "RECIPIENT_ADDRESS", 100n);
        setTxHash(hash);
      } catch (err: unknown) {
        setError(err instanceof Error ? err.message : "Transaction failed");
      } finally {
        setLoading(false);
      }
    };

    return (
      <div>
        <button onClick={handleSend} disabled={loading}>
          {loading ? "Processing…" : "Send 100 Tokens"}
        </button>
        {txHash && <p>Sent! Transaction: {txHash}</p>}
        {error && <p>Error: {error}</p>}
      </div>
    );
  }
  ```

  ### Set up backend verification

  Your API endpoints should verify the `socketfiAccessToken` using the `@socketfi/server` package before trusting any client request.

  Install the server SDK:

  ```bash theme={null}
  npm install @socketfi/server
  ```

  Create an Express middleware:

  ```typescript src/server/middleware/auth.ts theme={null}
  import { Request, Response, NextFunction } from "express";
  import { verifyAuth } from "@socketfi/server";

  export async function requireAuth(
    req: Request,
    res: Response,
    next: NextFunction
  ) {
    const header = req.headers.authorization;
    if (!header?.startsWith("Bearer ")) {
      return res.status(401).json({ error: "Missing authorization header" });
    }

    const token = header.slice(7);

    try {
      const result = await verifyAuth(token);

      if (!result.valid) {
        return res.status(401).json({ error: "Invalid or expired token" });
      }

      // Attach verified identity to the request
      (req as any).socketfi = {
        userId: result.user.id,
        walletAddress: result.wallet.address,
      };

      next();
    } catch {
      return res.status(401).json({ error: "Token verification failed" });
    }
  }
  ```

  Apply the middleware to any protected route:

  ```typescript src/server/routes/profile.ts theme={null}
  import { Router } from "express";
  import { requireAuth } from "../middleware/auth";

  const router = Router();

  router.get("/profile", requireAuth, (req, res) => {
    const { userId, walletAddress } = (req as any).socketfi;
    res.json({ userId, walletAddress });
  });

  export default router;
  ```

  And send the token from your frontend:

  ```typescript src/api/client.ts theme={null}
  import { useAccessToken } from "../auth/hooks";

  export function useApiClient() {
    const token = useAccessToken();

    return {
      async get(path: string) {
        const res = await fetch(path, {
          headers: { Authorization: `Bearer ${token}` },
        });
        if (!res.ok) throw new Error(`API error ${res.status}`);
        return res.json();
      },
    };
  }
  ```
</Steps>

## Protected routes

Redirect unauthenticated users away from sensitive pages:

```typescript src/components/ProtectedRoute.tsx theme={null}
import { Navigate } from "react-router-dom";
import { useAuth } from "../auth/context";
import { ReactNode } from "react";

export function ProtectedRoute({ children }: { children: ReactNode }) {
  const { loading, authenticated } = useAuth();

  if (loading) return <p>Loading…</p>;
  if (!authenticated) return <Navigate to="/login" replace />;

  return <>{children}</>;
}
```

## Recommended project structure

```text theme={null}
src/
├── socketfi/
│   └── client.ts        # SocketFi instance
├── auth/
│   ├── context.ts        # Context type + useAuth hook
│   ├── provider.tsx      # AuthProvider component
│   └── hooks.ts          # useWalletAddress, useAccessToken
├── components/
│   ├── LoginButton.tsx
│   ├── WalletCard.tsx
│   └── ProtectedRoute.tsx
├── services/
│   └── token.ts          # requestTransaction wrappers
└── server/
    └── middleware/
        └── auth.ts        # verifyAuth middleware
```

## Production tips

<Note>
  Switch `network` from `"TESTNET"` to `"MAINNET"` when you deploy to production, and make sure `VITE_SOCKETFI_CLIENT_ID` points to your production Client ID.
</Note>

* **Never trust client-side state alone.** Always verify `socketfiAccessToken` with `@socketfi/server` before granting access to sensitive backend resources.
* **Use `localStorage` for convenience, `sessionStorage` for stricter security.** For highly sensitive applications, clear the session on tab close.
* **Wrap every SDK call in `try/catch`.** Users can cancel passkey prompts, network requests can time out, and contracts can revert.
* **Show descriptive intent before transactions.** Users should understand exactly what they are signing — display the recipient, amount, and contract clearly in your UI before calling `requestTransaction`.
* **Monitor errors in production.** Pipe `onError` events to your observability tool (Sentry, Datadog, etc.) so you catch auth failures and transaction errors before users report them.
