@explorins/pers-sdk-react-native 2.3.6 → 2.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1266 -1244
  3. package/dist/__tests__/PersSDKProvider.test.js +33 -0
  4. package/dist/hooks/useBusiness.d.ts +2 -2
  5. package/dist/hooks/useBusiness.d.ts.map +1 -1
  6. package/dist/hooks/useBusiness.js +2 -2
  7. package/dist/hooks/useIPFSUrl.d.ts +3 -3
  8. package/dist/hooks/useIPFSUrl.d.ts.map +1 -1
  9. package/dist/hooks/useIPFSUrl.js +7 -7
  10. package/dist/hooks/useTransactions.d.ts.map +1 -1
  11. package/dist/hooks/useTransactions.js +13 -5
  12. package/dist/hooks/useWeb3.js +4 -4
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +2412 -492
  16. package/dist/index.js.map +1 -1
  17. package/dist/providers/PersSDKProvider.d.ts +4 -0
  18. package/dist/providers/PersSDKProvider.d.ts.map +1 -1
  19. package/dist/providers/PersSDKProvider.js +14 -0
  20. package/dist/providers/react-native-http-client.d.ts +2 -0
  21. package/dist/providers/react-native-http-client.d.ts.map +1 -1
  22. package/dist/providers/react-native-http-client.js +5 -2
  23. package/dist/storage/rn-secure-storage.d.ts.map +1 -1
  24. package/dist/storage/rn-secure-storage.js +21 -8
  25. package/package.json +3 -3
  26. package/src/__tests__/PersSDKProvider.test.tsx +484 -420
  27. package/src/__tests__/setup.ts +40 -40
  28. package/src/hooks/index.ts +60 -60
  29. package/src/hooks/useAnalytics.ts +327 -327
  30. package/src/hooks/useAuth.ts +265 -265
  31. package/src/hooks/useBookings.ts +191 -191
  32. package/src/hooks/useBusiness.ts +251 -251
  33. package/src/hooks/useCampaigns.ts +500 -500
  34. package/src/hooks/useDonations.ts +66 -66
  35. package/src/hooks/useEvents.ts +372 -372
  36. package/src/hooks/useFiles.ts +155 -155
  37. package/src/hooks/useIPFSUrl.ts +192 -192
  38. package/src/hooks/usePurchases.ts +65 -65
  39. package/src/hooks/useRedemptions.ts +467 -467
  40. package/src/hooks/useTenants.ts +72 -72
  41. package/src/hooks/useTokenBalances.ts +385 -385
  42. package/src/hooks/useTokens.ts +313 -313
  43. package/src/hooks/useTransactionSigner.ts +611 -611
  44. package/src/hooks/useTransactions.ts +299 -290
  45. package/src/hooks/useTriggerSources.ts +301 -301
  46. package/src/hooks/useUserStatus.ts +61 -61
  47. package/src/hooks/useUsers.ts +167 -167
  48. package/src/hooks/useWeb3.ts +355 -355
  49. package/src/index.ts +777 -774
  50. package/src/polyfills/index.ts +160 -160
  51. package/src/polyfills/web3-polyfills.ts +97 -97
  52. package/src/providers/PersSDKProvider.tsx +393 -374
  53. package/src/providers/react-native-auth-provider.ts +110 -110
  54. package/src/providers/react-native-http-client.ts +150 -145
  55. package/src/providers/rn-dpop-provider.ts +190 -190
  56. package/src/storage/async-storage-token-storage.ts +185 -185
  57. package/src/storage/rn-secure-storage.ts +182 -170
  58. package/src/types/react-native-globals.d.ts +45 -45
  59. package/src/utils/index.ts +8 -8
  60. package/src/utils/platform-detection.ts +86 -86
  61. package/dist/index-CzLxqePZ.js +0 -7717
  62. package/dist/index-CzLxqePZ.js.map +0 -1
