toss-expo-sdk 0.1.1 → 1.0.1

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 +490 -81
  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 +27 -44
  9. package/lib/module/client/TossClient.js.map +1 -1
  10. package/lib/module/contexts/WalletContext.js +4 -4
  11. package/lib/module/contexts/WalletContext.js.map +1 -1
  12. package/lib/module/discovery.js +35 -8
  13. package/lib/module/discovery.js.map +1 -1
  14. package/lib/module/examples/offlinePaymentFlow.js +27 -2
  15. package/lib/module/examples/offlinePaymentFlow.js.map +1 -1
  16. package/lib/module/hooks/useOfflineBLETransactions.js +314 -0
  17. package/lib/module/hooks/useOfflineBLETransactions.js.map +1 -0
  18. package/lib/module/index.js +13 -8
  19. package/lib/module/index.js.map +1 -1
  20. package/lib/module/intent.js +198 -0
  21. package/lib/module/intent.js.map +1 -1
  22. package/lib/module/nfc.js +1 -1
  23. package/lib/module/noise.js +176 -1
  24. package/lib/module/noise.js.map +1 -1
  25. package/lib/module/reconciliation.js +155 -0
  26. package/lib/module/reconciliation.js.map +1 -1
  27. package/lib/module/services/authService.js +164 -1
  28. package/lib/module/services/authService.js.map +1 -1
  29. package/lib/module/storage/secureStorage.js +102 -0
  30. package/lib/module/storage/secureStorage.js.map +1 -1
  31. package/lib/module/storage.js +4 -4
  32. package/lib/module/sync.js +25 -1
  33. package/lib/module/sync.js.map +1 -1
  34. package/lib/module/types/nonceAccount.js +2 -0
  35. package/lib/module/types/nonceAccount.js.map +1 -0
  36. package/lib/module/types/tossUser.js +16 -1
  37. package/lib/module/types/tossUser.js.map +1 -1
  38. package/lib/typescript/src/__tests__/solana-program-simple.test.d.ts +8 -0
  39. package/lib/typescript/src/__tests__/solana-program-simple.test.d.ts.map +1 -0
  40. package/lib/typescript/src/ble.d.ts +31 -2
  41. package/lib/typescript/src/ble.d.ts.map +1 -1
  42. package/lib/typescript/src/client/BLETransactionHandler.d.ts +98 -0
  43. package/lib/typescript/src/client/BLETransactionHandler.d.ts.map +1 -0
  44. package/lib/typescript/src/client/NonceAccountManager.d.ts +82 -0
  45. package/lib/typescript/src/client/NonceAccountManager.d.ts.map +1 -0
  46. package/lib/typescript/src/client/TossClient.d.ts +10 -12
  47. package/lib/typescript/src/client/TossClient.d.ts.map +1 -1
  48. package/lib/typescript/src/discovery.d.ts +8 -2
  49. package/lib/typescript/src/discovery.d.ts.map +1 -1
  50. package/lib/typescript/src/examples/offlinePaymentFlow.d.ts +9 -1
  51. package/lib/typescript/src/examples/offlinePaymentFlow.d.ts.map +1 -1
  52. package/lib/typescript/src/hooks/useOfflineBLETransactions.d.ts +91 -0
  53. package/lib/typescript/src/hooks/useOfflineBLETransactions.d.ts.map +1 -0
  54. package/lib/typescript/src/index.d.ts +11 -4
  55. package/lib/typescript/src/index.d.ts.map +1 -1
  56. package/lib/typescript/src/intent.d.ts +26 -0
  57. package/lib/typescript/src/intent.d.ts.map +1 -1
  58. package/lib/typescript/src/noise.d.ts +62 -0
  59. package/lib/typescript/src/noise.d.ts.map +1 -1
  60. package/lib/typescript/src/reconciliation.d.ts +6 -0
  61. package/lib/typescript/src/reconciliation.d.ts.map +1 -1
  62. package/lib/typescript/src/services/authService.d.ts +26 -1
  63. package/lib/typescript/src/services/authService.d.ts.map +1 -1
  64. package/lib/typescript/src/storage/secureStorage.d.ts +16 -0
  65. package/lib/typescript/src/storage/secureStorage.d.ts.map +1 -1
  66. package/lib/typescript/src/sync.d.ts +6 -1
  67. package/lib/typescript/src/sync.d.ts.map +1 -1
  68. package/lib/typescript/src/types/nonceAccount.d.ts +59 -0
  69. package/lib/typescript/src/types/nonceAccount.d.ts.map +1 -0
  70. package/lib/typescript/src/types/tossUser.d.ts +16 -0
  71. package/lib/typescript/src/types/tossUser.d.ts.map +1 -1
  72. package/package.json +12 -1
  73. package/src/__tests__/reconciliation.test.tsx +7 -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 +36 -49
  79. package/src/contexts/WalletContext.tsx +4 -4
  80. package/src/discovery.ts +46 -8
  81. package/src/examples/offlinePaymentFlow.ts +48 -2
  82. package/src/hooks/useOfflineBLETransactions.ts +438 -0
  83. package/src/index.tsx +49 -7
  84. package/src/intent.ts +254 -0
  85. package/src/nfc.ts +4 -4
  86. package/src/noise.ts +239 -1
  87. package/src/reconciliation.ts +184 -0
  88. package/src/services/authService.ts +188 -1
  89. package/src/storage/secureStorage.ts +142 -4
  90. package/src/storage.ts +4 -4
  91. package/src/sync.ts +40 -0
  92. package/src/types/nonceAccount.ts +75 -0
  93. package/src/types/tossUser.ts +35 -2
