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

# Full-Stack SocketFi App: Reference Architecture Guide

> A reference architecture for a complete SocketFi integration — React frontend, Express backend, authentication, wallet dashboard, and transactions.

This page describes **SocketPay**, a reference application that demonstrates every major SocketFi capability working together end-to-end. Rather than listing every line of code, it explains the architecture — how the layers connect, what each module does, and which patterns you should replicate in your own production application.

By the time you finish reading this page, you'll understand the recommended full-stack SocketFi structure and be ready to adapt it to your own product.

## What SocketPay covers

<CardGroup cols={2}>
  <Card title="Authentication" icon="key">
    Passkey-powered sign-in that creates or loads a smart wallet on first use.
  </Card>

  <Card title="Wallet dashboard" icon="wallet">
    Real-time balance reads and asset display using `readContract()`.
  </Card>

  <Card title="Token transfers" icon="arrow-right-arrow-left">
    Full transfer form with status lifecycle, approval flow, and post-tx refresh.
  </Card>

  <Card title="Backend verification" icon="server">
    Express API with `verifyAuth()` middleware protecting every sensitive route.
  </Card>

  <Card title="Activity feed" icon="list">
    Transaction history stored server-side and displayed in the frontend.
  </Card>

  <Card title="Security center" icon="shield">
    Credential rotation and account recovery entry points in a settings page.
  </Card>
</CardGroup>

## High-level architecture

```text theme={null}
┌─────────────────────────────────────┐
│           React Frontend            │
│  Auth → Dashboard → Transactions    │
│  @socketfi/react                    │
└────────────────┬────────────────────┘
                 │  HTTPS API calls
                 │  Authorization: Bearer <token>
                 ▼
┌─────────────────────────────────────┐
│          Express Backend            │
│  authMiddleware → verifyAuth()      │
│  @socketfi/server                   │
└────────────────┬────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────┐
│        SocketFi Services            │
│  Smart Wallet · Passkey Auth        │
└────────────────┬────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────┐
│     Soroban / Stellar Network       │
│  Smart contracts · On-chain state   │
└─────────────────────────────────────┘
```

## Repository structure

```text theme={null}
socketpay/
├── apps/
│   └── web/                        # React frontend (Vite + TypeScript)
│       └── src/
│           ├── socketfi/
│           │   └── client.ts       # SDK singleton
│           ├── auth/
│           │   ├── AuthContext.tsx
│           │   ├── AuthProvider.tsx
│           │   └── useAuth.ts
│           ├── wallet/
│           │   ├── useBalance.ts   # readContract() hook
│           │   └── WalletCard.tsx
│           ├── transactions/
│           │   ├── transactionService.ts
│           │   ├── TransferForm.tsx
│           │   └── TransactionHistory.tsx
│           ├── settings/
│           │   └── SecurityPage.tsx
│           ├── pages/
│           │   ├── LoginPage.tsx
│           │   ├── DashboardPage.tsx
│           │   └── SettingsPage.tsx
│           └── App.tsx
│
└── server/                         # Express backend (Node.js + TypeScript)
    └── src/
        ├── middleware/
        │   └── auth.ts             # verifyAuth() middleware
        ├── routes/
        │   ├── profile.ts
        │   ├── wallet.ts
        │   └── activity.ts
        ├── services/
        │   └── activityService.ts  # Persist & query tx history
        └── app.ts
```

## Environment variables

```bash theme={null}
# apps/web/.env
VITE_SOCKETFI_CLIENT_ID=your_client_id
VITE_TOKEN_CONTRACT_ID=your_token_contract
VITE_API_BASE_URL=https://api.socketpay.app

# server/.env
DATABASE_URL=postgres://...
PORT=3000
```

## Authentication flow

SDK initialization is the first thing that runs. The `AuthProvider` wraps the entire application and restores any persisted session before rendering routes.

```typescript apps/web/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: "MAINNET",
});
```