@@ -1,611 +1,611 @@
1
- import { useCallback, useState, useRef, useEffect } from 'react';
2
- // import { usePersSDK } from '../providers/PersSDKProvider';
3
-
4
- // TYPE-ONLY imports from /types entry - NO native module deps, safe at any time
5
- import type { PersSignerConfig, StatusCallback, StatusUpdateData } from '@explorins/pers-signer/types';
6
- import type { SigningStatus as SigningStatusType } from '@explorins/pers-signer/types';
7
- // Runtime const import - also from /types (no native deps)
8
- import { SigningStatus as SigningStatusConst } from '@explorins/pers-signer/types';
9
-
10
- // Import actual SDK types from the React Native-specific export
11
- type PersSignerSDK = import('@explorins/pers-signer/react-native').PersSignerSDK;
12
- type SubmissionResult = import('@explorins/pers-signer/react-native').SubmissionResult;
13
- type AuthenticatedUser = import('@explorins/pers-signer/react-native').AuthenticatedUser;
14
- type TransactionSigningResult = import('@explorins/pers-signer/react-native').TransactionSigningResult;
15
-
16
- // Re-export the type for external consumers
17
- export type { SigningStatusType as SigningStatus };
18
- export type { StatusUpdateData };
19
-
20
- /**
21
- * Simple status callback function type for React Native consumers
22
- * (Simplified from the signer SDK's object-based StatusCallback interface)
23
- */
24
- export type OnStatusUpdateFn = (status: SigningStatusType, message: string, data?: StatusUpdateData) => void;
25
-
26
-
27
- // Lazy-loaded signer SDK - initialized on first use to avoid native module timing issues
28
- let _createPersSignerSDK: ((config: PersSignerConfig) => Promise<PersSignerSDK>) | null = null;
29
- let _signerInitAttempted = false;
30
-
31
- /**
32
- * Get the SigningStatus const object
33
- * Now directly imported from /types (no native deps, always available)
34
- */
35
- function getSigningStatus(): typeof SigningStatusType {
36
- return SigningStatusConst;
37
- }
38
-
39
- /**
40
- * Lazily load the PERS Signer SDK.
41
- *
42
- * Supports customer pre-initialization via global.__PersSigner for environments
43
- * where native module timing is unpredictable (e.g., certain React Native setups).
44
- *
45
- * @returns The createPersSignerSDK function or null if unavailable
46
- */
47
- function getSignerSDKFactory(): ((config: PersSignerConfig) => Promise<PersSignerSDK>) | null {
48
- if (_signerInitAttempted) {
49
- // Already tried - return cached result (could be null if failed)
50
- return _createPersSignerSDK;
51
- }
52
-
53
- _signerInitAttempted = true;
54
-
55
- // Check for customer pre-initialized global (for native module timing issues)
56
- if (typeof global !== 'undefined' && (global as any).__PersSigner?.createPersSignerSDK) {
57
- const globalModule = (global as any).__PersSigner;
58
- _createPersSignerSDK = globalModule.createPersSignerSDK;
59
- return _createPersSignerSDK;
60
- }
61
-
62
- // Standard lazy load
63
- try {
64
- const signerModule = require('@explorins/pers-signer/react-native');
65
- _createPersSignerSDK = signerModule.createPersSignerSDK;
66
- return _createPersSignerSDK;
67
- } catch (error: unknown) {
68
- console.warn('[useTransactionSigner] PERS Signer SDK not available:', (error as Error).message);
69
- console.warn('[useTransactionSigner] Real blockchain signing will not be available');
70
- console.warn('[useTransactionSigner] Tip: If timing issues, pre-initialize in index.js: global.__PersSigner = require("@explorins/pers-signer/react-native")');
71
- _createPersSignerSDK = null; // Mark as unavailable
72
- return null;
73
- }
74
- }
75
-
76
-
77
- /**
78
- * Return interface for the useTransactionSigner hook
79
- *
80
- * Provides secure transaction signing capabilities for EVM blockchain interactions
81
- * and error handling for React Native applications.
82
- *
83
- * @interface TransactionSignerHook
84
- * @property {Function} signAndSubmitTransactionWithJWT - Main method to sign and submit transactions
85
- * @property {Function} signPersTransaction - Sign transaction without submitting (for POS flows)
86
- * @property {boolean} isSignerInitialized - Whether the signer SDK has been initialized
87
- * @property {boolean} isSignerAvailable - Whether signing functionality is fully available
88
- * @property {SigningStatus | null} currentStatus - Current signing status for UI feedback
89
- * @property {string | null} statusMessage - Human-readable status message
90
- */
91
- export interface TransactionSignerHook {
92
- signAndSubmitTransactionWithJWT: (jwt: string, onStatusUpdate?: OnStatusUpdateFn) => Promise<SubmissionResult>;
93
- signPersTransaction: (jwt: string, onStatusUpdate?: OnStatusUpdateFn) => Promise<TransactionSigningResult>;
94
- isSignerInitialized: boolean;
95
- isSignerAvailable: boolean;
96
- currentStatus: SigningStatusType | null;
97
- statusMessage: string | null;
98
- }
99
-
100
- // Constants - TODO: Move to environment config later
101
- const DEFAULT_ETHERS_PROVIDER = "https://sepolia.infura.io/v3/2781b4b5242343d5b0954c98f287b29e";
102
-
103
- /**
104
- * React Native hook for blockchain transaction signing using PERS Signer SDK
105
- *
106
- * This hook provides a complete blockchain transaction signing solution for React Native
107
- * applications, integrating with the PERS ecosystem for tourism loyalty and reward systems.
108
- * It automatically handles WebAuthn authentication, transaction preparation, signing, and
109
- * blockchain submission in a single convenient interface.
110
- *
111
- * **Features:**
112
- * - Automatic WebAuthn provider initialization for React Native
113
- * - 5-minute authentication caching to reduce repeated logins
114
- * - Complete transaction lifecycle management (sign + submit)
115
- * - Comprehensive error handling with detailed error messages
116
- * - Real-time status monitoring for UI feedback
117
- * - Automatic retry logic for network failures
118
- *
119
- * **Underlying SDK Methods:**
120
- * The hook uses the PERS Signer SDK which provides 5 core methods:
121
- * 1. `loginUser(jwtToken)` - Authenticate user with 5-minute caching
122
- * 2. `signTransaction(signingData, jwtToken)` - Sign transactions with auto-login
123
- * 3. `submitTransaction(signingResult, jwtToken)` - Submit signed transactions
124
- * 4. `signPersTransaction(jwtToken)` - Legacy one-liner for backward compatibility
125
- * 5. `signAndSubmitPersTransaction(jwtToken)` - Complete sign + submit flow (used by this hook)
126
- *
127
- * **Security:**
128
- * - WebAuthn-based secure authentication (no passwords stored)
129
- * - JWT token validation and expiration checking
130
- * - Secure transaction signing using device biometrics/PIN
131
- * - No sensitive data stored locally
132
- *
133
- * @returns {TransactionSignerHook} Hook interface with signing methods and status
134
- *
135
- * @example
136
- * **Basic Usage:**
137
- * ```typescript
138
- * import { useTransactionSigner } from '@explorins/pers-sdk-react-native';
139
- *
140
- * function TransactionScreen() {
141
- * const {
142
- * signAndSubmitTransactionWithJWT,
143
- * isSignerAvailable,
144
- * isSignerInitialized
145
- * } = useTransactionSigner();
146
- *
147
- * const handleSign = async (jwtFromRedemption: string) => {
148
- * if (!isSignerAvailable) {
149
- * console.error('Signer not available');
150
- * return;
151
- * }
152
- *
153
- * try {
154
- * const result = await signAndSubmitTransactionWithJWT(jwtFromRedemption);
155
- * if (result.success) {
156
- * console.log('Transaction completed:', result.transactionHash);
157
- * // Handle successful transaction
158
- * if (result.shouldRedirect && result.redirectUrl) {
159
- * // Navigate to success page or external URL
160
- * }
161
- * }
162
- * } catch (error) {
163
- * console.error('Transaction failed:', error.message);
164
- * // Handle error (show user-friendly message)
165
- * }
166
- * };
167
- *
168
- * return (
169
- * <View style={styles.container}>
170
- * <TouchableOpacity
171
- * onPress={() => handleSign(redeemJWT)}
172
- * disabled={!isSignerAvailable}
173
- * style={[styles.button, !isSignerAvailable && styles.disabled]}
174
- * >
175
- * <Text style={styles.buttonText}>
176
- * {isSignerInitialized ? 'Sign Transaction' : 'Initializing...'}
177
- * </Text>
178
- * </TouchableOpacity>
179
- * </View>
180
- * );
181
- * }
182
- * ```
183
- *
184
- * @example
185
- * **Advanced Usage with Error Handling:**
186
- * ```typescript
187
- * function AdvancedTransactionComponent() {
188
- * const [isLoading, setIsLoading] = useState(false);
189
- * const [error, setError] = useState<string | null>(null);
190
- * const { signAndSubmitTransactionWithJWT, isSignerAvailable } = useTransactionSigner();
191
- *
192
- * const handleTransaction = async (jwt: string) => {
193
- * setIsLoading(true);
194
- * setError(null);
195
- *
196
- * try {
197
- * const result = await signAndSubmitTransactionWithJWT(jwt);
198
- *
199
- * if (result.success) {
200
- * // Success handling
201
- * Alert.alert(
202
- * 'Success',
203
- * `Transaction completed!\nHash: ${result.transactionHash}`
204
- * );
205
- * } else {
206
- * throw new Error(result.error || 'Transaction failed');
207
- * }
208
- * } catch (err) {
209
- * const errorMessage = err instanceof Error ? err.message : 'Unknown error';
210
- * setError(errorMessage);
211
- *
212
- * // Different error handling based on error type
213
- * if (errorMessage.includes('expired')) {
214
- * Alert.alert('Session Expired', 'Please log in again');
215
- * } else if (errorMessage.includes('network')) {
216
- * Alert.alert('Network Error', 'Please check your connection');
217
- * } else {
218
- * Alert.alert('Transaction Failed', errorMessage);
219
- * }
220
- * } finally {
221
- * setIsLoading(false);
222
- * }
223
- * };
224
- *
225
- * return (
226
- * <View>
227
- * {error && (
228
- * <Text style={styles.errorText}>{error}</Text>
229
- * )}
230
- * <TouchableOpacity
231
- * onPress={() => handleTransaction(jwtToken)}
232
- * disabled={!isSignerAvailable || isLoading}
233
- * >
234
- * <Text>
235
- * {isLoading ? 'Processing...' : 'Sign & Submit Transaction'}
236
- * </Text>
237
- * </TouchableOpacity>
238
- * </View>
239
- * );
240
- * }
241
- * ```
242
- *
243
- * @example
244
- * **Integration with PERS SDK:**
245
- * ```typescript
246
- * import { usePersSDK } from '@explorins/pers-sdk-react-native';
247
- * import { useTransactionSigner } from '@explorins/pers-sdk-react-native';
248
- *
249
- * function RedemptionFlow() {
250
- * const { user, redeemTokens } = usePersSDK();
251
- * const { signAndSubmitTransactionWithJWT } = useTransactionSigner();
252
- *
253
- * const handleRedemption = async (tokenAmount: number) => {
254
- * try {
255
- * // Step 1: Create redemption with PERS SDK
256
- * const redemption = await redeemTokens({
257
- * tokenAmount,
258
- * destinationAddress: user?.walletAddress
259
- * });
260
- *
261
- * // Step 2: Sign and submit transaction
262
- * const txResult = await signAndSubmitTransactionWithJWT(redemption.jwtToken);
263
- *
264
- * if (txResult.success) {
265
- * console.log('Redemption completed:', txResult.transactionHash);
266
- * }
267
- * } catch (error) {
268
- * console.error('Redemption failed:', error);
269
- * }
270
- * };
271
- * }
272
- * ```
273
- *
274
- * @see {@link SubmissionResult} for transaction result structure
275
- * @see {@link TransactionSigningResult} for signing result details
276
- * @see {@link AuthenticatedUser} for user authentication data
277
- *
278
- * @since 1.5.0
279
- */
280
- export const useTransactionSigner = (): TransactionSignerHook => {
281
- // const { isInitialized, isAuthenticated, user } = usePersSDK();
282
- const [isSignerInitialized, setIsSignerInitialized] = useState(false);
283
- const [currentStatus, setCurrentStatus] = useState<SigningStatusType | null>(null);
284
- const [statusMessage, setStatusMessage] = useState<string | null>(null);
285
- const signerSDKRef = useRef<PersSignerSDK | null>(null);
286
-
287
- // Auto-initialize signer SDK when PERS SDK is ready
288
- useEffect(() => {
289
- const initializeSignerSDK = async () => {
290
- const createPersSignerSDK = getSignerSDKFactory();
291
- if (!createPersSignerSDK) {
292
- console.warn('[useTransactionSigner] Signer SDK not available');
293
- return;
294
- }
295
-
296
- try {
297
- // React Native createPersSignerSDK automatically includes WebAuthn provider
298
- const signerSDK = await createPersSignerSDK({
299
- ethersProviderUrl: DEFAULT_ETHERS_PROVIDER,
300
- relyingPartyName: 'PERS React Native App'
301
- });
302
-
303
- signerSDKRef.current = signerSDK;
304
- setIsSignerInitialized(true);
305
-
306
- } catch (error) {
307
- console.error('[useTransactionSigner] Failed to initialize signer SDK:', error);
308
- }
309
- };
310
-
311
- if (!isSignerInitialized) {
312
- initializeSignerSDK();
313
- }
314
- }, [isSignerInitialized]);
315
-
316
- /**
317
- * Sign and submit a blockchain transaction using JWT token
318
- *
319
- * This is the main method for executing blockchain transactions in React Native.
320
- * It handles the complete transaction lifecycle: authentication, signing, and
321
- * blockchain submission in a single call.
322
- *
323
- * **Process Flow:**
324
- * 1. Validates the JWT token and extracts transaction data
325
- * 2. Authenticates the user using WebAuthn (cached for 5 minutes)
326
- * 3. Fetches transaction data from PERS backend
327
- * 4. Signs the transaction using device security (biometrics/PIN)
328
- * 5. Submits the signed transaction to the blockchain
329
- * 6. Returns detailed result with transaction hash and status
330
- *
331
- * **Security Features:**
332
- * - JWT token validation and expiration checking
333
- * - WebAuthn authentication with device biometrics
334
- * - Secure transaction signing without exposing private keys
335
- * - Automatic session management with secure caching
336
- *
337
- * @param {string} jwt - JWT token containing transaction ID and user information
338
- * Must include: `transactionId`, `identifierEmail`, `tenantId`
339
- * @param {StatusCallback} [onStatusUpdate] - Optional callback for real-time status updates
340
- * @returns {Promise<SubmissionResult>} Complete transaction result
341
- *
342
- * @throws {Error} 'Transaction signer not initialized' - Hook not ready
343
- * @throws {Error} 'PERS Signer SDK not available' - SDK installation issue
344
- * @throws {Error} 'Invalid or expired JWT token' - Token validation failed
345
- * @throws {Error} 'Authentication failed' - WebAuthn authentication failed
346
- * @throws {Error} 'Transaction signing failed' - Signing process failed
347
- * @throws {Error} 'Transaction submission failed' - Blockchain submission failed
348
- *
349
- * @example
350
- * **Basic Transaction:**
351
- * ```typescript
352
- * const { signAndSubmitTransactionWithJWT } = useTransactionSigner();
353
- *
354
- * const result = await signAndSubmitTransactionWithJWT(jwtToken);
355
- * if (result.success) {
356
- * console.log('Transaction Hash:', result.transactionHash);
357
- * console.log('Blockchain explorer: [Chain-specific explorer URL]');
358
- * }
359
- * ```
360
- *
361
- * @example
362
- * **With Status Updates:**
363
- * ```typescript
364
- * const result = await signAndSubmitTransactionWithJWT(jwtToken, (status, message) => {
365
- * console.log(`Status: ${status} - ${message}`);
366
- * setStatusText(message);
367
- * });
368
- * ```
369
- *
370
- * @example
371
- * **With Error Handling:**
372
- * ```typescript
373
- * try {
374
- * const result = await signAndSubmitTransactionWithJWT(redeemJWT);
375
- *
376
- * if (result.success) {
377
- * // Transaction successful
378
- * setTransactionHash(result.transactionHash);
379
- *
380
- * if (result.shouldRedirect && result.redirectUrl) {
381
- * // Handle post-transaction redirect
382
- * Linking.openURL(result.redirectUrl);
383
- * }
384
- * } else {
385
- * // Transaction failed but didn't throw
386
- * setError(result.error || 'Transaction failed');
387
- * }
388
- * } catch (error) {
389
- * // Handle different error types
390
- * if (error.message.includes('expired')) {
391
- * // Token expired - redirect to login
392
- * navigation.navigate('Login');
393
- * } else if (error.message.includes('network')) {
394
- * // Network error - suggest retry
395
- * setError('Network error. Please try again.');
396
- * } else {
397
- * // Other errors
398
- * setError(error.message);
399
- * }
400
- * }
401
- * ```
402
- *
403
- * @see {@link SubmissionResult} for detailed result structure
404
- */
405
- const signAndSubmitTransactionWithJWT = useCallback(async (jwt: string, onStatusUpdate?: OnStatusUpdateFn): Promise<SubmissionResult> => {
406
- if (!isSignerInitialized || !signerSDKRef.current) {
407
- throw new Error('Transaction signer not initialized');
408
- }
409
- if (!getSignerSDKFactory()) {
410
- throw new Error('PERS Signer SDK not available. Blockchain signing is not supported.');
411
- }
412
-
413
- // Create status callback wrapper that updates both hook state and calls user callback
414
- const statusCallback: StatusCallback = {
415
- onStatusUpdate: (status: SigningStatusType, message: string, data?: StatusUpdateData) => {
416
- setCurrentStatus(status);
417
- setStatusMessage(message);
418
- onStatusUpdate?.(status, message, data);
419
- }
420
- };
421
-
422
- try {
423
- // Use the actual SDK method that handles the complete sign + submit flow
424
- const submissionResult = await signerSDKRef.current.signAndSubmitPersTransaction(jwt, statusCallback);
425
-
426
- return submissionResult;
427
-
428
- } catch (error) {
429
- console.error('[useTransactionSigner] JWT transaction signing failed:', error);
430
- const SigningStatusEnum = getSigningStatus();
431
- if (SigningStatusEnum) setCurrentStatus(SigningStatusEnum.ERROR);
432
- setStatusMessage(error instanceof Error ? error.message : 'Transaction failed');
433
- throw error; // Re-throw to maintain error handling upstream
434
- }
435
- }, [isSignerInitialized]);
436
-
437
- /**
438
- * Sign a PERS transaction without submitting (for POS flows with QR codes)
439
- *
440
- * This method signs the transaction but does NOT submit it to the blockchain.
441
- * Instead, it returns the signature which can be displayed as a QR code for
442
- * the business to scan and submit.
443
- *
444
- * **Use Case:** Point-of-Sale (POS) flows where:
445
- * 1. User signs the transaction
446
- * 2. App generates QR code with signature
447
- * 3. Business scans QR code
448
- * 4. Business submits transaction to blockchain
449
- *
450
- * @param {string} jwt - JWT token with transaction and user data
451
- * @param {StatusCallback} [onStatusUpdate] - Optional callback for status updates
452
- * @returns {Promise<TransactionSigningResult>} Signing result with signature for QR code
453
- *
454
- * @example
455
- * ```typescript
456
- * const { signPersTransaction } = useTransactionSigner();
457
- *
458
- * const result = await signPersTransaction(jwtToken);
459
- * if (result.success) {
460
- * const qrData = {
461
- * transactionId: result.transactionId,
462
- * signature: result.signature,
463
- * transactionFormat: result.signingData.format,
464
- * txType: 'PENDING_SUBMISSION'
465
- * };
466
- * // Display QR code with qrData
467
- * }
468
- * ```
469
- */
470
- const signPersTransaction = useCallback(async (jwt: string, onStatusUpdate?: OnStatusUpdateFn): Promise<TransactionSigningResult> => {
471
- if (!isSignerInitialized || !signerSDKRef.current) {
472
- throw new Error('Signer SDK not initialized');
473
- }
474
-
475
- // Create status callback wrapper
476
- const statusCallback: StatusCallback = {
477
- onStatusUpdate: (status: SigningStatusType, message: string, data?: StatusUpdateData) => {
478
- setCurrentStatus(status);
479
- setStatusMessage(message);
480
- onStatusUpdate?.(status, message, data);
481
- }
482
- };
483
-
484
- try {
485
- // Use the SDK's sign-only method
486
- const signingResult = await signerSDKRef.current.signPersTransaction(jwt, statusCallback);
487
- return signingResult;
488
- } catch (error) {
489
- console.error('[useTransactionSigner] Sign-only transaction failed:', error);
490
- const SigningStatusEnum = getSigningStatus();
491
- if (SigningStatusEnum) setCurrentStatus(SigningStatusEnum.ERROR);
492
- setStatusMessage(error instanceof Error ? error.message : 'Signing failed');
493
- throw error;
494
- }
495
- }, [isSignerInitialized]);
496
-
497
- return {
498
- /**
499
- * Sign and submit a blockchain transaction using JWT token
500
- *
501
- * Main method for executing blockchain transactions. Handles complete flow
502
- * from authentication to blockchain submission in a single call.
503
- *
504
- * @param {string} jwt - JWT token with transaction and user data
505
- * @param {StatusCallback} [onStatusUpdate] - Optional callback for real-time status updates
506
- * @returns {Promise<SubmissionResult>} Transaction result with hash and status
507
- */
508
- signAndSubmitTransactionWithJWT,
509
-
510
- /**
511
- * Sign a transaction without submitting (for POS flows)
512
- *
513
- * Signs the transaction and returns the signature without submitting to blockchain.
514
- * Use this for Point-of-Sale flows where the business scans a QR code and submits.
515
- *
516
- * @param {string} jwt - JWT token with transaction and user data
517
- * @param {StatusCallback} [onStatusUpdate] - Optional callback for status updates
518
- * @returns {Promise<TransactionSigningResult>} Signing result with signature for QR code
519
- */
520
- signPersTransaction,
521
-
522
- /**
523
- * Whether the transaction signer SDK has been successfully initialized
524
- *
525
- * Use this to show loading states while the signer is being set up.
526
- * When `false`, the signer is still initializing and transactions cannot be processed.
527
- *
528
- * @example
529
- * ```typescript
530
- * const { isSignerInitialized } = useTransactionSigner();
531
- *
532
- * if (!isSignerInitialized) {
533
- * return <LoadingSpinner text="Initializing signer..." />;
534
- * }
535
- * ```
536
- */
537
- isSignerInitialized,
538
-
539
- /**
540
- * Whether blockchain transaction signing is fully available
541
- *
542
- * This combines multiple checks: SDK availability, initialization status,
543
- * and configuration validity. Use this to enable/disable transaction buttons.
544
- *
545
- * Returns `true` only when:
546
- * - PERS Signer SDK is installed and available
547
- * - Signer has been successfully initialized
548
- * - All required dependencies are loaded
549
- *
550
- * @example
551
- * ```typescript
552
- * const { isSignerAvailable } = useTransactionSigner();
553
- *
554
- * return (
555
- * <TouchableOpacity
556
- * disabled={!isSignerAvailable}
557
- * style={[styles.button, !isSignerAvailable && styles.disabled]}
558
- * >
559
- * <Text>{isSignerAvailable ? 'Sign Transaction' : 'Signer Unavailable'}</Text>
560
- * </TouchableOpacity>
561
- * );
562
- * ```
563
- */
564
- isSignerAvailable: isSignerInitialized && !!getSignerSDKFactory(),
565
-
566
- /**
567
- * Current signing status for UI feedback
568
- *
569
- * Tracks the current state of the signing process. Use this to show
570
- * progress indicators or status messages during transaction signing.
571
- *
572
- * Possible values: 'initializing', 'authenticating', 'preparing',
573
- * 'signing', 'submitting', 'completed', 'error', 'expired'
574
- *
575
- * @example
576
- * ```typescript
577
- * const { currentStatus, statusMessage } = useTransactionSigner();
578
- *
579
- * return (
580
- * <View>
581
- * {currentStatus && (
582
- * <Text>Status: {statusMessage}</Text>
583
- * )}
584
- * </View>
585
- * );
586
- * ```
587
- */
588
- currentStatus,
589
-
590
- /**
591
- * Human-readable status message
592
- *
593
- * Provides a user-friendly description of the current signing status.
594
- * Updates in real-time as the transaction progresses.
595
- */
596
- statusMessage,
597
- };
598
- };
599
-
600
- /**
601
- * Type exports for external usage
602
- *
603
- * These types are re-exported from the PERS Signer SDK for convenience
604
- * when working with transaction signing in React Native applications.
605
- *
606
- * @see {@link SubmissionResult} - Complete transaction submission result
607
- * @see {@link AuthenticatedUser} - User authentication data structure
608
- * @see {@link TransactionSigningResult} - Transaction signing result details
609
- * @see {@link TransactionSignerHook} - Hook return interface
610
- */
611
- export type { SubmissionResult, AuthenticatedUser, TransactionSigningResult };
1
+ import { useCallback, useState, useRef, useEffect } from 'react';
2
+ // import { usePersSDK } from '../providers/PersSDKProvider';
3
+
4
+ // TYPE-ONLY imports from /types entry - NO native module deps, safe at any time
5
+ import type { PersSignerConfig, StatusCallback, StatusUpdateData } from '@explorins/pers-signer/types';
6
+ import type { SigningStatus as SigningStatusType } from '@explorins/pers-signer/types';
7
+ // Runtime const import - also from /types (no native deps)
8
+ import { SigningStatus as SigningStatusConst } from '@explorins/pers-signer/types';
9
+
10
+ // Import actual SDK types from the React Native-specific export
11
+ type PersSignerSDK = import('@explorins/pers-signer/react-native').PersSignerSDK;
12
+ type SubmissionResult = import('@explorins/pers-signer/react-native').SubmissionResult;
13
+ type AuthenticatedUser = import('@explorins/pers-signer/react-native').AuthenticatedUser;
14
+ type TransactionSigningResult = import('@explorins/pers-signer/react-native').TransactionSigningResult;
15
+
16
+ // Re-export the type for external consumers
17
+ export type { SigningStatusType as SigningStatus };
18
+ export type { StatusUpdateData };
19
+
20
+ /**
21
+ * Simple status callback function type for React Native consumers
22
+ * (Simplified from the signer SDK's object-based StatusCallback interface)
23
+ */
24
+ export type OnStatusUpdateFn = (status: SigningStatusType, message: string, data?: StatusUpdateData) => void;
25
+
26
+
27
+ // Lazy-loaded signer SDK - initialized on first use to avoid native module timing issues
28
+ let _createPersSignerSDK: ((config: PersSignerConfig) => Promise<PersSignerSDK>) | null = null;
29
+ let _signerInitAttempted = false;
30
+
31
+ /**
32
+ * Get the SigningStatus const object
33
+ * Now directly imported from /types (no native deps, always available)
34
+ */
35
+ function getSigningStatus(): typeof SigningStatusType {
36
+ return SigningStatusConst;
37
+ }
38
+
39
+ /**
40
+ * Lazily load the PERS Signer SDK.
41
+ *
42
+ * Supports customer pre-initialization via global.__PersSigner for environments
43
+ * where native module timing is unpredictable (e.g., certain React Native setups).
44
+ *
45
+ * @returns The createPersSignerSDK function or null if unavailable
46
+ */
47
+ function getSignerSDKFactory(): ((config: PersSignerConfig) => Promise<PersSignerSDK>) | null {
48
+ if (_signerInitAttempted) {
49
+ // Already tried - return cached result (could be null if failed)
50
+ return _createPersSignerSDK;
51
+ }
52
+
53
+ _signerInitAttempted = true;
54
+
55
+ // Check for customer pre-initialized global (for native module timing issues)
56
+ if (typeof global !== 'undefined' && (global as any).__PersSigner?.createPersSignerSDK) {
57
+ const globalModule = (global as any).__PersSigner;
58
+ _createPersSignerSDK = globalModule.createPersSignerSDK;
59
+ return _createPersSignerSDK;
60
+ }
61
+
62
+ // Standard lazy load
63
+ try {
64
+ const signerModule = require('@explorins/pers-signer/react-native');
65
+ _createPersSignerSDK = signerModule.createPersSignerSDK;
66
+ return _createPersSignerSDK;
67
+ } catch (error: unknown) {
68
+ console.warn('[useTransactionSigner] PERS Signer SDK not available:', (error as Error).message);
69
+ console.warn('[useTransactionSigner] Real blockchain signing will not be available');
70
+ console.warn('[useTransactionSigner] Tip: If timing issues, pre-initialize in index.js: global.__PersSigner = require("@explorins/pers-signer/react-native")');
71
+ _createPersSignerSDK = null; // Mark as unavailable
72
+ return null;
73
+ }
74
+ }
75
+
76
+
77
+ /**
78
+ * Return interface for the useTransactionSigner hook
79
+ *
80
+ * Provides secure transaction signing capabilities for EVM blockchain interactions
81
+ * and error handling for React Native applications.
82
+ *
83
+ * @interface TransactionSignerHook
84
+ * @property {Function} signAndSubmitTransactionWithJWT - Main method to sign and submit transactions
85
+ * @property {Function} signPersTransaction - Sign transaction without submitting (for POS flows)
86
+ * @property {boolean} isSignerInitialized - Whether the signer SDK has been initialized
87
+ * @property {boolean} isSignerAvailable - Whether signing functionality is fully available
88
+ * @property {SigningStatus | null} currentStatus - Current signing status for UI feedback
89
+ * @property {string | null} statusMessage - Human-readable status message
90
+ */
91
+ export interface TransactionSignerHook {
92
+ signAndSubmitTransactionWithJWT: (jwt: string, onStatusUpdate?: OnStatusUpdateFn) => Promise<SubmissionResult>;
93
+ signPersTransaction: (jwt: string, onStatusUpdate?: OnStatusUpdateFn) => Promise<TransactionSigningResult>;
94
+ isSignerInitialized: boolean;
95
+ isSignerAvailable: boolean;
96
+ currentStatus: SigningStatusType | null;
97
+ statusMessage: string | null;
98
+ }
99
+
100
+ // Constants - TODO: Move to environment config later
101
+ const DEFAULT_ETHERS_PROVIDER = "https://sepolia.infura.io/v3/2781b4b5242343d5b0954c98f287b29e";
102
+
103
+ /**
104
+ * React Native hook for blockchain transaction signing using PERS Signer SDK
105
+ *
106
+ * This hook provides a complete blockchain transaction signing solution for React Native
107
+ * applications, integrating with the PERS ecosystem for tourism loyalty and reward systems.
108
+ * It automatically handles WebAuthn authentication, transaction preparation, signing, and
109
+ * blockchain submission in a single convenient interface.
110
+ *
111
+ * **Features:**
112
+ * - Automatic WebAuthn provider initialization for React Native
113
+ * - 5-minute authentication caching to reduce repeated logins
114
+ * - Complete transaction lifecycle management (sign + submit)
115
+ * - Comprehensive error handling with detailed error messages
116
+ * - Real-time status monitoring for UI feedback
117
+ * - Automatic retry logic for network failures
118
+ *
119
+ * **Underlying SDK Methods:**
120
+ * The hook uses the PERS Signer SDK which provides 5 core methods:
121
+ * 1. `loginUser(jwtToken)` - Authenticate user with 5-minute caching
122
+ * 2. `signTransaction(signingData, jwtToken)` - Sign transactions with auto-login
123
+ * 3. `submitTransaction(signingResult, jwtToken)` - Submit signed transactions
124
+ * 4. `signPersTransaction(jwtToken)` - Legacy one-liner for backward compatibility
125
+ * 5. `signAndSubmitPersTransaction(jwtToken)` - Complete sign + submit flow (used by this hook)
126
+ *
127
+ * **Security:**
128
+ * - WebAuthn-based secure authentication (no passwords stored)
129
+ * - JWT token validation and expiration checking
130
+ * - Secure transaction signing using device biometrics/PIN
131
+ * - No sensitive data stored locally
132
+ *
133
+ * @returns {TransactionSignerHook} Hook interface with signing methods and status
134
+ *
135
+ * @example
136
+ * **Basic Usage:**
137
+ * ```typescript
138
+ * import { useTransactionSigner } from '@explorins/pers-sdk-react-native';
139
+ *
140
+ * function TransactionScreen() {
141
+ * const {
142
+ * signAndSubmitTransactionWithJWT,
143
+ * isSignerAvailable,
144
+ * isSignerInitialized
145
+ * } = useTransactionSigner();
146
+ *
147
+ * const handleSign = async (jwtFromRedemption: string) => {
148
+ * if (!isSignerAvailable) {
149
+ * console.error('Signer not available');
150
+ * return;
151
+ * }
152
+ *
153
+ * try {
154
+ * const result = await signAndSubmitTransactionWithJWT(jwtFromRedemption);
155
+ * if (result.success) {
156
+ * console.log('Transaction completed:', result.transactionHash);
157
+ * // Handle successful transaction
158
+ * if (result.shouldRedirect && result.redirectUrl) {
159
+ * // Navigate to success page or external URL
160
+ * }
161
+ * }
162
+ * } catch (error) {
163
+ * console.error('Transaction failed:', error.message);
164
+ * // Handle error (show user-friendly message)
165
+ * }
166
+ * };
167
+ *
168
+ * return (
169
+ * <View style={styles.container}>
170
+ * <TouchableOpacity
171
+ * onPress={() => handleSign(redeemJWT)}
172
+ * disabled={!isSignerAvailable}
173
+ * style={[styles.button, !isSignerAvailable && styles.disabled]}
174
+ * >
175
+ * <Text style={styles.buttonText}>
176
+ * {isSignerInitialized ? 'Sign Transaction' : 'Initializing...'}
177
+ * </Text>
178
+ * </TouchableOpacity>
179
+ * </View>
180
+ * );
181
+ * }
182
+ * ```
183
+ *
184
+ * @example
185
+ * **Advanced Usage with Error Handling:**
186
+ * ```typescript
187
+ * function AdvancedTransactionComponent() {
188
+ * const [isLoading, setIsLoading] = useState(false);
189
+ * const [error, setError] = useState<string | null>(null);
190
+ * const { signAndSubmitTransactionWithJWT, isSignerAvailable } = useTransactionSigner();
191
+ *
192
+ * const handleTransaction = async (jwt: string) => {
193
+ * setIsLoading(true);
194
+ * setError(null);
195
+ *
196
+ * try {
197
+ * const result = await signAndSubmitTransactionWithJWT(jwt);
198
+ *
199
+ * if (result.success) {
200
+ * // Success handling
201
+ * Alert.alert(
202
+ * 'Success',
203
+ * `Transaction completed!\nHash: ${result.transactionHash}`
204
+ * );
205
+ * } else {
206
+ * throw new Error(result.error || 'Transaction failed');
207
+ * }
208
+ * } catch (err) {
209
+ * const errorMessage = err instanceof Error ? err.message : 'Unknown error';
210
+ * setError(errorMessage);
211
+ *
212
+ * // Different error handling based on error type
213
+ * if (errorMessage.includes('expired')) {
214
+ * Alert.alert('Session Expired', 'Please log in again');
215
+ * } else if (errorMessage.includes('network')) {
216
+ * Alert.alert('Network Error', 'Please check your connection');
217
+ * } else {
218
+ * Alert.alert('Transaction Failed', errorMessage);
219
+ * }
220
+ * } finally {
221
+ * setIsLoading(false);
222
+ * }
223
+ * };
224
+ *
225
+ * return (
226
+ * <View>
227
+ * {error && (
228
+ * <Text style={styles.errorText}>{error}</Text>
229
+ * )}
230
+ * <TouchableOpacity
231
+ * onPress={() => handleTransaction(jwtToken)}
232
+ * disabled={!isSignerAvailable || isLoading}
233
+ * >
234
+ * <Text>
235
+ * {isLoading ? 'Processing...' : 'Sign & Submit Transaction'}
236
+ * </Text>
237
+ * </TouchableOpacity>
238
+ * </View>
239
+ * );
240
+ * }
241
+ * ```
242
+ *
243
+ * @example
244
+ * **Integration with PERS SDK:**
245
+ * ```typescript
246
+ * import { usePersSDK } from '@explorins/pers-sdk-react-native';
247
+ * import { useTransactionSigner } from '@explorins/pers-sdk-react-native';
248
+ *
249
+ * function RedemptionFlow() {
250
+ * const { user, redeemTokens } = usePersSDK();
251
+ * const { signAndSubmitTransactionWithJWT } = useTransactionSigner();
252
+ *
253
+ * const handleRedemption = async (tokenAmount: number) => {
254
+ * try {
255
+ * // Step 1: Create redemption with PERS SDK
256
+ * const redemption = await redeemTokens({
257
+ * tokenAmount,
258
+ * destinationAddress: user?.walletAddress
259
+ * });
260
+ *
261
+ * // Step 2: Sign and submit transaction
262
+ * const txResult = await signAndSubmitTransactionWithJWT(redemption.jwtToken);
263
+ *
264
+ * if (txResult.success) {
265
+ * console.log('Redemption completed:', txResult.transactionHash);
266
+ * }
267
+ * } catch (error) {
268
+ * console.error('Redemption failed:', error);
269
+ * }
270
+ * };
271
+ * }
272
+ * ```
273
+ *
274
+ * @see {@link SubmissionResult} for transaction result structure
275
+ * @see {@link TransactionSigningResult} for signing result details
276
+ * @see {@link AuthenticatedUser} for user authentication data
277
+ *
278
+ * @since 1.5.0
279
+ */
280
+ export const useTransactionSigner = (): TransactionSignerHook => {
281
+ // const { isInitialized, isAuthenticated, user } = usePersSDK();
282
+ const [isSignerInitialized, setIsSignerInitialized] = useState(false);
283
+ const [currentStatus, setCurrentStatus] = useState<SigningStatusType | null>(null);
284
+ const [statusMessage, setStatusMessage] = useState<string | null>(null);
285
+ const signerSDKRef = useRef<PersSignerSDK | null>(null);
286
+
287
+ // Auto-initialize signer SDK when PERS SDK is ready
288
+ useEffect(() => {
289
+ const initializeSignerSDK = async () => {
290
+ const createPersSignerSDK = getSignerSDKFactory();
291
+ if (!createPersSignerSDK) {
292
+ console.warn('[useTransactionSigner] Signer SDK not available');
293
+ return;
294
+ }
295
+
296
+ try {
297
+ // React Native createPersSignerSDK automatically includes WebAuthn provider
298
+ const signerSDK = await createPersSignerSDK({
299
+ ethersProviderUrl: DEFAULT_ETHERS_PROVIDER,
300
+ relyingPartyName: 'PERS React Native App'
301
+ });
302
+
303
+ signerSDKRef.current = signerSDK;
304
+ setIsSignerInitialized(true);
305
+
306
+ } catch (error) {
307
+ console.error('[useTransactionSigner] Failed to initialize signer SDK:', error);
308
+ }
309
+ };
310
+
311
+ if (!isSignerInitialized) {
312
+ initializeSignerSDK();
313
+ }
314
+ }, [isSignerInitialized]);
315
+
316
+ /**
317
+ * Sign and submit a blockchain transaction using JWT token
318
+ *
319
+ * This is the main method for executing blockchain transactions in React Native.
320
+ * It handles the complete transaction lifecycle: authentication, signing, and
321
+ * blockchain submission in a single call.
322
+ *
323
+ * **Process Flow:**
324
+ * 1. Validates the JWT token and extracts transaction data
325
+ * 2. Authenticates the user using WebAuthn (cached for 5 minutes)
326
+ * 3. Fetches transaction data from PERS backend
327
+ * 4. Signs the transaction using device security (biometrics/PIN)
328
+ * 5. Submits the signed transaction to the blockchain
329
+ * 6. Returns detailed result with transaction hash and status
330
+ *
331
+ * **Security Features:**
332
+ * - JWT token validation and expiration checking
333
+ * - WebAuthn authentication with device biometrics
334
+ * - Secure transaction signing without exposing private keys
335
+ * - Automatic session management with secure caching
336
+ *
337
+ * @param {string} jwt - JWT token containing transaction ID and user information
338
+ * Must include: `transactionId`, `identifierEmail`, `tenantId`
339
+ * @param {StatusCallback} [onStatusUpdate] - Optional callback for real-time status updates
340
+ * @returns {Promise<SubmissionResult>} Complete transaction result
341
+ *
342
+ * @throws {Error} 'Transaction signer not initialized' - Hook not ready
343
+ * @throws {Error} 'PERS Signer SDK not available' - SDK installation issue
344
+ * @throws {Error} 'Invalid or expired JWT token' - Token validation failed
345
+ * @throws {Error} 'Authentication failed' - WebAuthn authentication failed
346
+ * @throws {Error} 'Transaction signing failed' - Signing process failed
347
+ * @throws {Error} 'Transaction submission failed' - Blockchain submission failed
348
+ *
349
+ * @example
350
+ * **Basic Transaction:**
351
+ * ```typescript
352
+ * const { signAndSubmitTransactionWithJWT } = useTransactionSigner();
353
+ *
354
+ * const result = await signAndSubmitTransactionWithJWT(jwtToken);
355
+ * if (result.success) {
356
+ * console.log('Transaction Hash:', result.transactionHash);
357
+ * console.log('Blockchain explorer: [Chain-specific explorer URL]');
358
+ * }
359
+ * ```
360
+ *
361
+ * @example
362
+ * **With Status Updates:**
363
+ * ```typescript
364
+ * const result = await signAndSubmitTransactionWithJWT(jwtToken, (status, message) => {
365
+ * console.log(`Status: ${status} - ${message}`);
366
+ * setStatusText(message);
367
+ * });
368
+ * ```
369
+ *
370
+ * @example
371
+ * **With Error Handling:**
372
+ * ```typescript
373
+ * try {
374
+ * const result = await signAndSubmitTransactionWithJWT(redeemJWT);
375
+ *
376
+ * if (result.success) {
377
+ * // Transaction successful
378
+ * setTransactionHash(result.transactionHash);
379
+ *
380
+ * if (result.shouldRedirect && result.redirectUrl) {
381
+ * // Handle post-transaction redirect
382
+ * Linking.openURL(result.redirectUrl);
383
+ * }
384
+ * } else {
385
+ * // Transaction failed but didn't throw
386
+ * setError(result.error || 'Transaction failed');
387
+ * }
388
+ * } catch (error) {
389
+ * // Handle different error types
390
+ * if (error.message.includes('expired')) {
391
+ * // Token expired - redirect to login
392
+ * navigation.navigate('Login');
393
+ * } else if (error.message.includes('network')) {
394
+ * // Network error - suggest retry
395
+ * setError('Network error. Please try again.');
396
+ * } else {
397
+ * // Other errors
398
+ * setError(error.message);
399
+ * }
400
+ * }
401
+ * ```
402
+ *
403
+ * @see {@link SubmissionResult} for detailed result structure
404
+ */
405
+ const signAndSubmitTransactionWithJWT = useCallback(async (jwt: string, onStatusUpdate?: OnStatusUpdateFn): Promise<SubmissionResult> => {
406
+ if (!isSignerInitialized || !signerSDKRef.current) {
407
+ throw new Error('Transaction signer not initialized');
408
+ }
409
+ if (!getSignerSDKFactory()) {
410
+ throw new Error('PERS Signer SDK not available. Blockchain signing is not supported.');
411
+ }
412
+
413
+ // Create status callback wrapper that updates both hook state and calls user callback
414
+ const statusCallback: StatusCallback = {
415
+ onStatusUpdate: (status: SigningStatusType, message: string, data?: StatusUpdateData) => {
416
+ setCurrentStatus(status);
417
+ setStatusMessage(message);
418
+ onStatusUpdate?.(status, message, data);
419
+ }
420
+ };
421
+
422
+ try {
423
+ // Use the actual SDK method that handles the complete sign + submit flow
424
+ const submissionResult = await signerSDKRef.current.signAndSubmitPersTransaction(jwt, statusCallback);
425
+
426
+ return submissionResult;
427
+
428
+ } catch (error) {
429
+ console.error('[useTransactionSigner] JWT transaction signing failed:', error);
430
+ const SigningStatusEnum = getSigningStatus();
431
+ if (SigningStatusEnum) setCurrentStatus(SigningStatusEnum.ERROR);
432
+ setStatusMessage(error instanceof Error ? error.message : 'Transaction failed');
433
+ throw error; // Re-throw to maintain error handling upstream
434
+ }
435
+ }, [isSignerInitialized]);
436
+
437
+ /**
438
+ * Sign a PERS transaction without submitting (for POS flows with QR codes)
439
+ *
440
+ * This method signs the transaction but does NOT submit it to the blockchain.
441
+ * Instead, it returns the signature which can be displayed as a QR code for
442
+ * the business to scan and submit.
443
+ *
444
+ * **Use Case:** Point-of-Sale (POS) flows where:
445
+ * 1. User signs the transaction
446
+ * 2. App generates QR code with signature
447
+ * 3. Business scans QR code
448
+ * 4. Business submits transaction to blockchain
449
+ *
450
+ * @param {string} jwt - JWT token with transaction and user data
451
+ * @param {StatusCallback} [onStatusUpdate] - Optional callback for status updates
452
+ * @returns {Promise<TransactionSigningResult>} Signing result with signature for QR code
453
+ *
454
+ * @example
455
+ * ```typescript
456
+ * const { signPersTransaction } = useTransactionSigner();
457
+ *
458
+ * const result = await signPersTransaction(jwtToken);
459
+ * if (result.success) {
460
+ * const qrData = {
461
+ * transactionId: result.transactionId,
462
+ * signature: result.signature,
463
+ * transactionFormat: result.signingData.format,
464
+ * txType: 'PENDING_SUBMISSION'
465
+ * };
466
+ * // Display QR code with qrData
467
+ * }
468
+ * ```
469
+ */
470
+ const signPersTransaction = useCallback(async (jwt: string, onStatusUpdate?: OnStatusUpdateFn): Promise<TransactionSigningResult> => {
471
+ if (!isSignerInitialized || !signerSDKRef.current) {
472
+ throw new Error('Signer SDK not initialized');
473
+ }
474
+
475
+ // Create status callback wrapper
476
+ const statusCallback: StatusCallback = {
477
+ onStatusUpdate: (status: SigningStatusType, message: string, data?: StatusUpdateData) => {
478
+ setCurrentStatus(status);
479
+ setStatusMessage(message);
480
+ onStatusUpdate?.(status, message, data);
481
+ }
482
+ };
483
+
484
+ try {
485
+ // Use the SDK's sign-only method
486
+ const signingResult = await signerSDKRef.current.signPersTransaction(jwt, statusCallback);
487
+ return signingResult;
488
+ } catch (error) {
489
+ console.error('[useTransactionSigner] Sign-only transaction failed:', error);
490
+ const SigningStatusEnum = getSigningStatus();
491
+ if (SigningStatusEnum) setCurrentStatus(SigningStatusEnum.ERROR);
492
+ setStatusMessage(error instanceof Error ? error.message : 'Signing failed');
493
+ throw error;
494
+ }
495
+ }, [isSignerInitialized]);
496
+
497
+ return {
498
+ /**
499
+ * Sign and submit a blockchain transaction using JWT token
500
+ *
501
+ * Main method for executing blockchain transactions. Handles complete flow
502
+ * from authentication to blockchain submission in a single call.
503
+ *
504
+ * @param {string} jwt - JWT token with transaction and user data
505
+ * @param {StatusCallback} [onStatusUpdate] - Optional callback for real-time status updates
506
+ * @returns {Promise<SubmissionResult>} Transaction result with hash and status
507
+ */
508
+ signAndSubmitTransactionWithJWT,
509
+
510
+ /**
511
+ * Sign a transaction without submitting (for POS flows)
512
+ *
513
+ * Signs the transaction and returns the signature without submitting to blockchain.
514
+ * Use this for Point-of-Sale flows where the business scans a QR code and submits.
515
+ *
516
+ * @param {string} jwt - JWT token with transaction and user data
517
+ * @param {StatusCallback} [onStatusUpdate] - Optional callback for status updates
518
+ * @returns {Promise<TransactionSigningResult>} Signing result with signature for QR code
519
+ */
520
+ signPersTransaction,
521
+
522
+ /**
523
+ * Whether the transaction signer SDK has been successfully initialized
524
+ *
525
+ * Use this to show loading states while the signer is being set up.
526
+ * When `false`, the signer is still initializing and transactions cannot be processed.
527
+ *
528
+ * @example
529
+ * ```typescript
530
+ * const { isSignerInitialized } = useTransactionSigner();
531
+ *
532
+ * if (!isSignerInitialized) {
533
+ * return <LoadingSpinner text="Initializing signer..." />;
534
+ * }
535
+ * ```
536
+ */
537
+ isSignerInitialized,
538
+
539
+ /**
540
+ * Whether blockchain transaction signing is fully available
541
+ *
542
+ * This combines multiple checks: SDK availability, initialization status,
543
+ * and configuration validity. Use this to enable/disable transaction buttons.
544
+ *
545
+ * Returns `true` only when:
546
+ * - PERS Signer SDK is installed and available
547
+ * - Signer has been successfully initialized
548
+ * - All required dependencies are loaded
549
+ *
550
+ * @example
551
+ * ```typescript
552
+ * const { isSignerAvailable } = useTransactionSigner();
553
+ *
554
+ * return (
555
+ * <TouchableOpacity
556
+ * disabled={!isSignerAvailable}
557
+ * style={[styles.button, !isSignerAvailable && styles.disabled]}
558
+ * >
559
+ * <Text>{isSignerAvailable ? 'Sign Transaction' : 'Signer Unavailable'}</Text>
560
+ * </TouchableOpacity>
561
+ * );
562
+ * ```
563
+ */
564
+ isSignerAvailable: isSignerInitialized && !!getSignerSDKFactory(),
565
+
566
+ /**
567
+ * Current signing status for UI feedback
568
+ *
569
+ * Tracks the current state of the signing process. Use this to show
570
+ * progress indicators or status messages during transaction signing.
571
+ *
572
+ * Possible values: 'initializing', 'authenticating', 'preparing',
573
+ * 'signing', 'submitting', 'completed', 'error', 'expired'
574
+ *
575
+ * @example
576
+ * ```typescript
577
+ * const { currentStatus, statusMessage } = useTransactionSigner();
578
+ *
579
+ * return (
580
+ * <View>
581
+ * {currentStatus && (
582
+ * <Text>Status: {statusMessage}</Text>
583
+ * )}
584
+ * </View>
585
+ * );
586
+ * ```
587
+ */
588
+ currentStatus,
589
+
590
+ /**
591
+ * Human-readable status message
592
+ *
593
+ * Provides a user-friendly description of the current signing status.
594
+ * Updates in real-time as the transaction progresses.
595
+ */
596
+ statusMessage,
597
+ };
598
+ };
599
+
600
+ /**
601
+ * Type exports for external usage
602
+ *
603
+ * These types are re-exported from the PERS Signer SDK for convenience
604
+ * when working with transaction signing in React Native applications.
605
+ *
606
+ * @see {@link SubmissionResult} - Complete transaction submission result
607
+ * @see {@link AuthenticatedUser} - User authentication data structure
608
+ * @see {@link TransactionSigningResult} - Transaction signing result details
609
+ * @see {@link TransactionSignerHook} - Hook return interface
610
+ */
611
+ export type { SubmissionResult, AuthenticatedUser, TransactionSigningResult };