@explorins/pers-signer 1.0.11 → 1.0.16

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.
package/dist/index.esm.js CHANGED
@@ -49,7 +49,17 @@ class FetchHttpClient {
49
49
  setTimeout(() => controller.abort(), options.timeout);
50
50
  }
51
51
  const response = await fetch(url, fetchOptions);
52
- const data = await response.json();
52
+ // Check if response is actually JSON before parsing
53
+ const responseText = await response.text();
54
+ let data;
55
+ try {
56
+ data = JSON.parse(responseText);
57
+ }
58
+ catch (parseError) {
59
+ console.error('[HttpClient] JSON parse error:', parseError);
60
+ console.error('[HttpClient] Failed to parse response text:', responseText);
61
+ throw new Error(`Failed to parse JSON response: ${parseError instanceof Error ? parseError.message : 'Unknown error'}`);
62
+ }
53
63
  return {
54
64
  data,
55
65
  status: response.status,
@@ -160,187 +170,280 @@ function getServiceConfig() {
160
170
  /**
161
171
  * Authentication service for user login and registration
162
172
  * Uses constructor-based dependency injection for WebAuthn provider
173
+ * Updated for new v1 API endpoints
163
174
  */
164
175
  class AuthenticationService {
165
- constructor(webAuthnProvider) {
166
- this.webAuthnProvider = webAuthnProvider;
176
+ constructor(config) {
177
+ this.signerToken = null;
178
+ this.config = config;
179
+ this.webAuthnProvider = config.webAuthnProvider;
167
180
  }
168
181
  /**
169
- * Authenticate user with username
170
- * @param username - User identifier
171
- * @returns Promise resolving to authentication token
182
+ * Login with PERS token to get signer JWT
183
+ * @param persToken - PERS JWT from PERS authentication
184
+ * @returns Promise resolving to login response or provider challenge data
172
185
  */
173
- async login(username) {
186
+ async loginWithPersToken(persToken) {
174
187
  const httpClient = getHttpClient();
175
188
  const config = getServiceConfig();
176
189
  try {
177
- const response = await httpClient.post(`${config.apiUrl}/login`, {
178
- username,
179
- appId: config.appId,
180
- });
181
- // Check if the response contains an error
182
- if (response.data.status && response.data.status >= 400) {
183
- throw new Error(response.data.message || 'Login failed');
184
- }
185
- if (!response.data.token) {
186
- throw new Error(response.data.message || 'No token received from login');
190
+ const requestBody = {
191
+ authToken: persToken
192
+ };
193
+ const response = await httpClient.post(`${config.apiUrl}/auth/login`, requestBody
194
+ // Note: Login endpoint doesn't require Authorization header (public endpoint)
195
+ );
196
+ const backendResponse = response.data;
197
+ if (backendResponse && backendResponse.success) {
198
+ // Check if it's JWT response or wrapped provider response
199
+ if ('access_token' in backendResponse) {
200
+ // JWT response - set token and return
201
+ this.signerToken = backendResponse.access_token;
202
+ return backendResponse;
203
+ }
204
+ else {
205
+ // Wrapped provider response - return the data
206
+ return backendResponse.data;
207
+ }
187
208
  }
188
- console.info(`[WebAuthn] User login successful: ${username}`);
189
- return response.data.token;
209
+ console.error('[AuthenticationService] Invalid response - no success or success=false:', backendResponse);
210
+ throw new Error('Login failed: Invalid response format');
190
211
  }
191
212
  catch (error) {
192
- console.error(`[AuthenticationService] Login failed for ${username}:`, error);
213
+ // console.error(`[AuthenticationService] PERS token login failed:`, error);
193
214
  throw error;
194
215
  }
195
216
  }
196
217
  /**
197
- * Register new user with WebAuthn credential
198
- * @param username - User identifier
199
- * @returns Promise resolving to registration result
218
+ * Verify signer token validity
219
+ * @param token - Signer JWT to verify
220
+ * @returns Promise resolving to verification result
200
221
  */
201
- async register(username) {
222
+ async verifyToken(token) {
202
223
  const httpClient = getHttpClient();
203
224
  const config = getServiceConfig();
204
225
  try {
205
- // Step 1: Initialize registration and get challenge
206
- const initResponse = await httpClient.post(`${config.apiUrl}/register/init`, {
207
- username,
208
- appId: config.appId,
209
- });
210
- const challenge = initResponse.data;
211
- // Check if the registration init failed
212
- if (challenge.status && challenge.status >= 400) {
213
- throw new Error(challenge.message || 'Registration initialization failed');
214
- }
215
- // Step 2: Create WebAuthn credential using the challenge
216
- const attestation = await this.webAuthnProvider.create(challenge);
217
- console.info(`[WebAuthn] Credential created successfully for user: ${username}`);
218
- // Step 3: Complete registration with signed challenge
219
- const completeResponse = await httpClient.post(`${config.apiUrl}/register/complete`, {
220
- signedChallenge: { firstFactorCredential: attestation },
221
- temporaryAuthenticationToken: challenge.temporaryAuthenticationToken,
222
- appId: config.appId,
223
- });
224
- const result = completeResponse.data;
225
- // Check if the registration completion failed
226
- if (result.status && result.status >= 400) {
227
- throw new Error(result.message || 'Registration completion failed');
226
+ const requestBody = {
227
+ token
228
+ };
229
+ const response = await httpClient.post(`${config.apiUrl}/auth/verify`, requestBody, { Authorization: `Bearer ${token}` });
230
+ const verifyData = response.data;
231
+ if (!verifyData.valid) {
232
+ throw new Error('Token verification failed');
228
233
  }
229
- console.info(`[AuthenticationService] Registration successful for: ${username}`);
230
- return result;
234
+ return verifyData;
231
235
  }
232
236
  catch (error) {
233
- console.error(`[AuthenticationService] Registration failed for ${username}:`, error);
237
+ console.error(`[AuthenticationService] Token verification failed:`, error);
234
238
  throw error;
235
239
  }
236
240
  }
237
241
  /**
238
- * Validate authentication token (when backend supports it)
239
- * @param authToken - Token to validate
240
- * @returns Promise resolving to validation result
242
+ * Initialize user registration
243
+ * @param persToken - PERS JWT token (registration is public)
244
+ * @returns Promise resolving to registration challenge
241
245
  */
242
- async validateToken(authToken) {
246
+ async initializeRegistration(persToken) {
243
247
  const httpClient = getHttpClient();
244
248
  const config = getServiceConfig();
245
249
  try {
246
- const response = await httpClient.post(`${config.apiUrl}/validate-token`, {
247
- authToken,
248
- appId: config.appId,
249
- });
250
- return response.data.valid === true;
250
+ const requestBody = {
251
+ authToken: persToken
252
+ };
253
+ const response = await httpClient.post(`${config.apiUrl}/auth/register/init`, requestBody);
254
+ const backendResponse = response.data;
255
+ if (backendResponse.success && backendResponse.data) {
256
+ return backendResponse.data;
257
+ }
258
+ throw new Error('Registration initialization failed: No data returned');
251
259
  }
252
260
  catch (error) {
253
- console.error('[AuthenticationService] Error validating token:', error);
254
- return false;
261
+ // console.error(`[AuthenticationService] Registration initialization failed:`, error);
262
+ throw error;
255
263
  }
256
264
  }
257
265
  /**
258
- * Get user information (when backend supports it)
259
- * @param authToken - Authentication token
260
- * @returns Promise resolving to user info or null
266
+ * Get current signer token
267
+ * @returns The current signer JWT token
261
268
  */
262
- async getUser(authToken) {
263
- const httpClient = getHttpClient();
264
- try {
265
- const response = await httpClient.get(`${getServiceConfig().apiUrl}/user`, {
266
- 'Authorization': `Bearer ${authToken}`
267
- });
268
- return response.data;
269
- }
270
- catch (error) {
271
- console.error('[AuthenticationService] Error getting user:', error);
272
- return null;
273
- }
269
+ getSignerToken() {
270
+ return this.signerToken;
274
271
  }
275
- }
276
-
277
- /**
278
- * Wallet management service
279
- * Handles wallet listing and management operations
280
- */
281
- class WalletService {
282
272
  /**
283
- * List all wallets for authenticated user
284
- * @param authToken - Authentication token
285
- * @returns Promise resolving to wallet list
273
+ * Set signer token (for external token management)
274
+ * @param token - Signer JWT token
275
+ */
276
+ setSignerToken(token) {
277
+ this.signerToken = token;
278
+ }
279
+ /**
280
+ * Complete registration with WebAuthn challenge data (v1 API format)
281
+ * @param tmpAuthToken - Temporary auth token from init registration (temporaryAuthenticationToken)
282
+ * @param signedChallenge - WebAuthn credential response (will be restructured for backend)
283
+ * @param persToken - PERS JWT token (authToken)
284
+ * @returns Promise resolving to registration result
286
285
  */
287
- static async listWallets(authToken) {
286
+ async completeRegistrationWithChallenge(tmpAuthToken, signedChallenge, persToken) {
288
287
  const httpClient = getHttpClient();
289
288
  const config = getServiceConfig();
290
289
  try {
291
- const response = await httpClient.post(`${config.apiUrl}/wallets/list`, {
292
- authToken,
293
- appId: config.appId,
294
- });
295
- console.log('[WalletService] Raw API response:', response.data);
296
- return response.data;
290
+ const requestBody = {
291
+ temporaryAuthenticationToken: tmpAuthToken,
292
+ signedChallenge: {
293
+ firstFactorCredential: {
294
+ credentialKind: signedChallenge.credentialKind || "Fido2",
295
+ credentialInfo: {
296
+ credId: signedChallenge.credentialInfo?.credId || "",
297
+ clientData: signedChallenge.credentialInfo?.clientData || "",
298
+ attestationData: signedChallenge.credentialInfo?.attestationData || ""
299
+ }
300
+ }
301
+ },
302
+ authToken: persToken
303
+ };
304
+ const response = await httpClient.post(`${config.apiUrl}/auth/register/complete`, requestBody);
305
+ const backendResponse = response.data;
306
+ if (backendResponse && backendResponse.success) {
307
+ // Check if it's JWT response (has access_token at root level) or wrapped provider response
308
+ if ('access_token' in backendResponse) {
309
+ // JWT response - set token and return
310
+ this.signerToken = backendResponse.access_token;
311
+ return backendResponse;
312
+ }
313
+ else if ('data' in backendResponse) {
314
+ // Wrapped provider response - registration is not complete yet, needs more steps
315
+ return backendResponse.data;
316
+ }
317
+ else {
318
+ // Wrapped provider response - return the data
319
+ return backendResponse.data;
320
+ }
321
+ }
322
+ console.error('[AuthenticationService] Registration with challenge failed - invalid response:', backendResponse);
323
+ throw new Error('Registration completion with challenge failed');
297
324
  }
298
325
  catch (error) {
299
- console.error('[WalletService] Error listing wallets:', error);
326
+ console.error(`[AuthenticationService] Registration completion with challenge failed:`, error);
300
327
  throw error;
301
328
  }
302
329
  }
303
330
  /**
304
- * Get wallet by ID (when backend supports it)
305
- * @param authToken - Authentication token
306
- * @param walletId - Wallet identifier
307
- * @returns Promise resolving to wallet details
331
+ * Combined authentication flow - handles both login and registration
332
+ * @param identifier - User identifier (email/userId)
333
+ * @param persAccessToken - PERS JWT token for authentication
334
+ * @param webAuthnProvider - WebAuthn provider for credential creation
335
+ * @param relyingPartyConfig - Configuration for WebAuthn relying party
336
+ * @returns Promise resolving to authenticated user with signer token
308
337
  */
309
- static async getWallet(authToken, walletId) {
310
- const httpClient = getHttpClient();
311
- const config = getServiceConfig();
338
+ async combinedAuthentication(identifier, persAccessToken) {
312
339
  try {
313
- const response = await httpClient.post(`${config.apiUrl}/wallets/get`, {
314
- authToken,
315
- walletId,
316
- appId: config.appId,
317
- });
318
- return response.data;
340
+ // Step 1: Try to login with PERS token first (this will get signer JWT if user exists)
341
+ let signerToken;
342
+ try {
343
+ const loginResult = await this.loginWithPersToken(persAccessToken);
344
+ // Extract token from login result
345
+ if (loginResult && typeof loginResult === 'object' && 'access_token' in loginResult) {
346
+ signerToken = loginResult.access_token;
347
+ }
348
+ else {
349
+ throw new Error('Invalid login response format');
350
+ }
351
+ }
352
+ catch (loginError) {
353
+ // Step 2: User doesn't exist - register with v1 API
354
+ // 2a. Initialize registration
355
+ const initResult = await this.initializeRegistration(persAccessToken);
356
+ let signedChallenge = null;
357
+ let tmpAuthToken = null;
358
+ // 2b. Create WebAuthn credential using the challenge (if available)
359
+ if (initResult && typeof initResult === 'object') {
360
+ // Extract challenge and tmpAuthToken from init result with better type safety
361
+ const initData = initResult;
362
+ const challenge = initData.challenge;
363
+ tmpAuthToken = initData.temporaryAuthenticationToken || initData.tmpAuthToken || null;
364
+ if (challenge && initData.user) {
365
+ // Construct proper WebAuthn credential creation options
366
+ const credentialCreationOptions = {
367
+ challenge: challenge,
368
+ rp: {
369
+ name: this.config.relyingPartyName || 'PERS Signer',
370
+ id: typeof window !== 'undefined' ? window.location.hostname : 'localhost'
371
+ },
372
+ user: {
373
+ id: initData.user.id,
374
+ name: initData.user.name,
375
+ displayName: initData.user.displayName
376
+ },
377
+ pubKeyCredParams: initData.pubKeyCredParams || [
378
+ { alg: -7, type: "public-key" },
379
+ { alg: -257, type: "public-key" }
380
+ ],
381
+ authenticatorSelection: initData.authenticatorSelection || {},
382
+ attestation: initData.attestation || "direct",
383
+ excludeCredentials: initData.excludeCredentials || []
384
+ };
385
+ signedChallenge = await this.webAuthnProvider.create(credentialCreationOptions);
386
+ }
387
+ else {
388
+ console.warn(`[PersSignerSDK] Missing challenge or user data in init result`);
389
+ }
390
+ }
391
+ // 2c. Complete registration with proper challenge data - call AuthenticationService directly
392
+ const completeResult = await this.completeRegistrationWithChallenge(tmpAuthToken, signedChallenge, persAccessToken);
393
+ // Extract token from registration result
394
+ if (completeResult && typeof completeResult === 'object') {
395
+ // Check if this is a complete JWT response with access_token
396
+ if ('access_token' in completeResult) {
397
+ signerToken = completeResult.access_token;
398
+ }
399
+ else {
400
+ // This is a wrapped provider response - registration is not complete
401
+ // We have user created and wallet created, but need to get JWT token
402
+ // For now, throw an error indicating we need additional backend integration steps
403
+ throw new Error('Registration created user and wallet but JWT token not provided. Backend integration incomplete.');
404
+ }
405
+ }
406
+ else {
407
+ throw new Error('Registration completed but no response received');
408
+ }
409
+ }
410
+ return {
411
+ identifier,
412
+ signerAuthToken: signerToken,
413
+ persAccessToken
414
+ };
319
415
  }
320
416
  catch (error) {
321
- console.error(`[WalletService] Error getting wallet ${walletId}:`, error);
322
- throw error;
417
+ console.error(`[PersSignerSDK] Combined authentication failed for ${identifier}:`, error);
418
+ throw new Error(`Combined authentication failed: ${error}`);
323
419
  }
324
420
  }
421
+ }
422
+
423
+ /**
424
+ * Wallet management service
425
+ * Updated for v1 API endpoints per migration reference
426
+ */
427
+ class WalletService {
325
428
  /**
326
- * Create new wallet (when backend supports it)
327
- * @param authToken - Authentication token
328
- * @param walletConfig - Wallet configuration
329
- * @returns Promise resolving to wallet creation result
429
+ * List all wallets for authenticated user
430
+ * @param signerToken - Signer JWT token
431
+ * @returns Promise resolving to wallet data
330
432
  */
331
- static async createWallet(authToken, walletConfig) {
433
+ static async listWallets(signerToken) {
332
434
  const httpClient = getHttpClient();
333
435
  const config = getServiceConfig();
334
436
  try {
335
- const response = await httpClient.post(`${config.apiUrl}/wallets/create`, {
336
- authToken,
337
- ...walletConfig,
338
- appId: config.appId,
339
- });
340
- return response.data;
437
+ const requestBody = {};
438
+ const response = await httpClient.post(`${config.apiUrl}/wallets/list`, requestBody, { Authorization: `Bearer ${signerToken}` });
439
+ const backendResponse = response.data;
440
+ if (backendResponse.success && backendResponse.data) {
441
+ return backendResponse.data;
442
+ }
443
+ throw new Error('Failed to list wallets: Invalid response');
341
444
  }
342
445
  catch (error) {
343
- console.error('[WalletService] Error creating wallet:', error);
446
+ console.error('[WalletService] Error listing wallets:', error);
344
447
  throw error;
345
448
  }
346
449
  }
@@ -400,27 +503,36 @@ class SigningService {
400
503
  const config = getServiceConfig();
401
504
  try {
402
505
  // Step 1: Initialize signing and get challenge
403
- const initResponse = await httpClient.post(`${config.apiUrl}/wallets/signatures/init`, {
404
- authToken,
506
+ const initRequest = {
405
507
  walletId,
406
- request,
407
- appId: config.appId,
408
- });
409
- const { requestBody, challenge } = initResponse.data;
508
+ request
509
+ };
510
+ const initResponse = await httpClient.post(`${config.apiUrl}/wallets/signatures/init`, initRequest, { Authorization: `Bearer ${authToken}` });
511
+ const backendInitResponse = initResponse.data;
512
+ if (!backendInitResponse.success || !backendInitResponse.data) {
513
+ console.error('[SigningService] Init response invalid:', backendInitResponse);
514
+ throw new Error('Signature initialization failed');
515
+ }
516
+ const { requestBody, challenge } = backendInitResponse.data;
410
517
  // Step 2: Sign the challenge using WebAuthn
411
518
  const assertion = await this.webAuthnProvider.sign(challenge);
412
519
  // Step 3: Complete signing with signed challenge
413
- const completeResponse = await httpClient.post(`${config.apiUrl}/wallets/signatures/complete`, {
414
- authToken,
520
+ const completeRequest = {
415
521
  walletId,
416
522
  requestBody,
417
523
  signedChallenge: {
418
- challengeIdentifier: challenge.challengeIdentifier,
524
+ challengeIdentifier: challenge?.challengeIdentifier,
419
525
  firstFactor: assertion,
420
- },
421
- appId: config.appId,
422
- });
423
- return completeResponse.data;
526
+ }
527
+ };
528
+ const completeResponse = await httpClient.post(`${config.apiUrl}/wallets/signatures/complete`, completeRequest, { Authorization: `Bearer ${authToken}` });
529
+ const backendCompleteResponse = completeResponse.data;
530
+ if (!backendCompleteResponse.success || !backendCompleteResponse.data) {
531
+ console.error('[SigningService] Complete response invalid:', backendCompleteResponse);
532
+ throw new Error('Signature completion failed');
533
+ }
534
+ // Return the complete signature data from backend response
535
+ return backendCompleteResponse.data;
424
536
  }
425
537
  catch (error) {
426
538
  console.error(`[SigningService] Signing failed for wallet ${walletId}:`, error);
@@ -521,10 +633,10 @@ class PersService {
521
633
  * @returns Promise with tenant public information
522
634
  */
523
635
  static async getTenantById(tenantId, authToken) {
524
- // Check memory cache first
636
+ // Check memory cache first with expiration
525
637
  const cached = this.tenantCache.get(tenantId);
526
- if (cached) {
527
- return cached;
638
+ if (cached && Date.now() < cached.expiresAt) {
639
+ return cached.tenant;
528
640
  }
529
641
  try {
530
642
  const headers = {
@@ -562,10 +674,16 @@ class PersService {
562
674
  }
563
675
  }
564
676
  const tenantData = await response.json();
565
- // Cache the tenant data in memory only
566
- this.tenantCache.set(tenantId, tenantData);
677
+ // Cache the tenant data with expiration
678
+ const now = Date.now();
679
+ this.tenantCache.set(tenantId, {
680
+ tenant: tenantData,
681
+ cachedAt: now,
682
+ expiresAt: now + this.TENANT_CACHE_TTL
683
+ });
567
684
  // Update current project key (check multiple possible property names)
568
685
  this.currentProjectKey = tenantData.projectKey || tenantData.projectApiKey || tenantData.apiKey;
686
+ this.currentTenantId = tenantId;
569
687
  return tenantData;
570
688
  }
571
689
  catch (error) {
@@ -585,14 +703,35 @@ class PersService {
585
703
  const projectKey = tenantData.projectApiKey;
586
704
  if (projectKey) {
587
705
  this.currentProjectKey = projectKey;
706
+ this.currentTenantId = tenantId;
588
707
  }
589
708
  return tenantData;
590
709
  }
710
+ /**
711
+ * Ensure tenant is initialized and current, with automatic revalidation
712
+ * @param tenantId - The tenant ID to ensure is initialized
713
+ * @param authToken - Optional auth token for authentication
714
+ * @returns Promise with tenant information
715
+ */
716
+ static async ensureTenantInitialized(tenantId, authToken) {
717
+ // Check if we already have the right tenant initialized and it's still valid
718
+ const cached = this.tenantCache.get(tenantId);
719
+ if (this.currentTenantId === tenantId && cached && Date.now() < cached.expiresAt) {
720
+ return cached.tenant;
721
+ }
722
+ // Initialize or refresh tenant data
723
+ return await this.initializeTenant(tenantId, authToken);
724
+ }
591
725
  /**
592
726
  * Get the current project key (either from tenant or fallback)
727
+ * @param tenantId - Optional tenant ID to ensure is initialized
593
728
  * @returns The project key to use for API calls
594
729
  */
595
- static getProjectKey() {
730
+ static async getProjectKey(tenantId) {
731
+ // If tenantId provided and different from current, initialize it
732
+ if (tenantId && this.currentTenantId !== tenantId) {
733
+ await this.ensureTenantInitialized(tenantId);
734
+ }
596
735
  if (this.currentProjectKey) {
597
736
  return this.currentProjectKey;
598
737
  }
@@ -608,17 +747,19 @@ class PersService {
608
747
  static clearTenantCache() {
609
748
  this.tenantCache.clear();
610
749
  this.currentProjectKey = null;
750
+ this.currentTenantId = null;
611
751
  }
612
752
  /**
613
753
  * Authenticates a user with the PERS backend using their auth token
614
754
  *
615
755
  * @param authToken - The authentication token received from DFNS after login/registration
756
+ * @param tenantId - Optional tenant ID for automatic initialization
616
757
  * @returns A promise that resolves to the authentication response
617
758
  * @throws If the request fails
618
759
  */
619
- static async authenticateUser(authToken) {
760
+ static async authenticateUser(authToken, tenantId) {
620
761
  try {
621
- const projectKey = this.getProjectKey();
762
+ const projectKey = await this.getProjectKey(tenantId);
622
763
  const headers = {
623
764
  'accept': 'application/json',
624
765
  'x-project-key': projectKey,
@@ -668,30 +809,6 @@ class PersService {
668
809
  };
669
810
  }
670
811
  }
671
- /**
672
- * Prepares a transaction by calling the backend endpoint
673
- *
674
- * @param form - The transaction details
675
- * @param persAccessToken - The PERS access token for authentication (Bearer)
676
- * @returns A promise that resolves to the transaction preparation response
677
- * @throws If the request fails
678
- */
679
- static async prepareTransaction(form, persAccessToken) {
680
- // ✅ UPDATED: /transaction/auth/prepare-signing → /transactions/prepare
681
- const res = await fetch(`${getPersApiUrl(this.useStaging)}/transactions`, {
682
- method: "POST",
683
- headers: {
684
- "Content-Type": "application/json",
685
- 'x-project-key': this.getProjectKey(),
686
- Authorization: `Bearer ${persAccessToken}`,
687
- },
688
- body: JSON.stringify(form),
689
- });
690
- if (!res.ok)
691
- throw new Error("Failed to prepare transaction");
692
- const response = await res.json();
693
- return response;
694
- }
695
812
  /**
696
813
  * Submits a transaction by calling the backend endpoint
697
814
  *
@@ -699,208 +816,86 @@ class PersService {
699
816
  * @param signedTransactionOrSignature - The signed transaction data or EIP-712 signature
700
817
  * @param persAccessToken - The PERS access token for authentication (Bearer)
701
818
  * @param submissionType - The transaction format type
819
+ * @param tenantId - Optional tenant ID for automatic initialization
702
820
  * @returns A promise that resolves to the transaction submission response
703
821
  * @throws If the request fails
704
822
  */
705
- static async submitTransaction(transactionId, signedTransactionOrSignature, persAccessToken, submissionType) {
706
- // Map TransactionFormat to the backend's expected submission type
707
- const dto = {
708
- transactionId,
709
- type: submissionType,
710
- ...(submissionType === TRANSACTION_FORMATS.EIP_712
711
- ? { signature: signedTransactionOrSignature }
712
- : { signedTransaction: signedTransactionOrSignature })
713
- };
714
- // ✅ UPDATED: /transaction/auth/submit/${transactionId} → /transactions/${transactionId}/submit
715
- const res = await fetch(`${getPersApiUrl(this.useStaging)}/transactions/submit`, {
716
- method: "POST",
717
- headers: {
718
- "Content-Type": "application/json",
719
- 'x-project-key': this.getProjectKey(),
720
- Authorization: `Bearer ${persAccessToken}`,
721
- },
722
- body: JSON.stringify(dto),
723
- });
724
- if (!res.ok)
725
- throw new Error("Failed to submit transaction");
726
- return await res.json();
727
- }
728
- /**
729
- * Fetches a prepared transaction for signing by transactionId
730
- * @param transactionId - The transaction ID to fetch
731
- * @param persAccessToken - The PERS access token for authentication (Bearer)
732
- * @returns The prepared transaction data
733
- * @throws If the request fails
734
- */
735
- static async fetchPreparedTransaction(transactionId, persAccessToken) {
736
- // ✅ UPDATED: /transaction/auth/prepare-signing/${transactionId} → /transactions/${transactionId}/prepare
737
- const res = await fetch(`${getPersApiUrl(this.useStaging)}/transactions/${transactionId}/prepare`, {
738
- headers: {
739
- "Content-Type": "application/json",
740
- "x-project-key": this.getProjectKey(),
741
- Authorization: `Bearer ${persAccessToken}`,
742
- },
743
- });
744
- const response = await res.json();
745
- if (!res.ok) {
746
- // Throw structured error with the backend response
747
- throw {
748
- status: res.status,
749
- message: response.message || "Failed to fetch prepared transaction",
750
- error: response
751
- };
752
- }
753
- return response;
754
- }
755
- /**
756
- * Claims a reward from the PERS blockchain system
757
- * @param rewardId - The ID of the reward to claim
758
- * @param pointsCost - The points cost for the reward
759
- * @param persAccessToken - The PERS access token for authentication (Bearer)
760
- * @returns The reward claim response including reward image URL
761
- * @throws If the request fails
762
- */
763
- static async claimReward(rewardId, pointsCost, persAccessToken) {
764
- const res = await fetch(`${getPersApiUrl(this.useStaging)}/rewards/auth/claim`, {
765
- method: "POST",
766
- headers: {
767
- "Content-Type": "application/json",
768
- 'x-project-key': this.getProjectKey(),
769
- Authorization: `Bearer ${persAccessToken}`,
770
- },
771
- body: JSON.stringify({
772
- rewardId,
773
- pointsCost
774
- }),
775
- });
776
- if (!res.ok) {
777
- const error = await res.text();
778
- throw new Error(`Failed to claim reward: ${error}`);
779
- }
780
- return await res.json();
781
- }
782
- /**
783
- * Get all active campaigns
784
- * @returns Promise with list of active campaigns
785
- */
786
- static async getActiveCampaigns() {
787
- try {
788
- const response = await fetch(`${getPersApiUrl(this.useStaging)}/campaign`, {
789
- method: 'GET',
790
- headers: {
791
- 'accept': 'application/json',
792
- 'x-project-key': this.getProjectKey()
793
- }
794
- });
795
- if (!response.ok) {
796
- const errorData = await response.json().catch(() => ({}));
797
- throw {
798
- status: response.status,
799
- message: errorData.message || 'Failed to fetch active campaigns',
800
- error: errorData
801
- };
802
- }
803
- const data = await response.json();
804
- return data;
805
- }
806
- catch (error) {
807
- throw error;
808
- }
809
- }
810
- /**
811
- * Claims a campaign for a user
812
- * @param campaignId - The ID of the campaign to claim
813
- * @param persAccessToken - The PERS access token for authentication (Bearer)
814
- * @returns Promise with the campaign claim response
815
- * @throws If the request fails
816
- */
817
- static async claimCampaign(campaignId, persAccessToken) {
823
+ static async submitTransaction(transactionId, signedTransactionOrSignature, persAccessToken, submissionType, tenantId) {
818
824
  try {
819
- const response = await fetch(`${getPersApiUrl(this.useStaging)}/campaign/auth/claim`, {
820
- method: 'POST',
825
+ // Map TransactionFormat to the backend's expected submission type
826
+ const dto = {
827
+ transactionId,
828
+ type: submissionType,
829
+ ...(submissionType === TRANSACTION_FORMATS.EIP_712
830
+ ? { signature: signedTransactionOrSignature }
831
+ : { signedTransaction: signedTransactionOrSignature })
832
+ };
833
+ // ✅ UPDATED: /transaction/auth/submit/${transactionId} → /transactions/${transactionId}/submit
834
+ const res = await fetch(`${getPersApiUrl(this.useStaging)}/transactions/submit`, {
835
+ method: "POST",
821
836
  headers: {
822
- 'accept': 'application/json',
823
- 'x-project-key': this.getProjectKey(),
824
- 'Authorization': `Bearer ${persAccessToken}`,
825
- 'Content-Type': 'application/json'
837
+ "Content-Type": "application/json",
838
+ 'x-project-key': await this.getProjectKey(tenantId),
839
+ Authorization: `Bearer ${persAccessToken}`,
826
840
  },
827
- body: JSON.stringify({
828
- campaignId
829
- })
841
+ body: JSON.stringify(dto),
830
842
  });
831
- if (!response.ok) {
832
- const errorData = await response.json().catch(() => ({}));
843
+ const response = await res.json();
844
+ if (!res.ok) {
845
+ // Throw structured error with the backend response
833
846
  throw {
834
- status: response.status,
835
- message: errorData.message || 'Failed to claim campaign',
836
- error: errorData
847
+ status: res.status,
848
+ message: response.message || "Failed to submit transaction",
849
+ error: response
837
850
  };
838
851
  }
839
- const data = await response.json();
840
- return data;
852
+ return response;
841
853
  }
842
854
  catch (error) {
843
- throw error;
855
+ // Rethrow a structured error
856
+ throw {
857
+ status: error.status || 500,
858
+ message: error.message || 'Failed to submit transaction to PERS backend',
859
+ error
860
+ };
844
861
  }
845
862
  }
846
863
  /**
847
- * Gets all campaign claims for the authenticated user
864
+ * Fetches a prepared transaction for signing by transactionId
865
+ * @param transactionId - The transaction ID to fetch
848
866
  * @param persAccessToken - The PERS access token for authentication (Bearer)
849
- * @returns Promise with list of user's campaign claims
850
- * @throws If the request fails
851
- */
852
- static async getUserCampaignClaims(persAccessToken) {
853
- try {
854
- const response = await fetch(`${getPersApiUrl(this.useStaging)}/campaign/auth/claim`, {
855
- method: 'GET',
856
- headers: {
857
- 'accept': 'application/json',
858
- 'x-project-key': this.getProjectKey(),
859
- 'Authorization': `Bearer ${persAccessToken}`
860
- }
861
- });
862
- if (!response.ok) {
863
- const errorData = await response.json().catch(() => ({}));
864
- throw {
865
- status: response.status,
866
- message: errorData.message || 'Failed to fetch user campaign claims',
867
- error: errorData
868
- };
869
- }
870
- const data = await response.json();
871
- return data;
872
- }
873
- catch (error) {
874
- throw error;
875
- }
876
- }
877
- /**
878
- * Gets all available rewards for redemption
879
- * @returns Promise with list of available rewards that can be exchanged for points
867
+ * @param tenantId - Optional tenant ID for automatic initialization
868
+ * @returns The prepared transaction data
880
869
  * @throws If the request fails
881
870
  */
882
- static async getAvailableRedemptions() {
871
+ static async fetchPreparedTransaction(transactionId, persAccessToken, tenantId) {
883
872
  try {
884
- const response = await fetch(`${getPersApiUrl(this.useStaging)}/redemption`, {
885
- method: 'GET',
873
+ // UPDATED: /transaction/auth/prepare-signing/${transactionId} → /transactions/${transactionId}/prepare
874
+ const res = await fetch(`${getPersApiUrl(this.useStaging)}/transactions/${transactionId}/prepare`, {
886
875
  headers: {
887
- 'accept': 'application/json',
888
- 'x-project-key': this.getProjectKey()
889
- }
876
+ "Content-Type": "application/json",
877
+ "x-project-key": await this.getProjectKey(tenantId),
878
+ Authorization: `Bearer ${persAccessToken}`,
879
+ },
890
880
  });
891
- if (!response.ok) {
892
- const errorData = await response.json().catch(() => ({}));
881
+ const response = await res.json();
882
+ if (!res.ok) {
883
+ // Throw structured error with the backend response
893
884
  throw {
894
- status: response.status,
895
- message: errorData.message || 'Failed to fetch available redemptions',
896
- error: errorData
885
+ status: res.status,
886
+ message: response.message || "Failed to fetch prepared transaction",
887
+ error: response
897
888
  };
898
889
  }
899
- const data = await response.json();
900
- return data;
890
+ return response;
901
891
  }
902
892
  catch (error) {
903
- throw error;
893
+ // Rethrow a structured error
894
+ throw {
895
+ status: error.status || 500,
896
+ message: error.message || 'Failed to fetch prepared transaction from PERS backend',
897
+ error
898
+ };
904
899
  }
905
900
  }
906
901
  /**
@@ -918,7 +913,7 @@ class PersService {
918
913
  * @throws If transaction data is not available
919
914
  */
920
915
  static getTransactionDataForSigning(transactionResponse) {
921
- if (!this.isTransactionReadyForSigning(transactionResponse)) {
916
+ if (!transactionResponse.signingData) {
922
917
  throw new Error('Transaction data is not ready for signing');
923
918
  }
924
919
  return transactionResponse.signingData;
@@ -927,8 +922,79 @@ class PersService {
927
922
  PersService.config = DEFAULT_PERS_CONFIG;
928
923
  PersService.tenantCache = new Map();
929
924
  PersService.currentProjectKey = null;
925
+ PersService.currentTenantId = null;
930
926
  PersService.useStaging = false;
927
+ PersService.TENANT_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours - tenant configs are essentially static
928
+
929
+ /**
930
+ * Health check service for signer API
931
+ * Handles health check operations for the new v1 API
932
+ */
933
+ class HealthService {
934
+ /**
935
+ * Perform health check on the signer API
936
+ * @returns Promise resolving to health check result
937
+ */
938
+ static async checkHealth() {
939
+ const httpClient = getHttpClient();
940
+ const config = getServiceConfig();
941
+ try {
942
+ const response = await httpClient.get(`${config.apiUrl}/health`);
943
+ return response.data;
944
+ }
945
+ catch (error) {
946
+ console.error('[HealthService] Health check failed:', error);
947
+ // Return failed health check instead of throwing
948
+ return {
949
+ success: false
950
+ };
951
+ }
952
+ }
953
+ }
931
954
 
955
+ // Adapter function to convert backend signature response to legacy format
956
+ const adaptSignatureResponse = (newResponse) => {
957
+ if (!newResponse.signature) {
958
+ console.error('[KeyWallet] No signature in response:', newResponse);
959
+ throw new Error('Signature missing from response');
960
+ }
961
+ // Handle structured signature format (typically from EIP-712 signing)
962
+ if (typeof newResponse.signature === 'object' && 'r' in newResponse.signature) {
963
+ const { r, s, recid } = newResponse.signature;
964
+ return {
965
+ status: 'Signed',
966
+ signature: {
967
+ r,
968
+ s,
969
+ recid // Use recid directly from backend
970
+ }
971
+ };
972
+ }
973
+ // Handle string signature format (potentially from hash signing)
974
+ if (typeof newResponse.signature === 'string') {
975
+ // Parse string signature into components
976
+ // Assuming it's a hex string that can be split into r, s, v components
977
+ const signature = newResponse.signature;
978
+ if (signature.length === 132) { // 0x + 32 bytes r + 32 bytes s + 1 byte v
979
+ const r = '0x' + signature.slice(2, 66);
980
+ const s = '0x' + signature.slice(66, 130);
981
+ const v = parseInt(signature.slice(130, 132), 16);
982
+ const recid = v >= 27 ? v - 27 : v; // Convert v to recid
983
+ return {
984
+ status: 'Signed',
985
+ signature: {
986
+ r,
987
+ s,
988
+ recid
989
+ }
990
+ };
991
+ }
992
+ console.error('[KeyWallet] Invalid string signature length:', signature.length);
993
+ throw new Error(`Invalid string signature format - expected 132 characters, got ${signature.length}`);
994
+ }
995
+ console.error('[KeyWallet] Invalid signature format - expected structured object or string:', typeof newResponse.signature);
996
+ throw new Error('Invalid signature format in response - expected structured object or string from backend');
997
+ };
932
998
  let DfnsError$1 = class DfnsError extends Error {
933
999
  constructor(code, message, details) {
934
1000
  super(message);
@@ -947,14 +1013,16 @@ const assertSigned = (res) => {
947
1013
  };
948
1014
  const combineSignature = (res) => {
949
1015
  if (!res.signature) {
1016
+ console.error('[KeyWallet] No signature in response for combining:', res);
950
1017
  throw new DfnsError$1(-1, "signature missing", res);
951
1018
  }
952
1019
  const { r, s, recid } = res.signature;
953
- return Signature.from({
1020
+ const ethersSignature = Signature.from({
954
1021
  r,
955
1022
  s,
956
1023
  v: recid ? 0x1c : 0x1b, // Assuming 0x1c for chain_id > 0, 0x1b otherwise. DFNS usually provides recid.
957
- }).serialized;
1024
+ });
1025
+ return ethersSignature.serialized;
958
1026
  };
959
1027
  class KeyWallet extends AbstractSigner {
960
1028
  constructor(options, provider) {
@@ -980,9 +1048,11 @@ class KeyWallet extends AbstractSigner {
980
1048
  return this.address;
981
1049
  }
982
1050
  async signHash(hash) {
983
- const res = await this.signingService.signHash(this.authToken, this.metadata.id, hash);
1051
+ const rawRes = await this.signingService.signHash(this.authToken, this.metadata.id, hash);
1052
+ const res = adaptSignatureResponse(rawRes);
984
1053
  assertSigned(res);
985
- return combineSignature(res);
1054
+ const combinedSignature = combineSignature(res);
1055
+ return combinedSignature;
986
1056
  }
987
1057
  async signTransaction(tx) {
988
1058
  // Resolve any addresses
@@ -1019,9 +1089,11 @@ class KeyWallet extends AbstractSigner {
1019
1089
  async signTypedData(domain, types, value) {
1020
1090
  try {
1021
1091
  // Use DFNS native EIP-712 support with correct structure
1022
- const res = await this.signingService.signTypedData(this.authToken, this.metadata.id, domain, types, value);
1092
+ const rawRes = await this.signingService.signTypedData(this.authToken, this.metadata.id, domain, types, value);
1093
+ const res = adaptSignatureResponse(rawRes);
1023
1094
  assertSigned(res);
1024
- return combineSignature(res);
1095
+ const combinedSignature = combineSignature(res);
1096
+ return combinedSignature;
1025
1097
  }
1026
1098
  catch (error) {
1027
1099
  console.error('[KeyWallet] EIP-712 signing failed:', error);
@@ -1269,8 +1341,8 @@ class TransactionValidator {
1269
1341
  if (!params.authTokens) {
1270
1342
  throw TransactionErrorHandler.createError(TransactionSigningErrorCode.INVALID_TOKENS, 'Authentication tokens are required', params.transactionId);
1271
1343
  }
1272
- if (!params.authTokens.backendAuthToken || !params.authTokens.persAccessToken) {
1273
- throw TransactionErrorHandler.createError(TransactionSigningErrorCode.INVALID_TOKENS, 'Both backend and PERS authentication tokens are required', params.transactionId);
1344
+ if (!params.authTokens.signerAuthToken || !params.authTokens.persAccessToken) {
1345
+ throw TransactionErrorHandler.createError(TransactionSigningErrorCode.INVALID_TOKENS, 'Both signer and PERS authentication tokens are required', params.transactionId);
1274
1346
  }
1275
1347
  if (!params.ethersProviderUrl || typeof params.ethersProviderUrl !== 'string') {
1276
1348
  throw TransactionErrorHandler.createError(TransactionSigningErrorCode.INVALID_TOKENS, 'Ethers provider URL is required', params.transactionId);
@@ -1282,84 +1354,21 @@ class TransactionValidator {
1282
1354
  * @returns true if valid, throws error if invalid
1283
1355
  */
1284
1356
  static validateAuthTokens(authTokens) {
1285
- if (!authTokens.persAccessToken || !authTokens.backendAuthToken) {
1286
- throw TransactionErrorHandler.createError(TransactionSigningErrorCode.INVALID_TOKENS, 'Both PERS access token and backend auth token are required');
1357
+ if (!authTokens.persAccessToken || !authTokens.signerAuthToken) {
1358
+ throw TransactionErrorHandler.createError(TransactionSigningErrorCode.INVALID_TOKENS, 'Both PERS access token and signer auth token are required');
1287
1359
  }
1288
1360
  return true;
1289
1361
  }
1290
1362
  }
1291
1363
 
1292
1364
  /**
1293
- * Handles transaction submission, success flows, and redirect logic
1365
+ * Utility class for coordinating WebAuthn operations to prevent conflicts
1366
+ * Manages global state flags to ensure only one WebAuthn operation runs at a time
1294
1367
  */
1295
- class TransactionSubmissionHandler {
1368
+ class WebAuthnCoordinator {
1296
1369
  /**
1297
- * Create redirect URL with transaction parameters
1298
- * @param returnUrl - Base return URL
1299
- * @param transactionHash - Transaction hash to include
1300
- * @param metadata - Optional metadata to include as parameters
1301
- * @returns Promise resolving to the complete redirect URL
1302
- */
1303
- static async createRedirectUrl(returnUrl, transactionHash, metadata) {
1304
- const additionalParams = {
1305
- txHash: transactionHash,
1306
- success: 'true'
1307
- };
1308
- // Add metadata if available
1309
- if (metadata) {
1310
- Object.entries(metadata).forEach(([key, value]) => {
1311
- // metadata is already constrained to string values by TransactionMetadata interface
1312
- additionalParams[key] = value;
1313
- });
1314
- }
1315
- // Dynamic import to avoid build issues
1316
- const { createUrlWithSearchParams } = await Promise.resolve().then(function () { return searchParams; });
1317
- return createUrlWithSearchParams(returnUrl, ['transactionId', 'returnUrl', 'jwt', 'token'], additionalParams);
1318
- }
1319
- /**
1320
- * Submit transaction and handle success flow
1321
- * @param preparedTransaction - The prepared transaction
1322
- * @param signature - The transaction signature
1323
- * @param signingData - The signing data used
1324
- * @param authTokens - Authentication tokens
1325
- * @param transactionId - Transaction ID for tracking
1326
- * @param returnUrl - Optional return URL for redirect
1327
- * @param metadata - Optional metadata for redirect parameters
1328
- * @returns Promise resolving to submission result
1329
- */
1330
- static async handleTransactionSubmission(preparedTransaction, signature, signingData, authTokens, returnUrl, metadata) {
1331
- // Submit signed transaction
1332
- const submitResult = await PersService.submitTransaction(preparedTransaction.transaction.id, signature, authTokens.persAccessToken, signingData.format);
1333
- // Get transaction hash for return URL or response
1334
- const transactionHash = submitResult.transaction?.transactionHash || '';
1335
- // Handle success - check for return URL
1336
- if (returnUrl) {
1337
- const redirectUrl = await this.createRedirectUrl(returnUrl, transactionHash, metadata);
1338
- return { submitResult, shouldRedirect: true, redirectUrl };
1339
- }
1340
- else {
1341
- return { submitResult, shouldRedirect: false };
1342
- }
1343
- }
1344
- }
1345
-
1346
- /**
1347
- * Utility class for coordinating WebAuthn operations to prevent conflicts
1348
- * Manages global state flags to ensure only one WebAuthn operation runs at a time
1349
- */
1350
- class WebAuthnCoordinator {
1351
- /**
1352
- * Clear the landing authentication flag
1353
- * Used when starting a new transaction signing flow
1354
- */
1355
- static clearLandingAuthentication() {
1356
- if (typeof window !== 'undefined') {
1357
- window.landingAuthenticationInProgress = false;
1358
- }
1359
- }
1360
- /**
1361
- * Check if a WebAuthn operation is currently in progress
1362
- * @returns True if an operation is in progress, false otherwise
1370
+ * Check if a WebAuthn operation is currently in progress
1371
+ * @returns True if an operation is in progress, false otherwise
1363
1372
  */
1364
1373
  static checkConcurrentOperations() {
1365
1374
  if (typeof window !== 'undefined') {
@@ -1389,16 +1398,16 @@ const SIGNABLE_STATUSES = [TransactionStatus.PENDING_SIGNATURE, TransactionStatu
1389
1398
  * Uses constructor-based dependency injection for WebAuthn provider
1390
1399
  */
1391
1400
  class TransactionSigningService {
1392
- constructor(webAuthnProvider) {
1393
- this.webAuthnProvider = webAuthnProvider;
1401
+ constructor(config) {
1402
+ this.webAuthnProvider = config.webAuthnProvider;
1394
1403
  }
1395
1404
  /**
1396
1405
  * Prepare transaction for signing - fetch and validate
1397
1406
  */
1398
- async prepareTransaction(transactionId, authTokens) {
1407
+ async prepareTransaction(transactionId, authTokens, tenantId) {
1399
1408
  let preparedTransaction;
1400
1409
  try {
1401
- preparedTransaction = await PersService.fetchPreparedTransaction(transactionId, authTokens.persAccessToken);
1410
+ preparedTransaction = await PersService.fetchPreparedTransaction(transactionId, authTokens.persAccessToken, tenantId);
1402
1411
  }
1403
1412
  catch (err) {
1404
1413
  // Use TransactionErrorHandler to process PERS API errors
@@ -1419,7 +1428,7 @@ class TransactionSigningService {
1419
1428
  // Authenticate with PERS using backend signer token to set up signing account
1420
1429
  let updatedPersAccessToken = authTokens.persAccessToken;
1421
1430
  try {
1422
- const persSignerAuth = await PersService.authenticateUser(authTokens.backendAuthToken);
1431
+ const persSignerAuth = await PersService.authenticateUser(authTokens.signerAuthToken, tenantId);
1423
1432
  // Update PERS access token with the new one that has signing account linked
1424
1433
  const newPersAccessToken = persSignerAuth.accessToken;
1425
1434
  if (newPersAccessToken) {
@@ -1433,7 +1442,7 @@ class TransactionSigningService {
1433
1442
  }
1434
1443
  // If transaction is 'created' status but doesn't have signingData, fetch again with wallet-enabled token
1435
1444
  if (transactionStatus === TransactionStatus.CREATED && !PersService.isTransactionReadyForSigning(preparedTransaction)) {
1436
- preparedTransaction = await PersService.fetchPreparedTransaction(transactionId, updatedPersAccessToken);
1445
+ preparedTransaction = await PersService.fetchPreparedTransaction(transactionId, updatedPersAccessToken, tenantId);
1437
1446
  }
1438
1447
  if (!PersService.isTransactionReadyForSigning(preparedTransaction)) {
1439
1448
  throw TransactionErrorHandler.createError(TransactionSigningErrorCode.TRANSACTION_NOT_READY, 'Transaction is not ready for signing', transactionId);
@@ -1448,19 +1457,21 @@ class TransactionSigningService {
1448
1457
  */
1449
1458
  async prepareWallet(authTokens, ethersProviderUrl) {
1450
1459
  // Wallet validation will be handled through API responses - no localStorage needed
1451
- let walletData;
1460
+ let walletListResult;
1452
1461
  try {
1453
- walletData = await WalletService.listWallets(authTokens.backendAuthToken);
1454
- console.log('[TransactionSigningService] Wallet list response:', walletData);
1462
+ // Use new WalletService API with signer token
1463
+ walletListResult = await WalletService.listWallets(authTokens.signerAuthToken);
1455
1464
  }
1456
1465
  catch (error) {
1457
1466
  console.error('[TransactionSigningService] Wallet list API failed:', error);
1458
1467
  throw TransactionErrorHandler.createError(TransactionSigningErrorCode.WALLET_NOT_AVAILABLE, 'Failed to retrieve wallet information. Please refresh the page and try again.', undefined, error);
1459
1468
  }
1460
- // Check both possible response structures for compatibility
1461
- const wallets = walletData?.wallets || walletData?.items || [];
1469
+ // Handle multiple response formats for compatibility
1470
+ const wallets = walletListResult?.items ||
1471
+ walletListResult?.data?.wallets ||
1472
+ walletListResult?.wallets || [];
1462
1473
  if (!wallets?.length) {
1463
- console.error('[TransactionSigningService] No wallets found in response:', walletData);
1474
+ console.error('[TransactionSigningService] No wallets found in response:', walletListResult);
1464
1475
  throw TransactionErrorHandler.createError(TransactionSigningErrorCode.WALLET_NOT_AVAILABLE, 'No wallet found for transaction signing. Please refresh the page and complete account setup.');
1465
1476
  }
1466
1477
  // Create SigningService with injected WebAuthn provider
@@ -1468,7 +1479,7 @@ class TransactionSigningService {
1468
1479
  // Create wallet instance with provider - use unknown type for flexibility
1469
1480
  const provider = new JsonRpcProvider(ethersProviderUrl);
1470
1481
  const wallet = new KeyWallet({
1471
- authToken: authTokens.backendAuthToken,
1482
+ authToken: authTokens.signerAuthToken,
1472
1483
  wallet: wallets[0],
1473
1484
  signingService: signingService,
1474
1485
  }).connect(provider);
@@ -1477,7 +1488,9 @@ class TransactionSigningService {
1477
1488
  /**
1478
1489
  * Execute the transaction signing with WebAuthn coordination
1479
1490
  */
1480
- async executeTransactionSigning(wallet, preparedTransaction) {
1491
+ async executeTransactionSigning(wallet,
1492
+ // preparedTransaction: TransactionRequestResponseDTO,
1493
+ signingData) {
1481
1494
  // Check for concurrent WebAuthn operations
1482
1495
  if (WebAuthnCoordinator.checkConcurrentOperations()) {
1483
1496
  throw TransactionErrorHandler.createError(TransactionSigningErrorCode.WEBAUTHN_OPERATION_IN_PROGRESS, 'Another WebAuthn operation is in progress. Please wait and try again.');
@@ -1485,7 +1498,7 @@ class TransactionSigningService {
1485
1498
  // Set WebAuthn operation flag for transaction signing
1486
1499
  WebAuthnCoordinator.setOperationInProgress(true);
1487
1500
  try {
1488
- const signingData = PersService.getTransactionDataForSigning(preparedTransaction);
1501
+ // const signingData = PersService.getTransactionDataForSigning(preparedTransaction);
1489
1502
  const signature = await wallet.signPersTransaction(signingData);
1490
1503
  return signature;
1491
1504
  }
@@ -1504,6 +1517,13 @@ class TransactionSigningService {
1504
1517
  WebAuthnCoordinator.setOperationInProgress(false);
1505
1518
  }
1506
1519
  }
1520
+ async getPersSigningData(data) {
1521
+ // Step 1: Prepare transaction for signing
1522
+ const { preparedTransaction } = await this.prepareTransaction(data.transactionId, data.authTokens, data.tenantId);
1523
+ // const signingData = PersService.getTransactionDataForSigning(preparedTransaction);
1524
+ const signingData = PersService.getTransactionDataForSigning(preparedTransaction);
1525
+ return signingData;
1526
+ }
1507
1527
  /**
1508
1528
  * Main transaction signing orchestration method
1509
1529
  * Handles the complete flow from preparation to submission
@@ -1511,31 +1531,23 @@ class TransactionSigningService {
1511
1531
  * @returns Promise resolving to transaction signing result
1512
1532
  * @throws TransactionSigningError for validation and operation failures
1513
1533
  */
1514
- async signTransaction(params) {
1534
+ async signTransaction(params, signingData) {
1515
1535
  // Validate input parameters first using TransactionValidator
1516
1536
  TransactionValidator.validateSigningParams(params);
1517
- const { transactionId, authTokens, ethersProviderUrl, returnUrl, metadata } = params;
1537
+ const { transactionId, authTokens, ethersProviderUrl } = params;
1518
1538
  console.info(`[TransactionSigningService] Starting signature process for ${transactionId}`);
1519
1539
  try {
1520
- // Clear previous landing authentication state
1521
- WebAuthnCoordinator.clearLandingAuthentication();
1522
- // Step 1: Prepare transaction for signing
1523
- const { preparedTransaction, updatedTokens } = await this.prepareTransaction(transactionId, authTokens);
1524
1540
  // Step 2: Prepare wallet for signing
1525
- const wallet = await this.prepareWallet(updatedTokens, ethersProviderUrl);
1541
+ const wallet = await this.prepareWallet(authTokens, ethersProviderUrl);
1526
1542
  // Step 3: Execute transaction signing
1527
- const signature = await this.executeTransactionSigning(wallet, preparedTransaction);
1528
- const signingData = PersService.getTransactionDataForSigning(preparedTransaction);
1529
- // Step 4: Submit transaction and handle success using TransactionSubmissionHandler
1530
- const { submitResult, shouldRedirect, redirectUrl } = await TransactionSubmissionHandler.handleTransactionSubmission(preparedTransaction, signature, signingData, updatedTokens, returnUrl, metadata);
1531
- console.info(`[TransactionSigningService] Completed successfully: ${transactionId}`);
1543
+ const signature = await this.executeTransactionSigning(wallet, signingData);
1544
+ console.info(`[TransactionSigningService] Completed signing successfully: ${transactionId}`);
1532
1545
  return {
1533
1546
  success: true,
1534
1547
  transactionId,
1535
- transactionHash: submitResult.transaction?.transactionHash ?? undefined,
1548
+ signingData,
1549
+ // transactionHash: submitResult.transaction?.transactionHash ?? undefined,
1536
1550
  signature,
1537
- shouldRedirect,
1538
- redirectUrl
1539
1551
  };
1540
1552
  }
1541
1553
  catch (error) {
@@ -1561,6 +1573,41 @@ class TransactionSigningService {
1561
1573
  }
1562
1574
  }
1563
1575
 
1576
+ /**
1577
+ * Handles transaction submission, success flows, and redirect logic
1578
+ */
1579
+ class TransactionSubmissionHandler {
1580
+ /**
1581
+ * Create redirect URL with transaction parameters
1582
+ * @param returnUrl - Base return URL
1583
+ * @param transactionHash - Transaction hash to include
1584
+ * @param metadata - Optional metadata to include as parameters
1585
+ * @returns Promise resolving to the complete redirect URL
1586
+ */
1587
+ static async createRedirectUrl(returnUrl, transactionHash) {
1588
+ const additionalParams = {
1589
+ txHash: transactionHash,
1590
+ success: 'true'
1591
+ };
1592
+ // Build URL with required params
1593
+ const paramKeys = ['transactionId', 'returnUrl', 'jwt', 'token'];
1594
+ const url = new URL(returnUrl, window.location.origin);
1595
+ // Add required params if present in additionalParams
1596
+ paramKeys.forEach(key => {
1597
+ if (additionalParams[key]) {
1598
+ url.searchParams.set(key, additionalParams[key]);
1599
+ }
1600
+ });
1601
+ // Add all other params
1602
+ Object.entries(additionalParams).forEach(([key, value]) => {
1603
+ if (!paramKeys.includes(key)) {
1604
+ url.searchParams.set(key, value);
1605
+ }
1606
+ });
1607
+ return url.toString();
1608
+ }
1609
+ }
1610
+
1564
1611
  /**
1565
1612
  * Browser-specific WebAuthn provider using DFNS browser SDK
1566
1613
  */
@@ -1681,600 +1728,478 @@ async function getReactNativeWebAuthnProvider() {
1681
1728
  }
1682
1729
 
1683
1730
  /**
1684
- * PERS Blockchain Signer SDK - Simple Orchestrator
1731
+ * JWT Utility Functions
1685
1732
  *
1686
- * Lightweight SDK that orchestrates existing services.
1687
- * Uses environment configuration and existing infrastructure.
1733
+ * Browser-compatible JWT handling without external dependencies
1688
1734
  */
1689
1735
  /**
1690
- * Main PERS Signer SDK class
1691
- *
1692
- * Simple orchestrator that uses existing services and environment configuration.
1693
- * No complex initialization needed - services are already configured.
1736
+ * Extract and validate JWT token payload
1737
+ * Uses a browser-compatible approach without external dependencies
1694
1738
  */
1695
- class PersSignerSDK {
1696
- constructor(config) {
1697
- this.config = config;
1698
- this.webAuthnProvider = config.webAuthnProvider;
1699
- // Set up the configuration provider with stable defaults from constants
1700
- const serviceConfig = {
1701
- apiUrl: config.apiUrl || SIGNER_CONFIG.DEFAULT_SIGNER_API_URL,
1702
- relyingParty: {
1703
- id: typeof window !== 'undefined' ? window.location.hostname : 'localhost',
1704
- name: config.relyingPartyName || SIGNER_CONFIG.DEFAULT_RELYING_PARTY_NAME,
1705
- origin: typeof window !== 'undefined' ? window.location.origin : undefined,
1706
- },
1707
- };
1708
- setConfigProvider(new WebConfigProvider(serviceConfig));
1709
- // Initialize services with the WebAuthn provider
1710
- this.authService = new AuthenticationService(this.webAuthnProvider);
1711
- this.signingService = new SigningService(this.webAuthnProvider);
1712
- this.transactionSigningService = new TransactionSigningService(this.webAuthnProvider);
1739
+ function extractJWTFromToken(jwtToken) {
1740
+ if (!jwtToken) {
1741
+ return { payload: null, isExpired: false };
1713
1742
  }
1714
- /**
1715
- * Re-export TransactionSigningService with WebAuthn provider already injected
1716
- * This provides the same interface as the original service but with automatic provider injection
1717
- */
1718
- get TransactionSigningService() {
1719
- return this.transactionSigningService;
1720
- }
1721
- // ========================================================================
1722
- // HIGH-LEVEL METHODS (Same as Web Project)
1723
- // ========================================================================
1724
- /**
1725
- * Complete user onboarding flow - same as web project
1726
- * Uses existing AuthenticationService and PersService
1727
- */
1728
- async authenticateUser(userInfo) {
1729
- const identifier = userInfo.identifier;
1730
- try {
1731
- // Step 1: Try login first (existing user)
1732
- let signerAuthToken;
1733
- try {
1734
- signerAuthToken = await this.authService.login(identifier);
1735
- }
1736
- catch (loginError) {
1737
- // User doesn't exist - register new user
1738
- await this.authService.register(identifier);
1739
- signerAuthToken = await this.authService.login(identifier);
1740
- }
1741
- // Step 2: Authenticate with PERS backend
1742
- let persAccessToken = '';
1743
- try {
1744
- const persResponse = await PersService.authenticateUser(signerAuthToken);
1745
- persAccessToken = persResponse?.accessToken || '';
1746
- }
1747
- catch (persError) {
1748
- console.warn(`PERS authentication failed:`, persError);
1749
- // Continue without PERS but provide empty token
1750
- persAccessToken = '';
1751
- }
1752
- return {
1753
- identifier,
1754
- signerAuthToken,
1755
- persAccessToken
1756
- };
1757
- }
1758
- catch (error) {
1759
- throw new Error(`User authentication failed: ${error}`);
1760
- }
1743
+ try {
1744
+ // Use a more compatible JWT decoding approach
1745
+ // Split JWT into parts
1746
+ const parts = jwtToken.split('.');
1747
+ if (parts.length !== 3) {
1748
+ throw new Error('Invalid JWT format');
1749
+ }
1750
+ // Decode the payload (middle part)
1751
+ const payload = JSON.parse(atob(parts[1]));
1752
+ // Check expiration
1753
+ const isExpired = payload.exp ? Date.now() >= payload.exp * 1000 : false;
1754
+ return { payload, isExpired };
1755
+ }
1756
+ catch (error) {
1757
+ console.warn('JWT decode failed:', error);
1758
+ return { payload: null, isExpired: false };
1761
1759
  }
1760
+ }
1761
+ /**
1762
+ * Check if a JWT token is expired
1763
+ */
1764
+ function isJWTExpired(jwtToken) {
1765
+ const { isExpired } = extractJWTFromToken(jwtToken);
1766
+ return isExpired;
1767
+ }
1768
+ /**
1769
+ * Get JWT payload without validation
1770
+ */
1771
+ function getJWTPayload(jwtToken) {
1772
+ const { payload } = extractJWTFromToken(jwtToken);
1773
+ return payload;
1774
+ }
1775
+
1776
+ /**
1777
+ * In-memory User Cache
1778
+ *
1779
+ * Provides secure, storage-free user authentication caching
1780
+ * Uses memory-only storage with TTL expiration for security
1781
+ */
1782
+ /**
1783
+ * In-memory user cache with TTL expiration
1784
+ * Security-first approach - no persistent storage
1785
+ */
1786
+ class UserCache {
1762
1787
  /**
1763
- * Complete PERS transaction signing flow - exactly like web project
1764
- * Uses existing TransactionSigningService with injected WebAuthn provider
1788
+ * Get cached user by identifier
1789
+ * @param identifier - User identifier (email/userId)
1790
+ * @returns Cached user or null if not found/expired
1765
1791
  */
1766
- async signPersTransaction(user, transactionId) {
1767
- if (!user.signerAuthToken) {
1768
- throw new Error('Signer authentication token required');
1769
- }
1770
- if (!user.persAccessToken) {
1771
- throw new Error('PERS access token required for transaction signing');
1772
- }
1773
- try {
1774
- // Use the existing high-level service with injected WebAuthn provider
1775
- const result = await this.transactionSigningService.signTransaction({
1776
- transactionId,
1777
- authTokens: {
1778
- backendAuthToken: user.signerAuthToken,
1779
- persAccessToken: user.persAccessToken
1780
- },
1781
- ethersProviderUrl: this.config.ethersProviderUrl || 'https://sepolia.infura.io/v3/2781b4b5242343d5b0954c98f287b29e'
1782
- });
1783
- return {
1784
- success: result.success,
1785
- transactionHash: result.transactionHash,
1786
- error: result.error
1787
- };
1792
+ static get(identifier) {
1793
+ const cached = this.cache.get(identifier);
1794
+ if (!cached) {
1795
+ return null;
1788
1796
  }
1789
- catch (error) {
1790
- return {
1791
- success: false,
1792
- error: `Transaction signing failed: ${error}`
1793
- };
1797
+ // Check if expired
1798
+ if (Date.now() > cached.expiresAt) {
1799
+ this.cache.delete(identifier);
1800
+ return null;
1794
1801
  }
1802
+ return cached.user;
1795
1803
  }
1796
- // ========================================================================
1797
- // GRANULAR METHODS (For Custom Integrations)
1798
- // ========================================================================
1799
1804
  /**
1800
- * Register new user - uses existing AuthenticationService
1805
+ * Cache user with TTL
1806
+ * @param identifier - User identifier
1807
+ * @param user - Authenticated user data
1808
+ * @param ttlMs - Time to live in milliseconds (default: 5 minutes)
1801
1809
  */
1802
- async registerUser(identifier) {
1803
- try {
1804
- await this.authService.register(identifier);
1805
- const authToken = await this.authService.login(identifier);
1806
- return { authToken };
1807
- }
1808
- catch (error) {
1809
- throw new Error(`User registration failed: ${error}`);
1810
- }
1810
+ static set(identifier, user, ttlMs = this.DEFAULT_TTL_MS) {
1811
+ this.cache.set(identifier, {
1812
+ user,
1813
+ expiresAt: Date.now() + ttlMs
1814
+ });
1811
1815
  }
1812
1816
  /**
1813
- * Login existing user - uses existing AuthenticationService
1817
+ * Remove user from cache
1818
+ * @param identifier - User identifier
1814
1819
  */
1815
- async loginUser(identifier) {
1816
- try {
1817
- return await this.authService.login(identifier);
1818
- }
1819
- catch (error) {
1820
- throw new Error(`User login failed: ${error}`);
1821
- }
1820
+ static delete(identifier) {
1821
+ return this.cache.delete(identifier);
1822
1822
  }
1823
1823
  /**
1824
- * Add wallet to PERS user - uses existing PersService
1824
+ * Clear all cached users
1825
1825
  */
1826
- async addWalletToPersUser(signerAuthToken) {
1827
- try {
1828
- const persResponse = await PersService.authenticateUser(signerAuthToken);
1829
- if (!persResponse?.accessToken) {
1830
- throw new Error('Failed to get PERS access token');
1831
- }
1832
- return persResponse.accessToken;
1833
- }
1834
- catch (error) {
1835
- throw new Error(`Failed to add wallet to PERS user: ${error}`);
1836
- }
1826
+ static clear() {
1827
+ this.cache.clear();
1837
1828
  }
1838
1829
  /**
1839
- * Retrieve transaction data from PERS - uses existing PersService
1830
+ * Get cache size (for debugging)
1840
1831
  */
1841
- async retrieveTransactionData(transactionId, persAccessToken) {
1842
- try {
1843
- return await PersService.fetchPreparedTransaction(transactionId, persAccessToken);
1844
- }
1845
- catch (error) {
1846
- // Preserve original error structure for proper error handling
1847
- throw error;
1848
- }
1832
+ static size() {
1833
+ return this.cache.size;
1849
1834
  }
1850
1835
  /**
1851
- * Sign transaction data - uses existing SigningService
1852
- * Follows same patterns as KeyWallet.signPersTransaction
1836
+ * Clean up expired entries
1853
1837
  */
1854
- async signTransactionData(signerAuthToken, signingData) {
1855
- try {
1856
- const wallets = await WalletService.listWallets(signerAuthToken);
1857
- if (!wallets.items || wallets.items.length === 0) {
1858
- throw new Error('No wallet found for user');
1838
+ static cleanup() {
1839
+ const now = Date.now();
1840
+ let cleaned = 0;
1841
+ for (const [identifier, cached] of this.cache.entries()) {
1842
+ if (now > cached.expiresAt) {
1843
+ this.cache.delete(identifier);
1844
+ cleaned++;
1859
1845
  }
1860
- const walletId = wallets.items[0].id;
1861
- // Sign based on transaction format - same pattern as KeyWallet
1862
- switch (signingData.format) {
1863
- case TRANSACTION_FORMATS.EIP_712: {
1864
- const eip712Data = signingData;
1865
- const typedData = eip712Data.typedData;
1866
- // Sign using same pattern as KeyWallet
1867
- const signResponse = await this.signingService.signTypedData(signerAuthToken, walletId, typedData.domain, { Transaction: typedData.types.Transaction }, typedData.message);
1868
- return `${signResponse.signature?.r}${signResponse.signature?.s}`;
1869
- }
1870
- default: {
1871
- // For legacy and other formats, use raw signing data
1872
- const signResponse = await this.signingService.signHash(signerAuthToken, walletId, JSON.stringify(signingData));
1873
- return `${signResponse.signature?.r}${signResponse.signature?.s}`;
1874
- }
1875
- }
1876
- }
1877
- catch (error) {
1878
- throw new Error(`Transaction signing failed: ${error}`);
1879
1846
  }
1847
+ return cleaned;
1880
1848
  }
1849
+ }
1850
+ UserCache.cache = new Map();
1851
+ UserCache.DEFAULT_TTL_MS = 300000; // 5 minutes
1852
+
1853
+ /**
1854
+ * PERS Blockchain Signer SDK
1855
+ *
1856
+ * A lightweight blockchain transaction signing SDK with WebAuthn authentication.
1857
+ * Provides 5 focused methods for complete transaction lifecycle management:
1858
+ *
1859
+ * 1. loginUser(jwtToken) - Authenticate user with 5-minute caching
1860
+ * 2. signTransaction(signingData, jwtToken) - Sign transactions with auto-login
1861
+ * 3. submitTransaction(signedTx, jwtToken) - Submit signed transactions to blockchain
1862
+ * 4. signPersTransaction(jwtToken) - Legacy one-liner for backward compatibility
1863
+ * 5. signAndSubmitPersTransaction(jwtToken) - Complete sign + submit flow
1864
+ *
1865
+ * @example
1866
+ * ```typescript
1867
+ * import { createPersSignerSDK } from '@explorins/pers-signer';
1868
+ *
1869
+ * const sdk = createPersSignerSDK({
1870
+ * webAuthnProvider: myWebAuthnProvider,
1871
+ * ethersProviderUrl: 'https://ethereum-rpc.com'
1872
+ * });
1873
+ *
1874
+ * // Quick sign and submit
1875
+ * const result = await sdk.signAndSubmitPersTransaction(jwtToken);
1876
+ * if (result.success) {
1877
+ * console.log('Transaction submitted:', result.transactionHash);
1878
+ * }
1879
+ * ```
1880
+ */
1881
+ /**
1882
+ * PERS Blockchain Signer SDK Class
1883
+ *
1884
+ * Main SDK class providing blockchain transaction signing capabilities with WebAuthn authentication.
1885
+ * Implements a clean 5-method API for complete transaction lifecycle management.
1886
+ *
1887
+ * Features:
1888
+ * - WebAuthn-based secure authentication
1889
+ * - 5-minute user session caching
1890
+ * - Automatic transaction data fetching
1891
+ * - Blockchain transaction signing and submission
1892
+ * - Multi-tenant support
1893
+ *
1894
+ * @class PersSignerSDK
1895
+ */
1896
+ class PersSignerSDK {
1881
1897
  /**
1882
- * Submit signed transaction to PERS - uses existing PersService
1898
+ * Initialize the PERS Signer SDK
1899
+ *
1900
+ * @param {PersSignerConfig} config - SDK configuration object
1901
+ * @throws {Error} If required configuration is missing
1883
1902
  */
1884
- async submitTransaction(transactionId, signature, persAccessToken, transactionFormat = TRANSACTION_FORMATS.EIP_712) {
1885
- try {
1886
- const submitResult = await PersService.submitTransaction(transactionId, signature, persAccessToken, transactionFormat);
1887
- return {
1888
- transactionHash: submitResult.transaction?.transactionHash || undefined,
1889
- success: true
1890
- };
1891
- }
1892
- catch (error) {
1893
- throw new Error(`Transaction submission failed: ${error}`);
1894
- }
1903
+ constructor(config) {
1904
+ this.config = config;
1905
+ setConfigProvider(new WebConfigProvider({
1906
+ apiUrl: config.apiUrl || SIGNER_CONFIG.DEFAULT_SIGNER_API_URL,
1907
+ relyingParty: {
1908
+ id: typeof window !== 'undefined' ? window.location.hostname : 'localhost',
1909
+ name: config.relyingPartyName || SIGNER_CONFIG.DEFAULT_RELYING_PARTY_NAME,
1910
+ origin: typeof window !== 'undefined' ? window.location.origin : undefined,
1911
+ },
1912
+ }));
1913
+ this.authenticationService = new AuthenticationService(this.config);
1914
+ this.transactionSigningService = new TransactionSigningService(config);
1895
1915
  }
1896
1916
  /**
1897
- * Check if user exists - uses existing AuthenticationService
1917
+ * Authenticate user and cache session for 5 minutes
1918
+ *
1919
+ * Validates JWT token, authenticates with both signer and PERS backends,
1920
+ * and caches the authenticated user session to avoid repeated authentication.
1921
+ *
1922
+ * @param {string} jwtToken - JWT token containing user identifier and tenant info
1923
+ * @returns {Promise<AuthenticatedUser>} Authenticated user with access tokens
1924
+ * @throws {Error} If JWT is invalid, expired, or authentication fails
1925
+ *
1926
+ * @example
1927
+ * ```typescript
1928
+ * try {
1929
+ * const user = await sdk.loginUser(jwtToken);
1930
+ * console.log('Authenticated:', user.identifier);
1931
+ * } catch (error) {
1932
+ * console.error('Authentication failed:', error.message);
1933
+ * }
1934
+ * ```
1898
1935
  */
1899
- async checkUserExists(identifier) {
1900
- try {
1901
- await this.authService.login(identifier);
1902
- return true;
1936
+ async loginUser(jwtToken) {
1937
+ if (!jwtToken || typeof jwtToken !== 'string') {
1938
+ throw new Error('JWT token is required and must be a string');
1903
1939
  }
1904
- catch {
1905
- return false;
1940
+ const { payload, isExpired } = extractJWTFromToken(jwtToken);
1941
+ if (!payload || isExpired) {
1942
+ throw new Error('Invalid or expired JWT token');
1906
1943
  }
1907
- }
1908
- /**
1909
- * Get user wallets - uses existing WalletService
1910
- */
1911
- async getUserWallets(signerAuthToken) {
1912
- try {
1913
- return await WalletService.listWallets(signerAuthToken);
1944
+ const identifier = payload.identifierEmail || payload.email || payload.userId;
1945
+ const tenantId = payload.tenantId || this.config.tenantId || '';
1946
+ if (!identifier) {
1947
+ throw new Error('JWT token missing user identifier (identifierEmail, email, or userId)');
1914
1948
  }
1915
- catch (error) {
1916
- throw new Error(`Failed to get user wallets: ${error}`);
1949
+ // Check cache first
1950
+ const cachedUser = UserCache.get(identifier);
1951
+ if (cachedUser && cachedUser.tenantId === tenantId && Date.now() < cachedUser.expiresAt) {
1952
+ return cachedUser;
1917
1953
  }
1918
- }
1919
- /**
1920
- * Get transaction status - uses existing PersService
1921
- */
1922
- async getTransactionStatus(transactionId, persAccessToken) {
1923
1954
  try {
1924
- const transaction = await PersService.fetchPreparedTransaction(transactionId, persAccessToken);
1925
- return {
1926
- status: transaction.transactionStatus,
1927
- transactionHash: transaction.transaction?.transactionHash || undefined
1955
+ // Authenticate and cache
1956
+ const authResult = await this.authenticationService.combinedAuthentication(identifier, jwtToken);
1957
+ const user = {
1958
+ identifier: authResult.identifier,
1959
+ signerAuthToken: authResult.signerAuthToken,
1960
+ persAccessToken: authResult.persAccessToken,
1961
+ tenantId,
1962
+ expiresAt: Date.now() + 300000 // 5 minutes
1928
1963
  };
1964
+ UserCache.set(identifier, user);
1965
+ return user;
1929
1966
  }
1930
1967
  catch (error) {
1931
- throw new Error(`Failed to get transaction status: ${error}`);
1968
+ throw new Error(`Authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
1932
1969
  }
1933
1970
  }
1934
1971
  /**
1935
- * Initialize tenant - uses existing PersService
1936
- */
1937
- async initializeTenant(tenantId) {
1938
- await PersService.initializeTenant(tenantId);
1939
- }
1940
- // ========================================================================
1941
- // JWT AUTHENTICATION METHODS (For Legacy PERS Flow)
1942
- // ========================================================================
1943
- /**
1944
- * Extract and validate JWT token from URL search parameters
1945
- * Uses a simpler approach compatible with browser environments
1972
+ * Sign a PERS transaction (legacy compatibility method)
1973
+ *
1974
+ * Automatically handles user authentication, transaction data fetching,
1975
+ * and transaction signing in a single call. This is the legacy method
1976
+ * maintained for backward compatibility.
1977
+ *
1978
+ * @param {string} jwtToken - JWT token containing transaction ID and user info
1979
+ * @returns {Promise<TransactionSigningResult>} Signing result with signature and metadata
1980
+ * @throws {Error} If authentication fails, transaction ID missing, or signing fails
1981
+ *
1982
+ * @example
1983
+ * ```typescript
1984
+ * try {
1985
+ * const result = await sdk.signPersTransaction(jwtToken);
1986
+ * if (result.success) {
1987
+ * console.log('Transaction signed:', result.signature);
1988
+ * }
1989
+ * } catch (error) {
1990
+ * console.error('Signing failed:', error.message);
1991
+ * }
1992
+ * ```
1946
1993
  */
1947
- extractJWTFromURL(searchParams) {
1948
- const jwtToken = searchParams.get('jwt') || searchParams.get('token');
1949
- if (!jwtToken) {
1950
- return { payload: null, isExpired: false };
1951
- }
1994
+ async signPersTransaction(jwtToken) {
1995
+ if (!jwtToken || typeof jwtToken !== 'string') {
1996
+ throw new Error('JWT token is required and must be a string');
1997
+ }
1998
+ const user = await this.loginUser(jwtToken);
1999
+ const { payload } = extractJWTFromToken(jwtToken);
2000
+ if (!payload?.transactionId) {
2001
+ throw new Error('JWT token missing transactionId in payload');
2002
+ }
2003
+ const authTokens = {
2004
+ signerAuthToken: user.signerAuthToken,
2005
+ persAccessToken: user.persAccessToken
2006
+ };
1952
2007
  try {
1953
- // Use a more compatible JWT decoding approach
1954
- // Split JWT into parts
1955
- const parts = jwtToken.split('.');
1956
- if (parts.length !== 3) {
1957
- throw new Error('Invalid JWT format');
2008
+ const persSigningData = await this.transactionSigningService.getPersSigningData({
2009
+ transactionId: payload.transactionId,
2010
+ authTokens,
2011
+ tenantId: user.tenantId
2012
+ });
2013
+ const result = await this.signTransaction(persSigningData, jwtToken);
2014
+ if (!result.success) {
2015
+ throw new Error(result.error || 'Transaction signing failed');
1958
2016
  }
1959
- // Decode the payload (middle part)
1960
- const payload = JSON.parse(atob(parts[1]));
1961
- // Check expiration
1962
- const isExpired = payload.exp ? Date.now() >= payload.exp * 1000 : false;
1963
- return { payload, isExpired };
2017
+ return result;
1964
2018
  }
1965
2019
  catch (error) {
1966
- console.warn('JWT decode failed:', error);
1967
- return { payload: null, isExpired: false };
2020
+ throw new Error(`PERS transaction signing failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
1968
2021
  }
1969
2022
  }
1970
2023
  /**
1971
- * Initialize tenant from JWT payload using existing PersService
2024
+ * Sign a transaction with provided signing data
2025
+ *
2026
+ * Low-level method to sign transactions when you already have the signing data.
2027
+ * Automatically handles user authentication and applies the blockchain signature.
2028
+ *
2029
+ * @param {CounterfactualWalletTransactionResponse | LegacyTransaction} signingData - Transaction data to sign
2030
+ * @param {string} jwtToken - JWT token containing transaction ID and user info
2031
+ * @returns {Promise<TransactionSigningResult>} Signing result with signature and metadata
2032
+ * @throws {Error} If authentication fails, transaction ID missing, or signing fails
2033
+ *
2034
+ * @example
2035
+ * ```typescript
2036
+ * const signingData = await getTransactionData(transactionId);
2037
+ * const result = await sdk.signTransaction(signingData, jwtToken);
2038
+ * console.log('Signed transaction:', result.signature);
2039
+ * ```
1972
2040
  */
1973
- async initializeTenantFromJWT(payload) {
1974
- if (!payload.tenantId) {
1975
- return 'default';
2041
+ async signTransaction(signingData, jwtToken) {
2042
+ if (!signingData) {
2043
+ throw new Error('Signing data is required');
1976
2044
  }
2045
+ if (!jwtToken || typeof jwtToken !== 'string') {
2046
+ throw new Error('JWT token is required and must be a string');
2047
+ }
2048
+ const user = await this.loginUser(jwtToken);
2049
+ const { payload } = extractJWTFromToken(jwtToken);
2050
+ if (!payload?.transactionId) {
2051
+ throw new Error('JWT token missing transactionId in payload');
2052
+ }
2053
+ const authTokens = {
2054
+ signerAuthToken: user.signerAuthToken,
2055
+ persAccessToken: user.persAccessToken
2056
+ };
1977
2057
  try {
1978
- // Use existing PersService.initializeTenant method
1979
- const tenantData = await PersService.initializeTenant(payload.tenantId);
1980
- // Extract project key from tenant data
1981
- const projectKey = tenantData.projectKey ||
1982
- tenantData.projectApiKey ||
1983
- tenantData.apiKey ||
1984
- payload.tenantId;
1985
- return projectKey;
2058
+ const result = await this.transactionSigningService.signTransaction({
2059
+ transactionId: payload.transactionId,
2060
+ tenantId: user.tenantId,
2061
+ authTokens,
2062
+ ethersProviderUrl: this.config.ethersProviderUrl || ''
2063
+ }, signingData);
2064
+ return result;
1986
2065
  }
1987
2066
  catch (error) {
1988
- console.warn('Tenant initialization failed:', error);
1989
- return payload.tenantId;
2067
+ throw new Error(`Transaction signing failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
1990
2068
  }
1991
2069
  }
1992
2070
  /**
1993
- * Authenticate user with PERS API using existing PersService pattern
1994
- */
1995
- /**
1996
- * Authenticate user with PERS API using existing PersService
2071
+ * Complete transaction flow: sign and submit in one call
2072
+ *
2073
+ * Convenience method that combines signing and submission into a single operation.
2074
+ * This is the recommended method for most use cases as it handles the complete
2075
+ * transaction lifecycle automatically.
2076
+ *
2077
+ * @param {string} jwtToken - JWT token containing transaction ID and user info
2078
+ * @returns {Promise<SubmissionResult>} Submission result with transaction hash and status
2079
+ * @throws {Error} If authentication, signing, or submission fails
2080
+ *
2081
+ * @example
2082
+ * ```typescript
2083
+ * try {
2084
+ * const result = await sdk.signAndSubmitPersTransaction(jwtToken);
2085
+ * if (result.success) {
2086
+ * console.log('Transaction completed:', result.transactionHash);
2087
+ * if (result.shouldRedirect) {
2088
+ * window.location.href = result.redirectUrl;
2089
+ * }
2090
+ * }
2091
+ * } catch (error) {
2092
+ * console.error('Transaction failed:', error.message);
2093
+ * }
2094
+ * ```
1997
2095
  */
1998
- async authenticatePersUser(jwtToken, projectKey) {
2096
+ async signAndSubmitPersTransaction(jwtToken) {
2097
+ if (!jwtToken || typeof jwtToken !== 'string') {
2098
+ throw new Error('JWT token is required and must be a string');
2099
+ }
1999
2100
  try {
2000
- // Configure PersService with the project key from tenant initialization
2001
- PersService.configure({ projectKey });
2002
- // Use the existing PersService method which handles all the complexity
2003
- const result = await PersService.authenticateUser(jwtToken);
2004
- // Convert to expected PersAuthResult format
2005
- return {
2006
- user: {
2007
- email: result.user?.email || undefined,
2008
- id: result.user?.id || undefined
2009
- },
2010
- accessToken: result.accessToken
2011
- };
2101
+ const signedTx = await this.signPersTransaction(jwtToken);
2102
+ const submittedTx = await this.submitTransaction(signedTx, jwtToken);
2103
+ return submittedTx;
2012
2104
  }
2013
2105
  catch (error) {
2014
- throw new Error(`PERS authentication failed: ${error}`);
2106
+ throw new Error(`Sign and submit transaction failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
2015
2107
  }
2016
2108
  }
2017
2109
  /**
2018
- * Combined PERS + DFNS authentication flow
2110
+ * Submit a signed transaction to the blockchain
2111
+ *
2112
+ * Takes a signed transaction result and submits it to the blockchain network.
2113
+ * Returns detailed submission results including transaction hash and any
2114
+ * redirect information for UI flows.
2115
+ *
2116
+ * @param {TransactionSigningResult} signingResult - Result from a successful transaction signing
2117
+ * @param {string} jwtToken - JWT token containing tenant and user info
2118
+ * @returns {Promise<SubmissionResult>} Submission result with transaction hash and status
2119
+ * @throws {Error} If signing result is invalid, JWT is missing tenantId, or submission fails
2120
+ *
2121
+ * @example
2122
+ * ```typescript
2123
+ * const signedTx = await sdk.signPersTransaction(jwtToken);
2124
+ * const result = await sdk.submitTransaction(signedTx, jwtToken);
2125
+ * console.log('Transaction submitted:', result.transactionHash);
2126
+ * ```
2019
2127
  */
2020
- async combinedAuthentication(identifier, persAccessToken) {
2128
+ async submitTransaction(signingResult, jwtToken) {
2129
+ if (!signingResult) {
2130
+ throw new Error('Signing result is required');
2131
+ }
2132
+ if (!jwtToken || typeof jwtToken !== 'string') {
2133
+ throw new Error('JWT token is required and must be a string');
2134
+ }
2135
+ if (!signingResult.success || !signingResult.signature || !signingResult.signingData) {
2136
+ throw new Error('Invalid signing result: must be successful with signature and signing data');
2137
+ }
2138
+ const { payload } = extractJWTFromToken(jwtToken);
2139
+ if (!payload?.tenantId) {
2140
+ throw new Error('JWT token missing tenantId in payload');
2141
+ }
2142
+ const user = await this.loginUser(jwtToken);
2021
2143
  try {
2022
- // Authenticate with DFNS only (we already have PERS token)
2023
- const signerAuthToken = await this.loginUser(identifier);
2144
+ const submitResult = await PersService.submitTransaction(signingResult.transactionId, signingResult.signature, user.persAccessToken, signingResult.signingData.format, payload.tenantId);
2145
+ // Transform response to SubmissionResult
2024
2146
  return {
2025
- identifier,
2026
- signerAuthToken,
2027
- persAccessToken
2147
+ success: true,
2148
+ transactionHash: submitResult.transaction.transactionHash,
2149
+ shouldRedirect: false, // Can be configured based on business requirements
2150
+ redirectUrl: undefined, // Can be set based on business logic
2151
+ error: undefined,
2152
+ submitResult: submitResult
2028
2153
  };
2029
2154
  }
2030
2155
  catch (error) {
2031
- throw new Error(`Combined authentication failed: ${error}`);
2156
+ throw new Error(`Transaction submission failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
2032
2157
  }
2033
2158
  }
2034
2159
  /**
2035
- * Complete JWT-based authentication flow (legacy PERS flow)
2160
+ * Clear user authentication cache
2161
+ *
2162
+ * Removes all cached user sessions, forcing fresh authentication
2163
+ * on the next method call. Useful for logout scenarios or when
2164
+ * switching between different user contexts.
2165
+ *
2166
+ * @example
2167
+ * ```typescript
2168
+ * // Clear cache on user logout
2169
+ * sdk.clearCache();
2170
+ * console.log('User cache cleared');
2171
+ * ```
2036
2172
  */
2037
- async authenticateWithJWT(searchParams) {
2038
- // 1. Extract JWT from URL
2039
- const { payload, isExpired } = this.extractJWTFromURL(searchParams);
2040
- if (!payload) {
2041
- // Return null instead of throwing error to allow graceful handling
2042
- return null;
2043
- }
2044
- if (isExpired) {
2045
- return {
2046
- user: {
2047
- identifier: payload.email || payload.userId || 'unknown',
2048
- signerAuthToken: '',
2049
- persAccessToken: ''
2050
- },
2051
- isExpired: true
2052
- };
2053
- }
2054
- // 2. Initialize tenant and get project key
2055
- const projectKey = await this.initializeTenantFromJWT(payload);
2056
- // 3. Get JWT token from URL
2057
- const jwtToken = searchParams.get('jwt') || searchParams.get('token');
2058
- if (!jwtToken) {
2059
- throw new Error('JWT token not found in URL parameters');
2060
- }
2061
- // 4. Authenticate with PERS
2062
- const persResult = await this.authenticatePersUser(jwtToken, projectKey);
2063
- // 5. Get identifier for DFNS authentication
2064
- const identifier = persResult.user.email || persResult.user.id;
2065
- if (!identifier) {
2066
- throw new Error('No identifier found in PERS response');
2067
- }
2068
- // 6. Combined authentication
2069
- const user = await this.combinedAuthentication(identifier, persResult.accessToken);
2070
- return {
2071
- user,
2072
- isExpired: false
2073
- };
2173
+ clearCache() {
2174
+ UserCache.clear();
2074
2175
  }
2075
2176
  }
2076
2177
  /**
2077
- * Factory function to create SDK
2078
- * Requires WebAuthnProvider from platform-specific entry point
2079
- */
2080
- function createPersSignerSDK(config) {
2081
- return new PersSignerSDK(config);
2082
- }
2083
-
2084
- /**
2085
- * Utility functions for handling URL search parameters in a consistent way across applications
2086
- */
2087
- /**
2088
- * Creates a URL search string that preserves all current parameters
2089
- * @param paramsToExclude Array of parameter names to exclude from the result
2090
- * @param paramsToAdd Object with additional parameters to add or override
2091
- * @param baseParams Optional URLSearchParams object to use instead of window.location.search
2092
- * @returns A search string with '?' prefix if there are parameters, or empty string if no parameters
2093
- */
2094
- function createSearchString(paramsToExclude = [], paramsToAdd = {}, baseParams) {
2095
- // Get base search params from the provided value or window location
2096
- const currentParams = new URLSearchParams(baseParams instanceof URLSearchParams
2097
- ? baseParams
2098
- : baseParams !== undefined
2099
- ? baseParams
2100
- : (typeof window !== 'undefined' ? window.location.search : ''));
2101
- // Remove excluded parameters
2102
- paramsToExclude.forEach(param => {
2103
- if (currentParams.has(param)) {
2104
- currentParams.delete(param);
2105
- }
2106
- });
2107
- // Add or update new parameters
2108
- Object.entries(paramsToAdd).forEach(([key, value]) => {
2109
- if (value !== undefined && value !== null) {
2110
- currentParams.set(key, value);
2111
- }
2112
- });
2113
- const searchString = currentParams.toString();
2114
- return searchString ? `?${searchString}` : '';
2115
- }
2116
- /**
2117
- * Creates a URL path with search parameters
2118
- * @param basePath The base path without search parameters
2119
- * @param paramsToExclude Array of parameter names to exclude from the result
2120
- * @param paramsToAdd Object with additional parameters to add or override
2121
- * @param baseParams Optional URLSearchParams object to use instead of window.location.search
2122
- * @returns A full path with search parameters
2123
- */
2124
- function createUrlWithSearchParams(basePath, paramsToExclude = [], paramsToAdd = {}, baseParams) {
2125
- const searchString = createSearchString(paramsToExclude, paramsToAdd, baseParams);
2126
- return `${basePath}${searchString}`;
2127
- }
2128
- /**
2129
- * Combines the current search parameters with new ones
2130
- * @param searchParams The current URLSearchParams object
2131
- * @param additionalParams Object with parameters to add or update
2132
- * @returns A new URLSearchParams object
2133
- */
2134
- function mergeSearchParams(searchParams, additionalParams = {}) {
2135
- const merged = new URLSearchParams(searchParams.toString());
2136
- Object.entries(additionalParams).forEach(([key, value]) => {
2137
- if (value !== undefined && value !== null) {
2138
- merged.set(key, value);
2139
- }
2140
- });
2141
- return merged;
2142
- }
2143
- /**
2144
- * Gets a specific search parameter value
2145
- * @param key The parameter name to get
2146
- * @param search Optional search string to parse (defaults to window.location.search)
2147
- * @returns The parameter value or null if not found
2148
- */
2149
- function getSearchParam(key, search) {
2150
- const params = new URLSearchParams(search || (typeof window !== 'undefined' ? window.location.search : ''));
2151
- return params.get(key);
2152
- }
2153
- /**
2154
- * Gets all search parameters as a URLSearchParams object
2155
- * @param search Optional search string to parse (defaults to window.location.search)
2156
- * @returns A URLSearchParams object
2157
- */
2158
- function getAllSearchParams(search) {
2159
- return new URLSearchParams(search || (typeof window !== 'undefined' ? window.location.search : ''));
2160
- }
2161
- /**
2162
- * Removes a specific search parameter
2163
- * @param key The parameter name to remove
2164
- * @param search Optional search string to parse (defaults to window.location.search)
2165
- * @returns A new search string without the specified parameter
2166
- */
2167
- function removeSearchParam(key, search) {
2168
- const params = new URLSearchParams(search || (typeof window !== 'undefined' ? window.location.search : ''));
2169
- params.delete(key);
2170
- const searchString = params.toString();
2171
- return searchString ? `?${searchString}` : '';
2172
- }
2173
- /**
2174
- * Updates or adds a specific search parameter
2175
- * @param key The parameter name to update
2176
- * @param value The new value for the parameter
2177
- * @param search Optional search string to parse (defaults to window.location.search)
2178
- * @returns A new search string with the updated parameter
2179
- */
2180
- function updateSearchParam(key, value, search) {
2181
- const params = new URLSearchParams(search || (typeof window !== 'undefined' ? window.location.search : ''));
2182
- params.set(key, value);
2183
- const searchString = params.toString();
2184
- return searchString ? `?${searchString}` : '';
2185
- }
2186
-
2187
- var searchParams = /*#__PURE__*/Object.freeze({
2188
- __proto__: null,
2189
- createSearchString: createSearchString,
2190
- createUrlWithSearchParams: createUrlWithSearchParams,
2191
- getAllSearchParams: getAllSearchParams,
2192
- getSearchParam: getSearchParam,
2193
- mergeSearchParams: mergeSearchParams,
2194
- removeSearchParam: removeSearchParam,
2195
- updateSearchParam: updateSearchParam
2196
- });
2197
-
2198
- /**
2199
- * Utility for debugging search parameter handling
2200
- */
2201
- /**
2202
- * Logs the current search parameters with a custom message
2203
- * This is useful for debugging search parameter preservation across navigation
2178
+ * Create a new PERS Signer SDK instance
2204
2179
  *
2205
- * @param message - A descriptive message to identify where the logging happens
2206
- * @param additionalData - Optional additional data to log
2207
- */
2208
- function logSearchParams(message, additionalData) {
2209
- if (process.env.NODE_ENV !== 'production') {
2210
- // Check if window exists (for SSR compatibility)
2211
- if (typeof window === 'undefined') {
2212
- return;
2213
- }
2214
- const searchParams = new URLSearchParams(window.location.search);
2215
- searchParams.forEach((value, key) => {
2216
- });
2217
- console.group(`🔍 Search Params: ${message}`);
2218
- console.groupEnd();
2219
- }
2220
- }
2221
- /**
2222
- * Creates a search parameter tracking hook that can be used in React components
2223
- * Logs search parameter changes when the component mounts/updates
2180
+ * Factory function to create and configure a new SDK instance with the provided
2181
+ * configuration. This is the recommended way to initialize the SDK.
2224
2182
  *
2225
- * @param componentName - The name of the component (for logging)
2226
- */
2227
- function createSearchParamLogger(componentName) {
2228
- return () => {
2229
- logSearchParams(`${componentName} rendered`);
2230
- };
2231
- }
2232
-
2233
- /**
2234
- * Generates a RFC4122 version 4 compliant UUID
2235
- * @returns A randomly generated UUID
2236
- */
2237
- function generateUUID() {
2238
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
2239
- const r = Math.random() * 16 | 0;
2240
- const v = c === 'x' ? r : (r & 0x3 | 0x8);
2241
- return v.toString(16);
2242
- });
2243
- }
2244
- /**
2245
- * Create a semi-anonymous username for users
2246
- * @returns A username in the format "user_[random string]"
2247
- */
2248
- function generateAnonymousUsername() {
2249
- const randomPart = Math.random().toString(36).substring(2, 10);
2250
- return `user_${randomPart}`;
2251
- }
2252
- /**
2253
- * Create a guest username for the application with a random prefix
2254
- * @returns A guest email in the format "randomstring_guest@explorins.com"
2183
+ * @param {PersSignerConfig} config - SDK configuration object
2184
+ * @returns {PersSignerSDK} Configured SDK instance ready for use
2185
+ * @throws {Error} If required configuration is missing or invalid
2186
+ *
2187
+ * @example
2188
+ * ```typescript
2189
+ * import { createPersSignerSDK, getWebAuthnProvider } from '@explorins/pers-signer';
2190
+ *
2191
+ * const webAuthnProvider = await getWebAuthnProvider();
2192
+ * const sdk = createPersSignerSDK({
2193
+ * webAuthnProvider,
2194
+ * ethersProviderUrl: 'https://mainnet.infura.io/v3/YOUR_KEY',
2195
+ * tenantId: 'your-tenant-id'
2196
+ * });
2197
+ * ```
2255
2198
  */
2256
- function generateGuestUsername() {
2257
- const randomPart = Math.random().toString(36).substring(2, 8); // 6 character random string
2258
- return `${randomPart}_guest@explorins.com`;
2199
+ function createPersSignerSDK(config) {
2200
+ return new PersSignerSDK(config);
2259
2201
  }
2260
2202
 
2261
- /**
2262
- * Utility functions for tenant-specific operations
2263
- */
2264
- /**
2265
- * Initialize tenant-specific analytics
2266
- * @param tenantId The tenant ID
2267
- * @param getTenantConfig Function to retrieve tenant configuration
2268
- */
2269
- const initTenantAnalytics = (tenantId, getTenantConfig) => {
2270
- const tenant = getTenantConfig(tenantId);
2271
- if (!tenant) {
2272
- console.warn(`Tenant ${tenantId} not found`);
2273
- return;
2274
- }
2275
- if (tenant.analytics && tenant.analytics.enabled && tenant.analytics.trackingId) ;
2276
- };
2277
-
2278
2203
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
2279
2204
 
2280
2205
  function getAugmentedNamespace(n) {
@@ -11949,5 +11874,5 @@ var index = /*#__PURE__*/_mergeNamespaces({
11949
11874
  __proto__: null
11950
11875
  }, [sdkReactNative]);
11951
11876
 
11952
- export { AuthenticationService, FetchHttpClient, KeyWallet, PersService, PersSignerSDK, ReactNativeConfigProvider, SIGNABLE_STATUSES, SigningService, StaticConfigProvider, TransactionErrorHandler, TransactionSigningErrorCode, TransactionSigningService, TransactionSubmissionHandler, TransactionValidator, WalletService, WebAuthnCoordinator, WebConfigProvider, createPersSignerSDK, createSearchParamLogger, createSearchString, createUrlWithSearchParams, generateAnonymousUsername, generateGuestUsername, generateUUID, getAllSearchParams, getBrowserWebAuthnProvider, getConfigProvider, getHttpClient, getReactNativeWebAuthnProvider, getSearchParam, getServiceConfig, initTenantAnalytics, logSearchParams, mergeSearchParams, removeSearchParam, setConfigProvider, setHttpClient, updateSearchParam };
11877
+ export { AuthenticationService, FetchHttpClient, HealthService, KeyWallet, PersService, PersSignerSDK, ReactNativeConfigProvider, SIGNABLE_STATUSES, SigningService, StaticConfigProvider, TransactionErrorHandler, TransactionSigningErrorCode, TransactionSigningService, TransactionSubmissionHandler, TransactionValidator, UserCache, WalletService, WebAuthnCoordinator, WebConfigProvider, createPersSignerSDK, extractJWTFromToken, getBrowserWebAuthnProvider, getConfigProvider, getHttpClient, getJWTPayload, getReactNativeWebAuthnProvider, getServiceConfig, isJWTExpired, setConfigProvider, setHttpClient };
11953
11878
  //# sourceMappingURL=index.esm.js.map