diff --git a/docs.json b/docs.json
index ea91f3b7..661f35d8 100644
--- a/docs.json
+++ b/docs.json
@@ -36,189 +36,181 @@
}
},
"navigation": {
- "tabs": [{
- "tab": "Auth",
- "groups": [
- {
- "group": "Overview",
- "pages": [
- "index",
- "overview/pricing",
- "overview/bring-your-app-to-production",
- {
- "group": "Login Options",
- "icon": "arrow-right-to-bracket",
- "pages": [
- "overview/login-options",
- "overview/passkeys"
- ]
- },
- "overview/faqs",
- "overview/authentication-flows",
- "overview/changelog"
- ]
- },
- {
- "group": "AI Prompts",
- "icon": "robot",
- "pages": [
- "ai-prompts/overview",
- "ai-prompts/nextjs",
- "ai-prompts/react",
+ "tabs": [
+ {
+ "tab": "Auth",
+ "groups": [
+ {
+ "group": "Overview",
+ "pages": [
+ "index",
+ "overview/pricing",
+ "overview/bring-your-app-to-production",
+ {
+ "group": "Login Options",
+ "icon": "arrow-right-to-bracket",
+ "pages": ["overview/login-options", "overview/passkeys"]
+ },
+ "overview/faqs",
+ "overview/authentication-flows",
+ "overview/changelog"
+ ]
+ },
+ {
+ "group": "AI Prompts",
+ "icon": "robot",
+ "pages": [
+ "ai-prompts/overview",
+ "ai-prompts/nextjs",
+ "ai-prompts/react",
+ {
+ "group": "Python",
+ "icon": "python",
+ "pages": [
+ "ai-prompts/python/fastapi",
+ "ai-prompts/python/flask",
+ "ai-prompts/python/django"
+ ]
+ },
+ {
+ "group": "Web3",
+ "icon": "wallet",
+ "pages": ["ai-prompts/web3/solana", "ai-prompts/web3/ethereum"]
+ },
+ {
+ "group": "No-code Platforms",
+ "icon": "wand-magic-sparkles",
+ "pages": [
+ "ai-prompts/no-code/lovable",
+ "ai-prompts/no-code/bolt",
+ "ai-prompts/no-code/v0",
+ "ai-prompts/no-code/replit"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Integration",
+ "pages": [
+ "integration/react",
+ "integration/nextjs",
+ "integration/vanillajs",
+ {
+ "group": "Node.JS",
+ "icon": "node-js",
+ "pages": [
+ "integration/nodejs",
+ "integration/nodejs/express",
+ "integration/nodejs/hono",
+ "integration/nodejs/fastify"
+ ]
+ },
+ {
+ "group": "Python",
+ "icon": "python",
+ "pages": [
+ "integration/python",
+ "integration/python/fastapi",
+ "integration/python/flask",
+ "integration/python/django"
+ ]
+ },
+ "integration/other",
+ "integration/error-codes"
+ ]
+ },
+ {
+ "group": "Web3",
+ "pages": [
+ "web3/embedded-wallets",
+ "web3/ethereum-evm",
+ "web3/solana"
+ ]
+ },
+ {
+ "group": "Libraries & Tools",
+ "icon": "code",
+ "pages": ["libraries/auth-verify"]
+ },
+ {
+ "group": "Mobile",
+ "pages": [
+ "mobile/installation",
+ "mobile/quick-start",
+ "mobile/authentication-only",
+ "mobile/web3-wallet",
+ "mobile/api-reference"
+ ]
+ },
+ {
+ "group": "Guides",
+ "pages": ["guides/add-auth-to-mcp"]
+ }
+ ],
+ "global": {
+ "anchors": [
{
- "group": "Python",
- "icon": "python",
- "pages": [
- "ai-prompts/python/fastapi",
- "ai-prompts/python/flask",
- "ai-prompts/python/django"
- ]
+ "anchor": "About us",
+ "href": "https://www.civic.com/about",
+ "icon": "circle-info"
},
{
- "group": "Web3",
- "icon": "wallet",
- "pages": [
- "ai-prompts/web3/solana",
- "ai-prompts/web3/ethereum"
- ]
+ "anchor": "Blog",
+ "href": "https://www.civic.com/blog",
+ "icon": "newspaper"
},
{
- "group": "No-code Platforms",
- "icon": "wand-magic-sparkles",
- "pages": [
- "ai-prompts/no-code/lovable",
- "ai-prompts/no-code/bolt",
- "ai-prompts/no-code/v0",
- "ai-prompts/no-code/replit"
- ]
+ "anchor": "Join Our Community",
+ "href": "https://join.slack.com/t/civic-developers/shared_invite/zt-37tv9fyo7-aDT43mUjOFQwdQFmfZLTRw",
+ "icon": "slack"
}
]
- },
- {
- "group": "Integration",
- "pages": [
- "integration/react",
- "integration/nextjs",
- "integration/vanillajs",
- {
- "group": "Node.JS",
- "icon": "node-js",
- "pages": [
- "integration/nodejs",
- "integration/nodejs/express",
- "integration/nodejs/hono",
- "integration/nodejs/fastify"
- ]
- },
- {
- "group": "Python",
- "icon": "python",
- "pages": [
- "integration/python",
- "integration/python/fastapi",
- "integration/python/flask",
- "integration/python/django"
- ]
- },
- {
- "group": "Mobile",
- "icon": "mobile-screen-button",
- "pages": [
- "integration/mobile/react-native"
- ]
- },
- "integration/other",
- "integration/error-codes"
- ]
- },
- {
- "group": "Web3",
- "pages": [
- "web3/embedded-wallets",
- "web3/ethereum-evm",
- "web3/solana"
- ]
- },
- {
- "group": "Libraries & Tools",
- "icon": "code",
- "pages": [
- "libraries/auth-verify"
- ]
- },
- {
- "group": "Guides",
- "pages": [
- "guides/add-auth-to-mcp"
- ]
}
- ],
- "global": {
- "anchors": [
+ },
+ {
+ "tab": "Labs",
+ "groups": [
{
- "anchor": "About us",
- "href": "https://www.civic.com/about",
- "icon": "circle-info"
+ "group": " ",
+ "pages": ["labs", "labs/flask-status", "labs/feedback"]
},
{
- "anchor": "Blog",
- "href": "https://www.civic.com/blog",
- "icon": "newspaper"
+ "group": "Flasks",
+ "pages": [
+ "labs/projects/mcp-hub",
+ "labs/projects/x402-mcp",
+ "labs/projects/guardrail-proxy",
+ "labs/projects/bodyguard",
+ "labs/projects/passthrough-proxy",
+ "labs/projects/civic-knowledge"
+ ]
},
{
- "anchor": "Join Our Community",
- "href": "https://join.slack.com/t/civic-developers/shared_invite/zt-37tv9fyo7-aDT43mUjOFQwdQFmfZLTRw",
- "icon": "slack"
+ "group": "Concepts",
+ "pages": [
+ "labs/concepts/mcp",
+ "labs/concepts/guardrails",
+ "labs/concepts/prompt-injection",
+ "labs/concepts/auth-strategies",
+ "labs/concepts/hooks",
+ "labs/concepts/rag"
+ ]
+ },
+ {
+ "group": "🔓Integration",
+ "pages": [
+ "labs/private/getting-started",
+ "labs/private/mcp-hub",
+ "labs/private/x402-mcp",
+ "labs/private/guardrail-proxy",
+ "labs/private/bodyguard",
+ "labs/private/passthrough-proxy",
+ "labs/private/civic-knowledge"
+ ]
}
]
}
- },{
- "tab": "Labs",
- "groups": [
- {
- "group": " ",
- "pages": [
- "labs",
- "labs/flask-status",
- "labs/feedback"
- ]
- },
- {
- "group": "Flasks",
- "pages": [
- "labs/projects/mcp-hub",
- "labs/projects/x402-mcp",
- "labs/projects/guardrail-proxy",
- "labs/projects/bodyguard",
- "labs/projects/passthrough-proxy",
- "labs/projects/civic-knowledge"
- ]
- },
- {
- "group": "Concepts",
- "pages": [
- "labs/concepts/mcp",
- "labs/concepts/guardrails",
- "labs/concepts/prompt-injection",
- "labs/concepts/auth-strategies",
- "labs/concepts/hooks",
- "labs/concepts/rag"
- ]
- },
- {
- "group": "🔓Integration",
- "pages": [
- "labs/private/getting-started",
- "labs/private/mcp-hub",
- "labs/private/x402-mcp",
- "labs/private/guardrail-proxy",
- "labs/private/bodyguard",
- "labs/private/passthrough-proxy",
- "labs/private/civic-knowledge"
- ]
- }
- ]
- }]
+ ]
},
"logo": {
"light": "/logo/light.png",
diff --git a/index.mdx b/index.mdx
index cb8e4fd5..95262179 100644
--- a/index.mdx
+++ b/index.mdx
@@ -132,7 +132,7 @@ Choose your framework for instructions on how to integrate Civic Auth into your
Python
-
+
React Native
diff --git a/mobile/api-reference.mdx b/mobile/api-reference.mdx
new file mode 100644
index 00000000..03054650
--- /dev/null
+++ b/mobile/api-reference.mdx
@@ -0,0 +1,424 @@
+---
+title: "API Reference"
+icon: "book"
+description: "Complete API reference for React Native Civic Auth integration."
+public: true
+---
+
+## AuthContext API
+
+### AuthContextType
+
+Main authentication context interface for both authentication-only and Web3 integrations.
+
+```typescript
+interface AuthContextType {
+ state: AuthState;
+ signIn?: () => Promise;
+ signOut?: () => Promise;
+ web3Client?: Web3Client | null; // Only available in Web3 integration
+}
+```
+
+### AuthState
+
+Authentication state object containing user session information.
+
+```typescript
+interface AuthState {
+ isLoading: boolean;
+ isAuthenticated: boolean;
+ user?: AuthUser;
+ accessToken?: string;
+ refreshToken?: string;
+ idToken?: string;
+ expiresIn?: number;
+}
+```
+
+### AuthUser
+
+User profile information returned from Civic Auth.
+
+```typescript
+interface AuthUser {
+ email?: string;
+ name: string;
+ picture?: string;
+ sub: string; // Unique user identifier
+}
+```
+
+### Methods
+
+#### `signIn()`
+
+Initiates the OAuth2 authentication flow.
+
+**Returns:** `Promise`
+
+**Example:**
+```typescript
+const { signIn } = useContext(AuthContext);
+await signIn();
+```
+
+#### `signOut()`
+
+Signs out the user and clears the session.
+
+**Returns:** `Promise`
+
+**Example:**
+```typescript
+const { signOut } = useContext(AuthContext);
+await signOut();
+```
+
+---
+
+## Web3Client API
+
+
+ Web3Client is only available when using the [Web3 Wallet Integration](/mobile/web3-wallet).
+
+
+### Web3Client Interface
+
+Main interface for blockchain operations.
+
+```typescript
+interface Web3Client {
+ solana: SolanaWeb3Client; // Solana wallet operations
+ connected: boolean; // Connection status
+ createWallets(): Promise; // Create embedded wallets
+ disconnect(): Promise; // Disconnect and cleanup
+}
+```
+
+### SolanaWeb3Client Interface
+
+Solana-specific wallet operations.
+
+```typescript
+interface SolanaWeb3Client {
+ readonly address: string; // Wallet public key
+
+ // Core transaction methods
+ sendTransaction(address: string, amount: number): Promise;
+ signTransaction(transaction: Transaction, reason: string): Promise;
+ signMessage(message: string, reason: string): Promise;
+
+ // Utility methods
+ getBalance(): Promise;
+ disconnect(): Promise;
+}
+```
+
+### Methods
+
+#### `createWallets()`
+
+Creates embedded wallets for the authenticated user.
+
+**Returns:** `Promise`
+
+**Requirements:**
+- User must be authenticated
+- Valid ID token required
+
+**Example:**
+```typescript
+const { web3Client } = useContext(AuthContext);
+if (!web3Client?.solana) {
+ const wallets = await web3Client?.createWallets();
+ console.log('Wallets created:', wallets);
+}
+```
+
+#### `sendTransaction(address, amount)`
+
+Sends SOL to a recipient address. Handles transaction creation, signing, and broadcasting.
+
+**Parameters:**
+- `address` (string): Recipient's public key
+- `amount` (number): Amount in SOL
+
+**Returns:** `Promise` - Transaction hash
+
+**Example:**
+```typescript
+const txHash = await web3Client?.solana?.sendTransaction(
+ "RecipientPublicKeyHere",
+ 0.5 // 0.5 SOL
+);
+console.log(`Transaction: ${txHash}`);
+```
+
+#### `signTransaction(transaction, reason)`
+
+Signs a custom Solana transaction.
+
+**Parameters:**
+- `transaction` (Transaction): Solana transaction object
+- `reason` (string): Human-readable reason shown to user
+
+**Returns:** `Promise` - Transaction signature
+
+**Example:**
+```typescript
+import { Transaction, SystemProgram, PublicKey } from "@solana/web3.js";
+
+const transaction = new Transaction().add(
+ SystemProgram.transfer({
+ fromPubkey: new PublicKey(web3Client.solana.address),
+ toPubkey: new PublicKey(recipientAddress),
+ lamports: 0.001 * 1e9,
+ })
+);
+
+const signature = await web3Client?.solana?.signTransaction(
+ transaction,
+ "Approve transfer"
+);
+```
+
+#### `signMessage(message, reason)`
+
+Signs an arbitrary message for verification purposes.
+
+**Parameters:**
+- `message` (string): Message to sign
+- `reason` (string): Human-readable reason shown to user
+
+**Returns:** `Promise` - Base64 encoded signature
+
+**Example:**
+```typescript
+const message = "Verify wallet ownership";
+const signature = await web3Client?.solana?.signMessage(
+ message,
+ "Sign to verify your wallet"
+);
+console.log(`Signature: ${signature}`);
+```
+
+#### `getBalance()`
+
+Gets the current SOL balance of the wallet.
+
+**Returns:** `Promise` - Balance in SOL
+
+**Example:**
+```typescript
+const balance = await web3Client?.solana?.getBalance();
+console.log(`Balance: ${balance} SOL`);
+```
+
+#### `disconnect()`
+
+Disconnects the Web3 client and cleans up resources.
+
+**Returns:** `Promise`
+
+**Example:**
+```typescript
+await web3Client?.disconnect();
+```
+
+---
+
+## Configuration Objects
+
+### CivicWeb3ClientConfig
+
+Configuration for Web3 client initialization.
+
+```typescript
+interface CivicWeb3ClientConfig {
+ solana: {
+ endpoint: string; // Solana RPC endpoint
+ };
+}
+```
+
+**Example:**
+```typescript
+const web3Config: CivicWeb3ClientConfig = {
+ solana: {
+ endpoint: "https://api.devnet.solana.com",
+ },
+};
+```
+
+### AuthRequestConfig
+
+Configuration for OAuth2 authentication.
+
+```typescript
+interface AuthRequestConfig {
+ clientId: string;
+ scopes: string[];
+ redirectUri: string;
+ authorizationEndpoint: string;
+ tokenEndpoint: string;
+ userInfoEndpoint?: string;
+ endSessionEndpoint?: string;
+}
+```
+
+**Example:**
+```typescript
+import { makeRedirectUri } from "expo-auth-session";
+
+const authConfig: AuthRequestConfig = {
+ clientId: "your-client-id",
+ scopes: ["openid", "profile", "email"],
+ redirectUri: makeRedirectUri({ scheme: "your-app" }),
+ authorizationEndpoint: "https://auth.civic.com/oauth/auth",
+ tokenEndpoint: "https://auth.civic.com/oauth/token",
+ userInfoEndpoint: "https://auth.civic.com/oauth/userinfo",
+ endSessionEndpoint: "https://auth.civic.com/oauth/session/end",
+};
+```
+
+---
+
+## Hooks
+
+### `useWeb3Client(config, idToken)`
+
+React hook for initializing and managing Web3 client.
+
+**Parameters:**
+- `config` (CivicWeb3ClientConfig): Web3 configuration
+- `idToken` (string | undefined): User's ID token from authentication
+
+**Returns:** `Web3Client | null | undefined`
+
+**Example:**
+```typescript
+import { useWeb3Client } from "@civic/react-native-auth-web3";
+
+const web3Config = {
+ solana: {
+ endpoint: "https://api.devnet.solana.com",
+ },
+};
+
+const web3Client = useWeb3Client(web3Config, authState.idToken);
+```
+
+---
+
+## Error Handling
+
+### Common Error Types
+
+#### Authentication Errors
+
+```typescript
+// Handle auth session errors
+if (response?.type === "error") {
+ console.error("Authentication error:", response.error);
+ // Handle specific errors:
+ // - "access_denied": User denied access
+ // - "invalid_request": Malformed request
+ // - "server_error": Server-side error
+}
+
+if (response?.type === "cancel") {
+ console.log("User cancelled authentication");
+}
+```
+
+#### Web3 Errors
+
+```typescript
+try {
+ const txHash = await web3Client?.solana?.sendTransaction(address, amount);
+} catch (error) {
+ console.error("Transaction failed:", error);
+ // Common errors:
+ // - Insufficient balance
+ // - Invalid recipient address
+ // - Network connectivity issues
+ // - User rejected transaction
+}
+```
+
+#### Wallet Creation Errors
+
+```typescript
+try {
+ const wallets = await web3Client?.createWallets();
+} catch (error) {
+ console.error("Wallet creation failed:", error);
+ // Common causes:
+ // - Missing or invalid ID token
+ // - Network connectivity issues
+ // - Service temporarily unavailable
+}
+```
+
+---
+
+## TypeScript Support
+
+All interfaces and types are fully typed for TypeScript projects. Import types from the respective packages:
+
+```typescript
+// Authentication-only types
+import { AuthRequestConfig } from "expo-auth-session";
+
+// Web3 types
+import type {
+ Web3Client,
+ SolanaWeb3Client,
+ CivicWeb3ClientConfig
+} from "@civic/react-native-auth-web3";
+
+// Solana types
+import type { Transaction, PublicKey } from "@solana/web3.js";
+```
+
+---
+
+## Migration Guide
+
+### From Authentication Only to Web3
+
+To upgrade from authentication-only to Web3 integration:
+
+1. **Install additional packages:**
+ ```bash
+ npm install @civic/react-native-auth-web3 @solana/web3.js
+ ```
+
+2. **Add Web3 configuration:**
+ ```typescript
+ const web3Config = {
+ solana: {
+ endpoint: "https://api.devnet.solana.com",
+ },
+ };
+ ```
+
+3. **Initialize Web3 client:**
+ ```typescript
+ const web3Client = useWeb3Client(web3Config, authState.idToken);
+ ```
+
+4. **Complete native setup** as described in [Web3 Wallet Integration](/mobile/web3-wallet)
+
+### Breaking Changes
+
+- None currently, as Web3 integration is additive to authentication-only functionality
+
+---
+
+## Support
+
+- **Documentation Issues:** [GitHub Issues](https://github.com/civicteam/civic-auth-examples/issues)
+- **Developer Community:** [Slack Channel](https://join.slack.com/t/civic-developers/shared_invite/zt-37tv9fyo7-aDT43mUjOFQwdQFmfZLTRw)
+- **Example Code:** [GitHub Repository](https://github.com/civicteam/civic-auth-examples/tree/main/packages/mobile/react-native-expo)
\ No newline at end of file
diff --git a/mobile/authentication-only.mdx b/mobile/authentication-only.mdx
new file mode 100644
index 00000000..94acce8a
--- /dev/null
+++ b/mobile/authentication-only.mdx
@@ -0,0 +1,439 @@
+---
+title: "Authentication Only"
+icon: "user-check"
+description: "Implement OAuth2 authentication in React Native without Web3 functionality."
+public: true
+---
+
+## Overview
+
+For apps that only need user authentication without wallet functionality. This approach uses standard OAuth2/OIDC flow to authenticate users and access their profile information.
+
+**What you get:**
+- User sign-in/sign-out
+- Access to user profile (email, name, picture)
+- Access and ID tokens for API calls
+- Session management
+
+**What you don't get:**
+- Blockchain wallet functionality
+- Transaction signing
+- Crypto payments
+
+## Installation
+
+Before starting, complete the [Installation & Setup](/mobile/installation) guide.
+
+For authentication-only integration, you only need these packages:
+
+```bash
+npx expo install expo-auth-session expo-web-browser
+```
+
+## OAuth2 Configuration
+
+Civic Auth uses standard OAuth2/OIDC endpoints:
+
+- **Authorization**: `https://auth.civic.com/oauth/auth`
+- **Token**: `https://auth.civic.com/oauth/token`
+- **UserInfo**: `https://auth.civic.com/oauth/userinfo`
+- **Scopes**: `openid profile email`
+
+## App Scheme Configuration
+
+Before implementing authentication, you need to configure your app scheme properly for mobile redirects.
+
+### 1. Configure app.json/app.config.js
+
+Add your app scheme to your Expo configuration:
+
+```json app.json
+{
+ "expo": {
+ "name": "Your App Name",
+ "scheme": "your-app",
+ // ... other config
+ }
+}
+```
+
+Or in `app.config.js`:
+
+```js app.config.js
+export default {
+ expo: {
+ name: "Your App Name",
+ scheme: "your-app",
+ // ... other config
+ },
+};
+```
+
+### 2. Register URL Scheme (iOS)
+
+If using bare workflow, also add to your `ios/YourApp/Info.plist`:
+
+```xml
+CFBundleURLTypes
+
+
+ CFBundleURLName
+ your-app
+ CFBundleURLSchemes
+
+ your-app
+
+
+
+```
+
+### 3. Register Intent Filter (Android)
+
+If using bare workflow, add to your `android/app/src/main/AndroidManifest.xml`:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+```
+
+
+ Make sure to replace `"your-app"` with your actual app scheme throughout your configuration. The scheme should be unique and match across all configurations.
+
+
+## Implementation
+
+### 1. Basic Authentication Flow
+
+React Native applications can integrate with Civic Auth using OAuth2/OIDC-compatible libraries. Popular options include:
+
+- [**Expo AuthSession**](https://docs.expo.dev/versions/latest/sdk/auth-session/) (Recommended)
+- [**react-native-app-auth**](https://github.com/FormidableLabs/react-native-app-auth)
+
+### 2. Authentication Flow Steps
+
+The implementation follows a standard OAuth2 authorization code flow with PKCE:
+
+1. User initiates sign-in
+2. App opens Civic Auth in a WebView
+3. User authenticates with Civic
+4. App receives authorization code through redirect
+5. App exchanges code for tokens
+6. App fetches user information
+
+### 3. Example AuthContext
+
+Here's a complete authentication context using Expo AuthSession:
+
+```typescript
+import React, { createContext, useEffect, useMemo, useReducer } from "react";
+import { AuthRequestConfig, useAuthRequest, makeRedirectUri } from "expo-auth-session";
+import * as WebBrowser from "expo-web-browser";
+
+// Auth configuration
+const authConfig = {
+ clientId: "your-client-id",
+ scopes: ["openid", "profile", "email"],
+ redirectUri: makeRedirectUri({ scheme: "your-app" }), // Use makeRedirectUri for proper scheme handling
+ authorizationEndpoint: "https://auth.civic.com/oauth/auth",
+ tokenEndpoint: "https://auth.civic.com/oauth/token",
+ userInfoEndpoint: "https://auth.civic.com/oauth/userinfo",
+ endSessionEndpoint: "https://auth.civic.com/oauth/session/end",
+};
+
+interface AuthState {
+ isLoading: boolean;
+ isAuthenticated: boolean;
+ user?: AuthUser;
+ accessToken?: string;
+ idToken?: string;
+ expiresIn?: number;
+}
+
+interface AuthUser {
+ email?: string;
+ name: string;
+ picture?: string;
+ sub: string;
+}
+
+interface AuthAction {
+ type: string;
+ payload?: any;
+}
+
+const initialState: AuthState = {
+ isLoading: false,
+ isAuthenticated: false,
+};
+
+export type AuthContextType = {
+ state: AuthState;
+ signIn?: () => Promise;
+ signOut?: () => Promise;
+};
+
+export const AuthContext = createContext({
+ state: initialState,
+});
+
+// Close webview after auth completion
+WebBrowser.maybeCompleteAuthSession();
+
+export const AuthProvider = ({
+ children,
+}: {
+ children: React.ReactNode;
+}) => {
+ const [request, response, promptAsync] = useAuthRequest(
+ {
+ clientId: authConfig.clientId,
+ scopes: authConfig.scopes,
+ redirectUri: authConfig.redirectUri,
+ usePKCE: true, // Required by Civic Auth
+ },
+ {
+ authorizationEndpoint: authConfig.authorizationEndpoint,
+ tokenEndpoint: authConfig.tokenEndpoint,
+ },
+ );
+
+ const [authState, dispatch] = useReducer(
+ (previousState: AuthState, action: AuthAction): AuthState => {
+ switch (action.type) {
+ case "SIGN_IN":
+ return {
+ ...previousState,
+ isAuthenticated: true,
+ accessToken: action.payload.access_token,
+ idToken: action.payload.id_token,
+ expiresIn: action.payload.expires_in,
+ };
+ case "USER_INFO":
+ return {
+ ...previousState,
+ user: action.payload,
+ };
+ case "SIGN_OUT":
+ return initialState;
+ default:
+ return previousState;
+ }
+ },
+ initialState,
+ );
+
+ const authContext = useMemo(
+ () => ({
+ state: authState,
+ signIn: async () => {
+ promptAsync();
+ },
+ signOut: async () => {
+ if (!authState.idToken) {
+ throw new Error("No idToken found");
+ }
+ try {
+ const endSessionUrl = new URL(authConfig.endSessionEndpoint);
+ endSessionUrl.searchParams.append("client_id", authConfig.clientId);
+ endSessionUrl.searchParams.append("id_token_hint", authState.idToken);
+ endSessionUrl.searchParams.append(
+ "post_logout_redirect_uri",
+ authConfig.redirectUri,
+ );
+
+ const result = await WebBrowser.openAuthSessionAsync(
+ endSessionUrl.toString(),
+ authConfig.redirectUri,
+ );
+
+ if (result.type === "success") {
+ dispatch({ type: "SIGN_OUT" });
+ }
+ } catch (e) {
+ console.warn(e);
+ }
+ },
+ }),
+ [authState, promptAsync],
+ );
+
+ useEffect(() => {
+ const getToken = async ({
+ code,
+ codeVerifier,
+ redirectUri,
+ }: {
+ code: string;
+ redirectUri: string;
+ codeVerifier?: string;
+ }) => {
+ try {
+ const response = await fetch(authConfig.tokenEndpoint, {
+ method: "POST",
+ headers: {
+ Accept: "application/json",
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ body: new URLSearchParams({
+ grant_type: "authorization_code",
+ client_id: authConfig.clientId,
+ code,
+ code_verifier: codeVerifier || "",
+ redirect_uri: redirectUri,
+ }).toString(),
+ });
+ if (response.ok) {
+ const payload = await response.json();
+ dispatch({ type: "SIGN_IN", payload });
+ }
+ } catch (e) {
+ console.warn(e);
+ }
+ };
+
+ if (response?.type === "success") {
+ const { code } = response.params;
+ getToken({
+ code,
+ codeVerifier: request?.codeVerifier,
+ redirectUri: authConfig.redirectUri || "",
+ });
+ } else if (response?.type === "error") {
+ console.warn("Authentication error: ", response.error);
+ }
+ }, [dispatch, request?.codeVerifier, response]);
+
+ useEffect(() => {
+ const fetchUserInfo = async () => {
+ try {
+ const response = await fetch(authConfig.userInfoEndpoint, {
+ headers: { Authorization: `Bearer ${authState.accessToken}` },
+ });
+ if (response.ok) {
+ const payload = await response.json();
+ dispatch({ type: "USER_INFO", payload });
+ }
+ } catch (e) {
+ console.warn("Failed to fetch user info:", e);
+ }
+ };
+
+ if (authState.isAuthenticated && authState.accessToken) {
+ fetchUserInfo();
+ }
+ }, [authState.isAuthenticated, authState.accessToken]);
+
+ return (
+ {children}
+ );
+};
+```
+
+### 4. Using the AuthContext
+
+```tsx
+import React, { useContext } from "react";
+import { View, Text, Button } from "react-native";
+import { AuthContext } from "./AuthProvider";
+
+export default function App() {
+ return (
+
+
+
+ );
+}
+
+function AuthScreen() {
+ const { state, signIn, signOut } = useContext(AuthContext);
+
+ if (state.isAuthenticated) {
+ return (
+
+ Welcome, {state.user?.name}!
+ Email: {state.user?.email}
+
+
+ );
+ }
+
+ return (
+
+
+
+ );
+}
+```
+
+## Token Management
+
+### Access Tokens
+Use access tokens to make authenticated API calls to your backend:
+
+```typescript
+const makeAuthenticatedRequest = async (url: string) => {
+ const response = await fetch(url, {
+ headers: {
+ 'Authorization': `Bearer ${state.accessToken}`,
+ 'Content-Type': 'application/json',
+ },
+ });
+ return response.json();
+};
+```
+
+### ID Tokens
+ID tokens contain user information and can be verified on your backend:
+
+```typescript
+// Send ID token to your backend for verification
+const verifyUser = async () => {
+ const response = await fetch('/api/verify', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ idToken: state.idToken,
+ }),
+ });
+ return response.json();
+};
+```
+
+## Error Handling
+
+```typescript
+const handleAuthError = (error: any) => {
+ switch (error.type) {
+ case 'cancel':
+ console.log('User cancelled authentication');
+ break;
+ case 'error':
+ console.error('Authentication error:', error.error);
+ break;
+ default:
+ console.error('Unknown error:', error);
+ }
+};
+```
+
+## Resources
+
+- [Civic Auth OAuth2/OIDC Integration](/integration/other)
+- [Complete React Native Example](https://github.com/civicteam/civic-auth-examples/tree/main/packages/mobile/react-native-expo)
+- [Expo AuthSession Documentation](https://docs.expo.dev/versions/latest/sdk/auth-session/)
+
+## Next Steps
+
+- Need Web3 functionality? Check out [Web3 Wallet Integration](/mobile/web3-wallet)
+- Review the [API Reference](/mobile/api-reference) for detailed documentation
+- Explore [example implementations](https://github.com/civicteam/civic-auth-examples)
\ No newline at end of file
diff --git a/mobile/installation.mdx b/mobile/installation.mdx
new file mode 100644
index 00000000..2c25537b
--- /dev/null
+++ b/mobile/installation.mdx
@@ -0,0 +1,203 @@
+---
+title: "Installation"
+icon: "download"
+description: "Install and configure the React Native SDK with Civic Auth."
+public: true
+---
+
+## Requirements
+
+* A React Native project using the latest version
+* iOS and Android platform support (Web is not supported)
+
+## Prerequisites
+
+First, ensure you have the necessary development tools:
+
+```bash
+brew update
+brew install watchman
+```
+
+## Installation
+
+### Core Dependencies
+
+Install the development client and core dependencies:
+
+```bash
+npx expo install expo-dev-client
+npx expo install expo-apple-authentication expo-application expo-crypto expo-linking expo-secure-store expo-web-browser react-native-passkeys react-native-webview @privy-io/expo-native-extensions @privy-io/expo
+```
+
+### Required Polyfills
+
+Install the necessary polyfills:
+
+```bash
+npm i fast-text-encoding react-native-get-random-values
+```
+
+
+ If your app uses the Expo [bare workflow](https://docs.expo.dev/bare/) ("React Native without Expo"), also run:
+
+ ```bash
+ npx pod-install
+ ```
+
+
+### Build Configuration
+
+Generate the native code to enable native module configuration:
+
+```bash
+npx expo prebuild
+```
+
+
+ The `expo prebuild` command builds the workspace so you can edit the app delegate and other native configurations.
+
+
+### Configure Polyfills
+
+
+
+ Create an `entrypoint.js` file and update your `package.json`:
+
+ ```js entrypoint.js
+ // Import required polyfills first
+ import 'fast-text-encoding';
+ import 'react-native-get-random-values';
+ // Then import the expo router
+ import 'expo-router/entry';
+ ```
+
+ ```json package.json
+ {
+ "name": "",
+ "main": "entrypoint.js"
+ }
+ ```
+
+
+
+ Import the polyfills at the root of your application:
+
+ ```jsx
+ // Import required polyfills first
+ import 'fast-text-encoding';
+ import 'react-native-get-random-values';
+
+ // Other imports
+ ...
+
+ // Your app's root component
+ export default function App() {
+ ...
+ }
+ ```
+
+
+
+
+ If you're using the `@solana/web3.js` package, install the buffer dependency:
+
+ ```bash
+ npm i buffer
+ ```
+
+ And add this code after importing `react-native-get-random-values`:
+
+ ```js
+ import 'react-native-get-random-values';
+ import {Buffer} from 'buffer';
+ global.Buffer = Buffer;
+ ```
+
+
+
+ This guide ensures that your application satisfies the following requirements for integrating:
+
+ * uses an [expo development build](https://docs.expo.dev/develop/development-builds/introduction/).
+ * has a custom [`metro.config.js` file](https://docs.expo.dev/guides/customizing-metro/#customizing) to customize the Metro bundler settings
+ * enables [package exports for the Metro bundler:](https://reactnative.dev/blog/2023/06/21/package-exports-support#for-app-developers)
+ * uses the `bundler` setting for [Typescript's `moduleResolution`](https://www.typescriptlang.org/tsconfig#moduleResolution)
+
+ ## Enabling Package Exports
+
+
+ React Native 0.79, and Expo 53, have [enabled package exports by default](https://reactnative.dev/blog/2025/04/08/react-native-0.79#metro-faster-startup-and-package-exports-support).
+
+ Some popular packages present incompatibilities with this change, and the community is working to get these fixed at source.
+ In the meantime, we present a fix below by disabling package exports for the incompatibilities we have found.
+
+
+ Update your `metro.config.js` like so:
+
+ ```ts
+ //...other config logic
+
+ // Enable package exports for select libraries
+ ...
+ const resolveRequestWithPackageExports = (context, moduleName, platform) => {
+ // Package exports in `isows` (a `viem`) dependency are incompatible, so they need to be disabled
+ if (moduleName === "isows") {
+ const ctx = {
+ ...context,
+ unstable_enablePackageExports: false,
+ };
+ return ctx.resolveRequest(ctx, moduleName, platform);
+ }
+
+ // Package exports in `zustand@4` are incompatible, so they need to be disabled
+ if (moduleName.startsWith("zustand")) {
+ const ctx = {
+ ...context,
+ unstable_enablePackageExports: false,
+ };
+ return ctx.resolveRequest(ctx, moduleName, platform);
+ }
+
+ // Package exports in `jose` are incompatible, so the browser version is used
+ if (moduleName === "jose") {
+ const ctx = {
+ ...context,
+ unstable_conditionNames: ["browser"],
+ };
+ return ctx.resolveRequest(ctx, moduleName, platform);
+ }
+
+ // The following block is only needed if you are
+ // running React Native 0.78 *or older*.
+ if (moduleName.startsWith('@privy-io/')) {
+ const ctx = {
+ ...context,
+ unstable_enablePackageExports: true,
+ };
+ return ctx.resolveRequest(ctx, moduleName, platform);
+ }
+
+ return context.resolveRequest(context, moduleName, platform);
+ };
+
+ config.resolver.resolveRequest = resolveRequestWithPackageExports;
+
+ ...
+ module.exports = config;
+ ```
+
+ ## Typescript's Module Resolution
+
+ Also configure your `tsconfig.json` like so:
+
+ ```json
+ {
+ "extends": "expo/tsconfig.base",
+ "compilerOptions": {
+ "strict": true,
+ // Allows us to use conditional/deep imports on published packages
+ "moduleResolution": "Bundler"
+ }
+ }
+ ```
+
\ No newline at end of file
diff --git a/mobile/quick-start.mdx b/mobile/quick-start.mdx
new file mode 100644
index 00000000..b63519e3
--- /dev/null
+++ b/mobile/quick-start.mdx
@@ -0,0 +1,131 @@
+---
+title: "Quick Start"
+icon: "rocket"
+description: "Choose your React Native integration approach with Civic Auth."
+public: true
+---
+
+## Choose Your Integration Path
+
+Civic Auth offers two integration approaches for React Native applications. Choose the one that fits your app's needs:
+
+
+
+ Standard OAuth2/OIDC authentication for user sign-in and profile access.
+
+ **Best for:**
+ - Apps that only need user authentication
+ - No blockchain functionality required
+ - Simpler setup and maintenance
+
+
+
+ Full authentication plus embedded Solana wallet for blockchain interactions.
+
+ **Best for:**
+ - DeFi applications
+ - NFT marketplaces
+ - Apps requiring transaction signing
+ - Blockchain-powered features
+
+ **Requires:**
+ - Enabling Web3 wallets in Civic dashboard
+ - Registering your app domain/scheme
+
+
+
+## Integration Comparison
+
+| Feature | Authentication Only | Web3 Wallet Integration |
+|---------|-------------------|------------------------|
+| **User Authentication** | ✅ OAuth2/OIDC flow | ✅ OAuth2/OIDC flow |
+| **User Profile Access** | ✅ Email, name, picture | ✅ Email, name, picture |
+| **Token Management** | ✅ Access & ID tokens | ✅ Access & ID tokens |
+| **Embedded Wallets** | ❌ Not available | ✅ Solana wallet creation |
+| **Transaction Signing** | ❌ Not available | ✅ Sign & send transactions |
+| **Balance Queries** | ❌ Not available | ✅ Check wallet balances |
+| **Message Signing** | ❌ Not available | ✅ Sign arbitrary messages |
+| **Native Configuration** | ❌ Minimal setup | ✅ Requires iOS/Android config |
+
+## Architecture Overview
+
+
+
+ ```mermaid
+ graph TD
+ A[React Native App] --> B[Civic Auth]
+ B --> C[User Profile]
+ B --> D[Access Tokens]
+ A --> E[Your App Logic]
+ C --> E
+ D --> E
+ ```
+
+ **Flow:**
+ 1. User initiates sign-in
+ 2. OAuth2 flow with Civic Auth
+ 3. Receive user profile and tokens
+ 4. Use data in your app
+
+
+
+ ```mermaid
+ graph TD
+ A[React Native App] --> B[Civic Auth]
+ B --> C[User Profile]
+ B --> D[Access Tokens]
+ B --> E[ID Token]
+ E --> F[Embedded Wallet]
+ F --> G[Solana Network]
+ A --> H[Your App Logic]
+ C --> H
+ D --> H
+ F --> H
+ ```
+
+ **Flow:**
+ 1. User initiates sign-in
+ 2. OAuth2 flow with Civic Auth
+ 3. Receive user profile, access token, and ID token
+ 4. Create embedded wallet using ID token
+ 5. Interact with Solana blockchain
+
+
+
+## Next Steps
+
+
+
+ Complete the [Installation & Setup](/mobile/installation) for both approaches.
+
+
+
+ - [Authentication Only](/mobile/authentication-only) - OAuth2 sign-in only
+ - [Web3 Wallet Integration](/mobile/web3-wallet) - Full blockchain functionality
+
+
+
+ Follow your chosen integration guide and test with your app.
+
+
+
+## Common Use Cases
+
+### Authentication Only
+- **Social Apps**: User profiles and content sharing
+- **Business Apps**: Employee authentication and access control
+- **Content Platforms**: User accounts and personalization
+- **SaaS Applications**: Customer authentication and dashboards
+
+### Web3 Wallet Integration
+- **DeFi Platforms**: Trading, lending, and yield farming
+- **NFT Marketplaces**: Buying, selling, and displaying NFTs
+- **Gaming**: In-game assets and rewards on blockchain
+- **Payment Apps**: Crypto payments and transfers
+- **DAO Applications**: Voting and governance participation
+
+## Need Help?
+
+- Check our [API Reference](/mobile/api-reference) for detailed method documentation
+- View complete examples in our [GitHub repository](https://github.com/civicteam/civic-auth-examples/tree/main/packages/mobile/react-native-expo)
+- Join our [developer community](https://join.slack.com/t/civic-developers/shared_invite/zt-37tv9fyo7-aDT43mUjOFQwdQFmfZLTRw) for support
\ No newline at end of file
diff --git a/integration/mobile/react-native.mdx b/mobile/react-native.mdx
similarity index 93%
rename from integration/mobile/react-native.mdx
rename to mobile/react-native.mdx
index 8edd26d4..aac37d28 100644
--- a/integration/mobile/react-native.mdx
+++ b/mobile/react-native.mdx
@@ -7,13 +7,12 @@ public: true
## Overview
-\
React Native applications can integrate with Civic Auth using any OAuth2/OIDC-compatible library. Popular options include:
- [**Expo AuthSession**](https://docs.expo.dev/versions/latest/sdk/auth-session/)
- [**react-native-app-auth**](https://github.com/FormidableLabs/react-native-app-auth)
-For a complete OAuth2/OIDC integration details, see the [Civic Auth Integration Guide](https://docs.civic.com/auth/integration/other).
+For a complete OAuth2/OIDC integration details, see the [Civic Auth Integration Guide](/integration/other).
## Reference Implementation
@@ -76,5 +75,5 @@ const authContext = {
## Resources
-- [Civic Auth OAuth2/OIDC Integration](https://docs.civic.com/auth/integration/other)
+- [Civic Auth OAuth2/OIDC Integration](/integration/other)
- [Complete React Native Example](https://github.com/civicteam/civic-auth-examples/tree/main/packages/mobile/react-native-expo)
diff --git a/mobile/web3-wallet.mdx b/mobile/web3-wallet.mdx
new file mode 100644
index 00000000..f3580f5d
--- /dev/null
+++ b/mobile/web3-wallet.mdx
@@ -0,0 +1,623 @@
+---
+title: "Web3 Wallet Integration"
+icon: "wallet"
+description: "Integrate React Native with Civic Auth and embedded Solana wallets."
+public: true
+---
+
+
+ The Civic Auth Web3 API for React Native is currently in early access and
+ subject to change.
+
+
+## Overview
+
+For apps requiring embedded wallet functionality with blockchain interactions. This approach includes everything from Authentication Only plus embedded Solana wallet creation and blockchain operations.
+
+**What you get:**
+- Everything from [Authentication Only](/mobile/authentication-only)
+- Embedded Solana wallet creation
+- Transaction signing and sending
+- Balance checking and message signing
+- Non-custodial wallet management
+
+## Prerequisites
+
+### 1. Complete Installation
+
+Before starting, complete the [Installation & Setup](/mobile/installation) guide.
+
+### 2. Enable Web3 Wallets in Civic Dashboard
+
+**Critical Step:** You must enable embedded wallets for your application in the Civic Auth dashboard:
+
+1. Go to [Civic Auth Dashboard](https://auth.civic.com/dashboard)
+2. Select your application
+3. Navigate to **Settings** or **Features** section
+4. **Enable "Embedded Wallets"** or **"Web3 Wallets"** feature
+5. Save your changes
+
+
+ Without enabling wallets in the dashboard, wallet creation will fail even with correct code implementation.
+
+
+### 3. Register Your App Domain/Scheme
+
+**Critical Step:** Register your mobile app scheme with Civic Auth:
+
+1. In the Civic Auth dashboard, go to your app settings
+2. Find the **"Allowed Origins"** or **"Redirect URIs"** section
+3. Add your mobile app scheme (e.g., `myapp://` or `yourapp://`)
+4. Make sure it matches exactly with your app configuration
+
+**Example Domain Registration:**
+- If your app scheme is `myapp://`, register: `myapp://`
+- If your app scheme is `com.yourcompany.yourapp://`, register: `com.yourcompany.yourapp://`
+
+
+ Your domain registration must match exactly with the scheme configured in your app's `app.json` or `app.config.js` file.
+
+
+## Getting Started
+
+After completing the prerequisites above, you can create a Web3 wallet for authenticated users using the `useWeb3Client` hook. Embedded wallets are generated on behalf of users through our non-custodial wallet partner—neither Civic nor your app has access to the private keys.
+
+
+ Only embedded wallets are supported (no self-custodial wallet connections yet)
+
+
+### Installation
+
+Install the SDK and its peer dependencies:
+
+
+
+ ```bash npm install @civic/react-native-auth-web3 @solana/web3.js ```
+
+
+ ```bash yarn add @civic/react-native-auth-web3 @solana/web3.js ```
+
+
+ ```bash pnpm add @civic/react-native-auth-web3 @solana/web3.js ```
+
+
+
+### Native Setup
+
+#### Android Configuration
+
+Add the following to your `android/app/build.gradle`:
+
+```gradle
+android {
+ defaultConfig {
+ minSdkVersion 26 // Required minimum SDK version
+
+ // Add manifest placeholders for embedded wallet integration
+ manifestPlaceholders = [
+ metakeepDomain: "*.auth.metakeep.xyz",
+ metakeepScheme:
+ ]
+ }
+}
+```
+
+#### iOS Configuration
+
+**Requirements:**
+
+- iOS 14.0 or higher
+- Xcode 14.0 or higher
+- Swift 5.0 or higher
+
+**Step 1: Generate Native Code**
+
+If you're using Expo managed workflow, first generate the native iOS code:
+
+```bash
+npx expo prebuild
+```
+
+
+ This creates the native iOS project files needed for the next steps.
+
+
+**Step 2: Add URL Type**
+
+1. Navigate to the **Info** tab of your app target settings in Xcode
+2. In the **URL Types** section, click the **+** button to add a new URL
+3. Enter the following values:
+ - **Identifier:** `metakeep`
+ - **URL Schemes:** `$(PRODUCT_BUNDLE_IDENTIFIER)`
+
+**Step 3: Import MetaKeep Framework**
+
+Add the MetaKeep import to your `ios//AppDelegate.swift` file:
+
+```swift
+import MetaKeep
+```
+
+**Step 4: Add MetaKeep SDK to iOS Dependencies**
+
+Add the MetaKeep pod to your `ios/Podfile`:
+
+```ruby
+target 'YourAppName' do
+ use_expo_modules!
+
+ # MetaKeep SDK
+ pod 'MetaKeep', '~> 2.0.0'
+
+ # ... other pods
+end
+```
+
+Install the pod dependencies:
+
+```bash
+cd ios && pod install
+```
+
+
+ If you encounter a "no such module 'MetaKeep'" error, this step resolves it by adding the MetaKeep SDK to your iOS project dependencies.
+
+
+**Step 5: Handle Callback URLs**
+
+Add the MetaKeep URL handling code inside the existing `application(_:open:options:)` method in your `ios//AppDelegate.swift` file:
+
+```swift
+public override func application(
+ _ app: UIApplication,
+ open url: URL,
+ options: [UIApplication.OpenURLOptionsKey: Any] = [:]
+) -> Bool {
+
+ // Add this MetaKeep handling code inside the existing method
+ if url.absoluteString.lowercased().contains("metakeep") {
+ MetaKeep.companion.resume(url: url.absoluteString)
+ return true
+ }
+
+ // Keep any existing URL handling code here
+ // ... existing code ...
+
+ return false
+}
+```
+
+
+ Make sure to add the MetaKeep code **inside** the existing `application(_:open:options:)` method, not as a separate method. The file path will be `ios//AppDelegate.swift` after running `expo prebuild`.
+
+
+### How Wallet Creation Works
+
+After authenticating with Civic Auth, the SDK can create a non-custodial embedded wallet for the user. The creation process requires an **ID token** - a JWT received from Civic Auth after successful authentication. This token proves the user's identity and links the wallet to their Civic account.
+
+Without a valid ID token, wallet creation will fail. This ensures only authenticated users can create wallets, with each wallet uniquely tied to a specific user account.
+
+### Quick Start
+
+```tsx
+import { useWeb3Client } from "@civic/react-native-auth-web3";
+
+const web3Config = {
+ solana: {
+ endpoint: "https://api.devnet.solana.com", // Your RPC endpoint
+ },
+};
+
+// Initialize the Web3 client with user's ID token
+const web3Client = useWeb3Client(web3Config, idToken);
+
+// Create wallets after login
+if (!web3Client?.solana) {
+ await web3Client?.createWallets();
+}
+```
+
+## The useWeb3Client Hook
+
+The `useWeb3Client` hook returns a `Web3Client` object for interacting with blockchain networks. The client manages both wallet creation and transaction operations.
+
+### Web3Client Interface
+
+```typescript
+interface Web3Client {
+ solana: SolanaWeb3Client; // Solana wallet operations
+ connected: boolean; // Connection status
+ createWallets(): Promise; // Create embedded wallets
+ disconnect(): Promise; // Disconnect and cleanup
+}
+```
+
+### SolanaWeb3Client Methods
+
+The `solana` property provides access to Solana-specific operations:
+
+```typescript
+interface SolanaWeb3Client {
+ readonly address: string; // Wallet public key
+
+ // Core transaction methods
+ sendTransaction(address: string, amount: number): Promise;
+ signTransaction(transaction: Transaction, reason: string): Promise;
+ signMessage(message: string, reason: string): Promise;
+
+ // Utility methods
+ getBalance(): Promise;
+ disconnect(): Promise;
+}
+```
+
+## Using the Wallet
+
+### Sending Transactions
+
+You have two options for sending transactions:
+
+#### Option 1: Simple Transfer (Recommended)
+
+Use `sendTransaction` for quick SOL transfers. It handles transaction creation, signing, and broadcasting:
+
+```tsx
+// Send 0.5 SOL to a recipient
+const txHash = await web3Client?.solana?.sendTransaction(
+ "RecipientPublicKeyHere",
+ 0.5, // Amount in SOL
+);
+console.log(`Transaction: ${txHash}`);
+```
+
+#### Option 2: Custom Transactions
+
+Use `signTransaction` for complex transactions with custom instructions:
+
+```tsx
+import {
+ Connection,
+ Transaction,
+ SystemProgram,
+ PublicKey,
+} from "@solana/web3.js";
+
+const connection = new Connection(web3Config.solana.endpoint);
+
+// Build custom transaction
+const transaction = new Transaction().add(
+ SystemProgram.transfer({
+ fromPubkey: new PublicKey(web3Client.solana.address),
+ toPubkey: new PublicKey(recipientAddress),
+ lamports: 0.001 * 1e9, // 0.001 SOL in lamports
+ }),
+);
+
+// Sign the transaction
+const signature = await web3Client?.solana?.signTransaction(
+ transaction,
+ "Approve transfer", // Reason shown to user
+);
+
+// Add signature and send
+transaction.addSignature(new PublicKey(web3Client.solana.address), signature);
+const txHash = await connection.sendRawTransaction(transaction.serialize());
+```
+
+### Checking Balance
+
+```tsx
+import { Connection, PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js";
+
+const connection = new Connection(web3Config.solana.endpoint);
+const balanceLamports = await connection.getBalance(
+ new PublicKey(web3Client.solana.address),
+);
+const balanceSOL = balanceLamports / LAMPORTS_PER_SOL;
+console.log(`Balance: ${balanceSOL} SOL`);
+```
+
+### Signing Messages
+
+```tsx
+const message = "Verify wallet ownership";
+const signature = await web3Client?.solana?.signMessage(
+ message,
+ "Sign to verify your wallet", // Shown to user
+);
+console.log(`Signature: ${signature}`);
+```
+
+## Complete Example
+
+Here's a complete authentication provider with Expo Auth Session and Web3 wallet creation:
+
+```tsx
+import { createContext, useEffect, useMemo, useReducer } from "react";
+import { AuthRequestConfig, useAuthRequest } from "expo-auth-session";
+import * as WebBrowser from "expo-web-browser";
+import { civicAuthConfig } from "@/config/civicAuth";
+import { useWeb3Client, type Web3Client } from "@civic/react-native-auth-web3";
+import { clusterApiUrl } from "@solana/web3.js";
+import { CivicWeb3ClientConfig } from "@civic/react-native-auth-web3/dist/types";
+
+interface AuthState {
+ isLoading: boolean;
+ isAuthenticated: boolean;
+ user?: AuthUser;
+ accessToken?: string;
+ refreshToken?: string;
+ idToken?: string;
+ expiresIn?: number;
+}
+
+interface AuthUser {
+ email?: string;
+ name: string;
+ picture?: string;
+ sub: string;
+}
+
+interface AuthAction {
+ type: string;
+ payload?: any;
+}
+
+const initialState: AuthState = {
+ isLoading: false,
+ isAuthenticated: false,
+};
+
+export type AuthContextType = {
+ state: AuthState;
+ signIn?: () => Promise;
+ signOut?: () => Promise;
+ web3Client?: Web3Client | null | undefined;
+};
+
+export const AuthContext = createContext({
+ state: initialState,
+});
+
+// This is needed to close the webview after a complete login
+WebBrowser.maybeCompleteAuthSession();
+
+export const AuthProvider = ({
+ config,
+ children,
+}: {
+ config?: Partial;
+ children: React.ReactNode;
+}) => {
+ const finalConfig = useMemo(() => {
+ return { ...civicAuthConfig, ...config };
+ }, [config]);
+
+ const [request, response, promptAsync] = useAuthRequest(
+ {
+ clientId: finalConfig.clientId,
+ scopes: finalConfig.scopes,
+ redirectUri: finalConfig.redirectUri,
+ usePKCE: true,
+ },
+ {
+ authorizationEndpoint: finalConfig.authorizationEndpoint,
+ tokenEndpoint: finalConfig.tokenEndpoint,
+ },
+ );
+
+ const [authState, dispatch] = useReducer(
+ (previousState: AuthState, action: AuthAction): AuthState => {
+ switch (action.type) {
+ case "SIGN_IN":
+ return {
+ ...previousState,
+ isAuthenticated: true,
+ accessToken: action.payload.access_token,
+ idToken: action.payload.id_token,
+ expiresIn: action.payload.expires_in,
+ };
+ case "USER_INFO":
+ return {
+ ...previousState,
+ user: action.payload,
+ };
+ case "SIGN_OUT":
+ return initialState;
+ default:
+ return previousState;
+ }
+ },
+ initialState,
+ );
+
+ const web3Config = useMemo(
+ () =>
+ ({
+ solana: {
+ endpoint: clusterApiUrl("devnet"),
+ },
+ }) as CivicWeb3ClientConfig,
+ [],
+ );
+
+ const web3Client = useWeb3Client(web3Config, authState.idToken);
+
+ const authContext = useMemo(
+ () => ({
+ state: authState,
+ web3Client,
+ signIn: async () => {
+ promptAsync();
+ },
+ signOut: async () => {
+ if (!authState.idToken) {
+ throw new Error("No idToken found");
+ }
+ try {
+ const endSessionUrl = new URL(finalConfig.endSessionEndpoint);
+ endSessionUrl.searchParams.append("client_id", finalConfig.clientId);
+ endSessionUrl.searchParams.append("id_token_hint", authState.idToken);
+ endSessionUrl.searchParams.append(
+ "post_logout_redirect_uri",
+ finalConfig.redirectUri,
+ );
+
+ const result = await WebBrowser.openAuthSessionAsync(
+ endSessionUrl.toString(),
+ finalConfig.redirectUri,
+ );
+
+ // Only sign out if the session was completed successfully
+ // If the user cancels (result.type === 'cancel'), we don't sign them out
+ if (result.type === "success") {
+ dispatch({ type: "SIGN_OUT" });
+ }
+ } catch (e) {
+ console.warn(e);
+ }
+ },
+ }),
+ [authState, web3Client, promptAsync, finalConfig],
+ );
+
+ useEffect(() => {
+ const getToken = async ({
+ code,
+ codeVerifier,
+ redirectUri,
+ }: {
+ code: string;
+ redirectUri: string;
+ codeVerifier?: string;
+ }) => {
+ try {
+ const response = await fetch(finalConfig.tokenEndpoint, {
+ method: "POST",
+ headers: {
+ Accept: "application/json",
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ body: new URLSearchParams({
+ grant_type: "authorization_code",
+ client_id: finalConfig.clientId,
+ code,
+ code_verifier: codeVerifier || "",
+ redirect_uri: redirectUri,
+ }).toString(),
+ });
+ if (response.ok) {
+ const payload = await response.json();
+ dispatch({ type: "SIGN_IN", payload });
+ }
+ } catch (e) {
+ console.warn(e);
+ }
+ };
+ if (response?.type === "success") {
+ const { code } = response.params;
+ getToken({
+ code,
+ codeVerifier: request?.codeVerifier,
+ redirectUri: finalConfig.redirectUri || "",
+ });
+ } else if (response?.type === "error") {
+ console.warn("Authentication error: ", response.error);
+ }
+ }, [dispatch, finalConfig, request?.codeVerifier, response]);
+
+ useEffect(() => {
+ const initializeUser = async () => {
+ // Fetch user info
+ try {
+ const response = await fetch(finalConfig.userInfoEndpoint || "", {
+ headers: { Authorization: `Bearer ${authState.accessToken}` },
+ });
+ if (response.ok) {
+ const payload = await response.json();
+ dispatch({ type: "USER_INFO", payload });
+ }
+ } catch (e) {
+ console.warn("Failed to fetch user info:", e);
+ }
+
+ // Create wallets if needed
+ if (!web3Client?.solana) {
+ await web3Client?.createWallets();
+ }
+ };
+
+ if (authState.isAuthenticated) {
+ initializeUser();
+ }
+ }, [
+ authState.isAuthenticated,
+ authState.accessToken,
+ finalConfig.userInfoEndpoint,
+ web3Client,
+ ]);
+
+ return (
+ {children}
+ );
+};
+```
+
+## Troubleshooting
+
+### Common Domain Registration Issues
+
+If wallet creation fails, check these common domain registration problems:
+
+#### ❌ Domain Mismatch
+**Problem:** Your registered domain doesn't match your app scheme exactly.
+
+**Example:**
+- App scheme: `myapp://`
+- Registered domain: `myapp` (missing `://`)
+- **Result:** Wallet creation fails
+
+**Solution:** Ensure exact match including `://`
+
+#### ❌ Missing Wallet Feature
+**Problem:** Embedded wallets not enabled in Civic dashboard.
+
+**Symptoms:**
+- Authentication works fine
+- `createWallets()` fails or returns null
+- Console errors about wallet features
+
+**Solution:** Enable "Embedded Wallets" feature in dashboard settings
+
+#### ❌ Incorrect Redirect URI
+**Problem:** Using hardcoded redirect URI instead of `makeRedirectUri()`
+
+**Example:**
+```typescript
+// ❌ Wrong - hardcoded
+redirectUri: "myapp://auth"
+
+// ✅ Correct - dynamic
+redirectUri: makeRedirectUri({ scheme: "myapp" })
+```
+
+### Verifying Your Setup
+
+To verify your domain registration is correct:
+
+1. Check your `app.json`/`app.config.js` scheme matches registered domain
+2. Test authentication first (should work without wallets)
+3. Only then test wallet creation
+4. Check browser developer tools for any CORS or domain errors
+
+## Example Repository
+
+For a complete working example of Civic Auth with embedded wallets in a React Native application, check out our example repository:
+
+[https://github.com/civicteam/civic-auth-examples/tree/main/packages/mobile/react-native-expo]
+
+## Crypto Polyfill
+
+The SDK automatically includes a crypto polyfill using [expo-crypto](https://docs.expo.dev/versions/latest/sdk/crypto/) to provide the `getRandomValues` function required by Solana's `PublicKey` object. This polyfill ensures cryptographic operations work correctly in React Native environments where the Web Crypto API is not natively available.
+
+The polyfill is applied automatically when you import the SDK, so no additional configuration is needed.