# PERS SDK React Native

A comprehensive React Native SDK for the PERS (Phygital Experience Rewards System) platform, designed specifically for tourism loyalty applications with blockchain transaction signing capabilities.

## Features

- **Secure Authentication**: WebAuthn-based authentication with device biometrics
- **Blockchain Integration**: Complete transaction signing with PERS Signer SDK supporting EVM-compatible chains (Ethereum, Polygon, Camino, etc.)
- **Token Operations**: Balance checking, earning, redeeming, and transfers
- **Business Management**: Business profiles, campaigns, and operations
- **Campaign System**: Marketing campaigns, participation, and rewards
- **Redemption Engine**: Comprehensive reward redemption and fulfillment
- **Analytics**: Real-time analytics and transaction monitoring
- **Donations**: Charitable giving and donation management
- **User Management**: Profile management and user operations
- **Web3 Support**: EVM-compatible chain wallet operations and blockchain interactions
- **Secure Storage**: Hybrid storage strategy using **Keychain** (High Security) for secrets and **AsyncStorage** for public data.
- **DPoP Integration**: Native C++ bridge for high-performance ECDSA signing via `react-native-quick-crypto`.
- **React Native Optimized**: Full platform support with automatic polyfills.

## Installation

```bash
npm install @explorins/pers-sdk-react-native
```

### Required Peer Dependencies

You must install `async-storage` manually as it is a peer dependency:

```bash
npm install @react-native-async-storage/async-storage
```

### Included Native Dependencies

The SDK automatically installs the following native libraries. **You must rebuild your native app** (e.g., `npx expo run:android` or `cd ios && pod install`) after installing the SDK to link these native modules:

- `react-native-quick-crypto` (High-performance Crypto)
- `react-native-keychain` (Secure Storage)
- `react-native-passkey` (WebAuthn/Passkeys)
- `react-native-get-random-values` (Crypto Primitives)

### Optional Dependencies

```bash
# For URL Polyfills (Recommended if not already included in your app)
npm install react-native-url-polyfill
```

### Metro Configuration (Required)

The PERS SDK uses subpath exports (`@explorins/pers-sdk/core`, etc.) which require Metro to be configured. Add to your `metro.config.js`:

```javascript
const { getDefaultConfig } = require('expo/metro-config');
// or for bare RN: const { getDefaultConfig } = require('@react-native/metro-config');

const config = getDefaultConfig(__dirname);

// REQUIRED: Enable package exports for PERS SDK subpath imports
config.resolver.unstable_enablePackageExports = true;

module.exports = config;
```

---

## ⚠️ Android Architecture Requirements

**The SDK requires React Native New Architecture** starting from v2.1.x.

### Why?