```text theme={null}
User lands on /login
       ↓
AuthProvider mounts → reads localStorage for existing session
       ↓  (no session)
LoginPage renders "Continue with passkey" button
       ↓
socketfi.authenticate() → hosted passkey flow
       ↓
{ socketfiAccessToken, userProfile: { id, wallet } } returned
       ↓
Session stored in localStorage → setSession(result)
       ↓
ProtectedRoute passes → user lands on /dashboard
```

See [React Authentication](/examples/react-authentication) for the complete file-by-file implementation.

## Wallet dashboard

The dashboard reads balances directly from Soroban using `readContract()` and caches them with React Query. After any transaction, the query is invalidated so the display stays accurate.

```typescript apps/web/src/wallet/useBalance.ts theme={null}
import { useQuery } from "@tanstack/react-query";
import { socketfi } from "../socketfi/client";

export function useBalance(walletAddress: string, contractId: string) {
  return useQuery({
    queryKey: ["balance", walletAddress, contractId],
    queryFn: async () => {
      const result = await socketfi.readContract({
        contractId,
        method: "balance",
        args: [walletAddress],
      });
      return result as string;
    },
    enabled: !!walletAddress,
    staleTime: 30_000, // 30 seconds
  });
}
```

```text theme={null}
Dashboard mounts
       ↓
useBalance() → socketfi.readContract({ method: "balance", args: [wallet] })
       ↓
Soroban simulation runs (no user approval needed for reads)
       ↓
Balance displayed
       ↓  (after a transfer)
queryClient.invalidateQueries(["balance", wallet])  →  fresh fetch
```

## Transaction flow

Every state-changing operation goes through `requestTransaction()`. SocketPay wraps it in a service layer, leaving `TransferForm` free of SDK-specific logic.

```typescript apps/web/src/transactions/transactionService.ts theme={null}
import { socketfi } from "../socketfi/client";

export async function sendTokens(
  contractId: string,
  from: string,
  to: string,
  amount: string
) {
  return socketfi.requestTransaction({
    contractId,
    method: "transfer",
    args: [from, to, amount],
  });
}
```

```text theme={null}
User fills TransferForm and clicks Send
       ↓
Client-side validation (address format, positive amount)
       ↓
transactionService.sendTokens() called
       ↓
Hosted approval screen launches
       ↓
User approves with passkey
       ↓
{ success: true, transactionHash: "abc123…" }
       ↓
queryClient.invalidateQueries(["balance"])  →  balance refreshed
Server receives POST /api/activity with hash → stored in DB
       ↓
ActivityFeed re-fetches and shows the new entry
```

See [React Transactions](/examples/react-transactions) for the complete form and status-lifecycle implementation.

## Backend verification

The Express backend protects all `/api` routes with `authMiddleware`. No route handler ever trusts user-supplied identity.

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

export async function authMiddleware(req: Request, res: Response, next: NextFunction) {
  const token = req.headers.authorization?.replace("Bearer ", "");
  if (!token) return res.status(401).json({ message: "Unauthorized." });

  const result = await verifyAuth(token);
  if (!result.valid) return res.status(401).json({ message: "Unauthorized." });

  req.user = { id: result.user.id, wallet: result.wallet };
  next();
}
```

```typescript server/src/app.ts theme={null}
import express from "express";
import { authMiddleware } from "./middleware/auth";
import profileRoutes from "./routes/profile";
import walletRoutes from "./routes/wallet";
import activityRoutes from "./routes/activity";

const app = express();
app.use(express.json());

app.get("/health", (_req, res) => res.json({ ok: true }));

app.use("/api", authMiddleware, profileRoutes);
app.use("/api", authMiddleware, walletRoutes);
app.use("/api", authMiddleware, activityRoutes);

export default app;
```

See [Server Verification](/examples/server-verification) for the full middleware, route, and TypeScript type-extension implementation.

## Activity feed

SocketPay stores transaction hashes server-side after every successful transfer. The frontend fetches them with React Query.

```typescript theme={null}
// Activity record shape
interface ActivityRecord {
  hash: string;
  type: "transfer" | "deposit" | "withdrawal" | "vote" | "stake";
  status: "pending" | "confirmed" | "failed";
  timestamp: string;
  wallet: string;
}
```

```typescript server/src/routes/activity.ts theme={null}
import { Router } from "express";

