toss-expo-sdk 0.1.2 → 1.0.2

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 (93) hide show
  1. package/README.md +380 -25
  2. package/lib/module/ble.js +59 -4
  3. package/lib/module/ble.js.map +1 -1
  4. package/lib/module/client/BLETransactionHandler.js +277 -0
  5. package/lib/module/client/BLETransactionHandler.js.map +1 -0
  6. package/lib/module/client/NonceAccountManager.js +364 -0
  7. package/lib/module/client/NonceAccountManager.js.map +1 -0
  8. package/lib/module/client/TossClient.js +1 -1
  9. package/lib/module/client/TossClient.js.map +1 -1
  10. package/lib/module/examples/enhancedFeaturesFlow.js +233 -0
  11. package/lib/module/examples/enhancedFeaturesFlow.js.map +1 -0
  12. package/lib/module/examples/offlinePaymentFlow.js +27 -27
  13. package/lib/module/examples/offlinePaymentFlow.js.map +1 -1
  14. package/lib/module/hooks/useOfflineBLETransactions.js +314 -0
  15. package/lib/module/hooks/useOfflineBLETransactions.js.map +1 -0
  16. package/lib/module/index.js +18 -8
  17. package/lib/module/index.js.map +1 -1
  18. package/lib/module/intent.js +129 -0
  19. package/lib/module/intent.js.map +1 -1
  20. package/lib/module/noise.js +175 -0
  21. package/lib/module/noise.js.map +1 -1
  22. package/lib/module/qr.js +2 -2
  23. package/lib/module/reconciliation.js +155 -0
  24. package/lib/module/reconciliation.js.map +1 -1
  25. package/lib/module/services/authService.js +166 -3
  26. package/lib/module/services/authService.js.map +1 -1
  27. package/lib/module/storage/secureStorage.js +102 -0
  28. package/lib/module/storage/secureStorage.js.map +1 -1
  29. package/lib/module/sync.js +25 -1
  30. package/lib/module/sync.js.map +1 -1
  31. package/lib/module/types/nonceAccount.js +2 -0
  32. package/lib/module/types/nonceAccount.js.map +1 -0
  33. package/lib/module/types/tossUser.js +16 -1
  34. package/lib/module/types/tossUser.js.map +1 -1
  35. package/lib/module/utils/compression.js +210 -0
  36. package/lib/module/utils/compression.js.map +1 -0
  37. package/lib/module/wifi.js +311 -0
  38. package/lib/module/wifi.js.map +1 -0
  39. package/lib/typescript/src/__tests__/solana-program-simple.test.d.ts +8 -0
  40. package/lib/typescript/src/__tests__/solana-program-simple.test.d.ts.map +1 -0
  41. package/lib/typescript/src/ble.d.ts +31 -2
  42. package/lib/typescript/src/ble.d.ts.map +1 -1
  43. package/lib/typescript/src/client/BLETransactionHandler.d.ts +98 -0
  44. package/lib/typescript/src/client/BLETransactionHandler.d.ts.map +1 -0
  45. package/lib/typescript/src/client/NonceAccountManager.d.ts +82 -0
  46. package/lib/typescript/src/client/NonceAccountManager.d.ts.map +1 -0
  47. package/lib/typescript/src/examples/enhancedFeaturesFlow.d.ts +45 -0
  48. package/lib/typescript/src/examples/enhancedFeaturesFlow.d.ts.map +1 -0
  49. package/lib/typescript/src/hooks/useOfflineBLETransactions.d.ts +91 -0
  50. package/lib/typescript/src/hooks/useOfflineBLETransactions.d.ts.map +1 -0
  51. package/lib/typescript/src/index.d.ts +11 -4
  52. package/lib/typescript/src/index.d.ts.map +1 -1
  53. package/lib/typescript/src/intent.d.ts +15 -0
  54. package/lib/typescript/src/intent.d.ts.map +1 -1
  55. package/lib/typescript/src/noise.d.ts +62 -0
  56. package/lib/typescript/src/noise.d.ts.map +1 -1
  57. package/lib/typescript/src/reconciliation.d.ts +6 -0
  58. package/lib/typescript/src/reconciliation.d.ts.map +1 -1
  59. package/lib/typescript/src/services/authService.d.ts +26 -1
  60. package/lib/typescript/src/services/authService.d.ts.map +1 -1
  61. package/lib/typescript/src/storage/secureStorage.d.ts +16 -0
  62. package/lib/typescript/src/storage/secureStorage.d.ts.map +1 -1
  63. package/lib/typescript/src/sync.d.ts +6 -1
  64. package/lib/typescript/src/sync.d.ts.map +1 -1
  65. package/lib/typescript/src/types/nonceAccount.d.ts +59 -0
  66. package/lib/typescript/src/types/nonceAccount.d.ts.map +1 -0
  67. package/lib/typescript/src/types/tossUser.d.ts +16 -0
  68. package/lib/typescript/src/types/tossUser.d.ts.map +1 -1
  69. package/lib/typescript/src/utils/compression.d.ts +52 -0
  70. package/lib/typescript/src/utils/compression.d.ts.map +1 -0
  71. package/lib/typescript/src/wifi.d.ts +116 -0
  72. package/lib/typescript/src/wifi.d.ts.map +1 -0
  73. package/package.json +1 -1
  74. package/src/__tests__/solana-program-simple.test.ts +256 -0
  75. package/src/ble.ts +105 -4
  76. package/src/client/BLETransactionHandler.ts +364 -0
  77. package/src/client/NonceAccountManager.ts +444 -0
  78. package/src/client/TossClient.ts +1 -1
  79. package/src/examples/enhancedFeaturesFlow.ts +272 -0
  80. package/src/examples/offlinePaymentFlow.ts +27 -27
  81. package/src/hooks/useOfflineBLETransactions.ts +438 -0
  82. package/src/index.tsx +52 -6
  83. package/src/intent.ts +166 -0
  84. package/src/noise.ts +238 -0
  85. package/src/qr.tsx +2 -2
  86. package/src/reconciliation.ts +184 -0
  87. package/src/services/authService.ts +190 -3
  88. package/src/storage/secureStorage.ts +138 -0
  89. package/src/sync.ts +40 -0
  90. package/src/types/nonceAccount.ts +75 -0
  91. package/src/types/tossUser.ts +35 -2
  92. package/src/utils/compression.ts +247 -0
  93. package/src/wifi.ts +401 -0
