@bsv/wallet-toolbox-client 2.0.5-beta.1 → 2.0.6

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.
@@ -96,6 +96,7 @@ class WalletPermissionsManager {
96
96
  this.recentGrants = new Map();
97
97
  this.manifestCache = new Map();
98
98
  this.manifestFetchInProgress = new Map();
99
+ this.groupedPermissionFlowTail = new Map();
99
100
  this.pactEstablishedCache = new Map();
100
101
  this.underlying = underlyingWallet;
101
102
  this.adminOriginator = this.normalizeOriginator(adminOriginator) || adminOriginator;
@@ -670,6 +671,7 @@ class WalletPermissionsManager {
670
671
  privileged,
671
672
  protocolID,
672
673
  counterparty,
674
+ usageType,
673
675
  reason,
674
676
  renewal: true,
675
677
  previousToken: token
@@ -687,6 +689,7 @@ class WalletPermissionsManager {
687
689
  privileged,
688
690
  protocolID,
689
691
  counterparty,
692
+ usageType,
690
693
  reason,
691
694
  renewal: false
692
695
  });
@@ -732,6 +735,7 @@ class WalletPermissionsManager {
732
735
  type: 'basket',
733
736
  originator,
734
737
  basket,
738
+ usageType,
735
739
  reason,
736
740
  renewal: true,
737
741
  previousToken: token
@@ -747,6 +751,7 @@ class WalletPermissionsManager {
747
751
  type: 'basket',
748
752
  originator,
749
753
  basket,
754
+ usageType,
750
755
  reason,
751
756
  renewal: false
752
757
  });
@@ -796,6 +801,7 @@ class WalletPermissionsManager {
796
801
  originator,
797
802
  privileged,
798
803
  certificate: { verifier, certType, fields },
804
+ usageType,
799
805
  reason,
800
806
  renewal: true,
801
807
  previousToken: token
@@ -811,6 +817,7 @@ class WalletPermissionsManager {
811
817
  originator,
812
818
  privileged,
813
819
  certificate: { verifier, certType, fields },
820
+ usageType,
814
821
  reason,
815
822
  renewal: false
816
823
  });
@@ -977,47 +984,58 @@ class WalletPermissionsManager {
977
984
  basketAccess: [],
978
985
  certificateAccess: []
979
986
  };
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
- }
987
+ const [spendingAuthorization, protocolPermissions, basketAccess, certificateAccess] = await Promise.all([
988
+ (async () => {
989
+ if (!groupPermissions.spendingAuthorization)
990
+ return undefined;
991
+ const hasAuth = await this.hasSpendingAuthorization({
992
+ originator,
993
+ satoshis: groupPermissions.spendingAuthorization.amount
994
+ });
995
+ return hasAuth ? undefined : groupPermissions.spendingAuthorization;
996
+ })(),
997
+ (async () => {
998
+ const protocolChecks = await Promise.all((groupPermissions.protocolPermissions || []).map(async (p) => {
999
+ const hasPerm = await this.hasProtocolPermission({
1000
+ originator,
1001
+ privileged: false,
1002
+ protocolID: p.protocolID,
1003
+ counterparty: p.counterparty || 'self'
1004
+ });
1005
+ return hasPerm ? null : p;
1006
+ }));
1007
+ return protocolChecks.filter(Boolean);
1008
+ })(),
1009
+ (async () => {
1010
+ const basketChecks = await Promise.all((groupPermissions.basketAccess || []).map(async (b) => {
1011
+ const hasAccess = await this.hasBasketAccess({
1012
+ originator,
1013
+ basket: b.basket
1014
+ });
1015
+ return hasAccess ? null : b;
1016
+ }));
1017
+ return basketChecks.filter(Boolean);
1018
+ })(),
1019
+ (async () => {
1020
+ const certChecks = await Promise.all((groupPermissions.certificateAccess || []).map(async (c) => {
1021
+ const hasAccess = await this.hasCertificateAccess({
1022
+ originator,
1023
+ privileged: false,
1024
+ verifier: c.verifierPublicKey,
1025
+ certType: c.type,
1026
+ fields: c.fields
1027
+ });
1028
+ return hasAccess ? null : c;
1029
+ }));
1030
+ return certChecks.filter(Boolean);
1031
+ })()
1032
+ ]);
1033
+ if (spendingAuthorization) {
1034
+ permissionsToRequest.spendingAuthorization = spendingAuthorization;
1035
+ }
1036
+ permissionsToRequest.protocolPermissions = protocolPermissions;
1037
+ permissionsToRequest.basketAccess = basketAccess;
1038
+ permissionsToRequest.certificateAccess = certificateAccess;
1021
1039
  return permissionsToRequest;
