@explorins/pers-signer 1.0.12 → 1.0.17
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/browser.cjs.js +819 -990
- package/dist/browser.cjs.js.map +1 -1
- package/dist/browser.d.ts +365 -499
- package/dist/browser.esm.js +820 -976
- package/dist/browser.esm.js.map +1 -1
- package/dist/index.cjs.js +890 -982
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +627 -605
- package/dist/index.esm.js +886 -970
- package/dist/index.esm.js.map +1 -1
- package/dist/react-native.cjs.js +820 -991
- package/dist/react-native.cjs.js.map +1 -1
- package/dist/react-native.d.ts +365 -499
- package/dist/react-native.esm.js +821 -977
- package/dist/react-native.esm.js.map +1 -1
- package/package.json +1 -1
package/dist/browser.cjs.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
|
-
|
|
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,
|
|
@@ -159,74 +169,31 @@ function getServiceConfig() {
|
|
|
159
169
|
|
|
160
170
|
/**
|
|
161
171
|
* Wallet management service
|
|
162
|
-
*
|
|
172
|
+
* Updated for v1 API endpoints per migration reference
|
|
163
173
|
*/
|
|
164
174
|
class WalletService {
|
|
165
175
|
/**
|
|
166
176
|
* List all wallets for authenticated user
|
|
167
|
-
* @param
|
|
168
|
-
* @returns Promise resolving to wallet
|
|
177
|
+
* @param signerToken - Signer JWT token
|
|
178
|
+
* @returns Promise resolving to wallet data
|
|
169
179
|
*/
|
|
170
|
-
static async listWallets(
|
|
180
|
+
static async listWallets(signerToken) {
|
|
171
181
|
const httpClient = getHttpClient();
|
|
172
182
|
const config = getServiceConfig();
|
|
173
183
|
try {
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
184
|
+
const requestBody = {};
|
|
185
|
+
const response = await httpClient.post(`${config.apiUrl}/wallets/list`, requestBody, { Authorization: `Bearer ${signerToken}` });
|
|
186
|
+
const backendResponse = response.data;
|
|
187
|
+
if (backendResponse.success && backendResponse.data) {
|
|
188
|
+
return backendResponse.data;
|
|
189
|
+
}
|
|
190
|
+
throw new Error('Failed to list wallets: Invalid response');
|
|
180
191
|
}
|
|
181
192
|
catch (error) {
|
|
182
193
|
console.error('[WalletService] Error listing wallets:', error);
|
|
183
194
|
throw error;
|
|
184
195
|
}
|
|
185
196
|
}
|
|
186
|
-
/**
|
|
187
|
-
* Get wallet by ID (when backend supports it)
|
|
188
|
-
* @param authToken - Authentication token
|
|
189
|
-
* @param walletId - Wallet identifier
|
|
190
|
-
* @returns Promise resolving to wallet details
|
|
191
|
-
*/
|
|
192
|
-
static async getWallet(authToken, walletId) {
|
|
193
|
-
const httpClient = getHttpClient();
|
|
194
|
-
const config = getServiceConfig();
|
|
195
|
-
try {
|
|
196
|
-
const response = await httpClient.post(`${config.apiUrl}/wallets/get`, {
|
|
197
|
-
authToken,
|
|
198
|
-
walletId,
|
|
199
|
-
appId: config.appId,
|
|
200
|
-
});
|
|
201
|
-
return response.data;
|
|
202
|
-
}
|
|
203
|
-
catch (error) {
|
|
204
|
-
console.error(`[WalletService] Error getting wallet ${walletId}:`, error);
|
|
205
|
-
throw error;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Create new wallet (when backend supports it)
|
|
210
|
-
* @param authToken - Authentication token
|
|
211
|
-
* @param walletConfig - Wallet configuration
|
|
212
|
-
* @returns Promise resolving to wallet creation result
|
|
213
|
-
*/
|
|
214
|
-
static async createWallet(authToken, walletConfig) {
|
|
215
|
-
const httpClient = getHttpClient();
|
|
216
|
-
const config = getServiceConfig();
|
|
217
|
-
try {
|
|
218
|
-
const response = await httpClient.post(`${config.apiUrl}/wallets/create`, {
|
|
219
|
-
authToken,
|
|
220
|
-
...walletConfig,
|
|
221
|
-
appId: config.appId,
|
|
222
|
-
});
|
|
223
|
-
return response.data;
|
|
224
|
-
}
|
|
225
|
-
catch (error) {
|
|
226
|
-
console.error('[WalletService] Error creating wallet:', error);
|
|
227
|
-
throw error;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
197
|
}
|
|
231
198
|
|
|
232
199
|
/**
|
|
@@ -305,10 +272,10 @@ class PersService {
|
|
|
305
272
|
* @returns Promise with tenant public information
|
|
306
273
|
*/
|
|
307
274
|
static async getTenantById(tenantId, authToken) {
|
|
308
|
-
// Check memory cache first
|
|
275
|
+
// Check memory cache first with expiration
|
|
309
276
|
const cached = this.tenantCache.get(tenantId);
|
|
310
|
-
if (cached) {
|
|
311
|
-
return cached;
|
|
277
|
+
if (cached && Date.now() < cached.expiresAt) {
|
|
278
|
+
return cached.tenant;
|
|
312
279
|
}
|
|
313
280
|
try {
|
|
314
281
|
const headers = {
|
|
@@ -346,10 +313,16 @@ class PersService {
|
|
|
346
313
|
}
|
|
347
314
|
}
|
|
348
315
|
const tenantData = await response.json();
|
|
349
|
-
// Cache the tenant data
|
|
350
|
-
|
|
316
|
+
// Cache the tenant data with expiration
|
|
317
|
+
const now = Date.now();
|
|
318
|
+
this.tenantCache.set(tenantId, {
|
|
319
|
+
tenant: tenantData,
|
|
320
|
+
cachedAt: now,
|
|
321
|
+
expiresAt: now + this.TENANT_CACHE_TTL
|
|
322
|
+
});
|
|
351
323
|
// Update current project key (check multiple possible property names)
|
|
352
324
|
this.currentProjectKey = tenantData.projectKey || tenantData.projectApiKey || tenantData.apiKey;
|
|
325
|
+
this.currentTenantId = tenantId;
|
|
353
326
|
return tenantData;
|
|
354
327
|
}
|
|
355
328
|
catch (error) {
|
|
@@ -369,14 +342,35 @@ class PersService {
|
|
|
369
342
|
const projectKey = tenantData.projectApiKey;
|
|
370
343
|
if (projectKey) {
|
|
371
344
|
this.currentProjectKey = projectKey;
|
|
345
|
+
this.currentTenantId = tenantId;
|
|
372
346
|
}
|
|
373
347
|
return tenantData;
|
|
374
348
|
}
|
|
349
|
+
/**
|
|
350
|
+
* Ensure tenant is initialized and current, with automatic revalidation
|
|
351
|
+
* @param tenantId - The tenant ID to ensure is initialized
|
|
352
|
+
* @param authToken - Optional auth token for authentication
|
|
353
|
+
* @returns Promise with tenant information
|
|
354
|
+
*/
|
|
355
|
+
static async ensureTenantInitialized(tenantId, authToken) {
|
|
356
|
+
// Check if we already have the right tenant initialized and it's still valid
|
|
357
|
+
const cached = this.tenantCache.get(tenantId);
|
|
358
|
+
if (this.currentTenantId === tenantId && cached && Date.now() < cached.expiresAt) {
|
|
359
|
+
return cached.tenant;
|
|
360
|
+
}
|
|
361
|
+
// Initialize or refresh tenant data
|
|
362
|
+
return await this.initializeTenant(tenantId, authToken);
|
|
363
|
+
}
|
|
375
364
|
/**
|
|
376
365
|
* Get the current project key (either from tenant or fallback)
|
|
366
|
+
* @param tenantId - Optional tenant ID to ensure is initialized
|
|
377
367
|
* @returns The project key to use for API calls
|
|
378
368
|
*/
|
|
379
|
-
static getProjectKey() {
|
|
369
|
+
static async getProjectKey(tenantId) {
|
|
370
|
+
// If tenantId provided and different from current, initialize it
|
|
371
|
+
if (tenantId && this.currentTenantId !== tenantId) {
|
|
372
|
+
await this.ensureTenantInitialized(tenantId);
|
|
373
|
+
}
|
|
380
374
|
if (this.currentProjectKey) {
|
|
381
375
|
return this.currentProjectKey;
|
|
382
376
|
}
|
|
@@ -392,17 +386,19 @@ class PersService {
|
|
|
392
386
|
static clearTenantCache() {
|
|
393
387
|
this.tenantCache.clear();
|
|
394
388
|
this.currentProjectKey = null;
|
|
389
|
+
this.currentTenantId = null;
|
|
395
390
|
}
|
|
396
391
|
/**
|
|
397
392
|
* Authenticates a user with the PERS backend using their auth token
|
|
398
393
|
*
|
|
399
394
|
* @param authToken - The authentication token received from DFNS after login/registration
|
|
395
|
+
* @param tenantId - Optional tenant ID for automatic initialization
|
|
400
396
|
* @returns A promise that resolves to the authentication response
|
|
401
397
|
* @throws If the request fails
|
|
402
398
|
*/
|
|
403
|
-
static async authenticateUser(authToken) {
|
|
399
|
+
static async authenticateUser(authToken, tenantId) {
|
|
404
400
|
try {
|
|
405
|
-
const projectKey = this.getProjectKey();
|
|
401
|
+
const projectKey = await this.getProjectKey(tenantId);
|
|
406
402
|
const headers = {
|
|
407
403
|
'accept': 'application/json',
|
|
408
404
|
'x-project-key': projectKey,
|
|
@@ -452,30 +448,6 @@ class PersService {
|
|
|
452
448
|
};
|
|
453
449
|
}
|
|
454
450
|
}
|
|
455
|
-
/**
|
|
456
|
-
* Prepares a transaction by calling the backend endpoint
|
|
457
|
-
*
|
|
458
|
-
* @param form - The transaction details
|
|
459
|
-
* @param persAccessToken - The PERS access token for authentication (Bearer)
|
|
460
|
-
* @returns A promise that resolves to the transaction preparation response
|
|
461
|
-
* @throws If the request fails
|
|
462
|
-
*/
|
|
463
|
-
static async prepareTransaction(form, persAccessToken) {
|
|
464
|
-
// ✅ UPDATED: /transaction/auth/prepare-signing → /transactions/prepare
|
|
465
|
-
const res = await fetch(`${getPersApiUrl(this.useStaging)}/transactions`, {
|
|
466
|
-
method: "POST",
|
|
467
|
-
headers: {
|
|
468
|
-
"Content-Type": "application/json",
|
|
469
|
-
'x-project-key': this.getProjectKey(),
|
|
470
|
-
Authorization: `Bearer ${persAccessToken}`,
|
|
471
|
-
},
|
|
472
|
-
body: JSON.stringify(form),
|
|
473
|
-
});
|
|
474
|
-
if (!res.ok)
|
|
475
|
-
throw new Error("Failed to prepare transaction");
|
|
476
|
-
const response = await res.json();
|
|
477
|
-
return response;
|
|
478
|
-
}
|
|
479
451
|
/**
|
|
480
452
|
* Submits a transaction by calling the backend endpoint
|
|
481
453
|
*
|
|
@@ -483,208 +455,86 @@ class PersService {
|
|
|
483
455
|
* @param signedTransactionOrSignature - The signed transaction data or EIP-712 signature
|
|
484
456
|
* @param persAccessToken - The PERS access token for authentication (Bearer)
|
|
485
457
|
* @param submissionType - The transaction format type
|
|
458
|
+
* @param tenantId - Optional tenant ID for automatic initialization
|
|
486
459
|
* @returns A promise that resolves to the transaction submission response
|
|
487
460
|
* @throws If the request fails
|
|
488
461
|
*/
|
|
489
|
-
static async submitTransaction(transactionId, signedTransactionOrSignature, persAccessToken, submissionType) {
|
|
490
|
-
// Map TransactionFormat to the backend's expected submission type
|
|
491
|
-
const dto = {
|
|
492
|
-
transactionId,
|
|
493
|
-
type: submissionType,
|
|
494
|
-
...(submissionType === browser.TRANSACTION_FORMATS.EIP_712
|
|
495
|
-
? { signature: signedTransactionOrSignature }
|
|
496
|
-
: { signedTransaction: signedTransactionOrSignature })
|
|
497
|
-
};
|
|
498
|
-
// ✅ UPDATED: /transaction/auth/submit/${transactionId} → /transactions/${transactionId}/submit
|
|
499
|
-
const res = await fetch(`${getPersApiUrl(this.useStaging)}/transactions/submit`, {
|
|
500
|
-
method: "POST",
|
|
501
|
-
headers: {
|
|
502
|
-
"Content-Type": "application/json",
|
|
503
|
-
'x-project-key': this.getProjectKey(),
|
|
504
|
-
Authorization: `Bearer ${persAccessToken}`,
|
|
505
|
-
},
|
|
506
|
-
body: JSON.stringify(dto),
|
|
507
|
-
});
|
|
508
|
-
if (!res.ok)
|
|
509
|
-
throw new Error("Failed to submit transaction");
|
|
510
|
-
return await res.json();
|
|
511
|
-
}
|
|
512
|
-
/**
|
|
513
|
-
* Fetches a prepared transaction for signing by transactionId
|
|
514
|
-
* @param transactionId - The transaction ID to fetch
|
|
515
|
-
* @param persAccessToken - The PERS access token for authentication (Bearer)
|
|
516
|
-
* @returns The prepared transaction data
|
|
517
|
-
* @throws If the request fails
|
|
518
|
-
*/
|
|
519
|
-
static async fetchPreparedTransaction(transactionId, persAccessToken) {
|
|
520
|
-
// ✅ UPDATED: /transaction/auth/prepare-signing/${transactionId} → /transactions/${transactionId}/prepare
|
|
521
|
-
const res = await fetch(`${getPersApiUrl(this.useStaging)}/transactions/${transactionId}/prepare`, {
|
|
522
|
-
headers: {
|
|
523
|
-
"Content-Type": "application/json",
|
|
524
|
-
"x-project-key": this.getProjectKey(),
|
|
525
|
-
Authorization: `Bearer ${persAccessToken}`,
|
|
526
|
-
},
|
|
527
|
-
});
|
|
528
|
-
const response = await res.json();
|
|
529
|
-
if (!res.ok) {
|
|
530
|
-
// Throw structured error with the backend response
|
|
531
|
-
throw {
|
|
532
|
-
status: res.status,
|
|
533
|
-
message: response.message || "Failed to fetch prepared transaction",
|
|
534
|
-
error: response
|
|
535
|
-
};
|
|
536
|
-
}
|
|
537
|
-
return response;
|
|
538
|
-
}
|
|
539
|
-
/**
|
|
540
|
-
* Claims a reward from the PERS blockchain system
|
|
541
|
-
* @param rewardId - The ID of the reward to claim
|
|
542
|
-
* @param pointsCost - The points cost for the reward
|
|
543
|
-
* @param persAccessToken - The PERS access token for authentication (Bearer)
|
|
544
|
-
* @returns The reward claim response including reward image URL
|
|
545
|
-
* @throws If the request fails
|
|
546
|
-
*/
|
|
547
|
-
static async claimReward(rewardId, pointsCost, persAccessToken) {
|
|
548
|
-
const res = await fetch(`${getPersApiUrl(this.useStaging)}/rewards/auth/claim`, {
|
|
549
|
-
method: "POST",
|
|
550
|
-
headers: {
|
|
551
|
-
"Content-Type": "application/json",
|
|
552
|
-
'x-project-key': this.getProjectKey(),
|
|
553
|
-
Authorization: `Bearer ${persAccessToken}`,
|
|
554
|
-
},
|
|
555
|
-
body: JSON.stringify({
|
|
556
|
-
rewardId,
|
|
557
|
-
pointsCost
|
|
558
|
-
}),
|
|
559
|
-
});
|
|
560
|
-
if (!res.ok) {
|
|
561
|
-
const error = await res.text();
|
|
562
|
-
throw new Error(`Failed to claim reward: ${error}`);
|
|
563
|
-
}
|
|
564
|
-
return await res.json();
|
|
565
|
-
}
|
|
566
|
-
/**
|
|
567
|
-
* Get all active campaigns
|
|
568
|
-
* @returns Promise with list of active campaigns
|
|
569
|
-
*/
|
|
570
|
-
static async getActiveCampaigns() {
|
|
571
|
-
try {
|
|
572
|
-
const response = await fetch(`${getPersApiUrl(this.useStaging)}/campaign`, {
|
|
573
|
-
method: 'GET',
|
|
574
|
-
headers: {
|
|
575
|
-
'accept': 'application/json',
|
|
576
|
-
'x-project-key': this.getProjectKey()
|
|
577
|
-
}
|
|
578
|
-
});
|
|
579
|
-
if (!response.ok) {
|
|
580
|
-
const errorData = await response.json().catch(() => ({}));
|
|
581
|
-
throw {
|
|
582
|
-
status: response.status,
|
|
583
|
-
message: errorData.message || 'Failed to fetch active campaigns',
|
|
584
|
-
error: errorData
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
|
-
const data = await response.json();
|
|
588
|
-
return data;
|
|
589
|
-
}
|
|
590
|
-
catch (error) {
|
|
591
|
-
throw error;
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
/**
|
|
595
|
-
* Claims a campaign for a user
|
|
596
|
-
* @param campaignId - The ID of the campaign to claim
|
|
597
|
-
* @param persAccessToken - The PERS access token for authentication (Bearer)
|
|
598
|
-
* @returns Promise with the campaign claim response
|
|
599
|
-
* @throws If the request fails
|
|
600
|
-
*/
|
|
601
|
-
static async claimCampaign(campaignId, persAccessToken) {
|
|
462
|
+
static async submitTransaction(transactionId, signedTransactionOrSignature, persAccessToken, submissionType, tenantId) {
|
|
602
463
|
try {
|
|
603
|
-
|
|
604
|
-
|
|
464
|
+
// Map TransactionFormat to the backend's expected submission type
|
|
465
|
+
const dto = {
|
|
466
|
+
transactionId,
|
|
467
|
+
type: submissionType,
|
|
468
|
+
...(submissionType === browser.TRANSACTION_FORMATS.EIP_712
|
|
469
|
+
? { signature: signedTransactionOrSignature }
|
|
470
|
+
: { signedTransaction: signedTransactionOrSignature })
|
|
471
|
+
};
|
|
472
|
+
// ✅ UPDATED: /transaction/auth/submit/${transactionId} → /transactions/${transactionId}/submit
|
|
473
|
+
const res = await fetch(`${getPersApiUrl(this.useStaging)}/transactions/submit`, {
|
|
474
|
+
method: "POST",
|
|
605
475
|
headers: {
|
|
606
|
-
|
|
607
|
-
'x-project-key': this.getProjectKey(),
|
|
608
|
-
|
|
609
|
-
'Content-Type': 'application/json'
|
|
476
|
+
"Content-Type": "application/json",
|
|
477
|
+
'x-project-key': await this.getProjectKey(tenantId),
|
|
478
|
+
Authorization: `Bearer ${persAccessToken}`,
|
|
610
479
|
},
|
|
611
|
-
body: JSON.stringify(
|
|
612
|
-
campaignId
|
|
613
|
-
})
|
|
480
|
+
body: JSON.stringify(dto),
|
|
614
481
|
});
|
|
615
|
-
|
|
616
|
-
|
|
482
|
+
const response = await res.json();
|
|
483
|
+
if (!res.ok) {
|
|
484
|
+
// Throw structured error with the backend response
|
|
617
485
|
throw {
|
|
618
|
-
status:
|
|
619
|
-
message:
|
|
620
|
-
error:
|
|
486
|
+
status: res.status,
|
|
487
|
+
message: response.message || "Failed to submit transaction",
|
|
488
|
+
error: response
|
|
621
489
|
};
|
|
622
490
|
}
|
|
623
|
-
|
|
624
|
-
return data;
|
|
491
|
+
return response;
|
|
625
492
|
}
|
|
626
493
|
catch (error) {
|
|
627
|
-
|
|
494
|
+
// Rethrow a structured error
|
|
495
|
+
throw {
|
|
496
|
+
status: error.status || 500,
|
|
497
|
+
message: error.message || 'Failed to submit transaction to PERS backend',
|
|
498
|
+
error
|
|
499
|
+
};
|
|
628
500
|
}
|
|
629
501
|
}
|
|
630
502
|
/**
|
|
631
|
-
*
|
|
503
|
+
* Fetches a prepared transaction for signing by transactionId
|
|
504
|
+
* @param transactionId - The transaction ID to fetch
|
|
632
505
|
* @param persAccessToken - The PERS access token for authentication (Bearer)
|
|
633
|
-
* @
|
|
634
|
-
* @
|
|
635
|
-
*/
|
|
636
|
-
static async getUserCampaignClaims(persAccessToken) {
|
|
637
|
-
try {
|
|
638
|
-
const response = await fetch(`${getPersApiUrl(this.useStaging)}/campaign/auth/claim`, {
|
|
639
|
-
method: 'GET',
|
|
640
|
-
headers: {
|
|
641
|
-
'accept': 'application/json',
|
|
642
|
-
'x-project-key': this.getProjectKey(),
|
|
643
|
-
'Authorization': `Bearer ${persAccessToken}`
|
|
644
|
-
}
|
|
645
|
-
});
|
|
646
|
-
if (!response.ok) {
|
|
647
|
-
const errorData = await response.json().catch(() => ({}));
|
|
648
|
-
throw {
|
|
649
|
-
status: response.status,
|
|
650
|
-
message: errorData.message || 'Failed to fetch user campaign claims',
|
|
651
|
-
error: errorData
|
|
652
|
-
};
|
|
653
|
-
}
|
|
654
|
-
const data = await response.json();
|
|
655
|
-
return data;
|
|
656
|
-
}
|
|
657
|
-
catch (error) {
|
|
658
|
-
throw error;
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
/**
|
|
662
|
-
* Gets all available rewards for redemption
|
|
663
|
-
* @returns Promise with list of available rewards that can be exchanged for points
|
|
506
|
+
* @param tenantId - Optional tenant ID for automatic initialization
|
|
507
|
+
* @returns The prepared transaction data
|
|
664
508
|
* @throws If the request fails
|
|
665
509
|
*/
|
|
666
|
-
static async
|
|
510
|
+
static async fetchPreparedTransaction(transactionId, persAccessToken, tenantId) {
|
|
667
511
|
try {
|
|
668
|
-
|
|
669
|
-
|
|
512
|
+
// ✅ UPDATED: /transaction/auth/prepare-signing/${transactionId} → /transactions/${transactionId}/prepare
|
|
513
|
+
const res = await fetch(`${getPersApiUrl(this.useStaging)}/transactions/${transactionId}/prepare`, {
|
|
670
514
|
headers: {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
515
|
+
"Content-Type": "application/json",
|
|
516
|
+
"x-project-key": await this.getProjectKey(tenantId),
|
|
517
|
+
Authorization: `Bearer ${persAccessToken}`,
|
|
518
|
+
},
|
|
674
519
|
});
|
|
675
|
-
|
|
676
|
-
|
|
520
|
+
const response = await res.json();
|
|
521
|
+
if (!res.ok) {
|
|
522
|
+
// Throw structured error with the backend response
|
|
677
523
|
throw {
|
|
678
|
-
status:
|
|
679
|
-
message:
|
|
680
|
-
error:
|
|
524
|
+
status: res.status,
|
|
525
|
+
message: response.message || "Failed to fetch prepared transaction",
|
|
526
|
+
error: response
|
|
681
527
|
};
|
|
682
528
|
}
|
|
683
|
-
|
|
684
|
-
return data;
|
|
529
|
+
return response;
|
|
685
530
|
}
|
|
686
531
|
catch (error) {
|
|
687
|
-
|
|
532
|
+
// Rethrow a structured error
|
|
533
|
+
throw {
|
|
534
|
+
status: error.status || 500,
|
|
535
|
+
message: error.message || 'Failed to fetch prepared transaction from PERS backend',
|
|
536
|
+
error
|
|
537
|
+
};
|
|
688
538
|
}
|
|
689
539
|
}
|
|
690
540
|
/**
|
|
@@ -702,7 +552,7 @@ class PersService {
|
|
|
702
552
|
* @throws If transaction data is not available
|
|
703
553
|
*/
|
|
704
554
|
static getTransactionDataForSigning(transactionResponse) {
|
|
705
|
-
if (!
|
|
555
|
+
if (!transactionResponse.signingData) {
|
|
706
556
|
throw new Error('Transaction data is not ready for signing');
|
|
707
557
|
}
|
|
708
558
|
return transactionResponse.signingData;
|
|
@@ -711,8 +561,53 @@ class PersService {
|
|
|
711
561
|
PersService.config = DEFAULT_PERS_CONFIG;
|
|
712
562
|
PersService.tenantCache = new Map();
|
|
713
563
|
PersService.currentProjectKey = null;
|
|
564
|
+
PersService.currentTenantId = null;
|
|
714
565
|
PersService.useStaging = false;
|
|
566
|
+
PersService.TENANT_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours - tenant configs are essentially static
|
|
715
567
|
|
|
568
|
+
// Adapter function to convert backend signature response to legacy format
|
|
569
|
+
const adaptSignatureResponse = (newResponse) => {
|
|
570
|
+
if (!newResponse.signature) {
|
|
571
|
+
console.error('[KeyWallet] No signature in response:', newResponse);
|
|
572
|
+
throw new Error('Signature missing from response');
|
|
573
|
+
}
|
|
574
|
+
// Handle structured signature format (typically from EIP-712 signing)
|
|
575
|
+
if (typeof newResponse.signature === 'object' && 'r' in newResponse.signature) {
|
|
576
|
+
const { r, s, recid } = newResponse.signature;
|
|
577
|
+
return {
|
|
578
|
+
status: 'Signed',
|
|
579
|
+
signature: {
|
|
580
|
+
r,
|
|
581
|
+
s,
|
|
582
|
+
recid // Use recid directly from backend
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
// Handle string signature format (potentially from hash signing)
|
|
587
|
+
if (typeof newResponse.signature === 'string') {
|
|
588
|
+
// Parse string signature into components
|
|
589
|
+
// Assuming it's a hex string that can be split into r, s, v components
|
|
590
|
+
const signature = newResponse.signature;
|
|
591
|
+
if (signature.length === 132) { // 0x + 32 bytes r + 32 bytes s + 1 byte v
|
|
592
|
+
const r = '0x' + signature.slice(2, 66);
|
|
593
|
+
const s = '0x' + signature.slice(66, 130);
|
|
594
|
+
const v = parseInt(signature.slice(130, 132), 16);
|
|
595
|
+
const recid = v >= 27 ? v - 27 : v; // Convert v to recid
|
|
596
|
+
return {
|
|
597
|
+
status: 'Signed',
|
|
598
|
+
signature: {
|
|
599
|
+
r,
|
|
600
|
+
s,
|
|
601
|
+
recid
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
console.error('[KeyWallet] Invalid string signature length:', signature.length);
|
|
606
|
+
throw new Error(`Invalid string signature format - expected 132 characters, got ${signature.length}`);
|
|
607
|
+
}
|
|
608
|
+
console.error('[KeyWallet] Invalid signature format - expected structured object or string:', typeof newResponse.signature);
|
|
609
|
+
throw new Error('Invalid signature format in response - expected structured object or string from backend');
|
|
610
|
+
};
|
|
716
611
|
let DfnsError$1 = class DfnsError extends Error {
|
|
717
612
|
constructor(code, message, details) {
|
|
718
613
|
super(message);
|
|
@@ -731,14 +626,16 @@ const assertSigned = (res) => {
|
|
|
731
626
|
};
|
|
732
627
|
const combineSignature = (res) => {
|
|
733
628
|
if (!res.signature) {
|
|
629
|
+
console.error('[KeyWallet] No signature in response for combining:', res);
|
|
734
630
|
throw new DfnsError$1(-1, "signature missing", res);
|
|
735
631
|
}
|
|
736
632
|
const { r, s, recid } = res.signature;
|
|
737
|
-
|
|
633
|
+
const ethersSignature = ethers.Signature.from({
|
|
738
634
|
r,
|
|
739
635
|
s,
|
|
740
636
|
v: recid ? 0x1c : 0x1b, // Assuming 0x1c for chain_id > 0, 0x1b otherwise. DFNS usually provides recid.
|
|
741
|
-
})
|
|
637
|
+
});
|
|
638
|
+
return ethersSignature.serialized;
|
|
742
639
|
};
|
|
743
640
|
class KeyWallet extends ethers.AbstractSigner {
|
|
744
641
|
constructor(options, provider) {
|
|
@@ -764,9 +661,11 @@ class KeyWallet extends ethers.AbstractSigner {
|
|
|
764
661
|
return this.address;
|
|
765
662
|
}
|
|
766
663
|
async signHash(hash) {
|
|
767
|
-
const
|
|
664
|
+
const rawRes = await this.signingService.signHash(this.authToken, this.metadata.id, hash);
|
|
665
|
+
const res = adaptSignatureResponse(rawRes);
|
|
768
666
|
assertSigned(res);
|
|
769
|
-
|
|
667
|
+
const combinedSignature = combineSignature(res);
|
|
668
|
+
return combinedSignature;
|
|
770
669
|
}
|
|
771
670
|
async signTransaction(tx) {
|
|
772
671
|
// Resolve any addresses
|
|
@@ -803,9 +702,11 @@ class KeyWallet extends ethers.AbstractSigner {
|
|
|
803
702
|
async signTypedData(domain, types, value) {
|
|
804
703
|
try {
|
|
805
704
|
// Use DFNS native EIP-712 support with correct structure
|
|
806
|
-
const
|
|
705
|
+
const rawRes = await this.signingService.signTypedData(this.authToken, this.metadata.id, domain, types, value);
|
|
706
|
+
const res = adaptSignatureResponse(rawRes);
|
|
807
707
|
assertSigned(res);
|
|
808
|
-
|
|
708
|
+
const combinedSignature = combineSignature(res);
|
|
709
|
+
return combinedSignature;
|
|
809
710
|
}
|
|
810
711
|
catch (error) {
|
|
811
712
|
console.error('[KeyWallet] EIP-712 signing failed:', error);
|
|
@@ -935,27 +836,36 @@ class SigningService {
|
|
|
935
836
|
const config = getServiceConfig();
|
|
936
837
|
try {
|
|
937
838
|
// Step 1: Initialize signing and get challenge
|
|
938
|
-
const
|
|
939
|
-
authToken,
|
|
839
|
+
const initRequest = {
|
|
940
840
|
walletId,
|
|
941
|
-
request
|
|
942
|
-
|
|
943
|
-
});
|
|
944
|
-
const
|
|
841
|
+
request
|
|
842
|
+
};
|
|
843
|
+
const initResponse = await httpClient.post(`${config.apiUrl}/wallets/signatures/init`, initRequest, { Authorization: `Bearer ${authToken}` });
|
|
844
|
+
const backendInitResponse = initResponse.data;
|
|
845
|
+
if (!backendInitResponse.success || !backendInitResponse.data) {
|
|
846
|
+
console.error('[SigningService] Init response invalid:', backendInitResponse);
|
|
847
|
+
throw new Error('Signature initialization failed');
|
|
848
|
+
}
|
|
849
|
+
const { requestBody, challenge } = backendInitResponse.data;
|
|
945
850
|
// Step 2: Sign the challenge using WebAuthn
|
|
946
851
|
const assertion = await this.webAuthnProvider.sign(challenge);
|
|
947
852
|
// Step 3: Complete signing with signed challenge
|
|
948
|
-
const
|
|
949
|
-
authToken,
|
|
853
|
+
const completeRequest = {
|
|
950
854
|
walletId,
|
|
951
855
|
requestBody,
|
|
952
856
|
signedChallenge: {
|
|
953
|
-
challengeIdentifier: challenge
|
|
857
|
+
challengeIdentifier: challenge?.challengeIdentifier,
|
|
954
858
|
firstFactor: assertion,
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
});
|
|
958
|
-
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
const completeResponse = await httpClient.post(`${config.apiUrl}/wallets/signatures/complete`, completeRequest, { Authorization: `Bearer ${authToken}` });
|
|
862
|
+
const backendCompleteResponse = completeResponse.data;
|
|
863
|
+
if (!backendCompleteResponse.success || !backendCompleteResponse.data) {
|
|
864
|
+
console.error('[SigningService] Complete response invalid:', backendCompleteResponse);
|
|
865
|
+
throw new Error('Signature completion failed');
|
|
866
|
+
}
|
|
867
|
+
// Return the complete signature data from backend response
|
|
868
|
+
return backendCompleteResponse.data;
|
|
959
869
|
}
|
|
960
870
|
catch (error) {
|
|
961
871
|
console.error(`[SigningService] Signing failed for wallet ${walletId}:`, error);
|
|
@@ -1152,8 +1062,8 @@ class TransactionValidator {
|
|
|
1152
1062
|
if (!params.authTokens) {
|
|
1153
1063
|
throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.INVALID_TOKENS, 'Authentication tokens are required', params.transactionId);
|
|
1154
1064
|
}
|
|
1155
|
-
if (!params.authTokens.
|
|
1156
|
-
throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.INVALID_TOKENS, 'Both
|
|
1065
|
+
if (!params.authTokens.signerAuthToken || !params.authTokens.persAccessToken) {
|
|
1066
|
+
throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.INVALID_TOKENS, 'Both signer and PERS authentication tokens are required', params.transactionId);
|
|
1157
1067
|
}
|
|
1158
1068
|
if (!params.ethersProviderUrl || typeof params.ethersProviderUrl !== 'string') {
|
|
1159
1069
|
throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.INVALID_TOKENS, 'Ethers provider URL is required', params.transactionId);
|
|
@@ -1165,81 +1075,18 @@ class TransactionValidator {
|
|
|
1165
1075
|
* @returns true if valid, throws error if invalid
|
|
1166
1076
|
*/
|
|
1167
1077
|
static validateAuthTokens(authTokens) {
|
|
1168
|
-
if (!authTokens.persAccessToken || !authTokens.
|
|
1169
|
-
throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.INVALID_TOKENS, 'Both PERS access token and
|
|
1078
|
+
if (!authTokens.persAccessToken || !authTokens.signerAuthToken) {
|
|
1079
|
+
throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.INVALID_TOKENS, 'Both PERS access token and signer auth token are required');
|
|
1170
1080
|
}
|
|
1171
1081
|
return true;
|
|
1172
1082
|
}
|
|
1173
1083
|
}
|
|
1174
1084
|
|
|
1175
|
-
/**
|
|
1176
|
-
* Handles transaction submission, success flows, and redirect logic
|
|
1177
|
-
*/
|
|
1178
|
-
class TransactionSubmissionHandler {
|
|
1179
|
-
/**
|
|
1180
|
-
* Create redirect URL with transaction parameters
|
|
1181
|
-
* @param returnUrl - Base return URL
|
|
1182
|
-
* @param transactionHash - Transaction hash to include
|
|
1183
|
-
* @param metadata - Optional metadata to include as parameters
|
|
1184
|
-
* @returns Promise resolving to the complete redirect URL
|
|
1185
|
-
*/
|
|
1186
|
-
static async createRedirectUrl(returnUrl, transactionHash, metadata) {
|
|
1187
|
-
const additionalParams = {
|
|
1188
|
-
txHash: transactionHash,
|
|
1189
|
-
success: 'true'
|
|
1190
|
-
};
|
|
1191
|
-
// Add metadata if available
|
|
1192
|
-
if (metadata) {
|
|
1193
|
-
Object.entries(metadata).forEach(([key, value]) => {
|
|
1194
|
-
// metadata is already constrained to string values by TransactionMetadata interface
|
|
1195
|
-
additionalParams[key] = value;
|
|
1196
|
-
});
|
|
1197
|
-
}
|
|
1198
|
-
// Dynamic import to avoid build issues
|
|
1199
|
-
const { createUrlWithSearchParams } = await Promise.resolve().then(function () { return searchParams; });
|
|
1200
|
-
return createUrlWithSearchParams(returnUrl, ['transactionId', 'returnUrl', 'jwt', 'token'], additionalParams);
|
|
1201
|
-
}
|
|
1202
|
-
/**
|
|
1203
|
-
* Submit transaction and handle success flow
|
|
1204
|
-
* @param preparedTransaction - The prepared transaction
|
|
1205
|
-
* @param signature - The transaction signature
|
|
1206
|
-
* @param signingData - The signing data used
|
|
1207
|
-
* @param authTokens - Authentication tokens
|
|
1208
|
-
* @param transactionId - Transaction ID for tracking
|
|
1209
|
-
* @param returnUrl - Optional return URL for redirect
|
|
1210
|
-
* @param metadata - Optional metadata for redirect parameters
|
|
1211
|
-
* @returns Promise resolving to submission result
|
|
1212
|
-
*/
|
|
1213
|
-
static async handleTransactionSubmission(preparedTransaction, signature, signingData, authTokens, returnUrl, metadata) {
|
|
1214
|
-
// Submit signed transaction
|
|
1215
|
-
const submitResult = await PersService.submitTransaction(preparedTransaction.transaction.id, signature, authTokens.persAccessToken, signingData.format);
|
|
1216
|
-
// Get transaction hash for return URL or response
|
|
1217
|
-
const transactionHash = submitResult.transaction?.transactionHash || '';
|
|
1218
|
-
// Handle success - check for return URL
|
|
1219
|
-
if (returnUrl) {
|
|
1220
|
-
const redirectUrl = await this.createRedirectUrl(returnUrl, transactionHash, metadata);
|
|
1221
|
-
return { submitResult, shouldRedirect: true, redirectUrl };
|
|
1222
|
-
}
|
|
1223
|
-
else {
|
|
1224
|
-
return { submitResult, shouldRedirect: false };
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
1085
|
/**
|
|
1230
1086
|
* Utility class for coordinating WebAuthn operations to prevent conflicts
|
|
1231
1087
|
* Manages global state flags to ensure only one WebAuthn operation runs at a time
|
|
1232
1088
|
*/
|
|
1233
1089
|
class WebAuthnCoordinator {
|
|
1234
|
-
/**
|
|
1235
|
-
* Clear the landing authentication flag
|
|
1236
|
-
* Used when starting a new transaction signing flow
|
|
1237
|
-
*/
|
|
1238
|
-
static clearLandingAuthentication() {
|
|
1239
|
-
if (typeof window !== 'undefined') {
|
|
1240
|
-
window.landingAuthenticationInProgress = false;
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
1090
|
/**
|
|
1244
1091
|
* Check if a WebAuthn operation is currently in progress
|
|
1245
1092
|
* @returns True if an operation is in progress, false otherwise
|
|
@@ -1272,16 +1119,16 @@ const SIGNABLE_STATUSES = [browser.TransactionStatus.PENDING_SIGNATURE, browser.
|
|
|
1272
1119
|
* Uses constructor-based dependency injection for WebAuthn provider
|
|
1273
1120
|
*/
|
|
1274
1121
|
class TransactionSigningService {
|
|
1275
|
-
constructor(
|
|
1276
|
-
this.webAuthnProvider = webAuthnProvider;
|
|
1122
|
+
constructor(config) {
|
|
1123
|
+
this.webAuthnProvider = config.webAuthnProvider;
|
|
1277
1124
|
}
|
|
1278
1125
|
/**
|
|
1279
1126
|
* Prepare transaction for signing - fetch and validate
|
|
1280
1127
|
*/
|
|
1281
|
-
async prepareTransaction(transactionId, authTokens) {
|
|
1128
|
+
async prepareTransaction(transactionId, authTokens, tenantId) {
|
|
1282
1129
|
let preparedTransaction;
|
|
1283
1130
|
try {
|
|
1284
|
-
preparedTransaction = await PersService.fetchPreparedTransaction(transactionId, authTokens.persAccessToken);
|
|
1131
|
+
preparedTransaction = await PersService.fetchPreparedTransaction(transactionId, authTokens.persAccessToken, tenantId);
|
|
1285
1132
|
}
|
|
1286
1133
|
catch (err) {
|
|
1287
1134
|
// Use TransactionErrorHandler to process PERS API errors
|
|
@@ -1302,7 +1149,7 @@ class TransactionSigningService {
|
|
|
1302
1149
|
// Authenticate with PERS using backend signer token to set up signing account
|
|
1303
1150
|
let updatedPersAccessToken = authTokens.persAccessToken;
|
|
1304
1151
|
try {
|
|
1305
|
-
const persSignerAuth = await PersService.authenticateUser(authTokens.
|
|
1152
|
+
const persSignerAuth = await PersService.authenticateUser(authTokens.signerAuthToken, tenantId);
|
|
1306
1153
|
// Update PERS access token with the new one that has signing account linked
|
|
1307
1154
|
const newPersAccessToken = persSignerAuth.accessToken;
|
|
1308
1155
|
if (newPersAccessToken) {
|
|
@@ -1316,7 +1163,7 @@ class TransactionSigningService {
|
|
|
1316
1163
|
}
|
|
1317
1164
|
// If transaction is 'created' status but doesn't have signingData, fetch again with wallet-enabled token
|
|
1318
1165
|
if (transactionStatus === browser.TransactionStatus.CREATED && !PersService.isTransactionReadyForSigning(preparedTransaction)) {
|
|
1319
|
-
preparedTransaction = await PersService.fetchPreparedTransaction(transactionId, updatedPersAccessToken);
|
|
1166
|
+
preparedTransaction = await PersService.fetchPreparedTransaction(transactionId, updatedPersAccessToken, tenantId);
|
|
1320
1167
|
}
|
|
1321
1168
|
if (!PersService.isTransactionReadyForSigning(preparedTransaction)) {
|
|
1322
1169
|
throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.TRANSACTION_NOT_READY, 'Transaction is not ready for signing', transactionId);
|
|
@@ -1331,19 +1178,21 @@ class TransactionSigningService {
|
|
|
1331
1178
|
*/
|
|
1332
1179
|
async prepareWallet(authTokens, ethersProviderUrl) {
|
|
1333
1180
|
// Wallet validation will be handled through API responses - no localStorage needed
|
|
1334
|
-
let
|
|
1181
|
+
let walletListResult;
|
|
1335
1182
|
try {
|
|
1336
|
-
|
|
1337
|
-
|
|
1183
|
+
// Use new WalletService API with signer token
|
|
1184
|
+
walletListResult = await WalletService.listWallets(authTokens.signerAuthToken);
|
|
1338
1185
|
}
|
|
1339
1186
|
catch (error) {
|
|
1340
1187
|
console.error('[TransactionSigningService] Wallet list API failed:', error);
|
|
1341
1188
|
throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.WALLET_NOT_AVAILABLE, 'Failed to retrieve wallet information. Please refresh the page and try again.', undefined, error);
|
|
1342
1189
|
}
|
|
1343
|
-
//
|
|
1344
|
-
const wallets =
|
|
1190
|
+
// Handle multiple response formats for compatibility
|
|
1191
|
+
const wallets = walletListResult?.items ||
|
|
1192
|
+
walletListResult?.data?.wallets ||
|
|
1193
|
+
walletListResult?.wallets || [];
|
|
1345
1194
|
if (!wallets?.length) {
|
|
1346
|
-
console.error('[TransactionSigningService] No wallets found in response:',
|
|
1195
|
+
console.error('[TransactionSigningService] No wallets found in response:', walletListResult);
|
|
1347
1196
|
throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.WALLET_NOT_AVAILABLE, 'No wallet found for transaction signing. Please refresh the page and complete account setup.');
|
|
1348
1197
|
}
|
|
1349
1198
|
// Create SigningService with injected WebAuthn provider
|
|
@@ -1351,7 +1200,7 @@ class TransactionSigningService {
|
|
|
1351
1200
|
// Create wallet instance with provider - use unknown type for flexibility
|
|
1352
1201
|
const provider = new ethers.JsonRpcProvider(ethersProviderUrl);
|
|
1353
1202
|
const wallet = new KeyWallet({
|
|
1354
|
-
authToken: authTokens.
|
|
1203
|
+
authToken: authTokens.signerAuthToken,
|
|
1355
1204
|
wallet: wallets[0],
|
|
1356
1205
|
signingService: signingService,
|
|
1357
1206
|
}).connect(provider);
|
|
@@ -1360,7 +1209,9 @@ class TransactionSigningService {
|
|
|
1360
1209
|
/**
|
|
1361
1210
|
* Execute the transaction signing with WebAuthn coordination
|
|
1362
1211
|
*/
|
|
1363
|
-
async executeTransactionSigning(wallet,
|
|
1212
|
+
async executeTransactionSigning(wallet,
|
|
1213
|
+
// preparedTransaction: TransactionRequestResponseDTO,
|
|
1214
|
+
signingData) {
|
|
1364
1215
|
// Check for concurrent WebAuthn operations
|
|
1365
1216
|
if (WebAuthnCoordinator.checkConcurrentOperations()) {
|
|
1366
1217
|
throw TransactionErrorHandler.createError(exports.TransactionSigningErrorCode.WEBAUTHN_OPERATION_IN_PROGRESS, 'Another WebAuthn operation is in progress. Please wait and try again.');
|
|
@@ -1368,7 +1219,7 @@ class TransactionSigningService {
|
|
|
1368
1219
|
// Set WebAuthn operation flag for transaction signing
|
|
1369
1220
|
WebAuthnCoordinator.setOperationInProgress(true);
|
|
1370
1221
|
try {
|
|
1371
|
-
const signingData = PersService.getTransactionDataForSigning(preparedTransaction);
|
|
1222
|
+
// const signingData = PersService.getTransactionDataForSigning(preparedTransaction);
|
|
1372
1223
|
const signature = await wallet.signPersTransaction(signingData);
|
|
1373
1224
|
return signature;
|
|
1374
1225
|
}
|
|
@@ -1387,6 +1238,13 @@ class TransactionSigningService {
|
|
|
1387
1238
|
WebAuthnCoordinator.setOperationInProgress(false);
|
|
1388
1239
|
}
|
|
1389
1240
|
}
|
|
1241
|
+
async getPersSigningData(data) {
|
|
1242
|
+
// Step 1: Prepare transaction for signing
|
|
1243
|
+
const { preparedTransaction } = await this.prepareTransaction(data.transactionId, data.authTokens, data.tenantId);
|
|
1244
|
+
// const signingData = PersService.getTransactionDataForSigning(preparedTransaction);
|
|
1245
|
+
const signingData = PersService.getTransactionDataForSigning(preparedTransaction);
|
|
1246
|
+
return signingData;
|
|
1247
|
+
}
|
|
1390
1248
|
/**
|
|
1391
1249
|
* Main transaction signing orchestration method
|
|
1392
1250
|
* Handles the complete flow from preparation to submission
|
|
@@ -1394,31 +1252,23 @@ class TransactionSigningService {
|
|
|
1394
1252
|
* @returns Promise resolving to transaction signing result
|
|
1395
1253
|
* @throws TransactionSigningError for validation and operation failures
|
|
1396
1254
|
*/
|
|
1397
|
-
async signTransaction(params) {
|
|
1255
|
+
async signTransaction(params, signingData) {
|
|
1398
1256
|
// Validate input parameters first using TransactionValidator
|
|
1399
1257
|
TransactionValidator.validateSigningParams(params);
|
|
1400
|
-
const { transactionId, authTokens, ethersProviderUrl
|
|
1258
|
+
const { transactionId, authTokens, ethersProviderUrl } = params;
|
|
1401
1259
|
console.info(`[TransactionSigningService] Starting signature process for ${transactionId}`);
|
|
1402
1260
|
try {
|
|
1403
|
-
// Clear previous landing authentication state
|
|
1404
|
-
WebAuthnCoordinator.clearLandingAuthentication();
|
|
1405
|
-
// Step 1: Prepare transaction for signing
|
|
1406
|
-
const { preparedTransaction, updatedTokens } = await this.prepareTransaction(transactionId, authTokens);
|
|
1407
1261
|
// Step 2: Prepare wallet for signing
|
|
1408
|
-
const wallet = await this.prepareWallet(
|
|
1262
|
+
const wallet = await this.prepareWallet(authTokens, ethersProviderUrl);
|
|
1409
1263
|
// Step 3: Execute transaction signing
|
|
1410
|
-
const signature = await this.executeTransactionSigning(wallet,
|
|
1411
|
-
|
|
1412
|
-
// Step 4: Submit transaction and handle success using TransactionSubmissionHandler
|
|
1413
|
-
const { submitResult, shouldRedirect, redirectUrl } = await TransactionSubmissionHandler.handleTransactionSubmission(preparedTransaction, signature, signingData, updatedTokens, returnUrl, metadata);
|
|
1414
|
-
console.info(`[TransactionSigningService] Completed successfully: ${transactionId}`);
|
|
1264
|
+
const signature = await this.executeTransactionSigning(wallet, signingData);
|
|
1265
|
+
console.info(`[TransactionSigningService] Completed signing successfully: ${transactionId}`);
|
|
1415
1266
|
return {
|
|
1416
1267
|
success: true,
|
|
1417
1268
|
transactionId,
|
|
1418
|
-
|
|
1269
|
+
signingData,
|
|
1270
|
+
// transactionHash: submitResult.transaction?.transactionHash ?? undefined,
|
|
1419
1271
|
signature,
|
|
1420
|
-
shouldRedirect,
|
|
1421
|
-
redirectUrl
|
|
1422
1272
|
};
|
|
1423
1273
|
}
|
|
1424
1274
|
catch (error) {
|
|
@@ -1444,719 +1294,727 @@ class TransactionSigningService {
|
|
|
1444
1294
|
}
|
|
1445
1295
|
}
|
|
1446
1296
|
|
|
1297
|
+
/**
|
|
1298
|
+
* Handles transaction submission, success flows, and redirect logic
|
|
1299
|
+
*/
|
|
1300
|
+
class TransactionSubmissionHandler {
|
|
1301
|
+
/**
|
|
1302
|
+
* Create redirect URL with transaction parameters
|
|
1303
|
+
* @param returnUrl - Base return URL
|
|
1304
|
+
* @param transactionHash - Transaction hash to include
|
|
1305
|
+
* @param metadata - Optional metadata to include as parameters
|
|
1306
|
+
* @returns Promise resolving to the complete redirect URL
|
|
1307
|
+
*/
|
|
1308
|
+
static async createRedirectUrl(returnUrl, transactionHash) {
|
|
1309
|
+
const additionalParams = {
|
|
1310
|
+
txHash: transactionHash,
|
|
1311
|
+
success: 'true'
|
|
1312
|
+
};
|
|
1313
|
+
// Build URL with required params
|
|
1314
|
+
const paramKeys = ['transactionId', 'returnUrl', 'jwt', 'token'];
|
|
1315
|
+
const url = new URL(returnUrl, window.location.origin);
|
|
1316
|
+
// Add required params if present in additionalParams
|
|
1317
|
+
paramKeys.forEach(key => {
|
|
1318
|
+
if (additionalParams[key]) {
|
|
1319
|
+
url.searchParams.set(key, additionalParams[key]);
|
|
1320
|
+
}
|
|
1321
|
+
});
|
|
1322
|
+
// Add all other params
|
|
1323
|
+
Object.entries(additionalParams).forEach(([key, value]) => {
|
|
1324
|
+
if (!paramKeys.includes(key)) {
|
|
1325
|
+
url.searchParams.set(key, value);
|
|
1326
|
+
}
|
|
1327
|
+
});
|
|
1328
|
+
return url.toString();
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1447
1332
|
/**
|
|
1448
1333
|
* Authentication service for user login and registration
|
|
1449
1334
|
* Uses constructor-based dependency injection for WebAuthn provider
|
|
1335
|
+
* Updated for new v1 API endpoints
|
|
1450
1336
|
*/
|
|
1451
1337
|
class AuthenticationService {
|
|
1452
|
-
constructor(
|
|
1453
|
-
this.
|
|
1338
|
+
constructor(config) {
|
|
1339
|
+
this.signerToken = null;
|
|
1340
|
+
this.config = config;
|
|
1341
|
+
this.webAuthnProvider = config.webAuthnProvider;
|
|
1454
1342
|
}
|
|
1455
1343
|
/**
|
|
1456
|
-
*
|
|
1457
|
-
* @param
|
|
1458
|
-
* @returns Promise resolving to
|
|
1344
|
+
* Login with PERS token to get signer JWT
|
|
1345
|
+
* @param persToken - PERS JWT from PERS authentication
|
|
1346
|
+
* @returns Promise resolving to login response or provider challenge data
|
|
1459
1347
|
*/
|
|
1460
|
-
async
|
|
1348
|
+
async loginWithPersToken(persToken) {
|
|
1461
1349
|
const httpClient = getHttpClient();
|
|
1462
1350
|
const config = getServiceConfig();
|
|
1463
1351
|
try {
|
|
1464
|
-
const
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
}
|
|
1468
|
-
//
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1352
|
+
const requestBody = {
|
|
1353
|
+
authToken: persToken
|
|
1354
|
+
};
|
|
1355
|
+
const response = await httpClient.post(`${config.apiUrl}/auth/login`, requestBody
|
|
1356
|
+
// Note: Login endpoint doesn't require Authorization header (public endpoint)
|
|
1357
|
+
);
|
|
1358
|
+
const backendResponse = response.data;
|
|
1359
|
+
if (backendResponse && backendResponse.success) {
|
|
1360
|
+
// Check if it's JWT response or wrapped provider response
|
|
1361
|
+
if ('access_token' in backendResponse) {
|
|
1362
|
+
// JWT response - set token and return
|
|
1363
|
+
this.signerToken = backendResponse.access_token;
|
|
1364
|
+
return backendResponse;
|
|
1365
|
+
}
|
|
1366
|
+
else {
|
|
1367
|
+
// Wrapped provider response - return the data
|
|
1368
|
+
return backendResponse.data;
|
|
1369
|
+
}
|
|
1474
1370
|
}
|
|
1475
|
-
console.
|
|
1476
|
-
|
|
1371
|
+
console.error('[AuthenticationService] Invalid response - no success or success=false:', backendResponse);
|
|
1372
|
+
throw new Error('Login failed: Invalid response format');
|
|
1477
1373
|
}
|
|
1478
1374
|
catch (error) {
|
|
1479
|
-
console.error(`[AuthenticationService]
|
|
1375
|
+
// console.error(`[AuthenticationService] PERS token login failed:`, error);
|
|
1480
1376
|
throw error;
|
|
1481
1377
|
}
|
|
1482
1378
|
}
|
|
1483
1379
|
/**
|
|
1484
|
-
*
|
|
1485
|
-
* @param
|
|
1486
|
-
* @returns Promise resolving to
|
|
1380
|
+
* Verify signer token validity
|
|
1381
|
+
* @param token - Signer JWT to verify
|
|
1382
|
+
* @returns Promise resolving to verification result
|
|
1487
1383
|
*/
|
|
1488
|
-
async
|
|
1384
|
+
async verifyToken(token) {
|
|
1489
1385
|
const httpClient = getHttpClient();
|
|
1490
1386
|
const config = getServiceConfig();
|
|
1491
1387
|
try {
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
if (challenge.status && challenge.status >= 400) {
|
|
1500
|
-
throw new Error(challenge.message || 'Registration initialization failed');
|
|
1501
|
-
}
|
|
1502
|
-
// Step 2: Create WebAuthn credential using the challenge
|
|
1503
|
-
const attestation = await this.webAuthnProvider.create(challenge);
|
|
1504
|
-
console.info(`[WebAuthn] Credential created successfully for user: ${username}`);
|
|
1505
|
-
// Step 3: Complete registration with signed challenge
|
|
1506
|
-
const completeResponse = await httpClient.post(`${config.apiUrl}/register/complete`, {
|
|
1507
|
-
signedChallenge: { firstFactorCredential: attestation },
|
|
1508
|
-
temporaryAuthenticationToken: challenge.temporaryAuthenticationToken,
|
|
1509
|
-
appId: config.appId,
|
|
1510
|
-
});
|
|
1511
|
-
const result = completeResponse.data;
|
|
1512
|
-
// Check if the registration completion failed
|
|
1513
|
-
if (result.status && result.status >= 400) {
|
|
1514
|
-
throw new Error(result.message || 'Registration completion failed');
|
|
1388
|
+
const requestBody = {
|
|
1389
|
+
token
|
|
1390
|
+
};
|
|
1391
|
+
const response = await httpClient.post(`${config.apiUrl}/auth/verify`, requestBody, { Authorization: `Bearer ${token}` });
|
|
1392
|
+
const verifyData = response.data;
|
|
1393
|
+
if (!verifyData.valid) {
|
|
1394
|
+
throw new Error('Token verification failed');
|
|
1515
1395
|
}
|
|
1516
|
-
|
|
1517
|
-
return result;
|
|
1396
|
+
return verifyData;
|
|
1518
1397
|
}
|
|
1519
1398
|
catch (error) {
|
|
1520
|
-
console.error(`[AuthenticationService]
|
|
1399
|
+
console.error(`[AuthenticationService] Token verification failed:`, error);
|
|
1521
1400
|
throw error;
|
|
1522
1401
|
}
|
|
1523
1402
|
}
|
|
1524
1403
|
/**
|
|
1525
|
-
*
|
|
1526
|
-
* @param
|
|
1527
|
-
* @returns Promise resolving to
|
|
1404
|
+
* Initialize user registration
|
|
1405
|
+
* @param persToken - PERS JWT token (registration is public)
|
|
1406
|
+
* @returns Promise resolving to registration challenge
|
|
1528
1407
|
*/
|
|
1529
|
-
async
|
|
1408
|
+
async initializeRegistration(persToken) {
|
|
1530
1409
|
const httpClient = getHttpClient();
|
|
1531
1410
|
const config = getServiceConfig();
|
|
1532
1411
|
try {
|
|
1533
|
-
const
|
|
1534
|
-
authToken
|
|
1535
|
-
|
|
1536
|
-
});
|
|
1537
|
-
|
|
1412
|
+
const requestBody = {
|
|
1413
|
+
authToken: persToken
|
|
1414
|
+
};
|
|
1415
|
+
const response = await httpClient.post(`${config.apiUrl}/auth/register/init`, requestBody);
|
|
1416
|
+
const backendResponse = response.data;
|
|
1417
|
+
if (backendResponse.success && backendResponse.data) {
|
|
1418
|
+
return backendResponse.data;
|
|
1419
|
+
}
|
|
1420
|
+
throw new Error('Registration initialization failed: No data returned');
|
|
1538
1421
|
}
|
|
1539
1422
|
catch (error) {
|
|
1540
|
-
console.error(
|
|
1541
|
-
|
|
1423
|
+
// console.error(`[AuthenticationService] Registration initialization failed:`, error);
|
|
1424
|
+
throw error;
|
|
1542
1425
|
}
|
|
1543
1426
|
}
|
|
1544
1427
|
/**
|
|
1545
|
-
* Get
|
|
1546
|
-
* @
|
|
1547
|
-
|
|
1428
|
+
* Get current signer token
|
|
1429
|
+
* @returns The current signer JWT token
|
|
1430
|
+
*/
|
|
1431
|
+
getSignerToken() {
|
|
1432
|
+
return this.signerToken;
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
1435
|
+
* Set signer token (for external token management)
|
|
1436
|
+
* @param token - Signer JWT token
|
|
1437
|
+
*/
|
|
1438
|
+
setSignerToken(token) {
|
|
1439
|
+
this.signerToken = token;
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Complete registration with WebAuthn challenge data (v1 API format)
|
|
1443
|
+
* @param tmpAuthToken - Temporary auth token from init registration (temporaryAuthenticationToken)
|
|
1444
|
+
* @param signedChallenge - WebAuthn credential response (will be restructured for backend)
|
|
1445
|
+
* @param persToken - PERS JWT token (authToken)
|
|
1446
|
+
* @returns Promise resolving to registration result
|
|
1548
1447
|
*/
|
|
1549
|
-
async
|
|
1448
|
+
async completeRegistrationWithChallenge(tmpAuthToken, signedChallenge, persToken) {
|
|
1550
1449
|
const httpClient = getHttpClient();
|
|
1450
|
+
const config = getServiceConfig();
|
|
1551
1451
|
try {
|
|
1552
|
-
const
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1452
|
+
const requestBody = {
|
|
1453
|
+
temporaryAuthenticationToken: tmpAuthToken,
|
|
1454
|
+
signedChallenge: {
|
|
1455
|
+
firstFactorCredential: {
|
|
1456
|
+
credentialKind: signedChallenge.credentialKind || "Fido2",
|
|
1457
|
+
credentialInfo: {
|
|
1458
|
+
credId: signedChallenge.credentialInfo?.credId || "",
|
|
1459
|
+
clientData: signedChallenge.credentialInfo?.clientData || "",
|
|
1460
|
+
attestationData: signedChallenge.credentialInfo?.attestationData || ""
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
},
|
|
1464
|
+
authToken: persToken
|
|
1465
|
+
};
|
|
1466
|
+
const response = await httpClient.post(`${config.apiUrl}/auth/register/complete`, requestBody);
|
|
1467
|
+
const backendResponse = response.data;
|
|
1468
|
+
if (backendResponse && backendResponse.success) {
|
|
1469
|
+
// Check if it's JWT response (has access_token at root level) or wrapped provider response
|
|
1470
|
+
if ('access_token' in backendResponse) {
|
|
1471
|
+
// JWT response - set token and return
|
|
1472
|
+
this.signerToken = backendResponse.access_token;
|
|
1473
|
+
return backendResponse;
|
|
1474
|
+
}
|
|
1475
|
+
else if ('data' in backendResponse) {
|
|
1476
|
+
// Wrapped provider response - registration is not complete yet, needs more steps
|
|
1477
|
+
return backendResponse.data;
|
|
1478
|
+
}
|
|
1479
|
+
else {
|
|
1480
|
+
// Wrapped provider response - return the data
|
|
1481
|
+
return backendResponse.data;
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
console.error('[AuthenticationService] Registration with challenge failed - invalid response:', backendResponse);
|
|
1485
|
+
throw new Error('Registration completion with challenge failed');
|
|
1486
|
+
}
|
|
1557
1487
|
catch (error) {
|
|
1558
|
-
console.error(
|
|
1559
|
-
|
|
1488
|
+
console.error(`[AuthenticationService] Registration completion with challenge failed:`, error);
|
|
1489
|
+
throw error;
|
|
1560
1490
|
}
|
|
1561
1491
|
}
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
/**
|
|
1565
|
-
* PERS Blockchain Signer SDK - Simple Orchestrator
|
|
1566
|
-
*
|
|
1567
|
-
* Lightweight SDK that orchestrates existing services.
|
|
1568
|
-
* Uses environment configuration and existing infrastructure.
|
|
1569
|
-
*/
|
|
1570
|
-
/**
|
|
1571
|
-
* Main PERS Signer SDK class
|
|
1572
|
-
*
|
|
1573
|
-
* Simple orchestrator that uses existing services and environment configuration.
|
|
1574
|
-
* No complex initialization needed - services are already configured.
|
|
1575
|
-
*/
|
|
1576
|
-
class PersSignerSDK {
|
|
1577
|
-
constructor(config) {
|
|
1578
|
-
this.config = config;
|
|
1579
|
-
this.webAuthnProvider = config.webAuthnProvider;
|
|
1580
|
-
// Set up the configuration provider with stable defaults from constants
|
|
1581
|
-
const serviceConfig = {
|
|
1582
|
-
apiUrl: config.apiUrl || SIGNER_CONFIG.DEFAULT_SIGNER_API_URL,
|
|
1583
|
-
relyingParty: {
|
|
1584
|
-
id: typeof window !== 'undefined' ? window.location.hostname : 'localhost',
|
|
1585
|
-
name: config.relyingPartyName || SIGNER_CONFIG.DEFAULT_RELYING_PARTY_NAME,
|
|
1586
|
-
origin: typeof window !== 'undefined' ? window.location.origin : undefined,
|
|
1587
|
-
},
|
|
1588
|
-
};
|
|
1589
|
-
setConfigProvider(new WebConfigProvider(serviceConfig));
|
|
1590
|
-
// Initialize services with the WebAuthn provider
|
|
1591
|
-
this.authService = new AuthenticationService(this.webAuthnProvider);
|
|
1592
|
-
this.signingService = new SigningService(this.webAuthnProvider);
|
|
1593
|
-
this.transactionSigningService = new TransactionSigningService(this.webAuthnProvider);
|
|
1594
|
-
}
|
|
1595
1492
|
/**
|
|
1596
|
-
*
|
|
1597
|
-
*
|
|
1493
|
+
* Combined authentication flow - handles both login and registration
|
|
1494
|
+
* @param identifier - User identifier (email/userId)
|
|
1495
|
+
* @param persAccessToken - PERS JWT token for authentication
|
|
1496
|
+
* @param webAuthnProvider - WebAuthn provider for credential creation
|
|
1497
|
+
* @param relyingPartyConfig - Configuration for WebAuthn relying party
|
|
1498
|
+
* @returns Promise resolving to authenticated user with signer token
|
|
1598
1499
|
*/
|
|
1599
|
-
|
|
1600
|
-
return this.transactionSigningService;
|
|
1601
|
-
}
|
|
1602
|
-
// ========================================================================
|
|
1603
|
-
// HIGH-LEVEL METHODS (Same as Web Project)
|
|
1604
|
-
// ========================================================================
|
|
1605
|
-
/**
|
|
1606
|
-
* Complete user onboarding flow - same as web project
|
|
1607
|
-
* Uses existing AuthenticationService and PersService
|
|
1608
|
-
*/
|
|
1609
|
-
async authenticateUser(userInfo) {
|
|
1610
|
-
const identifier = userInfo.identifier;
|
|
1500
|
+
async combinedAuthentication(identifier, persAccessToken) {
|
|
1611
1501
|
try {
|
|
1612
|
-
// Step 1: Try login first (
|
|
1613
|
-
let
|
|
1502
|
+
// Step 1: Try to login with PERS token first (this will get signer JWT if user exists)
|
|
1503
|
+
let signerToken;
|
|
1614
1504
|
try {
|
|
1615
|
-
|
|
1505
|
+
const loginResult = await this.loginWithPersToken(persAccessToken);
|
|
1506
|
+
// Extract token from login result
|
|
1507
|
+
if (loginResult && typeof loginResult === 'object' && 'access_token' in loginResult) {
|
|
1508
|
+
signerToken = loginResult.access_token;
|
|
1509
|
+
}
|
|
1510
|
+
else {
|
|
1511
|
+
throw new Error('Invalid login response format');
|
|
1512
|
+
}
|
|
1616
1513
|
}
|
|
1617
1514
|
catch (loginError) {
|
|
1618
|
-
// User doesn't exist - register
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1515
|
+
// Step 2: User doesn't exist - register with v1 API
|
|
1516
|
+
// 2a. Initialize registration
|
|
1517
|
+
const initResult = await this.initializeRegistration(persAccessToken);
|
|
1518
|
+
let signedChallenge = null;
|
|
1519
|
+
let tmpAuthToken = null;
|
|
1520
|
+
// 2b. Create WebAuthn credential using the challenge (if available)
|
|
1521
|
+
if (initResult && typeof initResult === 'object') {
|
|
1522
|
+
// Extract challenge and tmpAuthToken from init result with better type safety
|
|
1523
|
+
const initData = initResult;
|
|
1524
|
+
const challenge = initData.challenge;
|
|
1525
|
+
tmpAuthToken = initData.temporaryAuthenticationToken || initData.tmpAuthToken || null;
|
|
1526
|
+
if (challenge && initData.user) {
|
|
1527
|
+
// Construct proper WebAuthn credential creation options
|
|
1528
|
+
const credentialCreationOptions = {
|
|
1529
|
+
challenge: challenge,
|
|
1530
|
+
rp: {
|
|
1531
|
+
name: this.config.relyingPartyName || 'PERS Signer',
|
|
1532
|
+
id: typeof window !== 'undefined' ? window.location.hostname : 'localhost'
|
|
1533
|
+
},
|
|
1534
|
+
user: {
|
|
1535
|
+
id: initData.user.id,
|
|
1536
|
+
name: initData.user.name,
|
|
1537
|
+
displayName: initData.user.displayName
|
|
1538
|
+
},
|
|
1539
|
+
pubKeyCredParams: initData.pubKeyCredParams || [
|
|
1540
|
+
{ alg: -7, type: "public-key" },
|
|
1541
|
+
{ alg: -257, type: "public-key" }
|
|
1542
|
+
],
|
|
1543
|
+
authenticatorSelection: initData.authenticatorSelection || {},
|
|
1544
|
+
attestation: initData.attestation || "direct",
|
|
1545
|
+
excludeCredentials: initData.excludeCredentials || []
|
|
1546
|
+
};
|
|
1547
|
+
signedChallenge = await this.webAuthnProvider.create(credentialCreationOptions);
|
|
1548
|
+
}
|
|
1549
|
+
else {
|
|
1550
|
+
console.warn(`[PersSignerSDK] Missing challenge or user data in init result`);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
// 2c. Complete registration with proper challenge data - call AuthenticationService directly
|
|
1554
|
+
const completeResult = await this.completeRegistrationWithChallenge(tmpAuthToken, signedChallenge, persAccessToken);
|
|
1555
|
+
// Extract token from registration result
|
|
1556
|
+
if (completeResult && typeof completeResult === 'object') {
|
|
1557
|
+
// Check if this is a complete JWT response with access_token
|
|
1558
|
+
if ('access_token' in completeResult) {
|
|
1559
|
+
signerToken = completeResult.access_token;
|
|
1560
|
+
}
|
|
1561
|
+
else {
|
|
1562
|
+
// This is a wrapped provider response - registration is not complete
|
|
1563
|
+
// We have user created and wallet created, but need to get JWT token
|
|
1564
|
+
// For now, throw an error indicating we need additional backend integration steps
|
|
1565
|
+
throw new Error('Registration created user and wallet but JWT token not provided. Backend integration incomplete.');
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
else {
|
|
1569
|
+
throw new Error('Registration completed but no response received');
|
|
1570
|
+
}
|
|
1632
1571
|
}
|
|
1633
1572
|
return {
|
|
1634
1573
|
identifier,
|
|
1635
|
-
signerAuthToken,
|
|
1574
|
+
signerAuthToken: signerToken,
|
|
1636
1575
|
persAccessToken
|
|
1637
1576
|
};
|
|
1638
1577
|
}
|
|
1639
1578
|
catch (error) {
|
|
1640
|
-
|
|
1579
|
+
console.error(`[PersSignerSDK] Combined authentication failed for ${identifier}:`, error);
|
|
1580
|
+
throw new Error(`Combined authentication failed: ${error}`);
|
|
1641
1581
|
}
|
|
1642
1582
|
}
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
/**
|
|
1586
|
+
* JWT Utility Functions
|
|
1587
|
+
*
|
|
1588
|
+
* Browser-compatible JWT handling without external dependencies
|
|
1589
|
+
*/
|
|
1590
|
+
/**
|
|
1591
|
+
* Extract and validate JWT token payload
|
|
1592
|
+
* Uses a browser-compatible approach without external dependencies
|
|
1593
|
+
*/
|
|
1594
|
+
function extractJWTFromToken(jwtToken) {
|
|
1595
|
+
if (!jwtToken) {
|
|
1596
|
+
return { payload: null, isExpired: false };
|
|
1597
|
+
}
|
|
1598
|
+
try {
|
|
1599
|
+
// Use a more compatible JWT decoding approach
|
|
1600
|
+
// Split JWT into parts
|
|
1601
|
+
const parts = jwtToken.split('.');
|
|
1602
|
+
if (parts.length !== 3) {
|
|
1603
|
+
throw new Error('Invalid JWT format');
|
|
1604
|
+
}
|
|
1605
|
+
// Decode the payload (middle part)
|
|
1606
|
+
const payload = JSON.parse(atob(parts[1]));
|
|
1607
|
+
// Check expiration
|
|
1608
|
+
const isExpired = payload.exp ? Date.now() >= payload.exp * 1000 : false;
|
|
1609
|
+
return { payload, isExpired };
|
|
1610
|
+
}
|
|
1611
|
+
catch (error) {
|
|
1612
|
+
console.warn('JWT decode failed:', error);
|
|
1613
|
+
return { payload: null, isExpired: false };
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
/**
|
|
1618
|
+
* In-memory User Cache
|
|
1619
|
+
*
|
|
1620
|
+
* Provides secure, storage-free user authentication caching
|
|
1621
|
+
* Uses memory-only storage with TTL expiration for security
|
|
1622
|
+
*/
|
|
1623
|
+
/**
|
|
1624
|
+
* In-memory user cache with TTL expiration
|
|
1625
|
+
* Security-first approach - no persistent storage
|
|
1626
|
+
*/
|
|
1627
|
+
class UserCache {
|
|
1643
1628
|
/**
|
|
1644
|
-
*
|
|
1645
|
-
*
|
|
1629
|
+
* Get cached user by identifier
|
|
1630
|
+
* @param identifier - User identifier (email/userId)
|
|
1631
|
+
* @returns Cached user or null if not found/expired
|
|
1646
1632
|
*/
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
if (!user.persAccessToken) {
|
|
1652
|
-
throw new Error('PERS access token required for transaction signing');
|
|
1653
|
-
}
|
|
1654
|
-
try {
|
|
1655
|
-
// Use the existing high-level service with injected WebAuthn provider
|
|
1656
|
-
const result = await this.transactionSigningService.signTransaction({
|
|
1657
|
-
transactionId,
|
|
1658
|
-
authTokens: {
|
|
1659
|
-
backendAuthToken: user.signerAuthToken,
|
|
1660
|
-
persAccessToken: user.persAccessToken
|
|
1661
|
-
},
|
|
1662
|
-
ethersProviderUrl: this.config.ethersProviderUrl || 'https://sepolia.infura.io/v3/2781b4b5242343d5b0954c98f287b29e'
|
|
1663
|
-
});
|
|
1664
|
-
return {
|
|
1665
|
-
success: result.success,
|
|
1666
|
-
transactionHash: result.transactionHash,
|
|
1667
|
-
error: result.error
|
|
1668
|
-
};
|
|
1633
|
+
static get(identifier) {
|
|
1634
|
+
const cached = this.cache.get(identifier);
|
|
1635
|
+
if (!cached) {
|
|
1636
|
+
return null;
|
|
1669
1637
|
}
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
};
|
|
1638
|
+
// Check if expired
|
|
1639
|
+
if (Date.now() > cached.expiresAt) {
|
|
1640
|
+
this.cache.delete(identifier);
|
|
1641
|
+
return null;
|
|
1675
1642
|
}
|
|
1643
|
+
return cached.user;
|
|
1676
1644
|
}
|
|
1677
|
-
// ========================================================================
|
|
1678
|
-
// GRANULAR METHODS (For Custom Integrations)
|
|
1679
|
-
// ========================================================================
|
|
1680
1645
|
/**
|
|
1681
|
-
*
|
|
1646
|
+
* Cache user with TTL
|
|
1647
|
+
* @param identifier - User identifier
|
|
1648
|
+
* @param user - Authenticated user data
|
|
1649
|
+
* @param ttlMs - Time to live in milliseconds (default: 5 minutes)
|
|
1682
1650
|
*/
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
}
|
|
1689
|
-
catch (error) {
|
|
1690
|
-
throw new Error(`User registration failed: ${error}`);
|
|
1691
|
-
}
|
|
1651
|
+
static set(identifier, user, ttlMs = this.DEFAULT_TTL_MS) {
|
|
1652
|
+
this.cache.set(identifier, {
|
|
1653
|
+
user,
|
|
1654
|
+
expiresAt: Date.now() + ttlMs
|
|
1655
|
+
});
|
|
1692
1656
|
}
|
|
1693
1657
|
/**
|
|
1694
|
-
*
|
|
1658
|
+
* Remove user from cache
|
|
1659
|
+
* @param identifier - User identifier
|
|
1695
1660
|
*/
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
return await this.authService.login(identifier);
|
|
1699
|
-
}
|
|
1700
|
-
catch (error) {
|
|
1701
|
-
throw new Error(`User login failed: ${error}`);
|
|
1702
|
-
}
|
|
1661
|
+
static delete(identifier) {
|
|
1662
|
+
return this.cache.delete(identifier);
|
|
1703
1663
|
}
|
|
1704
1664
|
/**
|
|
1705
|
-
*
|
|
1665
|
+
* Clear all cached users
|
|
1706
1666
|
*/
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
const persResponse = await PersService.authenticateUser(signerAuthToken);
|
|
1710
|
-
if (!persResponse?.accessToken) {
|
|
1711
|
-
throw new Error('Failed to get PERS access token');
|
|
1712
|
-
}
|
|
1713
|
-
return persResponse.accessToken;
|
|
1714
|
-
}
|
|
1715
|
-
catch (error) {
|
|
1716
|
-
throw new Error(`Failed to add wallet to PERS user: ${error}`);
|
|
1717
|
-
}
|
|
1667
|
+
static clear() {
|
|
1668
|
+
this.cache.clear();
|
|
1718
1669
|
}
|
|
1719
1670
|
/**
|
|
1720
|
-
*
|
|
1671
|
+
* Get cache size (for debugging)
|
|
1721
1672
|
*/
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
return await PersService.fetchPreparedTransaction(transactionId, persAccessToken);
|
|
1725
|
-
}
|
|
1726
|
-
catch (error) {
|
|
1727
|
-
// Preserve original error structure for proper error handling
|
|
1728
|
-
throw error;
|
|
1729
|
-
}
|
|
1673
|
+
static size() {
|
|
1674
|
+
return this.cache.size;
|
|
1730
1675
|
}
|
|
1731
1676
|
/**
|
|
1732
|
-
*
|
|
1733
|
-
* Follows same patterns as KeyWallet.signPersTransaction
|
|
1677
|
+
* Clean up expired entries
|
|
1734
1678
|
*/
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
// Sign based on transaction format - same pattern as KeyWallet
|
|
1743
|
-
switch (signingData.format) {
|
|
1744
|
-
case browser.TRANSACTION_FORMATS.EIP_712: {
|
|
1745
|
-
const eip712Data = signingData;
|
|
1746
|
-
const typedData = eip712Data.typedData;
|
|
1747
|
-
// Sign using same pattern as KeyWallet
|
|
1748
|
-
const signResponse = await this.signingService.signTypedData(signerAuthToken, walletId, typedData.domain, { Transaction: typedData.types.Transaction }, typedData.message);
|
|
1749
|
-
return `${signResponse.signature?.r}${signResponse.signature?.s}`;
|
|
1750
|
-
}
|
|
1751
|
-
default: {
|
|
1752
|
-
// For legacy and other formats, use raw signing data
|
|
1753
|
-
const signResponse = await this.signingService.signHash(signerAuthToken, walletId, JSON.stringify(signingData));
|
|
1754
|
-
return `${signResponse.signature?.r}${signResponse.signature?.s}`;
|
|
1755
|
-
}
|
|
1679
|
+
static cleanup() {
|
|
1680
|
+
const now = Date.now();
|
|
1681
|
+
let cleaned = 0;
|
|
1682
|
+
for (const [identifier, cached] of this.cache.entries()) {
|
|
1683
|
+
if (now > cached.expiresAt) {
|
|
1684
|
+
this.cache.delete(identifier);
|
|
1685
|
+
cleaned++;
|
|
1756
1686
|
}
|
|
1757
1687
|
}
|
|
1758
|
-
|
|
1759
|
-
throw new Error(`Transaction signing failed: ${error}`);
|
|
1760
|
-
}
|
|
1688
|
+
return cleaned;
|
|
1761
1689
|
}
|
|
1690
|
+
}
|
|
1691
|
+
UserCache.cache = new Map();
|
|
1692
|
+
UserCache.DEFAULT_TTL_MS = 300000; // 5 minutes
|
|
1693
|
+
|
|
1694
|
+
/**
|
|
1695
|
+
* PERS Blockchain Signer SDK
|
|
1696
|
+
*
|
|
1697
|
+
* A lightweight blockchain transaction signing SDK with WebAuthn authentication.
|
|
1698
|
+
* Provides 5 focused methods for complete transaction lifecycle management:
|
|
1699
|
+
*
|
|
1700
|
+
* 1. loginUser(jwtToken) - Authenticate user with 5-minute caching
|
|
1701
|
+
* 2. signTransaction(signingData, jwtToken) - Sign transactions with auto-login
|
|
1702
|
+
* 3. submitTransaction(signedTx, jwtToken) - Submit signed transactions to blockchain
|
|
1703
|
+
* 4. signPersTransaction(jwtToken) - Legacy one-liner for backward compatibility
|
|
1704
|
+
* 5. signAndSubmitPersTransaction(jwtToken) - Complete sign + submit flow
|
|
1705
|
+
*
|
|
1706
|
+
* @example
|
|
1707
|
+
* ```typescript
|
|
1708
|
+
* import { createPersSignerSDK } from '@explorins/pers-signer';
|
|
1709
|
+
*
|
|
1710
|
+
* const sdk = createPersSignerSDK({
|
|
1711
|
+
* webAuthnProvider: myWebAuthnProvider,
|
|
1712
|
+
* ethersProviderUrl: 'https://ethereum-rpc.com'
|
|
1713
|
+
* });
|
|
1714
|
+
*
|
|
1715
|
+
* // Quick sign and submit
|
|
1716
|
+
* const result = await sdk.signAndSubmitPersTransaction(jwtToken);
|
|
1717
|
+
* if (result.success) {
|
|
1718
|
+
* console.log('Transaction submitted:', result.transactionHash);
|
|
1719
|
+
* }
|
|
1720
|
+
* ```
|
|
1721
|
+
*/
|
|
1722
|
+
/**
|
|
1723
|
+
* PERS Blockchain Signer SDK Class
|
|
1724
|
+
*
|
|
1725
|
+
* Main SDK class providing blockchain transaction signing capabilities with WebAuthn authentication.
|
|
1726
|
+
* Implements a clean 5-method API for complete transaction lifecycle management.
|
|
1727
|
+
*
|
|
1728
|
+
* Features:
|
|
1729
|
+
* - WebAuthn-based secure authentication
|
|
1730
|
+
* - 5-minute user session caching
|
|
1731
|
+
* - Automatic transaction data fetching
|
|
1732
|
+
* - Blockchain transaction signing and submission
|
|
1733
|
+
* - Multi-tenant support
|
|
1734
|
+
*
|
|
1735
|
+
* @class PersSignerSDK
|
|
1736
|
+
*/
|
|
1737
|
+
class PersSignerSDK {
|
|
1762
1738
|
/**
|
|
1763
|
-
*
|
|
1739
|
+
* Initialize the PERS Signer SDK
|
|
1740
|
+
*
|
|
1741
|
+
* @param {ExtendedPersSignerConfig} config - SDK configuration object
|
|
1742
|
+
* @throws {Error} If required configuration is missing
|
|
1764
1743
|
*/
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1744
|
+
constructor(config) {
|
|
1745
|
+
this.config = config;
|
|
1746
|
+
setConfigProvider(new WebConfigProvider({
|
|
1747
|
+
apiUrl: config.apiUrl || SIGNER_CONFIG.DEFAULT_SIGNER_API_URL,
|
|
1748
|
+
relyingParty: {
|
|
1749
|
+
id: typeof window !== 'undefined' ? window.location.hostname : 'localhost',
|
|
1750
|
+
name: config.relyingPartyName || SIGNER_CONFIG.DEFAULT_RELYING_PARTY_NAME,
|
|
1751
|
+
origin: typeof window !== 'undefined' ? window.location.origin : undefined,
|
|
1752
|
+
},
|
|
1753
|
+
}));
|
|
1754
|
+
this.authenticationService = new AuthenticationService(this.config);
|
|
1755
|
+
this.transactionSigningService = new TransactionSigningService(config);
|
|
1776
1756
|
}
|
|
1777
1757
|
/**
|
|
1778
|
-
*
|
|
1758
|
+
* Authenticate user and cache session for 5 minutes
|
|
1759
|
+
*
|
|
1760
|
+
* Validates JWT token, authenticates with both signer and PERS backends,
|
|
1761
|
+
* and caches the authenticated user session to avoid repeated authentication.
|
|
1762
|
+
*
|
|
1763
|
+
* @param {string} jwtToken - JWT token containing user identifier and tenant info
|
|
1764
|
+
* @returns {Promise<AuthenticatedUser>} Authenticated user with access tokens
|
|
1765
|
+
* @throws {Error} If JWT is invalid, expired, or authentication fails
|
|
1766
|
+
*
|
|
1767
|
+
* @example
|
|
1768
|
+
* ```typescript
|
|
1769
|
+
* try {
|
|
1770
|
+
* const user = await sdk.loginUser(jwtToken);
|
|
1771
|
+
* console.log('Authenticated:', user.identifier);
|
|
1772
|
+
* } catch (error) {
|
|
1773
|
+
* console.error('Authentication failed:', error.message);
|
|
1774
|
+
* }
|
|
1775
|
+
* ```
|
|
1779
1776
|
*/
|
|
1780
|
-
async
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
return true;
|
|
1777
|
+
async loginUser(jwtToken) {
|
|
1778
|
+
if (!jwtToken || typeof jwtToken !== 'string') {
|
|
1779
|
+
throw new Error('JWT token is required and must be a string');
|
|
1784
1780
|
}
|
|
1785
|
-
|
|
1786
|
-
|
|
1781
|
+
const { payload, isExpired } = extractJWTFromToken(jwtToken);
|
|
1782
|
+
if (!payload || isExpired) {
|
|
1783
|
+
throw new Error('Invalid or expired JWT token');
|
|
1787
1784
|
}
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
async getUserWallets(signerAuthToken) {
|
|
1793
|
-
try {
|
|
1794
|
-
return await WalletService.listWallets(signerAuthToken);
|
|
1785
|
+
const identifier = payload.identifierEmail || payload.email || payload.userId;
|
|
1786
|
+
const tenantId = payload.tenantId || '';
|
|
1787
|
+
if (!identifier) {
|
|
1788
|
+
throw new Error('JWT token missing user identifier (identifierEmail, email, or userId)');
|
|
1795
1789
|
}
|
|
1796
|
-
|
|
1797
|
-
|
|
1790
|
+
// Check cache first
|
|
1791
|
+
const cachedUser = UserCache.get(identifier);
|
|
1792
|
+
if (cachedUser && cachedUser.tenantId === tenantId && Date.now() < cachedUser.expiresAt) {
|
|
1793
|
+
return cachedUser;
|
|
1798
1794
|
}
|
|
1799
|
-
}
|
|
1800
|
-
/**
|
|
1801
|
-
* Get transaction status - uses existing PersService
|
|
1802
|
-
*/
|
|
1803
|
-
async getTransactionStatus(transactionId, persAccessToken) {
|
|
1804
1795
|
try {
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1796
|
+
// Authenticate and cache
|
|
1797
|
+
const authResult = await this.authenticationService.combinedAuthentication(identifier, jwtToken);
|
|
1798
|
+
const user = {
|
|
1799
|
+
identifier: authResult.identifier,
|
|
1800
|
+
signerAuthToken: authResult.signerAuthToken,
|
|
1801
|
+
persAccessToken: authResult.persAccessToken,
|
|
1802
|
+
tenantId,
|
|
1803
|
+
expiresAt: Date.now() + 300000 // 5 minutes
|
|
1809
1804
|
};
|
|
1805
|
+
UserCache.set(identifier, user);
|
|
1806
|
+
return user;
|
|
1810
1807
|
}
|
|
1811
1808
|
catch (error) {
|
|
1812
|
-
throw new Error(`
|
|
1809
|
+
throw new Error(`Authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
1813
1810
|
}
|
|
1814
1811
|
}
|
|
1815
1812
|
/**
|
|
1816
|
-
*
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
*
|
|
1826
|
-
*
|
|
1813
|
+
* Sign a PERS transaction (legacy compatibility method)
|
|
1814
|
+
*
|
|
1815
|
+
* Automatically handles user authentication, transaction data fetching,
|
|
1816
|
+
* and transaction signing in a single call. This is the legacy method
|
|
1817
|
+
* maintained for backward compatibility.
|
|
1818
|
+
*
|
|
1819
|
+
* @param {string} jwtToken - JWT token containing transaction ID and user info
|
|
1820
|
+
* @returns {Promise<TransactionSigningResult>} Signing result with signature and metadata
|
|
1821
|
+
* @throws {Error} If authentication fails, transaction ID missing, or signing fails
|
|
1822
|
+
*
|
|
1823
|
+
* @example
|
|
1824
|
+
* ```typescript
|
|
1825
|
+
* try {
|
|
1826
|
+
* const result = await sdk.signPersTransaction(jwtToken);
|
|
1827
|
+
* if (result.success) {
|
|
1828
|
+
* console.log('Transaction signed:', result.signature);
|
|
1829
|
+
* }
|
|
1830
|
+
* } catch (error) {
|
|
1831
|
+
* console.error('Signing failed:', error.message);
|
|
1832
|
+
* }
|
|
1833
|
+
* ```
|
|
1827
1834
|
*/
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1835
|
+
async signPersTransaction(jwtToken) {
|
|
1836
|
+
if (!jwtToken || typeof jwtToken !== 'string') {
|
|
1837
|
+
throw new Error('JWT token is required and must be a string');
|
|
1838
|
+
}
|
|
1839
|
+
const user = await this.loginUser(jwtToken);
|
|
1840
|
+
const { payload } = extractJWTFromToken(jwtToken);
|
|
1841
|
+
if (!payload?.transactionId) {
|
|
1842
|
+
throw new Error('JWT token missing transactionId in payload');
|
|
1843
|
+
}
|
|
1844
|
+
const authTokens = {
|
|
1845
|
+
signerAuthToken: user.signerAuthToken,
|
|
1846
|
+
persAccessToken: user.persAccessToken
|
|
1847
|
+
};
|
|
1833
1848
|
try {
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1849
|
+
const persSigningData = await this.transactionSigningService.getPersSigningData({
|
|
1850
|
+
transactionId: payload.transactionId,
|
|
1851
|
+
authTokens,
|
|
1852
|
+
tenantId: user.tenantId
|
|
1853
|
+
});
|
|
1854
|
+
const result = await this.signTransaction(persSigningData, jwtToken);
|
|
1855
|
+
if (!result.success) {
|
|
1856
|
+
throw new Error(result.error || 'Transaction signing failed');
|
|
1839
1857
|
}
|
|
1840
|
-
|
|
1841
|
-
const payload = JSON.parse(atob(parts[1]));
|
|
1842
|
-
// Check expiration
|
|
1843
|
-
const isExpired = payload.exp ? Date.now() >= payload.exp * 1000 : false;
|
|
1844
|
-
return { payload, isExpired };
|
|
1858
|
+
return result;
|
|
1845
1859
|
}
|
|
1846
1860
|
catch (error) {
|
|
1847
|
-
|
|
1848
|
-
return { payload: null, isExpired: false };
|
|
1861
|
+
throw new Error(`PERS transaction signing failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
1849
1862
|
}
|
|
1850
1863
|
}
|
|
1851
1864
|
/**
|
|
1852
|
-
*
|
|
1865
|
+
* Sign a transaction with provided signing data
|
|
1866
|
+
*
|
|
1867
|
+
* Low-level method to sign transactions when you already have the signing data.
|
|
1868
|
+
* Automatically handles user authentication and applies the blockchain signature.
|
|
1869
|
+
*
|
|
1870
|
+
* @param {CounterfactualWalletTransactionResponse | LegacyTransaction} signingData - Transaction data to sign
|
|
1871
|
+
* @param {string} jwtToken - JWT token containing transaction ID and user info
|
|
1872
|
+
* @returns {Promise<TransactionSigningResult>} Signing result with signature and metadata
|
|
1873
|
+
* @throws {Error} If authentication fails, transaction ID missing, or signing fails
|
|
1874
|
+
*
|
|
1875
|
+
* @example
|
|
1876
|
+
* ```typescript
|
|
1877
|
+
* const signingData = await getTransactionData(transactionId);
|
|
1878
|
+
* const result = await sdk.signTransaction(signingData, jwtToken);
|
|
1879
|
+
* console.log('Signed transaction:', result.signature);
|
|
1880
|
+
* ```
|
|
1853
1881
|
*/
|
|
1854
|
-
async
|
|
1855
|
-
if (!
|
|
1856
|
-
|
|
1882
|
+
async signTransaction(signingData, jwtToken) {
|
|
1883
|
+
if (!signingData) {
|
|
1884
|
+
throw new Error('Signing data is required');
|
|
1885
|
+
}
|
|
1886
|
+
if (!jwtToken || typeof jwtToken !== 'string') {
|
|
1887
|
+
throw new Error('JWT token is required and must be a string');
|
|
1888
|
+
}
|
|
1889
|
+
const user = await this.loginUser(jwtToken);
|
|
1890
|
+
const { payload } = extractJWTFromToken(jwtToken);
|
|
1891
|
+
if (!payload?.transactionId) {
|
|
1892
|
+
throw new Error('JWT token missing transactionId in payload');
|
|
1857
1893
|
}
|
|
1894
|
+
const authTokens = {
|
|
1895
|
+
signerAuthToken: user.signerAuthToken,
|
|
1896
|
+
persAccessToken: user.persAccessToken
|
|
1897
|
+
};
|
|
1858
1898
|
try {
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
return projectKey;
|
|
1899
|
+
const result = await this.transactionSigningService.signTransaction({
|
|
1900
|
+
transactionId: payload.transactionId,
|
|
1901
|
+
tenantId: user.tenantId,
|
|
1902
|
+
authTokens,
|
|
1903
|
+
ethersProviderUrl: this.config.ethersProviderUrl || ''
|
|
1904
|
+
}, signingData);
|
|
1905
|
+
return result;
|
|
1867
1906
|
}
|
|
1868
1907
|
catch (error) {
|
|
1869
|
-
|
|
1870
|
-
return payload.tenantId;
|
|
1908
|
+
throw new Error(`Transaction signing failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
1871
1909
|
}
|
|
1872
1910
|
}
|
|
1873
1911
|
/**
|
|
1874
|
-
*
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
*
|
|
1912
|
+
* Complete transaction flow: sign and submit in one call
|
|
1913
|
+
*
|
|
1914
|
+
* Convenience method that combines signing and submission into a single operation.
|
|
1915
|
+
* This is the recommended method for most use cases as it handles the complete
|
|
1916
|
+
* transaction lifecycle automatically.
|
|
1917
|
+
*
|
|
1918
|
+
* @param {string} jwtToken - JWT token containing transaction ID and user info
|
|
1919
|
+
* @returns {Promise<SubmissionResult>} Submission result with transaction hash and status
|
|
1920
|
+
* @throws {Error} If authentication, signing, or submission fails
|
|
1921
|
+
*
|
|
1922
|
+
* @example
|
|
1923
|
+
* ```typescript
|
|
1924
|
+
* try {
|
|
1925
|
+
* const result = await sdk.signAndSubmitPersTransaction(jwtToken);
|
|
1926
|
+
* if (result.success) {
|
|
1927
|
+
* console.log('Transaction completed:', result.transactionHash);
|
|
1928
|
+
* if (result.shouldRedirect) {
|
|
1929
|
+
* window.location.href = result.redirectUrl;
|
|
1930
|
+
* }
|
|
1931
|
+
* }
|
|
1932
|
+
* } catch (error) {
|
|
1933
|
+
* console.error('Transaction failed:', error.message);
|
|
1934
|
+
* }
|
|
1935
|
+
* ```
|
|
1878
1936
|
*/
|
|
1879
|
-
async
|
|
1937
|
+
async signAndSubmitPersTransaction(jwtToken) {
|
|
1938
|
+
if (!jwtToken || typeof jwtToken !== 'string') {
|
|
1939
|
+
throw new Error('JWT token is required and must be a string');
|
|
1940
|
+
}
|
|
1880
1941
|
try {
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
const result = await PersService.authenticateUser(jwtToken);
|
|
1885
|
-
// Convert to expected PersAuthResult format
|
|
1886
|
-
return {
|
|
1887
|
-
user: {
|
|
1888
|
-
email: result.user?.email || undefined,
|
|
1889
|
-
id: result.user?.id || undefined
|
|
1890
|
-
},
|
|
1891
|
-
accessToken: result.accessToken
|
|
1892
|
-
};
|
|
1942
|
+
const signedTx = await this.signPersTransaction(jwtToken);
|
|
1943
|
+
const submittedTx = await this.submitTransaction(signedTx, jwtToken);
|
|
1944
|
+
return submittedTx;
|
|
1893
1945
|
}
|
|
1894
1946
|
catch (error) {
|
|
1895
|
-
throw new Error(`
|
|
1947
|
+
throw new Error(`Sign and submit transaction failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
1896
1948
|
}
|
|
1897
1949
|
}
|
|
1898
1950
|
/**
|
|
1899
|
-
*
|
|
1951
|
+
* Submit a signed transaction to the blockchain
|
|
1952
|
+
*
|
|
1953
|
+
* Takes a signed transaction result and submits it to the blockchain network.
|
|
1954
|
+
* Returns detailed submission results including transaction hash and any
|
|
1955
|
+
* redirect information for UI flows.
|
|
1956
|
+
*
|
|
1957
|
+
* @param {TransactionSigningResult} signingResult - Result from a successful transaction signing
|
|
1958
|
+
* @param {string} jwtToken - JWT token containing tenant and user info
|
|
1959
|
+
* @returns {Promise<SubmissionResult>} Submission result with transaction hash and status
|
|
1960
|
+
* @throws {Error} If signing result is invalid, JWT is missing tenantId, or submission fails
|
|
1961
|
+
*
|
|
1962
|
+
* @example
|
|
1963
|
+
* ```typescript
|
|
1964
|
+
* const signedTx = await sdk.signPersTransaction(jwtToken);
|
|
1965
|
+
* const result = await sdk.submitTransaction(signedTx, jwtToken);
|
|
1966
|
+
* console.log('Transaction submitted:', result.transactionHash);
|
|
1967
|
+
* ```
|
|
1900
1968
|
*/
|
|
1901
|
-
async
|
|
1969
|
+
async submitTransaction(signingResult, jwtToken) {
|
|
1970
|
+
if (!signingResult) {
|
|
1971
|
+
throw new Error('Signing result is required');
|
|
1972
|
+
}
|
|
1973
|
+
if (!jwtToken || typeof jwtToken !== 'string') {
|
|
1974
|
+
throw new Error('JWT token is required and must be a string');
|
|
1975
|
+
}
|
|
1976
|
+
if (!signingResult.success || !signingResult.signature || !signingResult.signingData) {
|
|
1977
|
+
throw new Error('Invalid signing result: must be successful with signature and signing data');
|
|
1978
|
+
}
|
|
1979
|
+
const { payload } = extractJWTFromToken(jwtToken);
|
|
1980
|
+
if (!payload?.tenantId) {
|
|
1981
|
+
throw new Error('JWT token missing tenantId in payload');
|
|
1982
|
+
}
|
|
1983
|
+
const user = await this.loginUser(jwtToken);
|
|
1902
1984
|
try {
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
try {
|
|
1906
|
-
signerAuthToken = await this.loginUser(identifier);
|
|
1907
|
-
}
|
|
1908
|
-
catch (loginError) {
|
|
1909
|
-
console.log(`[PersSignerSDK] User not found, registering new user: ${identifier}`);
|
|
1910
|
-
// User doesn't exist - register new user (registerUser already includes login)
|
|
1911
|
-
const registrationResult = await this.registerUser(identifier);
|
|
1912
|
-
signerAuthToken = registrationResult.authToken;
|
|
1913
|
-
}
|
|
1985
|
+
const submitResult = await PersService.submitTransaction(signingResult.transactionId, signingResult.signature, user.persAccessToken, signingResult.signingData.format, payload.tenantId);
|
|
1986
|
+
// Transform response to SubmissionResult
|
|
1914
1987
|
return {
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1988
|
+
success: true,
|
|
1989
|
+
transactionHash: submitResult.transaction.transactionHash,
|
|
1990
|
+
shouldRedirect: false, // Can be configured based on business requirements
|
|
1991
|
+
redirectUrl: undefined, // Can be set based on business logic
|
|
1992
|
+
error: undefined,
|
|
1993
|
+
submitResult: submitResult
|
|
1918
1994
|
};
|
|
1919
1995
|
}
|
|
1920
1996
|
catch (error) {
|
|
1921
|
-
throw new Error(`
|
|
1997
|
+
throw new Error(`Transaction submission failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
1922
1998
|
}
|
|
1923
1999
|
}
|
|
1924
2000
|
/**
|
|
1925
|
-
*
|
|
2001
|
+
* Clear user authentication cache
|
|
2002
|
+
*
|
|
2003
|
+
* Removes all cached user sessions, forcing fresh authentication
|
|
2004
|
+
* on the next method call. Useful for logout scenarios or when
|
|
2005
|
+
* switching between different user contexts.
|
|
2006
|
+
*
|
|
2007
|
+
* @example
|
|
2008
|
+
* ```typescript
|
|
2009
|
+
* // Clear cache on user logout
|
|
2010
|
+
* sdk.clearCache();
|
|
2011
|
+
* console.log('User cache cleared');
|
|
2012
|
+
* ```
|
|
1926
2013
|
*/
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
const { payload, isExpired } = this.extractJWTFromURL(searchParams);
|
|
1930
|
-
if (!payload) {
|
|
1931
|
-
// Return null instead of throwing error to allow graceful handling
|
|
1932
|
-
return null;
|
|
1933
|
-
}
|
|
1934
|
-
if (isExpired) {
|
|
1935
|
-
return {
|
|
1936
|
-
user: {
|
|
1937
|
-
identifier: payload.email || payload.userId || 'unknown',
|
|
1938
|
-
signerAuthToken: '',
|
|
1939
|
-
persAccessToken: ''
|
|
1940
|
-
},
|
|
1941
|
-
isExpired: true
|
|
1942
|
-
};
|
|
1943
|
-
}
|
|
1944
|
-
// 2. Initialize tenant and get project key
|
|
1945
|
-
const projectKey = await this.initializeTenantFromJWT(payload);
|
|
1946
|
-
// 3. Get JWT token from URL
|
|
1947
|
-
const jwtToken = searchParams.get('jwt') || searchParams.get('token');
|
|
1948
|
-
if (!jwtToken) {
|
|
1949
|
-
throw new Error('JWT token not found in URL parameters');
|
|
1950
|
-
}
|
|
1951
|
-
// 4. Authenticate with PERS
|
|
1952
|
-
const persResult = await this.authenticatePersUser(jwtToken, projectKey);
|
|
1953
|
-
// 5. Get identifier for DFNS authentication
|
|
1954
|
-
const identifier = persResult.user.email || persResult.user.id;
|
|
1955
|
-
if (!identifier) {
|
|
1956
|
-
throw new Error('No identifier found in PERS response');
|
|
1957
|
-
}
|
|
1958
|
-
// 6. Combined authentication
|
|
1959
|
-
const user = await this.combinedAuthentication(identifier, persResult.accessToken);
|
|
1960
|
-
return {
|
|
1961
|
-
user,
|
|
1962
|
-
isExpired: false
|
|
1963
|
-
};
|
|
1964
|
-
}
|
|
1965
|
-
}
|
|
1966
|
-
|
|
1967
|
-
/**
|
|
1968
|
-
* Utility functions for handling URL search parameters in a consistent way across applications
|
|
1969
|
-
*/
|
|
1970
|
-
/**
|
|
1971
|
-
* Creates a URL search string that preserves all current parameters
|
|
1972
|
-
* @param paramsToExclude Array of parameter names to exclude from the result
|
|
1973
|
-
* @param paramsToAdd Object with additional parameters to add or override
|
|
1974
|
-
* @param baseParams Optional URLSearchParams object to use instead of window.location.search
|
|
1975
|
-
* @returns A search string with '?' prefix if there are parameters, or empty string if no parameters
|
|
1976
|
-
*/
|
|
1977
|
-
function createSearchString(paramsToExclude = [], paramsToAdd = {}, baseParams) {
|
|
1978
|
-
// Get base search params from the provided value or window location
|
|
1979
|
-
const currentParams = new URLSearchParams(baseParams instanceof URLSearchParams
|
|
1980
|
-
? baseParams
|
|
1981
|
-
: baseParams !== undefined
|
|
1982
|
-
? baseParams
|
|
1983
|
-
: (typeof window !== 'undefined' ? window.location.search : ''));
|
|
1984
|
-
// Remove excluded parameters
|
|
1985
|
-
paramsToExclude.forEach(param => {
|
|
1986
|
-
if (currentParams.has(param)) {
|
|
1987
|
-
currentParams.delete(param);
|
|
1988
|
-
}
|
|
1989
|
-
});
|
|
1990
|
-
// Add or update new parameters
|
|
1991
|
-
Object.entries(paramsToAdd).forEach(([key, value]) => {
|
|
1992
|
-
if (value !== undefined && value !== null) {
|
|
1993
|
-
currentParams.set(key, value);
|
|
1994
|
-
}
|
|
1995
|
-
});
|
|
1996
|
-
const searchString = currentParams.toString();
|
|
1997
|
-
return searchString ? `?${searchString}` : '';
|
|
1998
|
-
}
|
|
1999
|
-
/**
|
|
2000
|
-
* Creates a URL path with search parameters
|
|
2001
|
-
* @param basePath The base path without search parameters
|
|
2002
|
-
* @param paramsToExclude Array of parameter names to exclude from the result
|
|
2003
|
-
* @param paramsToAdd Object with additional parameters to add or override
|
|
2004
|
-
* @param baseParams Optional URLSearchParams object to use instead of window.location.search
|
|
2005
|
-
* @returns A full path with search parameters
|
|
2006
|
-
*/
|
|
2007
|
-
function createUrlWithSearchParams(basePath, paramsToExclude = [], paramsToAdd = {}, baseParams) {
|
|
2008
|
-
const searchString = createSearchString(paramsToExclude, paramsToAdd, baseParams);
|
|
2009
|
-
return `${basePath}${searchString}`;
|
|
2010
|
-
}
|
|
2011
|
-
/**
|
|
2012
|
-
* Combines the current search parameters with new ones
|
|
2013
|
-
* @param searchParams The current URLSearchParams object
|
|
2014
|
-
* @param additionalParams Object with parameters to add or update
|
|
2015
|
-
* @returns A new URLSearchParams object
|
|
2016
|
-
*/
|
|
2017
|
-
function mergeSearchParams(searchParams, additionalParams = {}) {
|
|
2018
|
-
const merged = new URLSearchParams(searchParams.toString());
|
|
2019
|
-
Object.entries(additionalParams).forEach(([key, value]) => {
|
|
2020
|
-
if (value !== undefined && value !== null) {
|
|
2021
|
-
merged.set(key, value);
|
|
2022
|
-
}
|
|
2023
|
-
});
|
|
2024
|
-
return merged;
|
|
2025
|
-
}
|
|
2026
|
-
/**
|
|
2027
|
-
* Gets a specific search parameter value
|
|
2028
|
-
* @param key The parameter name to get
|
|
2029
|
-
* @param search Optional search string to parse (defaults to window.location.search)
|
|
2030
|
-
* @returns The parameter value or null if not found
|
|
2031
|
-
*/
|
|
2032
|
-
function getSearchParam(key, search) {
|
|
2033
|
-
const params = new URLSearchParams(search || (typeof window !== 'undefined' ? window.location.search : ''));
|
|
2034
|
-
return params.get(key);
|
|
2035
|
-
}
|
|
2036
|
-
/**
|
|
2037
|
-
* Gets all search parameters as a URLSearchParams object
|
|
2038
|
-
* @param search Optional search string to parse (defaults to window.location.search)
|
|
2039
|
-
* @returns A URLSearchParams object
|
|
2040
|
-
*/
|
|
2041
|
-
function getAllSearchParams(search) {
|
|
2042
|
-
return new URLSearchParams(search || (typeof window !== 'undefined' ? window.location.search : ''));
|
|
2043
|
-
}
|
|
2044
|
-
/**
|
|
2045
|
-
* Removes a specific search parameter
|
|
2046
|
-
* @param key The parameter name to remove
|
|
2047
|
-
* @param search Optional search string to parse (defaults to window.location.search)
|
|
2048
|
-
* @returns A new search string without the specified parameter
|
|
2049
|
-
*/
|
|
2050
|
-
function removeSearchParam(key, search) {
|
|
2051
|
-
const params = new URLSearchParams(search || (typeof window !== 'undefined' ? window.location.search : ''));
|
|
2052
|
-
params.delete(key);
|
|
2053
|
-
const searchString = params.toString();
|
|
2054
|
-
return searchString ? `?${searchString}` : '';
|
|
2055
|
-
}
|
|
2056
|
-
/**
|
|
2057
|
-
* Updates or adds a specific search parameter
|
|
2058
|
-
* @param key The parameter name to update
|
|
2059
|
-
* @param value The new value for the parameter
|
|
2060
|
-
* @param search Optional search string to parse (defaults to window.location.search)
|
|
2061
|
-
* @returns A new search string with the updated parameter
|
|
2062
|
-
*/
|
|
2063
|
-
function updateSearchParam(key, value, search) {
|
|
2064
|
-
const params = new URLSearchParams(search || (typeof window !== 'undefined' ? window.location.search : ''));
|
|
2065
|
-
params.set(key, value);
|
|
2066
|
-
const searchString = params.toString();
|
|
2067
|
-
return searchString ? `?${searchString}` : '';
|
|
2068
|
-
}
|
|
2069
|
-
|
|
2070
|
-
var searchParams = /*#__PURE__*/Object.freeze({
|
|
2071
|
-
__proto__: null,
|
|
2072
|
-
createSearchString: createSearchString,
|
|
2073
|
-
createUrlWithSearchParams: createUrlWithSearchParams,
|
|
2074
|
-
getAllSearchParams: getAllSearchParams,
|
|
2075
|
-
getSearchParam: getSearchParam,
|
|
2076
|
-
mergeSearchParams: mergeSearchParams,
|
|
2077
|
-
removeSearchParam: removeSearchParam,
|
|
2078
|
-
updateSearchParam: updateSearchParam
|
|
2079
|
-
});
|
|
2080
|
-
|
|
2081
|
-
/**
|
|
2082
|
-
* Utility for debugging search parameter handling
|
|
2083
|
-
*/
|
|
2084
|
-
/**
|
|
2085
|
-
* Logs the current search parameters with a custom message
|
|
2086
|
-
* This is useful for debugging search parameter preservation across navigation
|
|
2087
|
-
*
|
|
2088
|
-
* @param message - A descriptive message to identify where the logging happens
|
|
2089
|
-
* @param additionalData - Optional additional data to log
|
|
2090
|
-
*/
|
|
2091
|
-
function logSearchParams(message, additionalData) {
|
|
2092
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
2093
|
-
// Check if window exists (for SSR compatibility)
|
|
2094
|
-
if (typeof window === 'undefined') {
|
|
2095
|
-
return;
|
|
2096
|
-
}
|
|
2097
|
-
const searchParams = new URLSearchParams(window.location.search);
|
|
2098
|
-
searchParams.forEach((value, key) => {
|
|
2099
|
-
});
|
|
2100
|
-
console.group(`🔍 Search Params: ${message}`);
|
|
2101
|
-
console.groupEnd();
|
|
2014
|
+
clearCache() {
|
|
2015
|
+
UserCache.clear();
|
|
2102
2016
|
}
|
|
2103
2017
|
}
|
|
2104
|
-
/**
|
|
2105
|
-
* Creates a search parameter tracking hook that can be used in React components
|
|
2106
|
-
* Logs search parameter changes when the component mounts/updates
|
|
2107
|
-
*
|
|
2108
|
-
* @param componentName - The name of the component (for logging)
|
|
2109
|
-
*/
|
|
2110
|
-
function createSearchParamLogger(componentName) {
|
|
2111
|
-
return () => {
|
|
2112
|
-
logSearchParams(`${componentName} rendered`);
|
|
2113
|
-
};
|
|
2114
|
-
}
|
|
2115
|
-
|
|
2116
|
-
/**
|
|
2117
|
-
* Generates a RFC4122 version 4 compliant UUID
|
|
2118
|
-
* @returns A randomly generated UUID
|
|
2119
|
-
*/
|
|
2120
|
-
function generateUUID() {
|
|
2121
|
-
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
2122
|
-
const r = Math.random() * 16 | 0;
|
|
2123
|
-
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
2124
|
-
return v.toString(16);
|
|
2125
|
-
});
|
|
2126
|
-
}
|
|
2127
|
-
/**
|
|
2128
|
-
* Create a semi-anonymous username for users
|
|
2129
|
-
* @returns A username in the format "user_[random string]"
|
|
2130
|
-
*/
|
|
2131
|
-
function generateAnonymousUsername() {
|
|
2132
|
-
const randomPart = Math.random().toString(36).substring(2, 10);
|
|
2133
|
-
return `user_${randomPart}`;
|
|
2134
|
-
}
|
|
2135
|
-
/**
|
|
2136
|
-
* Create a guest username for the application with a random prefix
|
|
2137
|
-
* @returns A guest email in the format "randomstring_guest@explorins.com"
|
|
2138
|
-
*/
|
|
2139
|
-
function generateGuestUsername() {
|
|
2140
|
-
const randomPart = Math.random().toString(36).substring(2, 8); // 6 character random string
|
|
2141
|
-
return `${randomPart}_guest@explorins.com`;
|
|
2142
|
-
}
|
|
2143
|
-
|
|
2144
|
-
/**
|
|
2145
|
-
* Utility functions for tenant-specific operations
|
|
2146
|
-
*/
|
|
2147
|
-
/**
|
|
2148
|
-
* Initialize tenant-specific analytics
|
|
2149
|
-
* @param tenantId The tenant ID
|
|
2150
|
-
* @param getTenantConfig Function to retrieve tenant configuration
|
|
2151
|
-
*/
|
|
2152
|
-
const initTenantAnalytics = (tenantId, getTenantConfig) => {
|
|
2153
|
-
const tenant = getTenantConfig(tenantId);
|
|
2154
|
-
if (!tenant) {
|
|
2155
|
-
console.warn(`Tenant ${tenantId} not found`);
|
|
2156
|
-
return;
|
|
2157
|
-
}
|
|
2158
|
-
if (tenant.analytics && tenant.analytics.enabled && tenant.analytics.trackingId) ;
|
|
2159
|
-
};
|
|
2160
2018
|
|
|
2161
2019
|
/**
|
|
2162
2020
|
* Browser-specific WebAuthn provider using DFNS browser SDK
|
|
@@ -2211,20 +2069,6 @@ async function getBrowserWebAuthnProvider() {
|
|
|
2211
2069
|
* Includes only browser-compatible dependencies
|
|
2212
2070
|
*/
|
|
2213
2071
|
// Re-export all shared functionality
|
|
2214
|
-
/**
|
|
2215
|
-
* Create a new AuthenticationService instance with browser WebAuthn provider
|
|
2216
|
-
*/
|
|
2217
|
-
async function createAuthenticationService() {
|
|
2218
|
-
const webAuthnProvider = await getBrowserWebAuthnProvider();
|
|
2219
|
-
return new AuthenticationService(webAuthnProvider);
|
|
2220
|
-
}
|
|
2221
|
-
/**
|
|
2222
|
-
* Create a new SigningService instance with browser WebAuthn provider
|
|
2223
|
-
*/
|
|
2224
|
-
async function createSigningService() {
|
|
2225
|
-
const webAuthnProvider = await getBrowserWebAuthnProvider();
|
|
2226
|
-
return new SigningService(webAuthnProvider);
|
|
2227
|
-
}
|
|
2228
2072
|
/**
|
|
2229
2073
|
* Create a new PersSignerSDK instance with browser WebAuthn provider
|
|
2230
2074
|
* @param config - SDK configuration (ethersProviderUrl is required)
|
|
@@ -11472,27 +11316,12 @@ exports.TransactionValidator = TransactionValidator;
|
|
|
11472
11316
|
exports.WalletService = WalletService;
|
|
11473
11317
|
exports.WebAuthnCoordinator = WebAuthnCoordinator;
|
|
11474
11318
|
exports.WebConfigProvider = WebConfigProvider;
|
|
11475
|
-
exports.createAuthenticationService = createAuthenticationService;
|
|
11476
11319
|
exports.createPersSignerSDK = createPersSignerSDK;
|
|
11477
|
-
exports.createSearchParamLogger = createSearchParamLogger;
|
|
11478
|
-
exports.createSearchString = createSearchString;
|
|
11479
|
-
exports.createSigningService = createSigningService;
|
|
11480
|
-
exports.createUrlWithSearchParams = createUrlWithSearchParams;
|
|
11481
|
-
exports.generateAnonymousUsername = generateAnonymousUsername;
|
|
11482
|
-
exports.generateGuestUsername = generateGuestUsername;
|
|
11483
|
-
exports.generateUUID = generateUUID;
|
|
11484
|
-
exports.getAllSearchParams = getAllSearchParams;
|
|
11485
11320
|
exports.getBrowserWebAuthnProvider = getBrowserWebAuthnProvider;
|
|
11486
11321
|
exports.getConfigProvider = getConfigProvider;
|
|
11487
11322
|
exports.getHttpClient = getHttpClient;
|
|
11488
|
-
exports.getSearchParam = getSearchParam;
|
|
11489
11323
|
exports.getServiceConfig = getServiceConfig;
|
|
11490
11324
|
exports.getWebAuthnProvider = getBrowserWebAuthnProvider;
|
|
11491
|
-
exports.initTenantAnalytics = initTenantAnalytics;
|
|
11492
|
-
exports.logSearchParams = logSearchParams;
|
|
11493
|
-
exports.mergeSearchParams = mergeSearchParams;
|
|
11494
|
-
exports.removeSearchParam = removeSearchParam;
|
|
11495
11325
|
exports.setConfigProvider = setConfigProvider;
|
|
11496
11326
|
exports.setHttpClient = setHttpClient;
|
|
11497
|
-
exports.updateSearchParam = updateSearchParam;
|
|
11498
11327
|
//# sourceMappingURL=browser.cjs.js.map
|