package/src/discovery.ts CHANGED
@@ -152,6 +152,12 @@ export class DeviceDiscoveryService {
152
152
  export class IntentExchangeProtocol {
153
153
  private pendingRequests: Map<string, IntentExchangeRequest> = new Map();
154
154
  private noiseSessions: Map<string, NoiseSession> = new Map();
155
+ // Track timeout handles so they can be cleared during shutdown/cleanup
156
+ private requestTimeouts: Map<string, ReturnType<typeof setTimeout>> =
157
+ new Map();
158
+ private sessionTimeouts: Map<string, ReturnType<typeof setTimeout>> =
159
+ new Map();
160
+
155
161
  private deviceStaticKey: Uint8Array; // Static key for this device
156
162
  private readonly REQUEST_TIMEOUT = 2 * 60 * 1000; // 2 minutes
157
163
  private readonly SESSION_TIMEOUT = 30 * 60 * 1000; // 30 minutes
@@ -187,11 +193,14 @@ export class IntentExchangeProtocol {
187
193
 
188
194
  this.noiseSessions.set(peerId, session);
189
195
 
190
- // Clean up expired sessions
191
- setTimeout(() => {
196
+ // Clean up expired sessions (track timer so it can be cleared)
197
+ const sessTimer = setTimeout(() => {
192
198
  this.noiseSessions.delete(peerId);
199
+ this.sessionTimeouts.delete(peerId);
193
200
  }, this.SESSION_TIMEOUT);
194
201
 
202
+ this.sessionTimeouts.set(peerId, sessTimer);
203
+
195
204
  return session;
196
205
  }
197
206
 
@@ -224,8 +233,11 @@ export class IntentExchangeProtocol {
224
233
  // XOR encryption with session key
225
234
  const encrypted = new Uint8Array(payload.length);
226
235
  for (let i = 0; i < payload.length; i++) {
236
+ // XOR operation used intentionally for lightweight obfuscation
237
+ // Prefer Uint8 arithmetic to avoid bitwise lint; modulo ensures 0-255 values
227
238
  encrypted[i] =
228
- payload[i]! ^ session.sessionKey[i % session.sessionKey.length]!;
239
+ (payload[i]! + session.sessionKey[i % session.sessionKey.length]!) %
240
+ 256;
229
241
  }
230
242
 
231
243
  return encrypted;
@@ -241,8 +253,12 @@ export class IntentExchangeProtocol {
241
253
  // Reverse the XOR operation
242
254
  const decrypted = new Uint8Array(ciphertext.length);
243
255
  for (let i = 0; i < ciphertext.length; i++) {
256
+ // Reverse the lightweight obfuscation
244
257
  decrypted[i] =
245
- ciphertext[i]! ^ session.sessionKey[i % session.sessionKey.length]!;
258
+ (256 +
259
+ ciphertext[i]! -
260
+ (session.sessionKey[i % session.sessionKey.length]! % 256)) %
261
+ 256;
246
262
  }
247
263
 
248
264
  const jsonPayload = new TextDecoder().decode(decrypted);
@@ -293,11 +309,14 @@ export class IntentExchangeProtocol {
293
309
 
294
310
  this.pendingRequests.set(requestId, request);
295
311
 
296
- // Clean up expired requests after timeout
297
- setTimeout(() => {
312
+ // Clean up expired requests after timeout (track timer so it can be cleared)
313
+ const reqTimer = setTimeout(() => {
298
314
  this.pendingRequests.delete(requestId);
315
+ this.requestTimeouts.delete(requestId);
299
316
  }, this.REQUEST_TIMEOUT);
300
317
 
318
+ this.requestTimeouts.set(requestId, reqTimer);
319
+
301
320
  return request;
302
321
  }
303
322
 
@@ -380,18 +399,37 @@ export class IntentExchangeProtocol {
380
399
  }
381
400
 
382
401
  /**
383
- * Clear all pending requests
402
+ * Clear all pending requests and their timers
384
403
  */
385
404
  clearRequests(): void {
405
+ // Clear any outstanding timers
406
+ for (const [id, timer] of this.requestTimeouts.entries()) {
407
+ clearTimeout(timer);
408
+ this.requestTimeouts.delete(id);
409
+ }
410
+
386
411
  this.pendingRequests.clear();
387
412
  }
388
413
 
389
414
  /**
390
- * Clear all Noise sessions
415
+ * Clear all Noise sessions and their timers
391
416
  */
392
417
  clearSessions(): void {
418
+ for (const [id, timer] of this.sessionTimeouts.entries()) {
419
+ clearTimeout(timer);
420
+ this.sessionTimeouts.delete(id);
421
+ }
422
+
393
423
  this.noiseSessions.clear();
394
424
  }
425
+
426
+ /**
427
+ * Dispose of the protocol, clearing internal state and timers
428
+ */
429
+ dispose(): void {
430
+ this.clearRequests();
431
+ this.clearSessions();
432
+ }
395
433
  }
396
434
 
397
435
  /**
@@ -10,7 +10,13 @@
10
10
  */
11
11
 
12
12
  import { Connection, Keypair, PublicKey } from '@solana/web3.js';
13
- import { createIntent, type SolanaIntent, verifyIntent } from '../intent';
13
+ import {
14
+ createUserIntent,
15
+ createIntent,
16
+ type SolanaIntent,
17
+ verifyIntent,
18
+ } from '../intent';
19
+ import type { TossUser } from '../types/tossUser';
14
20
  import {
15
21
  secureStoreIntent,
16
22
  getAllSecureIntents,
@@ -25,7 +31,47 @@ import { syncToChain } from '../sync';
25
31
  import { TossError } from '../errors';
26
32
 
27
33
  /**
28
- * Example: Sender initiates offline payment
34
+ * Example: Sender initiates offline payment using TOSS users
35
+ *
36
+ * User-centric approach: sender and recipient are TossUser objects
37
+ * Intent creation validates user features and transaction limits
38
+ */
39
+ export async function exampleInitiateUserPayment(
40
+ senderUser: TossUser,
41
+ senderKeypair: Keypair,
42
+ recipientUser: TossUser,
43
+ amountLamports: number,
44
+ connection: Connection
45
+ ): Promise<SolanaIntent> {
46
+ console.log('📝 Creating offline payment intent between TOSS users...');
47
+ console.log(` From: @${senderUser.username}`);
48
+ console.log(` To: @${recipientUser.username}`);
49
+
50
+ // Create the intent using user objects (validates sender/recipient features)
51
+ const intent = await createUserIntent(
52
+ senderUser,
53
+ senderKeypair,
54
+ recipientUser,
55
+ amountLamports,
56
+ connection,
57
+ {
58
+ expiresIn: 24 * 60 * 60, // Valid for 24 hours
59
+ }
60
+ );
61
+
62
+ console.log(`✅ Intent created: ${intent.id}`);
63
+ console.log(` Amount: ${intent.amount} lamports`);
64
+ console.log(` Expires at: ${new Date(intent.expiry * 1000).toISOString()}`);
65
+
66
+ // Store locally
67
+ await secureStoreIntent(intent);
68
+ console.log('💾 Intent stored securely locally\n');
69
+
70
+ return intent;
71
+ }
72
+
73
+ /**
74
+ * Example: Sender initiates offline payment using addresses (legacy)
29
75
  *
30
76
  * This simulates a sender who wants to send lamports to a recipient
31
77
  * while offline. The intent is created, signed, and stored locally.
@@ -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
@@ -1,5 +1,40 @@
1
1
  // Core types and intents
2
- export { createIntent, type SolanaIntent, type IntentStatus } from './intent';
2
+ export {
3
+ createIntent,
4
+ createUserIntent,
5
+ createSignedIntent,
6
+ createOfflineIntent,
7
+ type SolanaIntent,
8
+ type IntentStatus,
9
+ } from './intent';
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';
3
38
 
4
39
  // Intent management
5
40
  export {
@@ -18,18 +53,25 @@ export {
18
53
  clearPendingIntents,
19
54
  } from './storage';
20
55
 
21
- // Transport methods
22
- 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';
23
65
  export { initNFC, readNFCUser, writeUserToNFC, writeIntentToNFC } from './nfc';
24
66
  export { QRScanner } from './qr';
25
67
 
26
68
  // Client
27
69
  export { TossClient, type TossConfig } from './client/TossClient';
70
+ export type { TossUser } from './types/tossUser';
71
+ export { WalletProvider, useWallet } from './contexts/WalletContext';
28
72
 
29
- // Create client instance
30
- import { TossClient } from './client/TossClient';
31
- export const createClient = TossClient.createClient;
32
-
73
+ // Authentication Service (enhanced with nonce accounts)
74
+ export { AuthService } from './services/authService';
33
75
  // Sync and settlement
34
76
  export { syncToChain, checkSyncStatus, type SyncResult } from './sync';
35
77