@bsv/wallet-toolbox-client 2.0.0-beta.1 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/out/src/Wallet.d.ts +1 -1
  2. package/out/src/Wallet.d.ts.map +1 -1
  3. package/out/src/Wallet.js +23 -7
  4. package/out/src/Wallet.js.map +1 -1
  5. package/out/src/WalletPermissionsManager.d.ts +84 -1
  6. package/out/src/WalletPermissionsManager.d.ts.map +1 -1
  7. package/out/src/WalletPermissionsManager.js +1045 -214
  8. package/out/src/WalletPermissionsManager.js.map +1 -1
  9. package/out/src/sdk/WalletServices.interfaces.d.ts.map +1 -1
  10. package/out/src/sdk/WalletStorage.interfaces.d.ts +3 -3
  11. package/out/src/sdk/WalletStorage.interfaces.d.ts.map +1 -1
  12. package/out/src/services/chaintracker/chaintracks/Ingest/BulkIngestorWhatsOnChainCdn.d.ts +1 -1
  13. package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageBase.d.ts.map +1 -1
  14. package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageBase.js +4 -1
  15. package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageBase.js.map +1 -1
  16. package/out/src/services/chaintracker/chaintracks/util/blockHeaderUtilities.d.ts.map +1 -1
  17. package/out/src/services/chaintracker/chaintracks/util/blockHeaderUtilities.js +1 -2
  18. package/out/src/services/chaintracker/chaintracks/util/blockHeaderUtilities.js.map +1 -1
  19. package/out/src/services/chaintracker/chaintracks/util/validBulkHeaderFilesByFileHash.d.ts.map +1 -1
  20. package/out/src/services/chaintracker/chaintracks/util/validBulkHeaderFilesByFileHash.js +12 -0
  21. package/out/src/services/chaintracker/chaintracks/util/validBulkHeaderFilesByFileHash.js.map +1 -1
  22. package/out/src/storage/StorageProvider.d.ts +16 -2
  23. package/out/src/storage/StorageProvider.d.ts.map +1 -1
  24. package/out/src/storage/StorageProvider.js +33 -4
  25. package/out/src/storage/StorageProvider.js.map +1 -1
  26. package/out/src/storage/methods/ListOutputsSpecOp.d.ts +15 -1
  27. package/out/src/storage/methods/ListOutputsSpecOp.d.ts.map +1 -1
  28. package/out/src/storage/methods/ListOutputsSpecOp.js +52 -2
  29. package/out/src/storage/methods/ListOutputsSpecOp.js.map +1 -1
  30. package/out/src/storage/methods/getBeefForTransaction.js +1 -1
  31. package/out/src/storage/methods/getBeefForTransaction.js.map +1 -1
  32. package/out/src/storage/methods/internalizeAction.d.ts.map +1 -1
  33. package/out/src/storage/methods/internalizeAction.js +2 -2
  34. package/out/src/storage/methods/internalizeAction.js.map +1 -1
  35. package/out/src/storage/methods/listOutputsIdb.d.ts.map +1 -1
  36. package/out/src/storage/methods/listOutputsIdb.js +10 -17
  37. package/out/src/storage/methods/listOutputsIdb.js.map +1 -1
  38. package/out/src/wab-client/WABClient.d.ts +65 -0
  39. package/out/src/wab-client/WABClient.d.ts.map +1 -1
  40. package/out/src/wab-client/WABClient.js +107 -0
  41. package/out/src/wab-client/WABClient.js.map +1 -1
  42. package/package.json +2 -2
  43. package/out/src/sdk/validationHelpers.d.ts +0 -303
  44. package/out/src/sdk/validationHelpers.d.ts.map +0 -1
  45. package/out/src/sdk/validationHelpers.js +0 -632
  46. package/out/src/sdk/validationHelpers.js.map +0 -1
  47. package/out/src/utility/ReaderUint8Array.d.ts +0 -28
  48. package/out/src/utility/ReaderUint8Array.d.ts.map +0 -1
  49. package/out/src/utility/ReaderUint8Array.js +0 -166
  50. package/out/src/utility/ReaderUint8Array.js.map +0 -1
@@ -77,7 +77,8 @@ class WalletPermissionsManager {
77
77
  onBasketAccessRequested: [],
78
78
  onCertificateAccessRequested: [],
79
79
  onSpendingAuthorizationRequested: [],
80
- onGroupedPermissionRequested: []
80
+ onGroupedPermissionRequested: [],
81
+ onCounterpartyPermissionRequested: []
81
82
  };
82
83
  /**
83
84
  * We queue parallel requests for the same resource so that only one
@@ -93,6 +94,9 @@ class WalletPermissionsManager {
93
94
  /** Cache recently confirmed permissions to avoid repeated lookups. */
94
95
  this.permissionCache = new Map();
95
96
  this.recentGrants = new Map();
97
+ this.manifestCache = new Map();
98
+ this.manifestFetchInProgress = new Map();
99
+ this.pactEstablishedCache = new Map();
96
100
  this.underlying = underlyingWallet;
97
101
  this.adminOriginator = this.normalizeOriginator(adminOriginator) || adminOriginator;
98
102
  // Default all config options to true unless specified
@@ -117,6 +121,7 @@ class WalletPermissionsManager {
117
121
  seekSpendingPermissions: true,
118
122
  seekGroupedPermission: true,
119
123
  differentiatePrivilegedOperations: true,
124
+ whitelistedCounterparties: {},
120
125
  ...config // override with user-specified config
121
126
  };
122
127
  }
@@ -159,6 +164,68 @@ class WalletPermissionsManager {
159
164
  originator
160
165
  });
161
166
  }
167
+ /**
168
+ * Adds a permission module for the given schemeID if needed, throwing if unsupported.
169
+ */
170
+ addPModuleByScheme(schemeID, kind, pModulesByScheme) {
171
+ var _a;
172
+ if (pModulesByScheme.has(schemeID))
173
+ return;
174
+ const module = (_a = this.config.permissionModules) === null || _a === void 0 ? void 0 : _a[schemeID];
175
+ if (!module) {
176
+ throw new Error(`Unsupported P-${kind} scheme: p ${schemeID}`);
177
+ }
178
+ pModulesByScheme.set(schemeID, module);
179
+ }
180
+ /**
181
+ * Splits labels into P and non-P lists, registering any P-modules encountered.
182
+ *
183
+ * P-labels follow BRC-111 format: `p <moduleId> <payload>`
184
+ * - Must start with "p " (lowercase p + space)
185
+ * - Module ID must be at least 1 character with no spaces
186
+ * - Single space separates module ID from payload
187
+ * - Payload must be at least 1 character
188
+ *
189
+ * @example Valid: "p btms token123", "p invoicing invoice 2026-02-02"
190
+ * @example Invalid: "p btms" (no payload), "p btms " (empty payload), "p data" (empty moduleId)
191
+ *
192
+ * @param labels - Array of label strings to process
193
+ * @param pModulesByScheme - Map to populate with discovered p-modules
194
+ * @returns Array of non-P labels for normal permission checks
195
+ * @throws Error if p-label format is invalid or module is unsupported
196
+ */
197
+ splitLabelsByPermissionModule(labels, pModulesByScheme) {
198
+ const nonPLabels = [];
199
+ if (!labels)
200
+ return nonPLabels;
201
+ for (const label of labels) {
202
+ if (label.startsWith('p ')) {
203
+ // Remove "p " prefix to get "moduleId payload"
204
+ const remainder = label.slice(2);
205
+ // Find the space that separates moduleId from payload
206
+ const separatorIndex = remainder.indexOf(' ');
207
+ // Validate: must have a space (separatorIndex > 0) and payload after it
208
+ // separatorIndex <= 0 means no space found or moduleId is empty
209
+ // separatorIndex === remainder.length - 1 means space is last char (no payload)
210
+ if (separatorIndex <= 0 || separatorIndex === remainder.length - 1) {
211
+ throw new Error(`Invalid P-label format: ${label}`);
212
+ }
213
+ // Reject double spaces after moduleId (payload can't start with space)
214
+ if (remainder[separatorIndex + 1] === ' ') {
215
+ throw new Error(`Invalid P-label format: ${label}`);
216
+ }
217
+ // Extract moduleId (substring before first space)
218
+ const schemeID = remainder.slice(0, separatorIndex);
219
+ // Register the module (throws if unsupported)
220
+ this.addPModuleByScheme(schemeID, 'label', pModulesByScheme);
221
+ }
222
+ else {
223
+ // Regular label - add to list for normal permission checks
224
+ nonPLabels.push(label);
225
+ }
226
+ }
227
+ return nonPLabels;
228
+ }
162
229
  /**
163
230
  * Decrypts custom instructions in listOutputs results if encryption is configured.
164
231
  */
@@ -172,6 +239,36 @@ class WalletPermissionsManager {
172
239
  }
173
240
  return results;
174
241
  }