`react-native-quick-crypto` v1.0+ (used for high-performance cryptographic operations) uses [Nitro Modules](https://github.com/margelo/nitro), which require New Architecture.

### Quick Check

If you see errors like:
```
NitroModulesProxy.createHybridObject(...) threw an unknown std::runtime_error
```

Your app is running with **Old Architecture** (`newArchEnabled=false`).

### Solution 1: Enable New Architecture (Recommended)

React Native 0.76+ has stable New Architecture support. Enable it in `android/gradle.properties`:

```properties
# android/gradle.properties

# Use this property to enable support to the new architecture.
newArchEnabled=true

# Use this property to enable or disable the Hermes JS engine.
hermesEnabled=true
```

Then clean and rebuild:

```bash
cd android && ./gradlew clean && cd ..
npx react-native start --reset-cache
npx react-native run-android
```

> **Reference**: See the [VQ Demo App's gradle.properties](https://github.com/explorins/vq-pers-integration-demo/blob/main/android/gradle.properties) for a working configuration with Expo SDK 54 and React Native 0.81.5.

### Solution 2: Downgrade quick-crypto (For Old Architecture)

If you must stay on Old Architecture, pin to v0.7.x:

```json
{
  "resolutions": {
    "react-native-quick-crypto": "0.7.17"
  }
}
```

Or for npm (in `package.json`):

```json
{
  "overrides": {
    "react-native-quick-crypto": "0.7.17"
  }
}
```

### Compatibility Matrix

| RN Version | Architecture | quick-crypto | Status |
|------------|--------------|--------------|--------|
| 0.76+ | New (`newArchEnabled=true`) | 1.0.6+ | ✅ Recommended |
| 0.76+ | Old (`newArchEnabled=false`) | 0.7.17 | ⚠️ Requires downgrade |
| < 0.76 | Old | 0.7.x | ⚠️ Use older quick-crypto |

> **See [Android Compatibility FAQ](https://github.com/eXplorins/pers-sdk/blob/main/packages/pers-sdk-react-native/docs/ANDROID_COMPATIBILITY_FAQ.md)** for detailed troubleshooting.

---

## Critical Setup Requirement: Passkeys

To enable Passkey authentication (WebAuthn) on iOS and Android, you **must**:

1. **Register your app with PERS backend** - Contact **support@explorins.com** with:
   - iOS: Bundle ID + Apple Team ID
   - Android: Package Name + SHA-256 fingerprint

2. **Configure native files** - See [Passkey Setup Guide](https://github.com/eXplorins/pers-sdk/blob/main/packages/pers-sdk-react-native/REACT_NATIVE_PASSKEY_SETUP.md)

3. **For Expo**: Use development builds (`npx expo run:android`), not Expo Go

## Quick Start

### 1. Setup the Provider

```typescript
import React from 'react';
import { PersSDKProvider } from '@explorins/pers-sdk-react-native';

export default function App() {
  return (
    <PersSDKProvider config={{
      apiUrl: 'https://api.pers.ninja',
      tenantId: 'your-tenant-id' // Optional
    }}>
      <NavigationContainer>
        <YourAppContent />
      </NavigationContainer>
    </PersSDKProvider>
  );
}
```

### 2. Authentication & Token Operations

```typescript
import { 
  useAuth, 
  useTokens, 
  useRedemptions, 
  useTransactionSigner 
} from '@explorins/pers-sdk-react-native';

function RewardScreen() {
  const { user, isAuthenticated, login } = useAuth();
  const { getTokens } = useTokens();
  const { redeem } = useRedemptions();
  const { signAndSubmitTransactionWithJWT, isSignerAvailable } = useTransactionSigner();

  const handleLogin = async () => {
    try {
      await login('your-jwt-token');
      console.log('Authenticated:', user?.identifier);
    } catch (error) {
      console.error('Login failed:', error);
    }
  };

  const handleLoadTokens = async () => {
    try {
      const tokens = await getTokens();
      console.log('User tokens:', tokens);
    } catch (error) {
      console.error('Failed to load tokens:', error);
    }
  };

  const handleRedemption = async () => {
    try {
      // Step 1: Create redemption
      const redemptionId = 'redemption-id-from-ui';
      const result = await redeem(redemptionId);
      
      // Note: The redeem method automatically handles blockchain signing if required
      // and if the signer is available.
      
      if (result.isSigned) {
        console.log('Redemption completed with blockchain signature!');
        console.log('Transaction Hash:', result.transactionHash);
      }
    } catch (error) {
      console.error('Redemption failed:', error);
    }
  };

  return (
    <ScrollView style={styles.container}>
      {!isAuthenticated ? (
        <TouchableOpacity onPress={handleLogin} style={styles.loginButton}>
          <Text style={styles.buttonText}>Login with PERS</Text>
        </TouchableOpacity>
      ) : (
        <View>
          <Text style={styles.welcome}>Welcome, {user?.identifier}!</Text>
          
          <TouchableOpacity onPress={handleLoadTokens} style={styles.button}>
            <Text style={styles.buttonText}>Load Tokens</Text>
          </TouchableOpacity>
          
          <TouchableOpacity 
            onPress={handleRedemption} 
            disabled={!isSignerAvailable}
            style={[styles.button, !isSignerAvailable && styles.disabled]}
          >
            <Text style={styles.buttonText}>
              {isSignerAvailable ? 'Redeem Rewards' : 'Signer Loading...'}
            </Text>
          </TouchableOpacity>
        </View>
      )}
    </ScrollView>
  );
}
```

## Core Hooks

### Authentication & Users

```typescript
// Authentication management
const { 
  isInitialized,
  isAuthenticated, 
  user, 
  login,
  loginWithRawData,
  logout,
  refreshUserData,
  getCurrentUser,
  checkIsAuthenticated,
  refreshTokens,
  clearAuth,
  hasValidAuth
} = useAuth();

// User profile operations  
const { 
  getCurrentUser,
  updateCurrentUser,
  getUserById,
  getAllUsersPublic,
  getAllUsers,        // Admin
  updateUser,         // Admin
  toggleUserStatus    // Admin
} = useUsers();
```

### Token & Financial Operations

```typescript
// Token management
const { 
  getTokens,
  getActiveCreditToken,
  getRewardTokens,
  getTokenTypes,
  getStatusTokens,
  getTokenByContract
} = useTokens();

// Token balance loading (NEW in v2.1.1)
const {
  tokenBalances,      // Array of balances with token metadata
  isLoading,          // Loading state
  error,              // Error state
  refresh             // Manual refresh function
} = useTokenBalances({
  availableTokens,    // From useTokens()
  autoLoad: true,     // Auto-load on mount
  refreshInterval: 30000  // Optional: refresh every 30s
});

// Transaction history
const { 
  createTransaction,
  getTransactionById,
  getUserTransactionHistory,    // With TransactionQueryOptions (includes pagination + filters + include)
  getPaginatedTransactions,     // Admin - single options object with include
  exportTransactionsCSV,        // Admin
  signingStatus,                // UI feedback during blockchain signing
  signingStatusMessage
} = useTransactions();

// Blockchain transaction signing
const { 
  signAndSubmitTransactionWithJWT,
  isSignerAvailable,
  isSignerInitialized,
  currentStatus,
  statusMessage
} = useTransactionSigner();
```

### Business & Campaign Management

```typescript
// Business operations
const { 
  getActiveBusinesses,
  getBusinessTypes,
  getBusinesses,
  getBusinessById,
  getBusinessByAccount,
  getBusinessesByType,
  createBusiness,       // Admin
  updateBusiness,       // Admin
  toggleBusinessStatus  // Admin
} = useBusiness();

// Campaign management
const { 
  getActiveCampaigns,
  getCampaigns,                 // With options: { active?, businessId?, sortBy?, include?, page?, limit? }
  getCampaignById,
  claimCampaign,
  getUserClaims,                // With options: { page?, limit?, include? }
  getCampaignTriggers,
  getAllCampaigns,              // Admin
  getCampaignClaims,            // Admin - with options object
  getCampaignClaimsByUserId,    // Admin
  getCampaignClaimsByBusinessId // Admin
} = useCampaigns();

// Redemption system
const { 
  getActiveRedemptions,
  getUserRedemptions,
  redeem,
  getRedemptionTypes,
  signingStatus,           // UI feedback during blockchain signing
  signingStatusMessage,
  createRedemption,        // Admin
  getAllRedemptions,       // Admin
  updateRedemption,        // Admin
  toggleRedemptionStatus   // Admin
} = useRedemptions();
```

### Platform & Analytics

```typescript
// Purchase processing
const { 
  createPaymentIntent,
  getActivePurchaseTokens,
  getAllUserPurchases
} = usePurchases();

// Multi-tenant support
const { 
  getTenantInfo,
  getClientConfig,
  getLoginToken,
  getAdmins
} = useTenants();

// Analytics & reporting
const { 
  getTransactionAnalytics
} = useAnalytics();

// Web3 & blockchain (wallet addresses from user.wallets)
const { 
  getTokenBalance,
  getTokenMetadata,
  getTokenCollection,
  resolveIPFSUrl,
  fetchAndProcessMetadata,
  getChainDataById,
  getExplorerUrl,
  // Helper methods for token collections
  extractTokenIds,                    // Extract tokenIds from TokenDTO metadata
  getAccountOwnedTokensFromContract,  // Recommended: Get owned tokens automatically
  buildCollectionRequest              // Build request for getTokenCollection
} = useWeb3();

// User status & achievements
const {
  getUserStatusTypes,
  getEarnedUserStatus,
  createUserStatusType  // Admin
} = useUserStatus();

// File management
const {
  getSignedPutUrl,
  getSignedGetUrl,
  getSignedUrl,
  optimizeMedia
} = useFiles();

// Donations
const {
  getDonationTypes
} = useDonations();

// Event subscriptions (notifications, logging)
const {
  subscribe,        // Subscribe to SDK events
  once,             // One-time event listener
  clear,            // Clear all subscriptions
  isAvailable,      // Event system available
  subscriberCount   // Active subscriber count
} = useEvents();
```

## Event System

The `useEvents` hook provides access to SDK-wide events for showing notifications, logging, and reacting to SDK operations. All events include a `userMessage` field ready for UI display.

### Basic Usage

```typescript
import { useEvents } from '@explorins/pers-sdk-react-native';
import { useEffect } from 'react';

function NotificationHandler() {
  const { subscribe, isAvailable } = useEvents();
  
  useEffect(() => {
    if (!isAvailable) return;
    
    // Subscribe to all events
    const unsubscribe = subscribe((event) => {
      showNotification(event.userMessage, event.level);
    });
    
    return () => unsubscribe();  // Cleanup on unmount
  }, [subscribe, isAvailable]);
}
```

### Filtering Events

```typescript
// Only transaction successes
subscribe(
  (event) => {
    playSuccessSound();
    showConfetti();
  },
  { domain: 'transaction', level: 'success' }
);

// Only errors (for logging)
subscribe(
  (event) => {
    logToSentry(event);
  },
  { level: 'error' }
);

// One-time event (auto-unsubscribes)
once(
  (event) => {
    console.log('First transaction completed!');
  },
  { domain: 'transaction', level: 'success' }
);
```

### Event Domains

| Domain | Events |
|--------|--------|
| `auth` | Login, logout, token refresh |
| `user` | Profile updates |
| `transaction` | Created, completed, failed |
| `campaign` | Claimed, activated |
| `redemption` | Redeemed, expired |
| `business` | Created, updated, membership |
| `api` | Network errors, validation errors |

### Event Structure

```typescript
interface PersEvent {
  id: string;           // Unique event ID
  timestamp: number;    // Unix timestamp (ms)
  domain: string;       // Event domain (transaction, auth, etc.)
  type: string;         // Event type within domain
  level: 'success' | 'error';
  userMessage: string;  // Ready for UI display
  action?: string;      // Suggested action
  details?: object;     // Additional data
}
```

---

## POS Transaction Flow

For Point-of-Sale scenarios where a **business submits a transaction on behalf of a user**, use the `buildPOSTransferRequest` helper:

```typescript
import { 
  useTransactions, 
  buildPOSTransferRequest,
  useEvents 
} from '@explorins/pers-sdk-react-native';

function POSScreen() {
  const { createTransaction, signingStatus } = useTransactions();
  const { subscribe, isAvailable } = useEvents();
  
  // Listen for transaction events
  useEffect(() => {
    if (!isAvailable) return;
    
    const unsubscribe = subscribe(
      (event) => {
        if (event.level === 'success') {
          Alert.alert('Success', event.userMessage);
        }
      },
      { domain: 'transaction' }
    );
    
    return () => unsubscribe();
  }, [subscribe, isAvailable]);
  
  const handlePOSTransaction = async (
    userId: string,
    businessId: string,
    amount: number,
    token: TokenDTO
  ) => {
    // Build POS transfer request
    const request = buildPOSTransferRequest({
      amount,
      contractAddress: token.contractAddress,
      chainId: token.chainId,
      userId,      // User sending tokens
      businessId   // Business receiving & authorized to submit
    });
    
    // Create and sign transaction
    const result = await createTransaction(request, (status, message) => {
      console.log(`Signing: ${status} - ${message}`);
    });
    
    console.log('Transaction created:', result.transaction?.id);
  };
}
```

### POS Authorization Fields

The `buildPOSTransferRequest` helper automatically sets:

| Field | Value | Purpose |
|-------|-------|---------|
| `engagedBusinessId` | Business ID | Business commercially involved (for reporting) |
| `authorizedSubmitterId` | Business ID | Entity authorized to submit the signed tx |
| `authorizedSubmitterType` | `BUSINESS` | Type of authorized submitter |

For custom scenarios, use `buildTransferRequest` with manual POS fields:

```typescript
import { buildTransferRequest, AccountOwnerType } from '@explorins/pers-sdk-react-native';

const request = buildTransferRequest({
  amount: 100,
  contractAddress: '0x...',
  chainId: 137,
  senderAccountId: 'user-123',
  senderAccountType: AccountOwnerType.USER,
  recipientAccountId: 'business-456',
  recipientAccountType: AccountOwnerType.BUSINESS,
  // POS authorization
  engagedBusinessId: 'business-456',
  authorizedSubmitterId: 'business-456',
  authorizedSubmitterType: AccountOwnerType.BUSINESS
});
```

---

## Token Collection Helper Methods

The `useWeb3` hook includes helper methods for querying token balances from any blockchain address. These work with **all token standards** (ERC-20, ERC-721, ERC-1155).

### Recommended: getAccountOwnedTokensFromContract

A unified API to get all tokens owned by any blockchain address:

```typescript
import { useWeb3, useTokens, usePersSDK } from '@explorins/pers-sdk-react-native';

function RewardTokensScreen() {
  const { user } = usePersSDK();
  const { getRewardTokens } = useTokens();
  const { getAccountOwnedTokensFromContract } = useWeb3();

  const loadUserRewards = async () => {
    const walletAddress = user?.wallets?.[0]?.address;
    if (!walletAddress) return;

    const rewardTokens = await getRewardTokens();
    
    for (const token of rewardTokens) {
      // Works with ERC-20, ERC-721, and ERC-1155 automatically
      const result = await getAccountOwnedTokensFromContract(walletAddress, token);
      
      console.log(`Token: ${token.symbol}`);
      console.log(`Owned: ${result.totalOwned}`);
      
      result.ownedTokens.forEach(owned => {
        console.log(`  - ${owned.metadata?.name}: ${owned.balance}`);
      });
    }
  };

  return (
    <TouchableOpacity onPress={loadUserRewards}>
      <Text>Load My Rewards</Text>
    </TouchableOpacity>
  );
}
```

### How It Handles Each Token Standard

| Token Type | What It Does |
|------------|--------------|
| **ERC-20** | Returns balance for fungible tokens |
| **ERC-721** | Enumerates all owned NFTs |
| **ERC-1155** | Extracts tokenIds from metadata, queries balances |

The helper abstracts away the complexity - especially for ERC-1155 which requires specific `tokenIds` that the helper extracts from `token.metadata[].tokenMetadataIncrementalId`.

### Return Type

```typescript
interface AccountOwnedTokensResult {
  token: TokenDTO;           // The token definition
  ownedTokens: TokenBalance[]; // Tokens with balance > 0
  totalOwned: number;        // Count of owned tokens
}
```

## EVM Blockchain Transaction Signing

The `useTransactionSigner` hook provides secure EVM blockchain transaction signing:

```typescript
import { useTransactionSigner } from '@explorins/pers-sdk-react-native';

function BlockchainRedemption() {
  const { 
    signAndSubmitTransactionWithJWT, 
    isSignerAvailable,
    isSignerInitialized 
  } = useTransactionSigner();

  const handleBlockchainRedemption = async (jwtToken: string) => {
    if (!isSignerAvailable) {
      console.error('EVM blockchain signer not available');
      return;
    }

    try {
      // This handles the complete flow:
      // 1. User authentication via WebAuthn
      // 2. Transaction data fetching from PERS backend  
      // 3. Secure EVM transaction signing using device biometrics
      // 4. EVM blockchain submission
      const result = await signAndSubmitTransactionWithJWT(jwtToken);
      
      if (result.success) {
        console.log('Transaction completed!');
        console.log('Hash:', result.transactionHash);
        console.log('View on blockchain explorer: [Chain-specific URL]');
        
        // Handle post-transaction flow
        if (result.shouldRedirect && result.redirectUrl) {
          // Navigate to success page or external URL
          Linking.openURL(result.redirectUrl);
        }
      }
    } catch (error) {
      if (error.message.includes('expired')) {
        // JWT token expired - redirect to login
        navigation.navigate('Login');
      } else if (error.message.includes('cancelled')) {
        // User cancelled WebAuthn authentication
        console.log('User cancelled transaction');
      } else {
        // Other errors
        Alert.alert('Transaction Failed', error.message);
      }
    }
  };

  // Show loading state while signer initializes
  if (!isSignerInitialized) {
    return (
      <View style={styles.loadingContainer}>
        <ActivityIndicator size="large" />
        <Text>Initializing EVM blockchain signer...</Text>
      </View>
    );
  }

  return (
    <TouchableOpacity 
      onPress={() => handleBlockchainRedemption(redemptionJWT)}
      disabled={!isSignerAvailable}
      style={[styles.button, !isSignerAvailable && styles.disabled]}
    >
      <Text style={styles.buttonText}>
        Sign & Submit Transaction
      </Text>
    </TouchableOpacity>
  );
}
```
### Error Handling

The SDK provides comprehensive structured error handling with utilities for consistent error management:

```typescript
import { 
  PersApiError, 
  ErrorUtils 
} from '@explorins/pers-sdk-react-native';
import { Alert } from 'react-native';

// Structured error handling
try {
  await sdk.campaigns.claimCampaign({ campaignId });
} catch (error) {
  if (error instanceof PersApiError) {
    // Structured error with backend details
    console.log('Error code:', error.code);        // 'CAMPAIGN_BUSINESS_REQUIRED'
    console.log('Status:', error.status);          // 400
    console.log('User message:', error.userMessage); // User-friendly message
    
    // Show user-friendly error
    Alert.alert('Error', error.userMessage || error.message);
    
    // Check if retryable
    if (error.retryable) {
      Alert.alert('Error', error.message, [
        { text: 'Cancel' },
        { text: 'Retry', onPress: () => retry() }
      ]);
    }
  } else {
    // Generic error fallback with ErrorUtils
    const message = ErrorUtils.getMessage(error);
    Alert.alert('Error', message);
  }
}

// Error utilities for any error type
const status = ErrorUtils.getStatus(error);         // Extract HTTP status
const message = ErrorUtils.getMessage(error);       // Extract user message
const retryable = ErrorUtils.isRetryable(error);    // Check if retryable
const tokenExpired = ErrorUtils.isTokenExpiredError(error); // Detect token expiration
```

### Transaction Helpers

**NEW in v2.1.1:** Factory functions for client-side transaction workflows:

```typescript
import {
  ClientTransactionType,
  buildPendingTransactionData,
  extractDeadlineFromSigningData,
  type PendingTransactionParams
} from '@explorins/pers-sdk-react-native';

// Build pending transaction data for QR codes, NFC, deep links, etc.
const qrData = buildPendingTransactionData(
  signingResult.transactionId,
  signingResult.signature,
  'EIP-712' // Default format
);

console.log(qrData);
// {
//   transactionId: '...',
//   signature: '0x...',
//   transactionFormat: 'EIP-712',
//   txType: 'PENDING_SUBMISSION'
// }

// Serialize for transfer (QR code, NFC, etc.)
const qrCodeValue = JSON.stringify(qrData);

// Extract deadline from EIP-712 signing data
const deadline = extractDeadlineFromSigningData(signingResult.signingData);
if (deadline) {
  const expiryTime = new Date(deadline * 1000);
  console.log('Transaction expires at:', expiryTime);
}

// Client transaction types
if (transaction.txType === ClientTransactionType.PENDING_SUBMISSION) {
  // Handle pending submission flow
}
```
## Security Features

### WebAuthn Authentication
- Device biometric authentication (fingerprint, face ID, PIN)
- No passwords or private keys stored locally
- Secure hardware-backed authentication

### Token Storage
- React Native Keychain integration for sensitive data
- Automatic token refresh and expiration handling
- Multiple storage strategies (AsyncStorage, Keychain, Memory)

### Transaction Security
- JWT token validation and expiration checking
- Secure transaction signing without exposing private keys
- Automatic session management with secure caching

## Platform Support

- **iOS**: Full support with Keychain integration
- **Android**: Full support with Keystore integration  
- **Expo**: Compatible with Expo managed workflow
- **Web**: React Native Web compatibility

---

## Analytics & Attribution Tracking

The SDK provides built-in analytics tracking for user acquisition and engagement. The `PersSDKProvider` **automatically detects the platform** (iOS/Android) and sends tracking headers with all API requests.

### Automatic Platform Detection

When you wrap your app with `PersSDKProvider`, platform info is auto-detected:

```typescript
// Platform is automatically detected and set:
// { os: 'iOS', osVersion: '17.4', deviceType: 'phone', app: 'MyApp' }
```

### Manual Data Source Tracking

For campaign attribution, use `usePersSDK()` to set custom tracking data:

```typescript
import { usePersSDK } from '@explorins/pers-sdk-react-native';
import { Linking } from 'react-native';

function DeepLinkHandler() {
  const { sdk } = usePersSDK();
  
  useEffect(() => {
    // Handle deep links with UTM params
    Linking.getInitialURL().then(url => {
      if (url) {
        const params = new URL(url).searchParams;
        
        sdk.setDataSource({
          channel: 'mobile',
          medium: params.get('utm_medium') || undefined,
          campaign: params.get('utm_campaign') || undefined,
          source: params.get('utm_source') || undefined,
        });
      }
    });
  }, [sdk]);
}
```

### HTTP Headers Sent

The SDK automatically sends tracking data as HTTP headers:

| Header | Source | Description |
|--------|--------|-------------|
| `x-source-channel` | `dataSource.channel` | `'mobile'` for native apps |
| `x-source-medium` | `dataSource.medium` | Traffic medium (push, email, etc.) |
| `x-source-campaign` | `dataSource.campaign` | Campaign identifier |
| `x-source` | `dataSource.source` | Traffic source |
| `x-platform` | Auto-detected | JSON: `{ os, osVersion, deviceType, app }` |

### Push Notification Attribution

Track users coming from push notifications:

```typescript
import { usePersSDK } from '@explorins/pers-sdk-react-native';

function usePushNotificationTracking() {
  const { sdk } = usePersSDK();
  
  const handlePushNotification = (notification: any) => {
    // Extract campaign from notification data
    const campaignId = notification.data?.campaignId;
    
    if (campaignId) {
      sdk.updateDataSource({
        medium: 'push',
        campaign: campaignId,
      });
    }
  };
  
  return { handlePushNotification };
}
```

---

## Advanced Configuration

### SDK Initialization & DPoP

The `PersSDKProvider` accepts a `config` object allowing you to customize behavior, including **DPoP (Distributed Proof of Possession)**.

**DPoP Behavior:**
- **Enabled by Default**: Automatically active on iOS/Android using a high-performance C++ crypto bridge (`react-native-quick-crypto`).
- **Web Support**: Uses standard WebCrypto API.
- **Security**: Binds access tokens to the device, preventing replay attacks.

**Customizing Initialization:**
```typescript
<PersSDKProvider config={{
  apiProjectKey: 'your-project-key',
  // DPoP is enabled by default. To disable (not recommended):
  dpop: {
    enabled: false
  }
}}>
  <YourApp />
</PersSDKProvider>
```

### Storage Fallback Strategy

The SDK implements a robust **Hybrid Storage Strategy**:
1.  **Primary**: Attempts to use **Android Keystore / iOS Keychain** (via `react-native-keychain`) for maximum security.
2.  **Fallback**: If hardware storage is unavailable or fails, it automatically falls back to **AsyncStorage**.
3.  **Web**: Uses `localStorage` automatically.

This ensures your app works reliably across all devices while prioritizing security where available.

### Custom Authentication Provider

The SDK automatically uses `ReactNativeSecureStorage` for React Native (iOS/Android) and `LocalStorageTokenStorage` for Web. You can customize this if needed:

```typescript
import { createReactNativeAuthProvider, ReactNativeSecureStorage } from '@explorins/pers-sdk-react-native';

const authProvider = createReactNativeAuthProvider('your-project-key', {
  keyPrefix: 'my_app_tokens_',
  debug: true,
  // Optional: Provide custom storage implementation
  // customStorage: new ReactNativeSecureStorage('custom_prefix_')
});
```

### Error Handling Patterns

```typescript
import { useTokens } from '@explorins/pers-sdk-react-native';

function ErrorHandlingExample() {
  const { getTokens } = useTokens();
  const [error, setError] = useState<string | null>(null);

  const handleLoadTokensWithErrorHandling = async () => {
    try {
      setError(null);
      await getTokens();
    } catch (err) {
      // Handle specific error types
      if (err.code === 'NETWORK_ERROR') {
        setError('Network connection required');
      } else if (err.code === 'INVALID_TOKEN') {
        setError('Please log in again');
      } else {
        setError(err.message || 'An unexpected error occurred');
      }
    }
  };
}
```

## Analytics Integration

```typescript
import { useAnalytics } from '@explorins/pers-sdk-react-native';

function AnalyticsExample() {
  const { getTransactionAnalytics } = useAnalytics();

  const loadAnalytics = async () => {
    try {
      const txAnalytics = await getTransactionAnalytics({
        groupBy: ['day'],
        metrics: ['count', 'sum'],
        startDate: '2023-01-01',
        endDate: '2023-12-31'
      });
      
      console.log('Transaction analytics:', txAnalytics.results);
      console.log('Execution time:', txAnalytics.metadata?.executionTime);
    } catch (error) {
      console.error('Failed to load analytics:', error);
    }
  };
}
```

## Migration Guide

### v2.0.0 Breaking Changes - Pagination

**Version 2.0.0 introduces standardized pagination** across all hooks that return lists. Previously, hooks returned raw arrays. Now they return `PaginatedResponseDTO<T>` with pagination metadata.

#### What Changed

All hooks returning lists now return paginated responses:

```typescript
// ❌ OLD (v1.x) - Direct array
const businesses: BusinessDTO[] = await getActiveBusinesses();

// ✅ NEW (v2.x) - Paginated response
const response: PaginatedResponseDTO<BusinessDTO> = await getActiveBusinesses();
const businesses: BusinessDTO[] = response.data;
```

#### Affected Hooks

| Hook | Method | Return Type |
|------|--------|-------------|
| `useBusiness` | `getActiveBusinesses()` | `PaginatedResponseDTO<BusinessDTO>` |
| `useBusiness` | `getBusinessTypes()` | `PaginatedResponseDTO<BusinessTypeDTO>` |
| `useCampaigns` | `getActiveCampaigns()` | `PaginatedResponseDTO<CampaignDTO>` |
| `useCampaigns` | `getUserClaims()` | `PaginatedResponseDTO<CampaignClaimDTO>` |
| `useTokens` | `getTokens()` | `PaginatedResponseDTO<TokenDTO>` |
| `useTokens` | `getRewardTokens()` | `PaginatedResponseDTO<TokenDTO>` |
| `useTokens` | `getStatusTokens()` | `PaginatedResponseDTO<TokenDTO>` |
| `useRedemptions` | `getActiveRedemptions()` | `PaginatedResponseDTO<RedemptionDTO>` |
| `useRedemptions` | `getUserRedemptions()` | `PaginatedResponseDTO<RedemptionDTO>` |
| `useTransactions` | `getUserTransactionHistory()` | `PaginatedResponseDTO<TransactionDTO>` |
| `usePurchases` | `getAllUserPurchases()` | `PaginatedResponseDTO<PurchaseDTO>` |
| `usePurchases` | `getActivePurchaseTokens()` | `PaginatedResponseDTO<PurchaseTokenDTO>` |
| `useDonations` | `getDonationTypes()` | `PaginatedResponseDTO<DonationTypeDTO>` |

#### PaginatedResponseDTO Structure

```typescript
import type { PaginatedResponseDTO } from '@explorins/pers-shared';

interface PaginatedResponseDTO<T> {
  data: T[];              // Array of results
  pagination: {
    currentPage: number;  // Current page number (1-indexed)
    pageSize: number;     // Items per page
    totalItems: number;   // Total number of items across all pages
    totalPages: number;   // Total number of pages
  };
}
```

#### Migration Examples

**Before (v1.x):**
```typescript
const businesses = await getActiveBusinesses();
console.log('Business count:', businesses.length);
businesses.forEach(b => console.log(b.name));
```

**After (v2.x):**
```typescript
const response = await getActiveBusinesses();
console.log('Business count:', response.data.length);
console.log('Total businesses:', response.pagination.totalItems);
response.data.forEach(b => console.log(b.name));
```

**With Pagination Parameters:**
```typescript
// Fetch page 2 with 20 items per page
const response = await getActiveBusinesses({ page: 2, pageSize: 20 });

console.log(`Page ${response.pagination.currentPage} of ${response.pagination.totalPages}`);
console.log(`Showing ${response.data.length} businesses`);
```

#### React Native Component Example

```typescript
import { useState, useEffect } from 'react';
import { useBusiness } from '@explorins/pers-sdk-react-native';
import { View, Text, FlatList, ActivityIndicator } from 'react-native';

function BusinessListScreen() {
  const { getActiveBusinesses } = useBusiness();
  const [businesses, setBusinesses] = useState<BusinessDTO[]>([]);
  const [pagination, setPagination] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    loadBusinesses();
  }, []);
  
  const loadBusinesses = async (page = 1) => {
    try {
      setLoading(true);
      const response = await getActiveBusinesses({ page, pageSize: 20 });
      setBusinesses(response.data);
      setPagination(response.pagination);
    } catch (error) {
      console.error('Failed to load businesses:', error);
    } finally {
      setLoading(false);
    }
  };
  
  if (loading) return <ActivityIndicator />;
  
  return (
    <View>
      <Text>
        Showing {businesses.length} of {pagination?.totalItems} businesses
      </Text>
      <FlatList
        data={businesses}
        keyExtractor={(item) => item.id}
        renderItem={({ item }) => <BusinessCard business={item} />}
      />
      <Text>Page {pagination?.currentPage} of {pagination?.totalPages}</Text>
    </View>
  );
}
```

#### Quick Fix for Existing Code

If you want minimal code changes, extract `.data` immediately:

```typescript
// Quick adaptation in your hooks
const businesses = (await getActiveBusinesses()).data;
const campaigns = (await getActiveCampaigns()).data;
const tokens = (await getTokens()).data;
```

#### Benefits of Pagination

- **Performance**: Load only what you need, not entire datasets
- **Consistency**: All list endpoints follow the same pattern
- **Metadata**: Access total counts without loading all items
- **Better UX**: Build proper pagination UI components
- **Memory Efficiency**: Reduced memory footprint for large datasets

---

## Documentation

All documentation is available on GitHub:

| Guide | Description |
|-------|-------------|
| [Quick Setup Guide](https://github.com/eXplorins/pers-sdk/blob/main/packages/pers-sdk-react-native/docs/QUICK_SETUP_GUIDE.md) | Android + Passkey setup in one page |
| [**Troubleshooting**](https://github.com/eXplorins/pers-sdk/blob/main/packages/pers-sdk-react-native/docs/TROUBLESHOOTING.md) | **Common issues & solutions** |
| [Passkey Setup](https://github.com/eXplorins/pers-sdk/blob/main/packages/pers-sdk-react-native/REACT_NATIVE_PASSKEY_SETUP.md) | WebAuthn/Passkey configuration |
| [Android Compatibility](https://github.com/eXplorins/pers-sdk/blob/main/packages/pers-sdk-react-native/docs/ANDROID_COMPATIBILITY_FAQ.md) | New Architecture troubleshooting |
| [Auth State Handling](https://github.com/eXplorins/pers-sdk/blob/main/packages/pers-sdk-react-native/docs/AUTH_STATE_HANDLING.md) | Session management patterns |
| [Changelog](https://github.com/eXplorins/pers-sdk/blob/main/packages/pers-sdk-react-native/CHANGELOG.md) | Version history |

### Quick Reference: Common Issues

- **User is null after restart?** → Session restoration is async. [See solution](./docs/TROUBLESHOOTING.md#1-sdkuser-is-null-after-app-restart)
- **Stamp transaction fails?** → Use `tokenBalance.tokenId`, not metadata ID. [See solution](./docs/TROUBLESHOOTING.md#3-stamp-erc721-voucher-transaction-fails)
- **Which wallet to use?** → Match by `token.chainId`, never use `wallets[0]`. [See solution](./docs/TROUBLESHOOTING.md#6-which-wallet-to-use-multiple-wallets)
- **Passkey "NotConfigured"?** → Device needs Google Play Services. [See solution](./docs/TROUBLESHOOTING.md#2-passkey-notconfigured-error-on-android)

---

## Contributing

We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.

## License

MIT License - see [LICENSE](LICENSE) file for details.

## Support

- Email: support@explorins.com
- Documentation: [https://docs.pers.ninja](https://docs.pers.ninja)
- Issues: [GitHub Issues](https://github.com/eXplorins/pers-sdk/issues)

---

Built with care by [eXplorins](https://explorins.com) for the tourism and loyalty industry.