1022
1040
  }
1023
1041
  hasAnyPermissionsToRequest(permissions) {
@@ -1098,18 +1116,16 @@ class WalletPermissionsManager {
1098
1116
  if (!((_a = counterpartyPermissions === null || counterpartyPermissions === void 0 ? void 0 : counterpartyPermissions.protocols) === null || _a === void 0 ? void 0 : _a.length)) {
1099
1117
  return null;
1100
1118
  }
1101
- const protocolsToRequest = [];
1102
- for (const p of counterpartyPermissions.protocols) {
1119
+ const protocolChecks = await Promise.all(counterpartyPermissions.protocols.map(async (p) => {
1103
1120
  const hasPerm = await this.hasProtocolPermission({
1104
1121
  originator,
1105
1122
  privileged: false,
1106
1123
  protocolID: p.protocolID,
1107
1124
  counterparty
1108
1125
  });
1109
- if (!hasPerm) {
1110
- protocolsToRequest.push(p);
1111
- }
1112
- }
1126
+ return hasPerm ? null : p;
1127
+ }));
1128
+ const protocolsToRequest = protocolChecks.filter(Boolean);
1113
1129
  if (protocolsToRequest.length === 0) {
1114
1130
  this.markPactEstablished(originator, counterparty);
1115
1131
  return null;
@@ -1201,21 +1217,22 @@ class WalletPermissionsManager {
1201
1217
  const permissionsToRequest = {
1202
1218
  protocolPermissions: []
1203
1219
  };
1204
- for (const p of manifestLevel2ForThisPeer) {
1220
+ const protocolChecks = await Promise.all(manifestLevel2ForThisPeer.map(async (p) => {
1205
1221
  const hasPerm = await this.hasProtocolPermission({
1206
1222
  originator,
1207
1223
  privileged,
1208
1224
  protocolID: p.protocolID,
1209
1225
  counterparty: p.counterparty
1210
1226
  });
1211
- if (!hasPerm) {
1212
- permissionsToRequest.protocolPermissions.push({
1227
+ return hasPerm
1228
+ ? null
1229
+ : {
1213
1230
  protocolID: p.protocolID,
1214
1231
  counterparty: p.counterparty,
1215
1232
  description: p.description
1216
- });
1217
- }
1218
- }
1233
+ };
1234
+ }));
1235
+ permissionsToRequest.protocolPermissions = protocolChecks.filter(Boolean);
1219
1236
  if (!this.hasAnyPermissionsToRequest(permissionsToRequest)) {
1220
1237
  return null;
1221
1238
  }
@@ -1256,6 +1273,28 @@ class WalletPermissionsManager {
1256
1273
  const satisfied = await this.checkSpecificPermissionAfterGroupFlow(currentRequest);
1257
1274
  return satisfied ? true : null;
1258
1275
  }
1276
+ async withGroupedPermissionFlowLock(originator, fn) {
1277
+ const priorTail = this.groupedPermissionFlowTail.get(originator) || Promise.resolve();
1278
+ const safePriorTail = priorTail.catch(() => { });
1279
+ let release;
1280
+ const gate = new Promise(resolve => {
1281
+ release = resolve;
1282
+ });
1283
+ const currentTail = safePriorTail.then(() => gate);
1284
+ this.groupedPermissionFlowTail.set(originator, currentTail);
1285
+ await safePriorTail;
1286
+ try {
1287
+ return await fn();
1288
+ }
1289
+ finally {
1290
+ release === null || release === void 0 ? void 0 : release();
1291
+ currentTail.finally(() => {
1292
+ if (this.groupedPermissionFlowTail.get(originator) === currentTail) {
1293
+ this.groupedPermissionFlowTail.delete(originator);
1294
+ }
1295
+ });
1296
+ }
1297
+ }
1259
1298
  async checkSpecificPermissionAfterGroupFlow(request) {
1260
1299
  var _a, _b, _c;
1261
1300
  switch (request.type) {
@@ -1390,6 +1429,14 @@ class WalletPermissionsManager {
1390
1429
  originator: normalizedOriginator,
1391
1430
  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
1392
1431
  };
1432
+ const key = this.buildActiveRequestKey(preparedRequest);
1433
+ // If there's already a queue for the same resource, we piggyback on it
1434
+ const existingQueue = this.activeRequests.get(key);
1435
+ if (existingQueue && existingQueue.pending.length > 0) {
1436
+ return new Promise((resolve, reject) => {
1437
+ existingQueue.pending.push({ resolve, reject });
1438
+ });
1439
+ }
1393
1440
  const pactResult = await this.maybeRequestPact(preparedRequest);
1394
1441
  if (pactResult !== null) {
1395
1442
  return pactResult;
@@ -1398,51 +1445,73 @@ class WalletPermissionsManager {
1398
1445
  if (peerGroupResult !== null) {
1399
1446
  return peerGroupResult;
1400
1447
  }
1401
- const groupResult = await this.maybeRequestGroupedPermissions(preparedRequest);
1448
+ const hadPendingGroupedFlowBefore = this.config.seekGroupedPermission && this.groupedPermissionFlowTail.has(preparedRequest.originator);
1449
+ let groupResult = null;
1450
+ if (this.config.seekGroupedPermission) {
1451
+ groupResult = await this.withGroupedPermissionFlowLock(preparedRequest.originator, async () => {
1452
+ return await this.maybeRequestGroupedPermissions(preparedRequest);
1453
+ });
1454
+ }
1455
+ else {
1456
+ groupResult = await this.maybeRequestGroupedPermissions(preparedRequest);
1457
+ }
1402
1458
  if (groupResult !== null) {
1403
1459
  return groupResult;
1404
1460
  }
1405
- const key = this.buildRequestKey(preparedRequest);
1406
- // If there's already a queue for the same resource, we piggyback on it
1407
- const existingQueue = this.activeRequests.get(key);
1408
- if (existingQueue && existingQueue.pending.length > 0) {
1461
+ if (this.config.seekGroupedPermission && hadPendingGroupedFlowBefore) {
1462
+ const satisfiedAfterGroup = await this.checkSpecificPermissionAfterGroupFlow(preparedRequest);
1463
+ if (satisfiedAfterGroup) {
1464
+ return true;
1465
+ }
1466
+ }
1467
+ const existingQueueAfterGroups = this.activeRequests.get(key);
1468
+ if (existingQueueAfterGroups && existingQueueAfterGroups.pending.length > 0) {
1409
1469
  return new Promise((resolve, reject) => {
1410
- existingQueue.pending.push({ resolve, reject });
1470
+ existingQueueAfterGroups.pending.push({ resolve, reject });
1411
1471
  });
1412
1472
  }
1413
- // Otherwise, create a new queue with a single entry
1414
- // Return a promise that resolves or rejects once the user grants/denies
1415
1473
  return new Promise(async (resolve, reject) => {
1416
1474
  this.activeRequests.set(key, {
1417
1475
  request: preparedRequest,
1418
1476
  pending: [{ resolve, reject }]
1419
1477
  });
1420
- // Fire the relevant onXXXRequested event (which one depends on r.type)
1421
- switch (preparedRequest.type) {
1422
- case 'protocol':
1423
- await this.callEvent('onProtocolPermissionRequested', {
1424
- ...preparedRequest,
1425
- requestID: key
1426
- });
1427
- break;
1428
- case 'basket':
1429
- await this.callEvent('onBasketAccessRequested', {
1430
- ...preparedRequest,
1431
- requestID: key
1432
- });
1433
- break;
1434
- case 'certificate':
1435
- await this.callEvent('onCertificateAccessRequested', {
1436
- ...preparedRequest,
1437
- requestID: key
1438
- });
1439
- break;
1440
- case 'spending':
1441
- await this.callEvent('onSpendingAuthorizationRequested', {
1442
- ...preparedRequest,
1443
- requestID: key
1444
- });
1445
- break;
1478
+ try {
1479
+ // Fire the relevant onXXXRequested event (which one depends on r.type)
1480
+ switch (preparedRequest.type) {
1481
+ case 'protocol':
1482
+ await this.callEvent('onProtocolPermissionRequested', {
1483
+ ...preparedRequest,
1484
+ requestID: key
1485
+ });
1486
+ break;
1487
+ case 'basket':
1488
+ await this.callEvent('onBasketAccessRequested', {
1489
+ ...preparedRequest,
1490
+ requestID: key
1491
+ });
1492
+ break;
1493
+ case 'certificate':
1494
+ await this.callEvent('onCertificateAccessRequested', {
1495
+ ...preparedRequest,
1496
+ requestID: key
1497
+ });
1498
+ break;
1499
+ case 'spending':
1500
+ await this.callEvent('onSpendingAuthorizationRequested', {
1501
+ ...preparedRequest,
1502
+ requestID: key
1503
+ });
1504
+ break;
1505
+ }
1506
+ }
1507
+ catch (e) {
1508
+ const matching = this.activeRequests.get(key);
1509
+ if (matching) {
1510
+ for (const p of matching.pending) {
1511
+ p.reject(e);
1512
+ }
1513
+ this.activeRequests.delete(key);
1514
+ }
1446
1515
  }
1447
1516
  });
1448
1517
  }
@@ -2510,6 +2579,137 @@ class WalletPermissionsManager {
2510
2579
  return false;
2511
2580
  }
2512
2581
  }
2582
+ async revokePermissions(oldTokens) {
2583
+ return await this.revokePermissionTokensBestEffort(oldTokens);
2584
+ }
2585
+ async revokeAllForOriginator(originator, opts) {
2586
+ const preparedOriginator = this.prepareOriginator(originator);
2587
+ const include = {
2588
+ protocol: true,
2589
+ basket: true,
2590
+ certificate: true,
2591
+ spending: true,
2592
+ ...(opts || {})
2593
+ };
2594
+ const [protocolTokens, basketTokens, certificateTokens, spendingTokens] = await Promise.all([
2595
+ include.protocol ? this.listProtocolPermissions({ originator }) : Promise.resolve([]),
2596
+ include.basket ? this.listBasketAccess({ originator }) : Promise.resolve([]),
2597
+ include.certificate ? this.listCertificateAccess({ originator }) : Promise.resolve([]),
2598
+ include.spending
2599
+ ? this.listSpendingAuthorizations({ originator: preparedOriginator.normalized })
2600
+ : Promise.resolve([])
2601
+ ]);
2602
+ const spendingTokenList = spendingTokens.length ? [spendingTokens[0]] : [];
2603
+ const allTokens = [...protocolTokens, ...basketTokens, ...certificateTokens, ...spendingTokenList];
2604
+ const seen = new Set();
2605
+ const deduped = allTokens.filter(t => {
2606
+ const key = `${t.txid}.${t.outputIndex}`;
2607
+ if (seen.has(key))
2608
+ return false;
2609
+ seen.add(key);
2610
+ return true;
2611
+ });
2612
+ return await this.revokePermissions(deduped);
2613
+ }
2614
+ async revokePermissionTokensBestEffort(items) {
2615
+ const CHUNK = 15;
2616
+ return this.runBestEffortBatches(items, CHUNK, async (chunk) => {
2617
+ await this.revokePermissionTokensChunk(chunk);
2618
+ return chunk;
2619
+ });
2620
+ }
2621
+ async revokePermissionTokensChunk(oldTokens) {
2622
+ if (!oldTokens.length)
2623
+ return;
2624
+ const inputBeef = new sdk_1.Beef();
2625
+ for (const token of oldTokens) {
2626
+ inputBeef.mergeBeef(sdk_1.Beef.fromBinary(token.tx));
2627
+ }
2628
+ const { signableTransaction } = await this.createAction({
2629
+ description: `Revoke ${oldTokens.length} permissions`,
2630
+ inputBEEF: inputBeef.toBinary(),
2631
+ inputs: oldTokens.map((t, i) => ({
2632
+ outpoint: `${t.txid}.${t.outputIndex}`,
2633
+ unlockingScriptLength: 73,
2634
+ inputDescription: `Consume old permission token #${i + 1}`
2635
+ })),
2636
+ options: {
2637
+ acceptDelayedBroadcast: true,
2638
+ randomizeOutputs: false,
2639
+ signAndProcess: false
2640
+ }
2641
+ }, this.adminOriginator);
2642
+ if (!(signableTransaction === null || signableTransaction === void 0 ? void 0 : signableTransaction.reference) || !signableTransaction.tx) {
2643
+ throw new Error('Failed to create signable transaction');
2644
+ }
2645
+ const tx = sdk_1.Transaction.fromAtomicBEEF(signableTransaction.tx);
2646
+ const normalizeTxid = (txid) => (txid !== null && txid !== void 0 ? txid : '').toLowerCase();
2647
+ const reverseHexTxid = (txid) => {
2648
+ const hex = normalizeTxid(txid);
2649
+ if (!/^[0-9a-f]{64}$/.test(hex))
2650
+ return hex;
2651
+ const bytes = hex.match(/../g);
2652
+ return bytes ? bytes.reverse().join('') : hex;
2653
+ };
2654
+ const matchesOutpointString = (outpoint, token) => {
2655
+ const dot = outpoint.lastIndexOf('.');
2656
+ const colon = outpoint.lastIndexOf(':');
2657
+ const sep = dot > colon ? dot : colon;
2658
+ if (sep === -1)
2659
+ return false;
2660
+ const txidPart = outpoint.slice(0, sep);
2661
+ const indexPart = outpoint.slice(sep + 1);
2662
+ const vout = Number(indexPart);
2663
+ if (!Number.isFinite(vout))
2664
+ return false;
2665
+ return normalizeTxid(txidPart) === normalizeTxid(token.txid) && vout === token.outputIndex;
2666
+ };
2667
+ const findInputIndexForToken = (token) => {
2668
+ return tx.inputs.findIndex((input) => {
2669
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
2670
+ 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;
2671
+ 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;
2672
+ if (typeof txidCandidate === 'string' && typeof voutCandidate === 'number') {
2673
+ const cand = normalizeTxid(txidCandidate);
2674
+ const target = normalizeTxid(token.txid);
2675
+ if (cand === target && voutCandidate === token.outputIndex)
2676
+ return true;
2677
+ if (cand === reverseHexTxid(token.txid) && voutCandidate === token.outputIndex)
2678
+ return true;
2679
+ }
2680
+ 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;
2681
+ if (typeof outpointCandidate === 'string' && matchesOutpointString(outpointCandidate, token))
2682
+ return true;
2683
+ return false;
2684
+ });
2685
+ };
2686
+ const inputsToSign = oldTokens.map(token => {
2687
+ let permInputIndex = findInputIndexForToken(token);
2688
+ if (permInputIndex === -1 && tx.inputs.length === 1) {
2689
+ permInputIndex = 0;
2690
+ }
2691
+ if (permInputIndex === -1) {
2692
+ throw new Error('Unable to locate permission token input for revocation.');
2693
+ }
2694
+ return { token, permInputIndex };
2695
+ });
2696
+ const pushdrop = new sdk_1.PushDrop(this.underlying);
2697
+ const spends = {};
2698
+ const signed = await this.mapWithConcurrency(inputsToSign, 8, async ({ token, permInputIndex }) => {
2699
+ const unlocker = pushdrop.unlock(WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL, '1', 'self', 'all', false, 1, sdk_1.LockingScript.fromHex(token.outputScript));
2700
+ const unlockingScript = await unlocker.sign(tx, permInputIndex);
2701
+ return { permInputIndex, unlockingScriptHex: unlockingScript.toHex() };
2702
+ });
2703
+ for (const s of signed) {
2704
+ spends[s.permInputIndex] = { unlockingScript: s.unlockingScriptHex };
2705
+ }
2706
+ const { txid } = await this.underlying.signAction({
2707
+ reference: signableTransaction.reference,
2708
+ spends
2709
+ });
2710
+ if (!txid)
2711
+ throw new Error('Failed to finalize revoke transaction');
2712
+ }
2513
2713
  /**
2514
2714
  * Revokes a permission token by spending it with no replacement output.
2515
2715
  * The manager builds a BRC-100 transaction that consumes the token, effectively invalidating it.
@@ -2644,22 +2844,28 @@ class WalletPermissionsManager {
2644
2844
  const originalDescription = args.description;
2645
2845
  const originalInputDescriptions = {};
2646
2846
  const originalOutputDescriptions = {};
2647
- args.description = await this.maybeEncryptMetadata(args.description);
2648
- for (let i = 0; i < (args.inputs || []).length; i++) {
2649
- if (args.inputs[i].inputDescription) {
2650
- originalInputDescriptions[i] = args.inputs[i].inputDescription;
2651
- args.inputs[i].inputDescription = await this.maybeEncryptMetadata(args.inputs[i].inputDescription);
2652
- }
2653
- }
2654
- for (let i = 0; i < (args.outputs || []).length; i++) {
2655
- if (args.outputs[i].outputDescription) {
2656
- originalOutputDescriptions[i] = args.outputs[i].outputDescription;
2657
- args.outputs[i].outputDescription = await this.maybeEncryptMetadata(args.outputs[i].outputDescription);
2847
+ const inputEncryptionTasks = (args.inputs || []).map(async (input, i) => {
2848
+ if (!input.inputDescription)
2849
+ return;
2850
+ originalInputDescriptions[i] = input.inputDescription;
2851
+ input.inputDescription = await this.maybeEncryptMetadata(input.inputDescription);
2852
+ });
2853
+ const outputEncryptionTasks = (args.outputs || []).map(async (output, i) => {
2854
+ if (output.outputDescription) {
2855
+ originalOutputDescriptions[i] = output.outputDescription;
2856
+ output.outputDescription = await this.maybeEncryptMetadata(output.outputDescription);
2658
2857
  }
2659
- if (args.outputs[i].customInstructions) {
2660
- args.outputs[i].customInstructions = await this.maybeEncryptMetadata(args.outputs[i].customInstructions);
2858
+ if (output.customInstructions) {
2859
+ output.customInstructions = await this.maybeEncryptMetadata(output.customInstructions);
2661
2860
  }
2662
- }
2861
+ });
2862
+ await Promise.all([
2863
+ (async () => {
2864
+ args.description = await this.maybeEncryptMetadata(args.description);
2865
+ })(),
2866
+ ...inputEncryptionTasks,
2867
+ ...outputEncryptionTasks
2868
+ ]);
2663
2869
  /**
2664
2870
  * 6) Call the underlying wallet's createAction.
2665
2871
  * - If P-modules are involved, chain request transformations through them first
@@ -3235,43 +3441,45 @@ class WalletPermissionsManager {
3235
3441
  let [_, originator] = args;
3236
3442
  if (this.config.seekGroupedPermission && originator) {
3237
3443
  const { normalized: normalizedOriginator } = this.prepareOriginator(originator);
3238
- originator = normalizedOriginator;
3239
- // 1. Fetch manifest.json from the originator
3240
- const groupPermissions = await this.fetchManifestGroupPermissions(originator);
3241
- if (groupPermissions) {
3242
- // 2. Filter out already-granted permissions
3243
- const permissionsToRequest = await this.filterAlreadyGrantedPermissions(originator, groupPermissions);
3244
- // 3. If any permissions are left to request, start the flow
3245
- if (this.hasAnyPermissionsToRequest(permissionsToRequest)) {
3246
- const key = `group:${originator}`;
3247
- if (this.activeRequests.has(key)) {
3248
- // Another call is already waiting, piggyback on it
3249
- await new Promise((resolve, reject) => {
3250
- this.activeRequests.get(key).pending.push({ resolve, reject });
3251
- });
3252
- }
3253
- else {
3254
- // This is the first call, create a new request
3255
- try {
3256
- await new Promise(async (resolve, reject) => {
3257
- this.activeRequests.set(key, {
3258
- request: { originator: originator, permissions: permissionsToRequest },
3259
- pending: [{ resolve, reject }]
3260
- });
3261
- await this.callEvent('onGroupedPermissionRequested', {
3262
- requestID: key,
3263
- originator,
3264
- permissions: permissionsToRequest
3265
- });
3444
+ const normalized = normalizedOriginator;
3445
+ await this.withGroupedPermissionFlowLock(normalized, async () => {
3446
+ // 1. Fetch manifest.json from the originator
3447
+ const groupPermissions = await this.fetchManifestGroupPermissions(normalized);
3448
+ if (groupPermissions) {
3449
+ // 2. Filter out already-granted permissions
3450
+ const permissionsToRequest = await this.filterAlreadyGrantedPermissions(normalized, groupPermissions);
3451
+ // 3. If any permissions are left to request, start the flow
3452
+ if (this.hasAnyPermissionsToRequest(permissionsToRequest)) {
3453
+ const key = `group:${normalized}`;
3454
+ if (this.activeRequests.has(key)) {
3455
+ // Another call is already waiting, piggyback on it
3456
+ await new Promise((resolve, reject) => {
3457
+ this.activeRequests.get(key).pending.push({ resolve, reject });
3266
3458
  });
3267
3459
  }
3268
- catch (e) {
3269
- // Permission was denied, re-throw to stop execution
3270
- throw e;
3460
+ else {
3461
+ // This is the first call, create a new request
3462
+ try {
3463
+ await new Promise(async (resolve, reject) => {
3464
+ this.activeRequests.set(key, {
3465
+ request: { originator: normalized, permissions: permissionsToRequest },
3466
+ pending: [{ resolve, reject }]
3467
+ });
3468
+ await this.callEvent('onGroupedPermissionRequested', {
3469
+ requestID: key,
3470
+ originator: normalized,
3471
+ permissions: permissionsToRequest
3472
+ });
3473
+ });
3474
+ }
3475
+ catch (e) {
3476
+ // Permission was denied, re-throw to stop execution
3477
+ throw e;
3478
+ }
3271
3479
  }
3272
3480
  }
3273
3481
  }
3274
- }
3482
+ });
3275
3483
  }
3276
3484
  // Finally, after handling grouped permissions, call the underlying method.
3277
3485
  return this.underlying.waitForAuthentication(...args);
@@ -3474,6 +3682,14 @@ class WalletPermissionsManager {
3474
3682
  return `spend:${normalizedOriginator}:${(_e = r.spending) === null || _e === void 0 ? void 0 : _e.satoshis}`;
3475
3683
  }
3476
3684
  }
3685
+ buildActiveRequestKey(r) {
3686
+ var _a;
3687
+ const base = this.buildRequestKey(r);
3688
+ if (r.type === 'protocol' || r.type === 'basket' || r.type === 'certificate') {
3689
+ return `${base}:${(_a = r.usageType) !== null && _a !== void 0 ? _a : ''}`;
3690
+ }
3691
+ return base;
3692
+ }
3477
3693
  }
3478
3694
  exports.WalletPermissionsManager = WalletPermissionsManager;
3479
3695
  WalletPermissionsManager.MANIFEST_CACHE_TTL_MS = 5 * 60 * 1000;