242
+ /**
243
+ * Decrypts metadata in listActions results if encryption is configured.
244
+ */
245
+ async decryptListActionsMetadata(results) {
246
+ if (results.actions) {
247
+ for (let i = 0; i < results.actions.length; i++) {
248
+ if (results.actions[i].description) {
249
+ results.actions[i].description = await this.maybeDecryptMetadata(results.actions[i].description);
250
+ }
251
+ if (results.actions[i].inputs) {
252
+ for (let j = 0; j < results.actions[i].inputs.length; j++) {
253
+ if (results.actions[i].inputs[j].inputDescription) {
254
+ results.actions[i].inputs[j].inputDescription = await this.maybeDecryptMetadata(results.actions[i].inputs[j].inputDescription);
255
+ }
256
+ }
257
+ }
258
+ if (results.actions[i].outputs) {
259
+ for (let j = 0; j < results.actions[i].outputs.length; j++) {
260
+ if (results.actions[i].outputs[j].outputDescription) {
261
+ results.actions[i].outputs[j].outputDescription = await this.maybeDecryptMetadata(results.actions[i].outputs[j].outputDescription);
262
+ }
263
+ if (results.actions[i].outputs[j].customInstructions) {
264
+ results.actions[i].outputs[j].customInstructions = await this.maybeDecryptMetadata(results.actions[i].outputs[j].customInstructions);
265
+ }
266
+ }
267
+ }
268
+ }
269
+ }
270
+ return results;
271
+ }
175
272
  /* ---------------------------------------------------------------------
176
273
  * 1) PUBLIC API FOR REGISTERING CALLBACKS (UI PROMPTS, LOGGING, ETC.)
177
274
  * --------------------------------------------------------------------- */