@@ -0,0 +1,438 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { Device } from 'react-native-ble-plx';
3
+ import { Connection } from '@solana/web3.js';
4
+ import type { SolanaIntent } from '../intent';
5
+ import type {
6
+ OfflineTransaction,
7
+ NonceAccountCacheEntry,
8
+ } from '../types/nonceAccount';
9
+ import type { TossUser } from '../types/tossUser';
10
+ import { BLETransactionHandler } from '../client/BLETransactionHandler';
11
+ import { NonceAccountManager } from '../client/NonceAccountManager';
12
+ import { AuthService } from '../services/authService';
13
+
14
+ /**
15
+ * State for BLE transaction transmission
16
+ */
17
+ export interface BLETransmissionState {
18
+ isTransmitting: boolean;
19
+ progress: {
20
+ sentFragments: number;
21
+ totalFragments: number;
22
+ messageId?: string;
23
+ };
24
+ error?: string;
25
+ lastSent?: {
26
+ messageId: string;
27
+ timestamp: number;
28
+ };
29
+ }
30
+
31
+ /**
32
+ * State for offline transaction preparation
33
+ */
34
+ export interface OfflineTransactionState {
35
+ isPreparing: boolean;
36
+ transaction?: OfflineTransaction;
37
+ error?: string;
38
+ isReady: boolean;
39
+ }
40
+
41
+ /**
42
+ * useOfflineTransaction Hook
43
+ * Manages offline transaction creation with nonce accounts
44
+ * Handles biometric protection and secure storage
45
+ */
46
+ export function useOfflineTransaction(user: TossUser, connection: Connection) {
47
+ const [state, setState] = useState<OfflineTransactionState>({
48
+ isPreparing: false,
49
+ isReady: false,
50
+ });
51
+ const nonceManagerRef = useRef<NonceAccountManager | null>(null);
52
+
53
+ // Initialize nonce manager
54
+ useEffect(() => {
55
+ nonceManagerRef.current = new NonceAccountManager(connection);
56
+
57
+ // Cleanup expired cache periodically
58
+ const interval = setInterval(
59
+ () => {
60
+ nonceManagerRef.current?.cleanupExpiredCache();
61
+ },
62
+ 5 * 60 * 1000
63
+ ); // Every 5 minutes
64
+
65
+ return () => clearInterval(interval);
66
+ }, [connection]);
67
+
68
+ /**
69
+ * Create offline transaction with nonce account
70
+ * Requires biometric verification if enabled
71
+ */
72
+ const createOfflineTransaction = useCallback(
73
+ async (
74
+ instructions: any[], // TransactionInstruction[]
75
+ metadata?: { description?: string; tags?: string[] }
76
+ ): Promise<OfflineTransaction | null> => {
77
+ if (!user.nonceAccount) {
78
+ setState((prev) => ({
79
+ ...prev,
80
+ error: 'User does not have nonce account configured',
81
+ }));
82
+ return null;
83
+ }
84
+
85
+ setState((prev) => ({
86
+ ...prev,
87
+ isPreparing: true,
88
+ error: undefined,
89
+ }));
90
+
91
+ try {
92
+ // Verify nonce account access (requires biometric if enabled)
93
+ if (user.security.nonceAccountRequiresBiometric) {
94
+ const hasAccess = await AuthService.verifyNonceAccountAccess(
95
+ user.userId
96
+ );
97
+ if (!hasAccess) {
98
+ throw new Error('Biometric verification failed');
99
+ }
100
+ }
101
+
102
+ // Get cached nonce account or retrieve from storage
103
+ const nonceManager = nonceManagerRef.current!;
104
+ let nonceAccountData = nonceManager.getCachedNonceAccount(user.userId);
105
+
106
+ let nonceAccountInfo: NonceAccountCacheEntry | null = null;
107
+ if (nonceAccountData) {
108
+ nonceAccountInfo = nonceAccountData;
109
+ } else {
110
+ const retrievedInfo = await nonceManager.getNonceAccountSecure(
111
+ user.userId
112
+ );
113
+ if (retrievedInfo) {
114
+ nonceAccountInfo = nonceManager.getCachedNonceAccount(user.userId);
115
+ }
116
+ }
117
+
118
+ if (!nonceAccountInfo) {
119
+ throw new Error('Failed to retrieve nonce account information');
120
+ }
121
+
122
+ // Validate nonce account
123
+ if (!nonceManager.isNonceAccountValid(nonceAccountInfo.accountInfo)) {
124
+ throw new Error('Nonce account is no longer valid');
125
+ }
126
+
127
+ // Prepare offline transaction
128
+ const transaction = await nonceManager.prepareOfflineTransaction(
129
+ user,
130
+ instructions,
131
+ nonceAccountInfo.accountInfo
132
+ );
133
+
134
+ transaction.metadata = metadata || {};
135
+
136
+ setState((prev) => ({
137
+ ...prev,
138
+ transaction,
139
+ isReady: true,
140
+ isPreparing: false,
141
+ }));
142
+
143
+ return transaction;
144
+ } catch (error) {
145
+ const errorMessage =
146
+ error instanceof Error ? error.message : String(error);
147
+ setState((prev) => ({
148
+ ...prev,
149
+ error: errorMessage,
150
+ isPreparing: false,
151
+ isReady: false,
152
+ }));
153
+ return null;
154
+ }
155
+ },
156
+ [user]
157
+ );
158
+
159
+ /**
160
+ * Clear current offline transaction
161
+ */
162
+ const clearTransaction = useCallback(() => {
163
+ setState({
164
+ isPreparing: false,
165
+ isReady: false,
166
+ });
167
+ }, []);
168
+
169
+ return {
170
+ ...state,
171
+ createOfflineTransaction,
172
+ clearTransaction,
173
+ nonceManager: nonceManagerRef.current,
174
+ };
175
+ }
176
+
177
+ /**
178
+ * useBLETransactionTransmission Hook
179
+ * Handles secure BLE transmission of fragmented transactions
180
+ * with Noise Protocol encryption
181
+ */
182
+ export function useBLETransactionTransmission(
183
+ platform: 'android' | 'ios' = 'android'
184
+ ) {
185
+ const [state, setState] = useState<BLETransmissionState>({
186
+ isTransmitting: false,
187
+ progress: {
188
+ sentFragments: 0,
189
+ totalFragments: 0,
190
+ },
191
+ });
192
+
193
+ const bleHandlerRef = useRef(new BLETransactionHandler(platform));
194
+
195
+ /**
196
+ * Send transaction over BLE with fragmentation
197
+ */
198
+ const sendTransactionBLE = useCallback(
199
+ async (
200
+ device: Device,
201
+ transaction: OfflineTransaction | SolanaIntent,
202
+ sendFn: (
203
+ deviceId: string,
204
+ characteristicUUID: string,
205
+ data: Buffer
206
+ ) => Promise<void>,
207
+ noiseEncryptFn?: (data: Uint8Array) => Promise<any>,
208
+ isIntent: boolean = false
209
+ ): Promise<boolean> => {
210
+ setState((prev) => ({
211
+ ...prev,
212
+ isTransmitting: true,
213
+ error: undefined,
214
+ }));
215
+
216
+ try {
217
+ const bleHandler = bleHandlerRef.current;
218
+ const result = await bleHandler.sendFragmentedTransactionBLE(
219
+ device,
220
+ transaction,
221
+ sendFn,
222
+ noiseEncryptFn,
223
+ isIntent
224
+ );
225
+
226
+ if (!result.success) {
227
+ const failedCount = result.failedFragments.length;
228
+ throw new Error(
229
+ `Failed to send ${failedCount} fragment(s): ${result.failedFragments.join(', ')}`
230
+ );
231
+ }
232
+
233
+ setState((prev) => ({
234
+ ...prev,
235
+ isTransmitting: false,
236
+ progress: {
237
+ sentFragments: result.sentFragments,
238
+ totalFragments:
239
+ result.sentFragments + result.failedFragments.length,
240
+ messageId: result.messageId,
241
+ },
242
+ lastSent: {
243
+ messageId: result.messageId,
244
+ timestamp: Date.now(),
245
+ },
246
+ }));
247
+
248
+ return true;
249
+ } catch (error) {
250
+ const errorMessage =
251
+ error instanceof Error ? error.message : String(error);
252
+ setState((prev) => ({
253
+ ...prev,
254
+ isTransmitting: false,
255
+ error: errorMessage,
256
+ }));
257
+ return false;
258
+ }
259
+ },
260
+ []
261
+ );
262
+
263
+ /**
264
+ * Receive fragmented transaction
265
+ */
266
+ const receiveTransactionFragment = useCallback(
267
+ async (
268
+ fragment: any, // BLEFragment
269
+ noiseDecryptFn?: (encrypted: any) => Promise<Uint8Array>
270
+ ): Promise<{
271
+ complete: boolean;
272
+ transaction?: OfflineTransaction | SolanaIntent;
273
+ progress: { received: number; total: number };
274
+ }> => {
275
+ try {
276
+ const bleHandler = bleHandlerRef.current;
277
+ const result = await bleHandler.receiveFragmentedMessage(
278
+ fragment,
279
+ noiseDecryptFn
280
+ );
281
+
282
+ setState((prev) => ({
283
+ ...prev,
284
+ progress: {
285
+ sentFragments: result.progress.received,
286
+ totalFragments: result.progress.total,
287
+ messageId: fragment.messageId,
288
+ },
289
+ }));
290
+
291
+ return result;
292
+ } catch (error) {
293
+ const errorMessage =
294
+ error instanceof Error ? error.message : String(error);
295
+ setState((prev) => ({
296
+ ...prev,
297
+ error: errorMessage,
298
+ }));
299
+
300
+ return {
301
+ complete: false,
302
+ progress: { received: 0, total: 0 },
303
+ };
304
+ }
305
+ },
306
+ []
307
+ );
308
+
309
+ const getMTUConfig = useCallback(() => {
310
+ return bleHandlerRef.current.getMTUConfig();
311
+ }, []);
312
+
313
+ const setMTUConfig = useCallback((config: Partial<any>) => {
314
+ bleHandlerRef.current.setMTUConfig(config);
315
+ }, []);
316
+
317
+ return {
318
+ ...state,
319
+ sendTransactionBLE,
320
+ receiveTransactionFragment,
321
+ getMTUConfig,
322
+ setMTUConfig,
323
+ };
324
+ }
325
+
326
+ /**
327
+ * useNonceAccountManagement Hook
328
+ * Manages nonce account lifecycle: creation, renewal, revocation
329
+ */
330
+ export function useNonceAccountManagement(
331
+ user: TossUser,
332
+ connection: Connection
333
+ ) {
334
+ const [isLoading, setIsLoading] = useState(false);
335
+ const [error, setError] = useState<string | undefined>();
336
+ const nonceManagerRef = useRef(new NonceAccountManager(connection));
337
+
338
+ /**
339
+ * Create nonce account with biometric protection
340
+ */
341
+ const createNonceAccount = useCallback(
342
+ async (userKeypair: any) => {
343
+ setIsLoading(true);
344
+ setError(undefined);
345
+
346
+ try {
347
+ const updatedUser = await AuthService.createSecureNonceAccount(
348
+ user,
349
+ connection,
350
+ userKeypair
351
+ );
352
+
353
+ return updatedUser;
354
+ } catch (err) {
355
+ const errorMessage = err instanceof Error ? err.message : String(err);
356
+ setError(errorMessage);
357
+ return null;
358
+ } finally {
359
+ setIsLoading(false);
360
+ }
361
+ },
362
+ [user, connection]
363
+ );
364
+
365
+ /**
366
+ * Renew nonce account (refresh from blockchain)
367
+ */
368
+ const renewNonceAccount = useCallback(async () => {
369
+ if (!user.nonceAccount) {
370
+ setError('No nonce account to renew');
371
+ return null;
372
+ }
373
+
374
+ setIsLoading(true);
375
+ setError(undefined);
376
+
377
+ try {
378
+ const updated = await nonceManagerRef.current.renewNonceAccount(
379
+ user.userId,
380
+ user.nonceAccount.address
381
+ );
382
+
383
+ return updated;
384
+ } catch (err) {
385
+ const errorMessage = err instanceof Error ? err.message : String(err);
386
+ setError(errorMessage);
387
+ return null;
388
+ } finally {
389
+ setIsLoading(false);
390
+ }
391
+ }, [user.userId, user.nonceAccount]);
392
+
393
+ /**
394
+ * Revoke nonce account
395
+ */
396
+ const revokeNonceAccount = useCallback(async () => {
397
+ setIsLoading(true);
398
+ setError(undefined);
399
+
400
+ try {
401
+ const updatedUser = await AuthService.revokeNonceAccount(
402
+ user.userId,
403
+ user
404
+ );
405
+
406
+ return updatedUser;
407
+ } catch (err) {
408
+ const errorMessage = err instanceof Error ? err.message : String(err);
409
+ setError(errorMessage);
410
+ return null;
411
+ } finally {
412
+ setIsLoading(false);
413
+ }
414
+ }, [user]);
415
+
416
+ const isNonceAccountValid = useCallback(() => {
417
+ if (!user.nonceAccount) {
418
+ return false;
419
+ }
420
+
421
+ const cached = nonceManagerRef.current.getCachedNonceAccount(user.userId);
422
+ if (cached) {
423
+ return nonceManagerRef.current.isNonceAccountValid(cached.accountInfo);
424
+ }
425
+
426
+ return user.nonceAccount.status === 'active';
427
+ }, [user.userId, user.nonceAccount]);
428
+
429
+ return {
430
+ isLoading,
431
+ error,
432
+ createNonceAccount,
433
+ renewNonceAccount,
434
+ revokeNonceAccount,
435
+ isNonceAccountValid,
436
+ hasNonceAccount: !!user.nonceAccount,
437
+ };
438
+ }
package/src/index.tsx CHANGED
@@ -3,10 +3,39 @@ export {
3
3
  createIntent,
4
4
  createUserIntent,
5
5
  createSignedIntent,
6
+ createOfflineIntent,
6
7
  type SolanaIntent,
7
8
  type IntentStatus,
8
9
  } from './intent';