const router = Router();

router.get("/activity", async (req, res) => {
  const activities = await activityService.getByWallet(req.user!.wallet);
  res.json(activities);
});

router.post("/activity", async (req, res) => {
  const { hash, type } = req.body as { hash: string; type: string };
  await activityService.create({ wallet: req.user!.wallet, hash, type });
  res.status(201).json({ created: true });
});

export default router;
```

## Security center

Give users a dedicated settings page to manage their passkey credentials and initiate account recovery if needed.

```text theme={null}
Security Settings
├── Registered passkeys (list + remove)
├── Credential rotation  →  generate new passkey, authorize change
└── Account recovery     →  entry point if device is lost
```

```typescript apps/web/src/settings/SecurityPage.tsx theme={null}
import { useAuth } from "../auth/useAuth";

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

  return (
    <main>
      <h1>Security</h1>
      <section>
        <h2>Your wallet</h2>
        <code>{session?.userProfile?.wallet}</code>
      </section>
      <section>
        <h2>Credential rotation</h2>
        <p>Register a new passkey to replace your current one.</p>
        {/* Link to credential rotation flow */}
      </section>
      <section>
        <h2>Account recovery</h2>
        <p>Restore access if you lose your device.</p>
        {/* Link to recovery flow */}
      </section>
      <button onClick={logout}>Sign out</button>
    </main>
  );
}
```

## React Query setup

Wrap your app with `QueryClientProvider` at the root alongside `AuthProvider`:

```typescript apps/web/src/main.tsx theme={null}
import React from "react";
import ReactDOM from "react-dom/client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { AuthProvider } from "./auth/AuthProvider";
import App from "./App";

const queryClient = new QueryClient({
  defaultOptions: {
    queries: { staleTime: 30_000, retry: 2 },
  },
});

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <AuthProvider>
        <App />
      </AuthProvider>
    </QueryClientProvider>
  </React.StrictMode>
);
```

## Recommended production stack

| Layer              | Technology                                   |
| ------------------ | -------------------------------------------- |
| Frontend framework | React 18+ with TypeScript                    |
| Bundler            | Vite or Next.js 14+                          |
| Data fetching      | TanStack Query (React Query)                 |
| Frontend SDK       | `@socketfi/react`                            |
| Backend runtime    | Node.js 20+                                  |
| Backend framework  | Express or Fastify                           |
| Backend SDK        | `@socketfi/server`                           |
| Database           | PostgreSQL (activity, user metadata)         |
| Hosting            | Any cloud provider with HTTPS support        |
| Observability      | Error tracking + APM (e.g., Sentry, Datadog) |

## Production checklist

```text theme={null}
✓ HTTPS enabled on all domains
✓ Environment variables set correctly (TESTNET → MAINNET)
✓ authMiddleware applied to every protected API route
✓ verifyAuth() called — never trust client-supplied wallet addresses
✓ Sessions stored securely (localStorage on web, SecureStore on mobile)
✓ Transaction hashes persisted server-side for history and support
✓ Balance queries invalidated after every confirmed transaction
✓ Error boundaries wrapping major UI sections
✓ USER_CANCELLED handled gracefully (not treated as an app error)
✓ Credential rotation and recovery entry points available to users
✓ Monitoring and alerting enabled for auth failures and tx errors
```

## Full user lifecycle

```text theme={null}
User arrives at socketpay.app
       ↓
Passkey authentication (sign-up or sign-in)
       ↓
Smart wallet created or loaded
       ↓
Dashboard: balance read from Soroban
       ↓
User initiates a transfer
       ↓
Hosted approval screen — user signs with passkey
       ↓
Transaction confirmed — transactionHash stored server-side
       ↓
Balance refreshed — activity feed updated
       ↓
User visits Security settings
       ↓
Optional: rotate credential or initiate recovery
```