@@ -312,93 +409,177 @@ class WalletPermissionsManager {
312
409
  if (!matching) {
313
410
  throw new Error('Request ID not found.');
314
411
  }
315
- const originalRequest = matching.request;
316
- const { originator, permissions: requestedPermissions, displayOriginator } = originalRequest;
317
- const originLookupValues = this.buildOriginatorLookupValues(displayOriginator, originator);
318
- // --- Validation: Ensure granted permissions are a subset of what was requested ---
319
- if (params.granted.spendingAuthorization &&
320
- !deepEqual(params.granted.spendingAuthorization, requestedPermissions.spendingAuthorization)) {
321
- throw new Error('Granted spending authorization does not match the original request.');
322
- }
323
- if ((_a = params.granted.protocolPermissions) === null || _a === void 0 ? void 0 : _a.some(g => { var _a; return !((_a = requestedPermissions.protocolPermissions) === null || _a === void 0 ? void 0 : _a.find(r => deepEqual(r, g))); })) {
324
- throw new Error('Granted protocol permissions are not a subset of the original request.');
325
- }
326
- if ((_b = params.granted.basketAccess) === null || _b === void 0 ? void 0 : _b.some(g => { var _a; return !((_a = requestedPermissions.basketAccess) === null || _a === void 0 ? void 0 : _a.find(r => deepEqual(r, g))); })) {
327
- throw new Error('Granted basket access permissions are not a subset of the original request.');
328
- }
329
- if ((_c = params.granted.certificateAccess) === null || _c === void 0 ? void 0 : _c.some(g => { var _a; return !((_a = requestedPermissions.certificateAccess) === null || _a === void 0 ? void 0 : _a.find(r => deepEqual(r, g))); })) {
330
- throw new Error('Granted certificate access permissions are not a subset of the original request.');
331
- }
332
- // --- End Validation ---
333
- const expiry = params.expiry || 0; // default: never expires
334
- if (params.granted.spendingAuthorization) {
335
- await this.createPermissionOnChain({
336
- type: 'spending',
337
- originator,
338
- spending: { satoshis: params.granted.spendingAuthorization.amount },
339
- reason: params.granted.spendingAuthorization.description
340
- }, 0, // No expiry for spending tokens
341
- params.granted.spendingAuthorization.amount);
342
- }
343
- for (const p of params.granted.protocolPermissions || []) {
344
- const token = await this.findProtocolToken(originator, false, // No privileged protocols allowed in groups for added security.
345
- p.protocolID, p.counterparty || 'self', true, originLookupValues);
346
- if (token) {
347
- const request = {
348
- type: 'protocol',
349
- originator,
350
- privileged: false, // No privileged protocols allowed in groups for added security.
351
- protocolID: p.protocolID,
352
- counterparty: p.counterparty || 'self',
353
- reason: p.description
354
- };
355
- await this.renewPermissionOnChain(token, request, expiry);
356
- this.markRecentGrant(request);
412
+ try {
413
+ const originalRequest = matching.request;
414
+ const { originator, permissions: requestedPermissions, displayOriginator } = originalRequest;
415
+ const originLookupValues = this.buildOriginatorLookupValues(displayOriginator, originator);
416
+ // --- Validation: Ensure granted permissions are a subset of what was requested ---
417
+ if (params.granted.spendingAuthorization && !requestedPermissions.spendingAuthorization) {
418
+ throw new Error('Granted spending authorization was not part of the original request.');
357
419
  }
358
- else {
420
+ if ((_a = params.granted.protocolPermissions) === null || _a === void 0 ? void 0 : _a.some(g => { var _a; return !((_a = requestedPermissions.protocolPermissions) === null || _a === void 0 ? void 0 : _a.find(r => deepEqual(r, g))); })) {
421
+ throw new Error('Granted protocol permissions are not a subset of the original request.');
422
+ }
423
+ if ((_b = params.granted.basketAccess) === null || _b === void 0 ? void 0 : _b.some(g => { var _a; return !((_a = requestedPermissions.basketAccess) === null || _a === void 0 ? void 0 : _a.find(r => deepEqual(r, g))); })) {
424
+ throw new Error('Granted basket access permissions are not a subset of the original request.');
425
+ }
426
+ if ((_c = params.granted.certificateAccess) === null || _c === void 0 ? void 0 : _c.some(g => { var _a; return !((_a = requestedPermissions.certificateAccess) === null || _a === void 0 ? void 0 : _a.find(r => deepEqual(r, g))); })) {
427
+ throw new Error('Granted certificate access permissions are not a subset of the original request.');
428
+ }
429
+ // --- End Validation ---
430
+ const expiry = params.expiry || 0; // default: never expires
431
+ const toCreate = [];
432
+ const toRenew = [];
433
+ if (params.granted.spendingAuthorization) {
434
+ toCreate.push({
435
+ request: {
436
+ type: 'spending',
437
+ originator,
438
+ spending: { satoshis: params.granted.spendingAuthorization.amount },
439
+ reason: params.granted.spendingAuthorization.description
440
+ },
441
+ expiry: 0,
442
+ amount: params.granted.spendingAuthorization.amount
443
+ });
444
+ }
445
+ const grantedProtocols = params.granted.protocolPermissions || [];
446
+ const protocolTokens = await this.mapWithConcurrency(grantedProtocols, 8, async (p) => {
447
+ const token = await this.findProtocolToken(originator, false, p.protocolID, p.counterparty || 'self', true, originLookupValues);
448
+ return { p, token };
449
+ });
450
+ for (const { p, token } of protocolTokens) {
359
451
  const request = {
360
452
  type: 'protocol',
361
453
  originator,
362
- privileged: false, // No privileged protocols allowed in groups for added security.
454
+ privileged: false,
363
455
  protocolID: p.protocolID,
364
456
  counterparty: p.counterparty || 'self',
365
457
  reason: p.description
366
458
  };
367
- await this.createPermissionOnChain(request, expiry);
368
- this.markRecentGrant(request);
459
+ if (token) {
460
+ toRenew.push({ oldToken: token, request, expiry });
461
+ }
462
+ else {
463
+ toCreate.push({ request, expiry });
464
+ }
465
+ }
466
+ for (const b of params.granted.basketAccess || []) {
467
+ toCreate.push({
468
+ request: { type: 'basket', originator, basket: b.basket, reason: b.description },
469
+ expiry
470
+ });
471
+ }
472
+ for (const c of params.granted.certificateAccess || []) {
473
+ toCreate.push({
474
+ request: {
475
+ type: 'certificate',
476
+ originator,
477
+ privileged: false,
478
+ certificate: {
479
+ verifier: c.verifierPublicKey,
480
+ certType: c.type,
481
+ fields: c.fields
482
+ },
483
+ reason: c.description
484
+ },
485
+ expiry
486
+ });
487
+ }
488
+ const created = await this.createPermissionTokensBestEffort(toCreate);
489
+ const renewed = await this.renewPermissionTokensBestEffort(toRenew);
490
+ for (const req of [...created, ...renewed]) {
491
+ this.markRecentGrant(req);
492
+ }
493
+ // Success - resolve all pending promises for this request
494
+ for (const p of matching.pending) {
495
+ p.resolve(true);
369
496
  }
370
497
  }
371
- for (const b of params.granted.basketAccess || []) {
372
- const request = { type: 'basket', originator, basket: b.basket, reason: b.description };
373
- await this.createPermissionOnChain(request, expiry);
374
- this.markRecentGrant(request);
498
+ catch (error) {
499
+ // Failure - reject all pending promises so callers don't hang forever
500
+ for (const p of matching.pending) {
501
+ p.reject(error);
502
+ }
503
+ throw error;
504
+ }
505
+ finally {
506
+ // Always clean up the request entry
507
+ this.activeRequests.delete(params.requestID);
375
508
  }
376
- for (const c of params.granted.certificateAccess || []) {
509
+ }
510
+ /**
511
+ * Denies a previously requested grouped permission.
512
+ * @param requestID The ID of the request being denied.
513
+ */
514
+ async denyGroupedPermission(requestID) {
515
+ const matching = this.activeRequests.get(requestID);
516
+ if (!matching) {
517
+ throw new Error('Request ID not found.');
518
+ }
519
+ const err = new Error('The user has denied the request for permission.');
520
+ err.code = 'ERR_PERMISSION_DENIED';
521
+ for (const p of matching.pending) {
522
+ p.reject(err);
523
+ }
524
+ this.activeRequests.delete(requestID);
525
+ }
526
+ async dismissGroupedPermission(requestID) {
527
+ const matching = this.activeRequests.get(requestID);
528
+ if (!matching) {
529
+ throw new Error('Request ID not found.');
530
+ }
531
+ for (const p of matching.pending) {
532
+ p.resolve(true);
533
+ }
534
+ this.activeRequests.delete(requestID);
535
+ }
536
+ async grantCounterpartyPermission(params) {
537
+ var _a;
538
+ const matching = this.activeRequests.get(params.requestID);
539
+ if (!matching) {
540
+ throw new Error('Request ID not found.');
541
+ }
542
+ const originalRequest = matching.request;
543
+ const { originator, counterparty, permissions: requestedPermissions, displayOriginator } = originalRequest;
544
+ const originLookupValues = this.buildOriginatorLookupValues(displayOriginator, originator);
545
+ if ((_a = params.granted.protocols) === null || _a === void 0 ? void 0 : _a.some(g => !requestedPermissions.protocols.find(r => deepEqual(r, g)))) {
546
+ throw new Error('Granted protocol permissions are not a subset of the original request.');
547
+ }
548
+ const expiry = params.expiry || 0;
549
+ const toCreate = [];
550
+ const toRenew = [];
551
+ const grantedProtocols = params.granted.protocols || [];
552
+ const protocolTokens = await this.mapWithConcurrency(grantedProtocols, 8, async (p) => {
553
+ const token = await this.findProtocolToken(originator, false, p.protocolID, counterparty, true, originLookupValues);
554
+ return { p, token };
555
+ });
556
+ for (const { p, token } of protocolTokens) {
377
557
  const request = {
378
- type: 'certificate',
558
+ type: 'protocol',
379
559
  originator,
380
- privileged: false, // No certificates on the privileged identity are allowed as part of groups.
381
- certificate: {
382
- verifier: c.verifierPublicKey,
383
- certType: c.type,
384
- fields: c.fields
385
- },
386
- reason: c.description
560
+ privileged: false,
561
+ protocolID: p.protocolID,
562
+ counterparty,
563
+ reason: p.description
387
564
  };
388
- await this.createPermissionOnChain(request, expiry);
389
- this.markRecentGrant(request);
565
+ if (token) {
566
+ toRenew.push({ oldToken: token, request, expiry });
567
+ }
568
+ else {
569
+ toCreate.push({ request, expiry });
570
+ }
571
+ }
572
+ const created = await this.createPermissionTokensBestEffort(toCreate);
573
+ const renewed = await this.renewPermissionTokensBestEffort(toRenew);
574
+ for (const req of [...created, ...renewed]) {
575
+ this.markRecentGrant(req);
390
576
  }
391
- // Resolve all pending promises for this request
392
577
  for (const p of matching.pending) {
393
578
  p.resolve(true);
394
579
  }
395
580
  this.activeRequests.delete(params.requestID);
396
581
  }
397
- /**
398
- * Denies a previously requested grouped permission.
399
- * @param requestID The ID of the request being denied.
400
- */
401
- async denyGroupedPermission(requestID) {
582
+ async denyCounterpartyPermission(requestID) {
402
583
  const matching = this.activeRequests.get(requestID);
403
584
  if (!matching) {
404
585
  throw new Error('Request ID not found.');
@@ -453,6 +634,9 @@ class WalletPermissionsManager {
453
634
  if (!this.config.differentiatePrivilegedOperations) {
454
635
  privileged = false;
455
636
  }
637
+ if (!privileged && this.isWhitelistedCounterpartyProtocol(counterparty, protocolID)) {
638
+ return true;
639
+ }
456
640
  const cacheKey = this.buildRequestKey({
457
641
  type: 'protocol',
458
642
  originator,
@@ -728,6 +912,469 @@ class WalletPermissionsManager {
728
912
  usageType: 'generic'
729
913
  });
730
914
  }
915
+ validateCounterpartyPermissions(raw) {
916
+ if (!raw || !Array.isArray(raw.protocols) || raw.protocols.length === 0)
917
+ return null;
918
+ const validProtocols = raw.protocols.filter((p) => {
919
+ return (Array.isArray(p === null || p === void 0 ? void 0 : p.protocolID) &&
920
+ p.protocolID[0] === 2 &&
921
+ typeof p.protocolID[1] === 'string' &&
922
+ typeof (p === null || p === void 0 ? void 0 : p.description) === 'string');
923
+ });
924
+ if (validProtocols.length === 0)
925
+ return null;
926
+ return {
927
+ description: typeof raw.description === 'string' ? raw.description : undefined,
928
+ protocols: validProtocols
929
+ };
930
+ }
931
+ async fetchManifestPermissions(originator) {
932
+ const cached = this.manifestCache.get(originator);
933
+ if (cached && Date.now() - cached.fetchedAt < WalletPermissionsManager.MANIFEST_CACHE_TTL_MS) {
934
+ return {
935
+ groupPermissions: cached.groupPermissions,
936
+ counterpartyPermissions: cached.counterpartyPermissions
937
+ };
938
+ }
939
+ const inProgress = this.manifestFetchInProgress.get(originator);
940
+ if (inProgress) {
941
+ return inProgress;
942
+ }
943
+ const fetchPromise = (async () => {
944
+ var _a, _b;
945
+ try {
946
+ const proto = originator.startsWith('localhost:') ? 'http' : 'https';
947
+ const response = await fetch(`${proto}://${originator}/manifest.json`);
948
+ if (response.ok) {
949
+ const manifest = await response.json();
950
+ const groupPermissions = ((_a = manifest === null || manifest === void 0 ? void 0 : manifest.babbage) === null || _a === void 0 ? void 0 : _a.groupPermissions) || null;
951
+ const counterpartyPermissions = this.validateCounterpartyPermissions((_b = manifest === null || manifest === void 0 ? void 0 : manifest.babbage) === null || _b === void 0 ? void 0 : _b.counterpartyPermissions);
952
+ this.manifestCache.set(originator, { groupPermissions, counterpartyPermissions, fetchedAt: Date.now() });
953
+ return { groupPermissions, counterpartyPermissions };
954
+ }
955
+ }
956
+ catch (e) { }
957
+ const result = { groupPermissions: null, counterpartyPermissions: null };
958
+ this.manifestCache.set(originator, { ...result, fetchedAt: Date.now() });
959
+ return result;
960
+ })();
961
+ this.manifestFetchInProgress.set(originator, fetchPromise);
962
+ try {
963
+ return await fetchPromise;
964
+ }
965
+ finally {
966
+ this.manifestFetchInProgress.delete(originator);
967
+ }
968
+ }
969
+ async fetchManifestGroupPermissions(originator) {
970
+ const { groupPermissions } = await this.fetchManifestPermissions(originator);
971
+ return groupPermissions;
972
+ }
973
+ async filterAlreadyGrantedPermissions(originator, groupPermissions) {
974
+ const permissionsToRequest = {
975
+ description: groupPermissions.description,
976
+ protocolPermissions: [],
977
+ basketAccess: [],
978
+ certificateAccess: []
979
+ };
980
+ if (groupPermissions.spendingAuthorization) {
981
+ const hasAuth = await this.hasSpendingAuthorization({
982
+ originator,
983
+ satoshis: groupPermissions.spendingAuthorization.amount
984
+ });
985
+ if (!hasAuth) {
986
+ permissionsToRequest.spendingAuthorization = groupPermissions.spendingAuthorization;
987
+ }
988
+ }
989
+ for (const p of groupPermissions.protocolPermissions || []) {
990
+ const hasPerm = await this.hasProtocolPermission({
991
+ originator,
992
+ privileged: false,
993
+ protocolID: p.protocolID,
994
+ counterparty: p.counterparty || 'self'
995
+ });
996
+ if (!hasPerm) {
997
+ permissionsToRequest.protocolPermissions.push(p);
998
+ }
999
+ }
1000
+ for (const b of groupPermissions.basketAccess || []) {
1001
+ const hasAccess = await this.hasBasketAccess({
1002
+ originator,
1003
+ basket: b.basket
1004
+ });
1005
+ if (!hasAccess) {
1006
+ permissionsToRequest.basketAccess.push(b);
1007
+ }
1008
+ }
1009
+ for (const c of groupPermissions.certificateAccess || []) {
1010
+ const hasAccess = await this.hasCertificateAccess({
1011
+ originator,
1012
+ privileged: false,
1013
+ verifier: c.verifierPublicKey,
1014
+ certType: c.type,
1015
+ fields: c.fields
1016
+ });
1017
+ if (!hasAccess) {
1018
+ permissionsToRequest.certificateAccess.push(c);
1019
+ }
1020
+ }
1021
+ return permissionsToRequest;
1022
+ }
1023
+ hasAnyPermissionsToRequest(permissions) {
1024
+ var _a, _b, _c, _d, _e, _f;
1025
+ return !!(permissions.spendingAuthorization ||
1026
+ ((_b = (_a = permissions.protocolPermissions) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0 ||
1027
+ ((_d = (_c = permissions.basketAccess) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0) > 0 ||
1028
+ ((_f = (_e = permissions.certificateAccess) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0) > 0);
1029
+ }
1030
+ hasGroupedPermissionRequestedHandlers() {
1031
+ const handlers = this.callbacks.onGroupedPermissionRequested || [];
1032
+ return handlers.some(h => typeof h === 'function');
1033
+ }
1034
+ hasCounterpartyPermissionRequestedHandlers() {
1035
+ const handlers = this.callbacks.onCounterpartyPermissionRequested || [];
1036
+ return handlers.some(h => typeof h === 'function');
1037
+ }
1038
+ async hasPactEstablished(originator, counterparty) {
1039
+ var _a;
1040
+ if (counterparty === 'self' || counterparty === 'anyone') {
1041
+ return true;
1042
+ }
1043
+ const cacheKey = `${originator}:${counterparty}`;
1044
+ if (this.pactEstablishedCache.has(cacheKey)) {
1045
+ return true;
1046
+ }
1047
+ const { counterpartyPermissions } = await this.fetchManifestPermissions(originator);
1048
+ if (!((_a = counterpartyPermissions === null || counterpartyPermissions === void 0 ? void 0 : counterpartyPermissions.protocols) === null || _a === void 0 ? void 0 : _a.length)) {
1049
+ return true;
1050
+ }
1051
+ const firstProtocol = counterpartyPermissions.protocols[0];
1052
+ const hasToken = await this.hasProtocolPermission({
1053
+ originator,
1054
+ privileged: false,
1055
+ protocolID: firstProtocol.protocolID,
1056
+ counterparty
1057
+ });
1058
+ if (hasToken) {
1059
+ this.pactEstablishedCache.set(cacheKey, Date.now());
1060
+ return true;
1061
+ }
1062
+ return false;
1063
+ }
1064
+ markPactEstablished(originator, counterparty) {
1065
+ const cacheKey = `${originator}:${counterparty}`;
1066
+ this.pactEstablishedCache.set(cacheKey, Date.now());
1067
+ }
1068
+ async maybeRequestPact(currentRequest) {
1069
+ var _a;
1070
+ if (!this.config.seekGroupedPermission) {
1071
+ return null;
1072
+ }
1073
+ if (!this.hasCounterpartyPermissionRequestedHandlers()) {
1074
+ return null;
1075
+ }
1076
+ if (currentRequest.type !== 'protocol') {
1077
+ return null;
1078
+ }
1079
+ if (currentRequest.privileged) {
1080
+ return null;
1081
+ }
1082
+ const [level] = currentRequest.protocolID;
1083
+ if (level !== 2) {
1084
+ return null;
1085
+ }
1086
+ const originator = currentRequest.originator;
1087
+ const counterparty = currentRequest.counterparty;
1088
+ if (!counterparty || counterparty === 'self' || counterparty === 'anyone') {
1089
+ return null;
1090
+ }
1091
+ if (!/^[0-9a-fA-F]{66}$/.test(counterparty)) {
1092
+ return null;
1093
+ }
1094
+ if (await this.hasPactEstablished(originator, counterparty)) {
1095
+ return null;
1096
+ }
1097
+ const { counterpartyPermissions } = await this.fetchManifestPermissions(originator);
1098
+ if (!((_a = counterpartyPermissions === null || counterpartyPermissions === void 0 ? void 0 : counterpartyPermissions.protocols) === null || _a === void 0 ? void 0 : _a.length)) {
1099
+ return null;
1100
+ }
1101
+ const protocolsToRequest = [];
1102
+ for (const p of counterpartyPermissions.protocols) {
1103
+ const hasPerm = await this.hasProtocolPermission({
1104
+ originator,
1105
+ privileged: false,
1106
+ protocolID: p.protocolID,
1107
+ counterparty
1108
+ });
1109
+ if (!hasPerm) {
1110
+ protocolsToRequest.push(p);
1111
+ }
1112
+ }
1113
+ if (protocolsToRequest.length === 0) {
1114
+ this.markPactEstablished(originator, counterparty);
1115
+ return null;
1116
+ }
1117
+ const permissionsToRequest = {
1118
+ description: counterpartyPermissions.description,
1119
+ protocols: protocolsToRequest
1120
+ };
1121
+ const key = `pact:${originator}:${counterparty}`;
1122
+ const existing = this.activeRequests.get(key);
1123
+ if (existing) {
1124
+ const existingRequest = existing.request;
1125
+ for (const p of permissionsToRequest.protocols) {
1126
+ if (!existingRequest.permissions.protocols.find(x => deepEqual(x, p))) {
1127
+ existingRequest.permissions.protocols.push(p);
1128
+ }
1129
+ }
1130
+ await new Promise((resolve, reject) => {
1131
+ existing.pending.push({ resolve, reject });
1132
+ });
1133
+ }
1134
+ else {
1135
+ await new Promise(async (resolve, reject) => {
1136
+ this.activeRequests.set(key, {
1137
+ request: {
1138
+ originator,
1139
+ counterparty,
1140
+ permissions: permissionsToRequest,
1141
+ displayOriginator: currentRequest.displayOriginator
1142
+ },
1143
+ pending: [{ resolve, reject }]
1144
+ });
1145
+ await this.callEvent('onCounterpartyPermissionRequested', {
1146
+ requestID: key,
1147
+ originator,
1148
+ counterparty,
1149
+ permissions: permissionsToRequest
1150
+ });
1151
+ });
1152
+ }
1153
+ this.markPactEstablished(originator, counterparty);
1154
+ const satisfied = await this.hasProtocolPermission({
1155
+ originator,
1156
+ privileged: false,
1157
+ protocolID: currentRequest.protocolID,
1158
+ counterparty
1159
+ });
1160
+ return satisfied ? true : null;
1161
+ }
1162
+ async maybeRequestPeerGroupedLevel2ProtocolPermissions(currentRequest) {
1163
+ var _a, _b;
1164
+ if (!this.config.seekGroupedPermission) {
1165
+ return null;
1166
+ }
1167
+ if (!this.hasGroupedPermissionRequestedHandlers()) {
1168
+ return null;
1169
+ }
1170
+ if (currentRequest.type !== 'protocol') {
1171
+ return null;
1172
+ }
1173
+ const [level] = currentRequest.protocolID;
1174
+ if (level !== 2) {
1175
+ return null;
1176
+ }
1177
+ const originator = currentRequest.originator;
1178
+ const privileged = (_a = currentRequest.privileged) !== null && _a !== void 0 ? _a : false;
1179
+ const counterparty = (_b = currentRequest.counterparty) !== null && _b !== void 0 ? _b : 'self';
1180
+ const groupPermissions = await this.fetchManifestGroupPermissions(originator);
1181
+ if (!groupPermissions) {
1182
+ return null;
1183
+ }
1184
+ const normalizeManifestCounterparty = (cp) => {
1185
+ if (cp === '')
1186
+ return counterparty;
1187
+ return cp !== null && cp !== void 0 ? cp : 'self';
1188
+ };
1189
+ const manifestLevel2ForThisPeer = (groupPermissions.protocolPermissions || [])
1190
+ .filter(p => { var _a, _b; return ((_b = (_a = p.protocolID) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 0) === 2; })
1191
+ .map(p => ({
1192
+ protocolID: p.protocolID,
1193
+ counterparty: normalizeManifestCounterparty(p.counterparty),
1194
+ description: p.description
1195
+ }))
1196
+ .filter(p => p.counterparty === counterparty);
1197
+ const isCurrentRequestInManifest = manifestLevel2ForThisPeer.some(p => deepEqual(p.protocolID, currentRequest.protocolID));
1198
+ if (!isCurrentRequestInManifest) {
1199
+ return null;
1200
+ }
1201
+ const permissionsToRequest = {
1202
+ protocolPermissions: []
1203
+ };
1204
+ for (const p of manifestLevel2ForThisPeer) {
1205
+ const hasPerm = await this.hasProtocolPermission({
1206
+ originator,
1207
+ privileged,
1208
+ protocolID: p.protocolID,
1209
+ counterparty: p.counterparty
1210
+ });
1211
+ if (!hasPerm) {
1212
+ permissionsToRequest.protocolPermissions.push({
1213
+ protocolID: p.protocolID,
1214
+ counterparty: p.counterparty,
1215
+ description: p.description
1216
+ });
1217
+ }
1218
+ }
1219
+ if (!this.hasAnyPermissionsToRequest(permissionsToRequest)) {
1220
+ return null;
1221
+ }
1222
+ const key = `group-peer:${originator}:${privileged}:${counterparty}`;
1223
+ const existing = this.activeRequests.get(key);
1224
+ if (existing) {
1225
+ const existingRequest = existing.request;
1226
+ if (!existingRequest.permissions.protocolPermissions) {
1227
+ existingRequest.permissions.protocolPermissions = [];
1228
+ }
1229
+ for (const p of permissionsToRequest.protocolPermissions || []) {
1230
+ if (!existingRequest.permissions.protocolPermissions.find(x => deepEqual(x, p))) {
1231
+ existingRequest.permissions.protocolPermissions.push(p);
1232
+ }
1233
+ }
1234
+ await new Promise((resolve, reject) => {
1235
+ existing.pending.push({ resolve, reject });
1236
+ });
1237
+ }
1238
+ else {
1239
+ await new Promise(async (resolve, reject) => {
1240
+ const permissions = permissionsToRequest;
1241
+ this.activeRequests.set(key, {
1242
+ request: {
1243
+ originator,
1244
+ permissions,
1245
+ displayOriginator: currentRequest.displayOriginator
1246
+ },
1247
+ pending: [{ resolve, reject }]
1248
+ });
1249
+ await this.callEvent('onGroupedPermissionRequested', {
1250
+ requestID: key,
1251
+ originator,
1252
+ permissions
1253
+ });
1254
+ });
1255
+ }
1256
+ const satisfied = await this.checkSpecificPermissionAfterGroupFlow(currentRequest);
1257
+ return satisfied ? true : null;
1258
+ }
1259
+ async checkSpecificPermissionAfterGroupFlow(request) {
1260
+ var _a, _b, _c;
1261
+ switch (request.type) {
1262
+ case 'protocol':
1263
+ return await this.hasProtocolPermission({
1264
+ originator: request.originator,
1265
+ privileged: (_a = request.privileged) !== null && _a !== void 0 ? _a : false,
1266
+ protocolID: request.protocolID,
1267
+ counterparty: (_b = request.counterparty) !== null && _b !== void 0 ? _b : 'self'
1268
+ });
1269
+ case 'basket':
1270
+ return await this.hasBasketAccess({
1271
+ originator: request.originator,
1272
+ basket: request.basket
1273
+ });
1274
+ case 'certificate':
1275
+ return await this.hasCertificateAccess({
1276
+ originator: request.originator,
1277
+ privileged: (_c = request.privileged) !== null && _c !== void 0 ? _c : false,
1278
+ verifier: request.certificate.verifier,
1279
+ certType: request.certificate.certType,
1280
+ fields: request.certificate.fields
1281
+ });
1282
+ case 'spending':
1283
+ return await this.hasSpendingAuthorization({
1284
+ originator: request.originator,
1285
+ satoshis: request.spending.satoshis
1286
+ });
1287
+ default:
1288
+ return false;
1289
+ }
1290
+ }
1291
+ isRequestIncludedInGroupPermissions(request, groupPermissions) {
1292
+ var _a, _b, _c, _d;
1293
+ switch (request.type) {
1294
+ case 'protocol': {
1295
+ if (request.privileged)
1296
+ return false;
1297
+ const pid = request.protocolID;
1298
+ if (!pid)
1299
+ return false;
1300
+ const cp = (_a = request.counterparty) !== null && _a !== void 0 ? _a : 'self';
1301
+ return !!((_b = groupPermissions.protocolPermissions) === null || _b === void 0 ? void 0 : _b.some(p => {
1302
+ var _a;
1303
+ const manifestCp = p.counterparty === '' ? cp : ((_a = p.counterparty) !== null && _a !== void 0 ? _a : 'self');
1304
+ return deepEqual(p.protocolID, pid) && manifestCp === cp;
1305
+ }));
1306
+ }
1307
+ case 'basket': {
1308
+ const basket = request.basket;
1309
+ if (!basket)
1310
+ return false;
1311
+ return !!((_c = groupPermissions.basketAccess) === null || _c === void 0 ? void 0 : _c.some(b => b.basket === basket));
1312
+ }
1313
+ case 'certificate': {
1314
+ if (request.privileged)
1315
+ return false;
1316
+ const cert = request.certificate;
1317
+ if (!cert)
1318
+ return false;
1319
+ return !!((_d = groupPermissions.certificateAccess) === null || _d === void 0 ? void 0 : _d.some(c => {
1320
+ const fieldsA = new Set(c.fields || []);
1321
+ const fieldsB = new Set(cert.fields || []);
1322
+ if (fieldsA.size !== fieldsB.size)
1323
+ return false;
1324
+ for (const f of fieldsA)
1325
+ if (!fieldsB.has(f))
1326
+ return false;
1327
+ return c.type === cert.certType && c.verifierPublicKey === cert.verifier;
1328
+ }));
1329
+ }
1330
+ case 'spending':
1331
+ return !!groupPermissions.spendingAuthorization;
1332
+ default:
1333
+ return false;
1334
+ }
1335
+ }
1336
+ async maybeRequestGroupedPermissions(currentRequest) {
1337
+ if (!this.config.seekGroupedPermission) {
1338
+ return null;
1339
+ }
1340
+ const originator = currentRequest.originator;
1341
+ const groupPermissions = await this.fetchManifestGroupPermissions(originator);
1342
+ if (!groupPermissions) {
1343
+ return null;
1344
+ }
1345
+ if (!this.isRequestIncludedInGroupPermissions(currentRequest, groupPermissions)) {
1346
+ return null;
1347
+ }
1348
+ const permissionsToRequest = await this.filterAlreadyGrantedPermissions(originator, groupPermissions);
1349
+ if (!this.hasAnyPermissionsToRequest(permissionsToRequest)) {
1350
+ return null;
1351
+ }
1352
+ const key = `group:${originator}`;
1353
+ if (this.activeRequests.has(key)) {
1354
+ await new Promise((resolve, reject) => {
1355
+ this.activeRequests.get(key).pending.push({ resolve, reject });
1356
+ });
1357
+ }
1358
+ else {
1359
+ await new Promise(async (resolve, reject) => {
1360
+ this.activeRequests.set(key, {
1361
+ request: {
1362
+ originator,
1363
+ permissions: permissionsToRequest,
1364
+ displayOriginator: currentRequest.displayOriginator
1365
+ },
1366
+ pending: [{ resolve, reject }]
1367
+ });
1368
+ await this.callEvent('onGroupedPermissionRequested', {
1369
+ requestID: key,
1370
+ originator,
1371
+ permissions: permissionsToRequest
1372
+ });
1373
+ });
1374
+ }
1375
+ const satisfied = await this.checkSpecificPermissionAfterGroupFlow(currentRequest);
1376
+ return satisfied ? true : null;
1377
+ }
731
1378
  /**
732
1379
  * A central method that triggers the permission request flow.
733
1380
  * - It checks if there's already an active request for the same key
@@ -736,13 +1383,25 @@ class WalletPermissionsManager {
736
1383
  * and return a promise that resolves once permission is granted or rejects if denied.
737
1384
  */
738
1385
  async requestPermissionFlow(r) {
739
- var _a;
1386
+ var _a, _b, _c;
740
1387
  const normalizedOriginator = this.normalizeOriginator(r.originator) || r.originator;
741
1388
  const preparedRequest = {
742
1389
  ...r,
743
1390
  originator: normalizedOriginator,
744
- displayOriginator: (_a = r.displayOriginator) !== null && _a !== void 0 ? _a : r.originator
1391
+ displayOriginator: (_c = (_a = r.displayOriginator) !== null && _a !== void 0 ? _a : (_b = r.previousToken) === null || _b === void 0 ? void 0 : _b.rawOriginator) !== null && _c !== void 0 ? _c : r.originator
745
1392
  };
1393
+ const pactResult = await this.maybeRequestPact(preparedRequest);
1394
+ if (pactResult !== null) {
1395
+ return pactResult;
1396
+ }
1397
+ const peerGroupResult = await this.maybeRequestPeerGroupedLevel2ProtocolPermissions(preparedRequest);
1398
+ if (peerGroupResult !== null) {
1399
+ return peerGroupResult;
1400
+ }
1401
+ const groupResult = await this.maybeRequestGroupedPermissions(preparedRequest);
1402
+ if (groupResult !== null) {
1403
+ return groupResult;
1404
+ }
746
1405
  const key = this.buildRequestKey(preparedRequest);
747
1406
  // If there's already a queue for the same resource, we piggyback on it
748
1407
  const existingQueue = this.activeRequests.get(key);
@@ -1196,10 +1855,128 @@ class WalletPermissionsManager {
1196
1855
  }
1197
1856
  ],
1198
1857
  options: {
1199
- acceptDelayedBroadcast: false
1858
+ acceptDelayedBroadcast: true
1200
1859
  }
1201
1860
  }, this.adminOriginator);
1202
1861
  }
1862
+ async mapWithConcurrency(items, concurrency, fn) {
1863
+ if (!items.length)
1864
+ return [];
1865
+ const results = new Array(items.length);
1866
+ let i = 0;
1867
+ const worker = async () => {
1868
+ while (true) {
1869
+ const idx = i++;
1870
+ if (idx >= items.length)
1871
+ return;
1872
+ results[idx] = await fn(items[idx]);
1873
+ }
1874
+ };
1875
+ await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()));
1876
+ return results;
1877
+ }
1878
+ async runBestEffortBatches(items, chunkSize, runChunk) {
1879
+ if (!items.length)
1880
+ return [];
1881
+ const out = [];
1882
+ for (let i = 0; i < items.length; i += chunkSize) {
1883
+ const chunk = items.slice(i, i + chunkSize);
1884
+ out.push(...(await this.runBestEffortChunk(chunk, runChunk)));
1885
+ }
1886
+ return out;
1887
+ }
1888
+ async runBestEffortChunk(chunk, runChunk) {
1889
+ try {
1890
+ return await runChunk(chunk);
1891
+ }
1892
+ catch (e) {
1893
+ if (chunk.length <= 1) {
1894
+ console.error('Permission batch failed:', e);
1895
+ return [];
1896
+ }
1897
+ const mid = Math.ceil(chunk.length / 2);
1898
+ const left = await this.runBestEffortChunk(chunk.slice(0, mid), runChunk);
1899
+ const right = await this.runBestEffortChunk(chunk.slice(mid), runChunk);
1900
+ return [...left, ...right];
1901
+ }
1902
+ }
1903
+ async buildPermissionOutput(r, expiry, amount) {
1904
+ const normalizedOriginator = this.normalizeOriginator(r.originator) || r.originator;
1905
+ r.originator = normalizedOriginator;
1906
+ const basketName = BASKET_MAP[r.type];
1907
+ if (!basketName) {
1908
+ throw new Error(`Unsupported permission type: ${r.type}`);
1909
+ }
1910
+ const fields = await this.buildPushdropFields(r, expiry, amount);
1911
+ const script = await new sdk_1.PushDrop(this.underlying).lock(fields, WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL, '1', 'self', true, true);
1912
+ const tags = this.buildTagsForRequest(r);
1913
+ return {
1914
+ request: r,
1915
+ output: {
1916
+ lockingScript: script.toHex(),
1917
+ satoshis: 1,
1918
+ outputDescription: `${r.type} permission token`,
1919
+ basket: basketName,
1920
+ tags
1921
+ }
1922
+ };
1923
+ }
1924
+ async createPermissionTokensBestEffort(items) {
1925
+ const CHUNK = 25;
1926
+ return this.runBestEffortBatches(items, CHUNK, async (chunk) => {
1927
+ const built = await this.mapWithConcurrency(chunk, 8, c => this.buildPermissionOutput(c.request, c.expiry, c.amount));
1928
+ await this.createAction({
1929
+ description: `Grant ${built.length} permissions`,
1930
+ outputs: built.map(b => b.output),
1931
+ options: { acceptDelayedBroadcast: true }
1932
+ }, this.adminOriginator);
1933
+ return built.map(b => b.request);
1934
+ });
1935
+ }
1936
+ async renewPermissionTokensBestEffort(items) {
1937
+ const CHUNK = 15;
1938
+ return this.runBestEffortBatches(items, CHUNK, async (chunk) => {
1939
+ const built = await this.mapWithConcurrency(chunk, 8, c => this.buildPermissionOutput(c.request, c.expiry, c.amount));
1940
+ const inputBeef = new sdk_1.Beef();
1941
+ for (const c of chunk) {
1942
+ inputBeef.mergeBeef(sdk_1.Beef.fromBinary(c.oldToken.tx));
1943
+ }
1944
+ const { signableTransaction } = await this.createAction({
1945
+ description: `Renew ${chunk.length} permissions`,
1946
+ inputBEEF: inputBeef.toBinary(),
1947
+ inputs: chunk.map((c, i) => ({
1948
+ outpoint: `${c.oldToken.txid}.${c.oldToken.outputIndex}`,
1949
+ unlockingScriptLength: 73,
1950
+ inputDescription: `Consume old permission token #${i + 1}`
1951
+ })),
1952
+ outputs: built.map(b => b.output),
1953
+ options: {
1954
+ acceptDelayedBroadcast: true,
1955
+ randomizeOutputs: false,
1956
+ signAndProcess: false
1957
+ }
1958
+ }, this.adminOriginator);
1959
+ if (!(signableTransaction === null || signableTransaction === void 0 ? void 0 : signableTransaction.reference) || !signableTransaction.tx) {
1960
+ throw new Error('Failed to create signable transaction');
1961
+ }
1962
+ const partialTx = sdk_1.Transaction.fromAtomicBEEF(signableTransaction.tx);
1963
+ const pushdrop = new sdk_1.PushDrop(this.underlying);
1964
+ const spends = {};
1965
+ for (let i = 0; i < chunk.length; i++) {
1966
+ const token = chunk[i].oldToken;
1967
+ const unlocker = pushdrop.unlock(WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL, '1', 'self', 'all', false, 1, sdk_1.LockingScript.fromHex(token.outputScript));
1968
+ const unlockingScript = await unlocker.sign(partialTx, i);
1969
+ spends[i] = { unlockingScript: unlockingScript.toHex() };
1970
+ }
1971
+ const { txid } = await this.underlying.signAction({
1972
+ reference: signableTransaction.reference,
1973
+ spends
1974
+ });
1975
+ if (!txid)
1976
+ throw new Error('Failed to finalize renewal transaction');
1977
+ return built.map(b => b.request);
1978
+ });
1979
+ }
1203
1980
  async coalescePermissionTokens(oldTokens, newScript, opts) {
1204
1981
  var _a;
1205
1982
  if (!(oldTokens === null || oldTokens === void 0 ? void 0 : oldTokens.length))
@@ -1230,7 +2007,7 @@ class WalletPermissionsManager {
1230
2007
  }
1231
2008
  ],
1232
2009
  options: {
1233
- acceptDelayedBroadcast: false,
2010
+ acceptDelayedBroadcast: true,
1234
2011
  randomizeOutputs: false,
1235
2012
  signAndProcess: false
1236
2013
  }
@@ -1309,7 +2086,7 @@ class WalletPermissionsManager {
1309
2086
  }
1310
2087
  ],
1311
2088
  options: {
1312
- acceptDelayedBroadcast: false
2089
+ acceptDelayedBroadcast: true
1313
2090
  }
1314
2091
  }, this.adminOriginator);
1315
2092
  const tx = sdk_1.Transaction.fromBEEF(signableTransaction.tx);
@@ -1445,7 +2222,7 @@ class WalletPermissionsManager {
1445
2222
  tags,
1446
2223
  tagQueryMode: 'all',
1447
2224
  include: 'entire transactions',
1448
- limit: 100
2225
+ limit: 10000
1449
2226
  }, this.adminOriginator);
1450
2227
  for (const out of result.outputs) {
1451
2228
  if (seen.has(out.outpoint))
@@ -1750,16 +2527,60 @@ class WalletPermissionsManager {
1750
2527
  }
1751
2528
  ],
1752
2529
  options: {
1753
- acceptDelayedBroadcast: false
2530
+ acceptDelayedBroadcast: true
1754
2531
  }
1755
2532
  }, this.adminOriginator);
1756
2533
  const tx = sdk_1.Transaction.fromBEEF(signableTransaction.tx);
2534
+ const normalizeTxid = (txid) => (txid !== null && txid !== void 0 ? txid : '').toLowerCase();
2535
+ const reverseHexTxid = (txid) => {
2536
+ const hex = normalizeTxid(txid);
2537
+ if (!/^[0-9a-f]{64}$/.test(hex))
2538
+ return hex;
2539
+ const bytes = hex.match(/../g);
2540
+ return bytes ? bytes.reverse().join('') : hex;
2541
+ };
2542
+ const matchesOutpointString = (outpoint) => {
2543
+ const dot = outpoint.lastIndexOf('.');
2544
+ const colon = outpoint.lastIndexOf(':');
2545
+ const sep = dot > colon ? dot : colon;
2546
+ if (sep === -1)
2547
+ return false;
2548
+ const txidPart = outpoint.slice(0, sep);
2549
+ const indexPart = outpoint.slice(sep + 1);
2550
+ const vout = Number(indexPart);
2551
+ if (!Number.isFinite(vout))
2552
+ return false;
2553
+ return normalizeTxid(txidPart) === normalizeTxid(oldToken.txid) && vout === oldToken.outputIndex;
2554
+ };
2555
+ let permInputIndex = tx.inputs.findIndex((input) => {
2556
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
2557
+ const txidCandidate = (_g = (_f = (_e = (_d = (_c = (_b = (_a = input === null || input === void 0 ? void 0 : input.sourceTXID) !== null && _a !== void 0 ? _a : input === null || input === void 0 ? void 0 : input.sourceTxid) !== null && _b !== void 0 ? _b : input === null || input === void 0 ? void 0 : input.sourceTxId) !== null && _c !== void 0 ? _c : input === null || input === void 0 ? void 0 : input.prevTxId) !== null && _d !== void 0 ? _d : input === null || input === void 0 ? void 0 : input.prevTxid) !== null && _e !== void 0 ? _e : input === null || input === void 0 ? void 0 : input.prevTXID) !== null && _f !== void 0 ? _f : input === null || input === void 0 ? void 0 : input.txid) !== null && _g !== void 0 ? _g : input === null || input === void 0 ? void 0 : input.txID;
2558
+ const voutCandidate = (_l = (_k = (_j = (_h = input === null || input === void 0 ? void 0 : input.sourceOutputIndex) !== null && _h !== void 0 ? _h : input === null || input === void 0 ? void 0 : input.sourceOutput) !== null && _j !== void 0 ? _j : input === null || input === void 0 ? void 0 : input.outputIndex) !== null && _k !== void 0 ? _k : input === null || input === void 0 ? void 0 : input.vout) !== null && _l !== void 0 ? _l : input === null || input === void 0 ? void 0 : input.prevOutIndex;
2559
+ if (typeof txidCandidate === 'string' && typeof voutCandidate === 'number') {
2560
+ const cand = normalizeTxid(txidCandidate);
2561
+ const target = normalizeTxid(oldToken.txid);
2562
+ if (cand === target && voutCandidate === oldToken.outputIndex)
2563
+ return true;
2564
+ if (cand === reverseHexTxid(oldToken.txid) && voutCandidate === oldToken.outputIndex)
2565
+ return true;
2566
+ }
2567
+ const outpointCandidate = (_o = (_m = input === null || input === void 0 ? void 0 : input.outpoint) !== null && _m !== void 0 ? _m : input === null || input === void 0 ? void 0 : input.sourceOutpoint) !== null && _o !== void 0 ? _o : input === null || input === void 0 ? void 0 : input.prevOutpoint;
2568
+ if (typeof outpointCandidate === 'string' && matchesOutpointString(outpointCandidate))
2569
+ return true;
2570
+ return false;
2571
+ });
2572
+ if (permInputIndex === -1 && tx.inputs.length === 1) {
2573
+ permInputIndex = 0;
2574
+ }
2575
+ if (permInputIndex === -1) {
2576
+ throw new Error('Unable to locate permission token input for revocation.');
2577
+ }
1757
2578
  const unlocker = new sdk_1.PushDrop(this.underlying).unlock(WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL, '1', 'self', 'all', false, 1, sdk_1.LockingScript.fromHex(oldToken.outputScript));
1758
- const unlockingScript = await unlocker.sign(tx, 0);
2579
+ const unlockingScript = await unlocker.sign(tx, permInputIndex);
1759
2580
  await this.underlying.signAction({
1760
2581
  reference: signableTransaction.reference,
1761
2582
  spends: {
1762
- 0: {
2583
+ [permInputIndex]: {
1763
2584
  unlockingScript: unlockingScript.toHex()
1764
2585
  }
1765
2586
  }
@@ -1769,22 +2590,16 @@ class WalletPermissionsManager {
1769
2590
  * 7) BRC-100 WALLET INTERFACE FORWARDING WITH PERMISSION CHECKS
1770
2591
  * --------------------------------------------------------------------- */
1771
2592
  async createAction(args, originator) {
1772
- var _a;
1773
- // 1) Identify unique P-modules involved (one per schemeID)
2593
+ // 1) Identify unique P-modules involved (one per schemeID) from both baskets and labels
1774
2594
  const pModulesByScheme = new Map();
1775
2595
  const nonPBaskets = [];
2596
+ // Check baskets for p modules
1776
2597
  if (args.outputs) {
1777
2598
  for (const out of args.outputs) {
1778
2599
  if (out.basket) {
1779
2600
  if (out.basket.startsWith('p ')) {
1780
2601
  const schemeID = out.basket.split(' ')[1];
1781
- if (!pModulesByScheme.has(schemeID)) {
1782
- const module = (_a = this.config.permissionModules) === null || _a === void 0 ? void 0 : _a[schemeID];
1783
- if (!module) {
1784
- throw new Error(`Unsupported P-basket scheme: p ${schemeID}`);
1785
- }
1786
- pModulesByScheme.set(schemeID, module);
1787
- }
2602
+ this.addPModuleByScheme(schemeID, 'basket', pModulesByScheme);
1788
2603
  }
1789
2604
  else {
1790
2605
  // Track non-P baskets for normal permission checks
@@ -1793,6 +2608,8 @@ class WalletPermissionsManager {
1793
2608
  }
1794
2609
  }
1795
2610
  }
2611
+ // Check labels for p modules
2612
+ const nonPLabels = this.splitLabelsByPermissionModule(args.labels, pModulesByScheme);
1796
2613
  // 2) Check permissions for non-P baskets
1797
2614
  for (const basket of nonPBaskets) {
1798
2615
  await this.ensureBasketAccess({
@@ -1802,15 +2619,14 @@ class WalletPermissionsManager {
1802
2619
  usageType: 'insertion'
1803
2620
  });
1804
2621
  }
1805
- if (args.labels) {
1806
- for (const lbl of args.labels) {
1807
- await this.ensureLabelAccess({
1808
- originator: originator,
1809
- label: lbl,
1810
- reason: args.description,
1811
- usageType: 'apply'
1812
- });
1813
- }
2622
+ // 3) Check permissions for non-P labels
2623
+ for (const lbl of nonPLabels) {
2624
+ await this.ensureLabelAccess({
2625
+ originator: originator,
2626
+ label: lbl,
2627
+ reason: args.description,
2628
+ usageType: 'apply'
2629
+ });
1814
2630
  }
1815
2631
  /**
1816
2632
  * 4) Force signAndProcess=false unless the originator is admin and explicitly sets it to true.
@@ -1988,74 +2804,132 @@ class WalletPermissionsManager {
1988
2804
  }
1989
2805
  async listActions(...args) {
1990
2806
  const [requestArgs, originator] = args;
1991
- // for each label, ensure label access
1992
- if (requestArgs.labels) {
1993
- for (const lbl of requestArgs.labels) {
1994
- await this.ensureLabelAccess({
1995
- originator: originator,
1996
- label: lbl,
1997
- reason: 'listActions',
1998
- usageType: 'list'
2807
+ // 1) Identify unique P-modules involved (one per schemeID, preserving label order)
2808
+ const pModulesByScheme = new Map();
2809
+ const nonPLabels = this.splitLabelsByPermissionModule(requestArgs.labels, pModulesByScheme);
2810
+ // 2) Check permissions for non-P labels
2811
+ for (const lbl of nonPLabels) {
2812
+ await this.ensureLabelAccess({
2813
+ originator: originator,
2814
+ label: lbl,
2815
+ reason: 'listActions',
2816
+ usageType: 'list'
2817
+ });
2818
+ }
2819
+ // 3) Call underlying wallet, with P-module transformations if needed
2820
+ let results;
2821
+ if (pModulesByScheme.size > 0) {
2822
+ // P-modules are involved - chain transformations
2823
+ const pModules = Array.from(pModulesByScheme.values());
2824
+ // Chain onRequest calls through all modules in order
2825
+ let transformedArgs = requestArgs;
2826
+ for (const module of pModules) {
2827
+ const transformed = await module.onRequest({
2828
+ method: 'listActions',
2829
+ args: transformedArgs,
2830
+ originator: originator
1999
2831
  });
2832
+ transformedArgs = transformed.args;
2000
2833
  }
2001
- }
2002
- const results = await this.underlying.listActions(...args);
2003
- // Transparently decrypt transaction metadata, if configured to do so.
2004
- if (results.actions) {
2005
- for (let i = 0; i < results.actions.length; i++) {
2006
- if (results.actions[i].description) {
2007
- results.actions[i].description = await this.maybeDecryptMetadata(results.actions[i].description);
2008
- }
2009
- if (results.actions[i].inputs) {
2010
- for (let j = 0; j < results.actions[i].inputs.length; j++) {
2011
- if (results.actions[i].inputs[j].inputDescription) {
2012
- results.actions[i].inputs[j].inputDescription = await this.maybeDecryptMetadata(results.actions[i].inputs[j].inputDescription);
2013
- }
2014
- }
2015
- }
2016
- if (results.actions[i].outputs) {
2017
- for (let j = 0; j < results.actions[i].outputs.length; j++) {
2018
- if (results.actions[i].outputs[j].outputDescription) {
2019
- results.actions[i].outputs[j].outputDescription = await this.maybeDecryptMetadata(results.actions[i].outputs[j].outputDescription);
2020
- }
2021
- if (results.actions[i].outputs[j].customInstructions) {
2022
- results.actions[i].outputs[j].customInstructions = await this.maybeDecryptMetadata(results.actions[i].outputs[j].customInstructions);
2023
- }
2024
- }
2025
- }
2834
+ // Call underlying wallet with transformed args
2835
+ results = await this.underlying.listActions(transformedArgs, originator);
2836
+ // Chain onResponse calls in reverse order
2837
+ for (let i = pModules.length - 1; i >= 0; i--) {
2838
+ results = await pModules[i].onResponse(results, {
2839
+ method: 'listActions',
2840
+ originator: originator
2841
+ });
2026
2842
  }
2027
2843
  }
2028
- return results;
2844
+ else {
2845
+ // No P-modules - call underlying wallet directly
2846
+ results = await this.underlying.listActions(...args);
2847
+ }
2848
+ // 4) Transparently decrypt transaction metadata, if configured to do so.
2849
+ return await this.decryptListActionsMetadata(results);
2029
2850
  }
2030
2851
  async internalizeAction(...args) {
2852
+ var _a;
2031
2853
  const [requestArgs, originator] = args;
2032
- // If the transaction is inserting outputs into baskets, we also ensure basket permission
2854
+ // 1) Identify unique P-modules involved (one per schemeID) from both baskets and labels
2855
+ const pModulesByScheme = new Map();
2856
+ const nonPBaskets = [];
2857
+ // Check baskets for p modules
2033
2858
  for (const outIndex in requestArgs.outputs) {
2034
2859
  const out = requestArgs.outputs[outIndex];
2035
2860
  if (out.protocol === 'basket insertion') {
2036
- // Delegate to permission module if needed
2037
- const pModuleResult = await this.delegateToPModuleIfNeeded(out.insertionRemittance.basket, 'internalizeAction', requestArgs, originator, async (transformedArgs) => {
2038
- if (out.insertionRemittance.customInstructions) {
2039
- ;
2040
- transformedArgs.outputs[outIndex].insertionRemittance.customInstructions =
2041
- await this.maybeEncryptMetadata(out.insertionRemittance.customInstructions);
2042
- }
2043
- return await this.underlying.internalizeAction(transformedArgs, originator);
2044
- });
2045
- if (pModuleResult !== null) {
2046
- return pModuleResult;
2861
+ const basket = out.insertionRemittance.basket;
2862
+ if (basket.startsWith('p ')) {
2863
+ const schemeID = basket.split(' ')[1];
2864
+ this.addPModuleByScheme(schemeID, 'basket', pModulesByScheme);
2047
2865
  }
2048
- await this.ensureBasketAccess({
2049
- originator: originator,
2050
- basket: out.insertionRemittance.basket,
2051
- reason: requestArgs.description,
2052
- usageType: 'insertion'
2866
+ else {
2867
+ // Track non-P baskets for normal permission checks
2868
+ nonPBaskets.push({
2869
+ outIndex,
2870
+ basket,
2871
+ customInstructions: out.insertionRemittance.customInstructions
2872
+ });
2873
+ }
2874
+ }
2875
+ }
2876
+ // Check labels for p modules
2877
+ const nonPLabels = this.splitLabelsByPermissionModule(requestArgs.labels, pModulesByScheme);
2878
+ // 2) Check permissions for non-P baskets
2879
+ for (const { outIndex, basket, customInstructions } of nonPBaskets) {
2880
+ await this.ensureBasketAccess({
2881
+ originator: originator,
2882
+ basket,
2883
+ reason: requestArgs.description,
2884
+ usageType: 'insertion'
2885
+ });
2886
+ if (customInstructions) {
2887
+ requestArgs.outputs[outIndex].insertionRemittance.customInstructions =
2888
+ await this.maybeEncryptMetadata(customInstructions);
2889
+ }
2890
+ }
2891
+ // 3) Check permissions for non-P labels
2892
+ for (const lbl of nonPLabels) {
2893
+ await this.ensureLabelAccess({
2894
+ originator: originator,
2895
+ label: lbl,
2896
+ reason: requestArgs.description,
2897
+ usageType: 'apply'
2898
+ });
2899
+ }
2900
+ // 4) Call underlying wallet, with P-module transformations if needed
2901
+ if (pModulesByScheme.size > 0) {
2902
+ // P-modules are involved - chain transformations
2903
+ const pModules = Array.from(pModulesByScheme.values());
2904
+ // Chain onRequest calls through all modules in order
2905
+ let transformedArgs = requestArgs;
2906
+ for (const module of pModules) {
2907
+ const transformed = await module.onRequest({
2908
+ method: 'internalizeAction',
2909
+ args: transformedArgs,
2910
+ originator: originator
2053
2911
  });
2054
- if (out.insertionRemittance.customInstructions) {
2055
- requestArgs.outputs[outIndex].insertionRemittance.customInstructions = await this.maybeEncryptMetadata(out.insertionRemittance.customInstructions);
2912
+ transformedArgs = transformed.args;
2913
+ }
2914
+ // Encrypt custom instructions for p basket outputs
2915
+ for (const outIndex in transformedArgs.outputs) {
2916
+ const out = transformedArgs.outputs[outIndex];
2917
+ if (out.protocol === 'basket insertion' && ((_a = out.insertionRemittance) === null || _a === void 0 ? void 0 : _a.customInstructions)) {
2918
+ out.insertionRemittance.customInstructions = await this.maybeEncryptMetadata(out.insertionRemittance.customInstructions);
2056
2919
  }
2057
2920
  }
2921
+ // Call underlying wallet with transformed args
2922
+ let results = await this.underlying.internalizeAction(transformedArgs, originator);
2923
+ // Chain onResponse calls in reverse order
2924
+ for (let i = pModules.length - 1; i >= 0; i--) {
2925
+ results = await pModules[i].onResponse(results, {
2926
+ method: 'internalizeAction',
2927
+ originator: originator
2928
+ });
2929
+ }
2930
+ return results;
2058
2931
  }
2932
+ // No P-modules - call underlying wallet directly
2059
2933
  return this.underlying.internalizeAction(...args);
2060
2934
  }
2061
2935
  async listOutputs(...args) {
@@ -2358,80 +3232,17 @@ class WalletPermissionsManager {
2358
3232
  return this.underlying.isAuthenticated(...args);
2359
3233
  }
2360
3234
  async waitForAuthentication(...args) {
2361
- var _a, _b, _c, _d, _e, _f, _g;
2362
3235
  let [_, originator] = args;
2363
3236
  if (this.config.seekGroupedPermission && originator) {
2364
3237
  const { normalized: normalizedOriginator } = this.prepareOriginator(originator);
2365
3238
  originator = normalizedOriginator;
2366
3239
  // 1. Fetch manifest.json from the originator
2367
- let groupPermissions;
2368
- try {
2369
- const proto = originator.startsWith('localhost:') ? 'http' : 'https';
2370
- const response = await fetch(`${proto}://${originator}/manifest.json`);
2371
- if (response.ok) {
2372
- const manifest = await response.json();
2373
- if ((_a = manifest === null || manifest === void 0 ? void 0 : manifest.babbage) === null || _a === void 0 ? void 0 : _a.groupPermissions) {
2374
- groupPermissions = manifest.babbage.groupPermissions;
2375
- }
2376
- }
2377
- }
2378
- catch (e) {
2379
- // Ignore fetch/parse errors, just proceed without group permissions.
2380
- }
3240
+ const groupPermissions = await this.fetchManifestGroupPermissions(originator);
2381
3241
  if (groupPermissions) {
2382
3242
  // 2. Filter out already-granted permissions
2383
- const permissionsToRequest = {
2384
- protocolPermissions: [],
2385
- basketAccess: [],
2386
- certificateAccess: []
2387
- };
2388
- if (groupPermissions.spendingAuthorization) {
2389
- const hasAuth = await this.hasSpendingAuthorization({
2390
- originator,
2391
- satoshis: groupPermissions.spendingAuthorization.amount
2392
- });
2393
- if (!hasAuth) {
2394
- permissionsToRequest.spendingAuthorization = groupPermissions.spendingAuthorization;
2395
- }
2396
- }
2397
- for (const p of groupPermissions.protocolPermissions || []) {
2398
- const hasPerm = await this.hasProtocolPermission({
2399
- originator,
2400
- privileged: false, // Privilege is never allowed here
2401
- protocolID: p.protocolID,
2402
- counterparty: p.counterparty || 'self'
2403
- });
2404
- if (!hasPerm) {
2405
- permissionsToRequest.protocolPermissions.push(p);
2406
- }
2407
- }
2408
- for (const b of groupPermissions.basketAccess || []) {
2409
- const hasAccess = await this.hasBasketAccess({
2410
- originator,
2411
- basket: b.basket
2412
- });
2413
- if (!hasAccess) {
2414
- permissionsToRequest.basketAccess.push(b);
2415
- }
2416
- }
2417
- for (const c of groupPermissions.certificateAccess || []) {
2418
- const hasAccess = await this.hasCertificateAccess({
2419
- originator,
2420
- privileged: false, // Privilege is never allowed here for security
2421
- verifier: c.verifierPublicKey,
2422
- certType: c.type,
2423
- fields: c.fields
2424
- });
2425
- if (!hasAccess) {
2426
- permissionsToRequest.certificateAccess.push(c);
2427
- }
2428
- }
3243
+ const permissionsToRequest = await this.filterAlreadyGrantedPermissions(originator, groupPermissions);
2429
3244
  // 3. If any permissions are left to request, start the flow
2430
- const hasRequests = permissionsToRequest.spendingAuthorization ||
2431
- ((_c = (_b = permissionsToRequest.protocolPermissions) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0) > 0 ||
2432
- ((_e = (_d = permissionsToRequest.basketAccess) === null || _d === void 0 ? void 0 : _d.length) !== null && _e !== void 0 ? _e : 0) > 0 ||
2433
- ((_g = (_f = permissionsToRequest.certificateAccess) === null || _f === void 0 ? void 0 : _f.length) !== null && _g !== void 0 ? _g : 0) > 0;
2434
- if (hasRequests) {
3245
+ if (this.hasAnyPermissionsToRequest(permissionsToRequest)) {
2435
3246
  const key = `group:${originator}`;
2436
3247
  if (this.activeRequests.has(key)) {
2437
3248
  // Another call is already waiting, piggyback on it
@@ -2503,6 +3314,7 @@ class WalletPermissionsManager {
2503
3314
  * Checks if the given label is admin-reserved per BRC-100 rules:
2504
3315
  *
2505
3316
  * - Must not start with `admin` (admin-reserved)
3317
+ * - Must not start with `p ` (permissioned labels requiring a permission module)
2506
3318
  *
2507
3319
  * If it violates these rules and the caller is not admin, we consider it "admin-only."
2508
3320
  */
@@ -2510,6 +3322,9 @@ class WalletPermissionsManager {
2510
3322
  if (label.startsWith('admin')) {
2511
3323
  return true;
2512
3324
  }
3325
+ if (label.startsWith('p ')) {
3326
+ return true;
3327
+ }
2513
3328
  return false;
2514
3329
  }
2515
3330
  /**
@@ -2598,6 +3413,21 @@ class WalletPermissionsManager {
2598
3413
  return trimmed.toLowerCase();
2599
3414
  }
2600
3415
  }
3416
+ isWhitelistedCounterpartyProtocol(counterparty, protocolID) {
3417
+ var _a;
3418
+ const whitelist = this.config.whitelistedCounterparties;
3419
+ if (!whitelist)
3420
+ return false;
3421
+ if (!counterparty || counterparty === 'self' || counterparty === 'anyone')
3422
+ return false;
3423
+ const protocols = whitelist[counterparty] || whitelist[counterparty.toLowerCase()] || whitelist[counterparty.toUpperCase()];
3424
+ if (!protocols || protocols.length === 0)
3425
+ return false;
3426
+ const protoName = ((_a = protocolID === null || protocolID === void 0 ? void 0 : protocolID[1]) !== null && _a !== void 0 ? _a : '').toString().toLowerCase();
3427
+ if (!protoName)
3428
+ return false;
3429
+ return protocols.some(p => (p !== null && p !== void 0 ? p : '').toString().toLowerCase() === protoName);
3430
+ }
2601
3431
  /**
2602
3432
  * Produces a normalized originator value along with the set of legacy
2603
3433
  * representations that should be considered when searching for existing
@@ -2646,6 +3476,7 @@ class WalletPermissionsManager {
2646
3476
  }
2647
3477
  }
2648
3478
  exports.WalletPermissionsManager = WalletPermissionsManager;
3479
+ WalletPermissionsManager.MANIFEST_CACHE_TTL_MS = 5 * 60 * 1000;
2649
3480
  /** How long a cached permission remains valid (5 minutes). */
2650
3481
  WalletPermissionsManager.CACHE_TTL_MS = 5 * 60 * 1000;
2651
3482
  /** Window during which freshly granted permissions are auto-allowed (except spending). */