9
10
 
11
+ // Nonce Account Management (for offline transactions)
12
+ export { NonceAccountManager } from './client/NonceAccountManager';
13
+ export type {
14
+ NonceAccountInfo,
15
+ NonceAccountCacheEntry,
16
+ CreateNonceAccountOptions,
17
+ OfflineTransaction,
18
+ } from './types/nonceAccount';
19
+
20
+ // BLE Transaction Handling (fragmentation & Noise encryption)
21
+ export { BLETransactionHandler } from './client/BLETransactionHandler';
22
+ export type {
23
+ BLEFragment,
24
+ EncryptedBLEMessage,
25
+ BLEMTUConfig,
26
+ } from './client/BLETransactionHandler';
27
+
28
+ // Custom Hooks for Offline BLE Transactions
29
+ export {
30
+ useOfflineTransaction,
31
+ useBLETransactionTransmission,
32
+ useNonceAccountManagement,
33
+ } from './hooks/useOfflineBLETransactions';
34
+ export type {
35
+ BLETransmissionState,
36
+ OfflineTransactionState,
37
+ } from './hooks/useOfflineBLETransactions';
38
+
10
39
  // Intent management
11
40
  export {
12
41
  verifyIntentSignature,
@@ -24,8 +53,15 @@ export {
24
53
  clearPendingIntents,
25
54
  } from './storage';
26
55
 
27
- // Transport methods
28
- export { startTossScan, requestBLEPermissions } from './ble';
56
+ // Transport methods (enhanced with fragmentation)
57
+ export {
58
+ startTossScan,
59
+ requestBLEPermissions,
60
+ sendOfflineTransactionFragmented,
61
+ receiveOfflineTransactionFragment,
62
+ getBLEMTUConfig,
63
+ setBLEMTUConfig,
64
+ } from './ble';
29
65
  export { initNFC, readNFCUser, writeUserToNFC, writeIntentToNFC } from './nfc';
30
66
  export { QRScanner } from './qr';
31
67
 
@@ -34,10 +70,8 @@ export { TossClient, type TossConfig } from './client/TossClient';
34
70
  export type { TossUser } from './types/tossUser';
35
71
  export { WalletProvider, useWallet } from './contexts/WalletContext';
36
72
 
37
- // Create client instance
38
- import { TossClient } from './client/TossClient';
39
- export const createClient = TossClient.createClient;
40
-
73
+ // Authentication Service (enhanced with nonce accounts)
74
+ export { AuthService } from './services/authService';
41
75
  // Sync and settlement
42
76
  export { syncToChain, checkSyncStatus, type SyncResult } from './sync';
43
77
 
@@ -67,3 +101,15 @@ export {
67
101
  type IntentExchangeRequest,
68
102
  type IntentExchangeResponse,
69
103
  } from './discovery';
104
+
105
+ // Compression utilities
106
+ export {
107
+ compressMetadata,
108
+ decompressMetadata,
109
+ compressIntentMetadata,
110
+ decompressIntentMetadata,
111
+ estimateCompressionSavings,
112
+ } from './utils/compression';
113
+
114
+ // WiFi Direct transport
115
+ export { WiFiDirectTransport, SmartTransportSelector } from './wifi';