@bsv/wallet-toolbox 1.6.40 → 1.6.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/client.md +67 -29
- package/docs/services.md +1 -1
- package/docs/storage.md +34 -8
- package/docs/wallet.md +67 -29
- package/mobile/out/src/WalletPermissionsManager.d.ts +29 -0
- package/mobile/out/src/WalletPermissionsManager.d.ts.map +1 -1
- package/mobile/out/src/WalletPermissionsManager.js +581 -371
- package/mobile/out/src/WalletPermissionsManager.js.map +1 -1
- package/mobile/out/src/sdk/WalletError.d.ts.map +1 -1
- package/mobile/out/src/sdk/WalletError.js +3 -1
- package/mobile/out/src/sdk/WalletError.js.map +1 -1
- package/mobile/out/src/storage/WalletStorageManager.d.ts.map +1 -1
- package/mobile/out/src/storage/WalletStorageManager.js +3 -1
- package/mobile/out/src/storage/WalletStorageManager.js.map +1 -1
- package/mobile/package-lock.json +6 -6
- package/mobile/package.json +2 -2
- package/out/src/WalletPermissionsManager.d.ts +29 -0
- package/out/src/WalletPermissionsManager.d.ts.map +1 -1
- package/out/src/WalletPermissionsManager.js +581 -371
- package/out/src/WalletPermissionsManager.js.map +1 -1
- package/out/src/__tests/WalletPermissionsManager.tokens.test.js +1 -80
- package/out/src/__tests/WalletPermissionsManager.tokens.test.js.map +1 -1
- package/out/src/sdk/WalletError.d.ts.map +1 -1
- package/out/src/sdk/WalletError.js +3 -1
- package/out/src/sdk/WalletError.js.map +1 -1
- package/out/src/storage/WalletStorageManager.d.ts.map +1 -1
- package/out/src/storage/WalletStorageManager.js +3 -1
- package/out/src/storage/WalletStorageManager.js.map +1 -1
- package/out/tsconfig.all.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/WalletPermissionsManager.ts +674 -444
- package/src/__tests/WalletPermissionsManager.tokens.test.ts +1 -83
- package/src/sdk/WalletError.ts +9 -3
- package/src/storage/WalletStorageManager.ts +7 -5
|
@@ -93,8 +93,9 @@ class WalletPermissionsManager {
|
|
|
93
93
|
this.activeRequests = new Map();
|
|
94
94
|
/** Cache recently confirmed permissions to avoid repeated lookups. */
|
|
95
95
|
this.permissionCache = new Map();
|
|
96
|
+
this.recentGrants = new Map();
|
|
96
97
|
this.underlying = underlyingWallet;
|
|
97
|
-
this.adminOriginator = adminOriginator;
|
|
98
|
+
this.adminOriginator = this.normalizeOriginator(adminOriginator) || adminOriginator;
|
|
98
99
|
// Default all config options to true unless specified
|
|
99
100
|
this.config = {
|
|
100
101
|
seekProtocolPermissionsForSigning: true,
|
|
@@ -226,6 +227,7 @@ class WalletPermissionsManager {
|
|
|
226
227
|
const expiry = params.expiry || Math.floor(Date.now() / 1000) + 3600 * 24 * 30;
|
|
227
228
|
const key = this.buildRequestKey(matching.request);
|
|
228
229
|
this.cachePermission(key, expiry);
|
|
230
|
+
this.markRecentGrant(matching.request);
|
|
229
231
|
}
|
|
230
232
|
}
|
|
231
233
|
/**
|
|
@@ -259,7 +261,8 @@ class WalletPermissionsManager {
|
|
|
259
261
|
throw new Error('Request ID not found.');
|
|
260
262
|
}
|
|
261
263
|
const originalRequest = matching.request;
|
|
262
|
-
const { originator, permissions: requestedPermissions } = originalRequest;
|
|
264
|
+
const { originator, permissions: requestedPermissions, displayOriginator } = originalRequest;
|
|
265
|
+
const originLookupValues = this.buildOriginatorLookupValues(displayOriginator, originator);
|
|
263
266
|
// --- Validation: Ensure granted permissions are a subset of what was requested ---
|
|
264
267
|
if (params.granted.spendingAuthorization &&
|
|
265
268
|
!deepEqual(params.granted.spendingAuthorization, requestedPermissions.spendingAuthorization)) {
|
|
@@ -287,33 +290,39 @@ class WalletPermissionsManager {
|
|
|
287
290
|
}
|
|
288
291
|
for (const p of params.granted.protocolPermissions || []) {
|
|
289
292
|
const token = await this.findProtocolToken(originator, false, // No privileged protocols allowed in groups for added security.
|
|
290
|
-
p.protocolID, p.counterparty || 'self', true);
|
|
293
|
+
p.protocolID, p.counterparty || 'self', true, originLookupValues);
|
|
291
294
|
if (token) {
|
|
292
|
-
|
|
295
|
+
const request = {
|
|
293
296
|
type: 'protocol',
|
|
294
297
|
originator,
|
|
295
298
|
privileged: false, // No privileged protocols allowed in groups for added security.
|
|
296
299
|
protocolID: p.protocolID,
|
|
297
300
|
counterparty: p.counterparty || 'self',
|
|
298
301
|
reason: p.description
|
|
299
|
-
}
|
|
302
|
+
};
|
|
303
|
+
await this.renewPermissionOnChain(token, request, expiry);
|
|
304
|
+
this.markRecentGrant(request);
|
|
300
305
|
}
|
|
301
306
|
else {
|
|
302
|
-
|
|
307
|
+
const request = {
|
|
303
308
|
type: 'protocol',
|
|
304
309
|
originator,
|
|
305
310
|
privileged: false, // No privileged protocols allowed in groups for added security.
|
|
306
311
|
protocolID: p.protocolID,
|
|
307
312
|
counterparty: p.counterparty || 'self',
|
|
308
313
|
reason: p.description
|
|
309
|
-
}
|
|
314
|
+
};
|
|
315
|
+
await this.createPermissionOnChain(request, expiry);
|
|
316
|
+
this.markRecentGrant(request);
|
|
310
317
|
}
|
|
311
318
|
}
|
|
312
319
|
for (const b of params.granted.basketAccess || []) {
|
|
313
|
-
|
|
320
|
+
const request = { type: 'basket', originator, basket: b.basket, reason: b.description };
|
|
321
|
+
await this.createPermissionOnChain(request, expiry);
|
|
322
|
+
this.markRecentGrant(request);
|
|
314
323
|
}
|
|
315
324
|
for (const c of params.granted.certificateAccess || []) {
|
|
316
|
-
|
|
325
|
+
const request = {
|
|
317
326
|
type: 'certificate',
|
|
318
327
|
originator,
|
|
319
328
|
privileged: false, // No certificates on the privileged identity are allowed as part of groups.
|
|
@@ -323,7 +332,9 @@ class WalletPermissionsManager {
|
|
|
323
332
|
fields: c.fields
|
|
324
333
|
},
|
|
325
334
|
reason: c.description
|
|
326
|
-
}
|
|
335
|
+
};
|
|
336
|
+
await this.createPermissionOnChain(request, expiry);
|
|
337
|
+
this.markRecentGrant(request);
|
|
327
338
|
}
|
|
328
339
|
// Resolve all pending promises for this request
|
|
329
340
|
for (const p of matching.pending) {
|
|
@@ -355,6 +366,8 @@ class WalletPermissionsManager {
|
|
|
355
366
|
* If no valid (unexpired) permission token is found, triggers a permission request flow.
|
|
356
367
|
*/
|
|
357
368
|
async ensureProtocolPermission({ originator, privileged, protocolID, counterparty, reason, seekPermission = true, usageType }) {
|
|
369
|
+
const { normalized: normalizedOriginator, lookupValues } = this.prepareOriginator(originator);
|
|
370
|
+
originator = normalizedOriginator;
|
|
358
371
|
// 1) adminOriginator can do anything
|
|
359
372
|
if (this.isAdminOriginator(originator))
|
|
360
373
|
return true;
|
|
@@ -398,9 +411,12 @@ class WalletPermissionsManager {
|
|
|
398
411
|
if (this.isPermissionCached(cacheKey)) {
|
|
399
412
|
return true;
|
|
400
413
|
}
|
|
414
|
+
if (this.isRecentlyGranted(cacheKey)) {
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
401
417
|
// 4) Attempt to find a valid token in the internal basket
|
|
402
418
|
const token = await this.findProtocolToken(originator, privileged, protocolID, counterparty,
|
|
403
|
-
/*includeExpired=*/ true);
|
|
419
|
+
/*includeExpired=*/ true, lookupValues);
|
|
404
420
|
if (token) {
|
|
405
421
|
if (!this.isTokenExpired(token.expiry)) {
|
|
406
422
|
// valid and unexpired
|
|
@@ -446,6 +462,8 @@ class WalletPermissionsManager {
|
|
|
446
462
|
* If not, triggers a permission request flow.
|
|
447
463
|
*/
|
|
448
464
|
async ensureBasketAccess({ originator, basket, reason, seekPermission = true, usageType }) {
|
|
465
|
+
const { normalized: normalizedOriginator, lookupValues } = this.prepareOriginator(originator);
|
|
466
|
+
originator = normalizedOriginator;
|
|
449
467
|
if (this.isAdminOriginator(originator))
|
|
450
468
|
return true;
|
|
451
469
|
if (this.isAdminBasket(basket)) {
|
|
@@ -461,7 +479,10 @@ class WalletPermissionsManager {
|
|
|
461
479
|
if (this.isPermissionCached(cacheKey)) {
|
|
462
480
|
return true;
|
|
463
481
|
}
|
|
464
|
-
|
|
482
|
+
if (this.isRecentlyGranted(cacheKey)) {
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
const token = await this.findBasketToken(originator, basket, true, lookupValues);
|
|
465
486
|
if (token) {
|
|
466
487
|
if (!this.isTokenExpired(token.expiry)) {
|
|
467
488
|
this.cachePermission(cacheKey, token.expiry);
|
|
@@ -501,6 +522,8 @@ class WalletPermissionsManager {
|
|
|
501
522
|
* This is relevant when revealing certificate fields in DCAP contexts.
|
|
502
523
|
*/
|
|
503
524
|
async ensureCertificateAccess({ originator, privileged, verifier, certType, fields, reason, seekPermission = true, usageType }) {
|
|
525
|
+
const { normalized: normalizedOriginator, lookupValues } = this.prepareOriginator(originator);
|
|
526
|
+
originator = normalizedOriginator;
|
|
504
527
|
if (this.isAdminOriginator(originator))
|
|
505
528
|
return true;
|
|
506
529
|
if (usageType === 'disclosure' && !this.config.seekCertificateDisclosurePermissions) {
|
|
@@ -518,8 +541,11 @@ class WalletPermissionsManager {
|
|
|
518
541
|
if (this.isPermissionCached(cacheKey)) {
|
|
519
542
|
return true;
|
|
520
543
|
}
|
|
544
|
+
if (this.isRecentlyGranted(cacheKey)) {
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
521
547
|
const token = await this.findCertificateToken(originator, privileged, verifier, certType, fields,
|
|
522
|
-
/*includeExpired=*/ true);
|
|
548
|
+
/*includeExpired=*/ true, lookupValues);
|
|
523
549
|
if (token) {
|
|
524
550
|
if (!this.isTokenExpired(token.expiry)) {
|
|
525
551
|
this.cachePermission(cacheKey, token.expiry);
|
|
@@ -560,6 +586,8 @@ class WalletPermissionsManager {
|
|
|
560
586
|
* If the existing token limit is insufficient, attempts to renew. If no token, attempts to create one.
|
|
561
587
|
*/
|
|
562
588
|
async ensureSpendingAuthorization({ originator, satoshis, lineItems, reason, seekPermission = true }) {
|
|
589
|
+
const { normalized: normalizedOriginator, lookupValues } = this.prepareOriginator(originator);
|
|
590
|
+
originator = normalizedOriginator;
|
|
563
591
|
if (this.isAdminOriginator(originator))
|
|
564
592
|
return true;
|
|
565
593
|
if (!this.config.seekSpendingPermissions) {
|
|
@@ -570,7 +598,7 @@ class WalletPermissionsManager {
|
|
|
570
598
|
if (this.isPermissionCached(cacheKey)) {
|
|
571
599
|
return true;
|
|
572
600
|
}
|
|
573
|
-
const token = await this.findSpendingToken(originator);
|
|
601
|
+
const token = await this.findSpendingToken(originator, lookupValues);
|
|
574
602
|
if (token === null || token === void 0 ? void 0 : token.authorizedAmount) {
|
|
575
603
|
// Check how much has been spent so far
|
|
576
604
|
const spentSoFar = await this.querySpentSince(token);
|
|
@@ -612,6 +640,8 @@ class WalletPermissionsManager {
|
|
|
612
640
|
* If no valid (unexpired) permission token is found, triggers a permission request flow.
|
|
613
641
|
*/
|
|
614
642
|
async ensureLabelAccess({ originator, label, reason, seekPermission = true, usageType }) {
|
|
643
|
+
const { normalized: normalizedOriginator } = this.prepareOriginator(originator);
|
|
644
|
+
originator = normalizedOriginator;
|
|
615
645
|
// 1) adminOriginator can do anything
|
|
616
646
|
if (this.isAdminOriginator(originator))
|
|
617
647
|
return true;
|
|
@@ -654,7 +684,14 @@ class WalletPermissionsManager {
|
|
|
654
684
|
* and return a promise that resolves once permission is granted or rejects if denied.
|
|
655
685
|
*/
|
|
656
686
|
async requestPermissionFlow(r) {
|
|
657
|
-
|
|
687
|
+
var _a;
|
|
688
|
+
const normalizedOriginator = this.normalizeOriginator(r.originator) || r.originator;
|
|
689
|
+
const preparedRequest = {
|
|
690
|
+
...r,
|
|
691
|
+
originator: normalizedOriginator,
|
|
692
|
+
displayOriginator: (_a = r.displayOriginator) !== null && _a !== void 0 ? _a : r.originator
|
|
693
|
+
};
|
|
694
|
+
const key = this.buildRequestKey(preparedRequest);
|
|
658
695
|
// If there's already a queue for the same resource, we piggyback on it
|
|
659
696
|
const existingQueue = this.activeRequests.get(key);
|
|
660
697
|
if (existingQueue && existingQueue.pending.length > 0) {
|
|
@@ -666,32 +703,32 @@ class WalletPermissionsManager {
|
|
|
666
703
|
// Return a promise that resolves or rejects once the user grants/denies
|
|
667
704
|
return new Promise(async (resolve, reject) => {
|
|
668
705
|
this.activeRequests.set(key, {
|
|
669
|
-
request:
|
|
706
|
+
request: preparedRequest,
|
|
670
707
|
pending: [{ resolve, reject }]
|
|
671
708
|
});
|
|
672
709
|
// Fire the relevant onXXXRequested event (which one depends on r.type)
|
|
673
|
-
switch (
|
|
710
|
+
switch (preparedRequest.type) {
|
|
674
711
|
case 'protocol':
|
|
675
712
|
await this.callEvent('onProtocolPermissionRequested', {
|
|
676
|
-
...
|
|
713
|
+
...preparedRequest,
|
|
677
714
|
requestID: key
|
|
678
715
|
});
|
|
679
716
|
break;
|
|
680
717
|
case 'basket':
|
|
681
718
|
await this.callEvent('onBasketAccessRequested', {
|
|
682
|
-
...
|
|
719
|
+
...preparedRequest,
|
|
683
720
|
requestID: key
|
|
684
721
|
});
|
|
685
722
|
break;
|
|
686
723
|
case 'certificate':
|
|
687
724
|
await this.callEvent('onCertificateAccessRequested', {
|
|
688
|
-
...
|
|
725
|
+
...preparedRequest,
|
|
689
726
|
requestID: key
|
|
690
727
|
});
|
|
691
728
|
break;
|
|
692
729
|
case 'spending':
|
|
693
730
|
await this.callEvent('onSpendingAuthorizationRequested', {
|
|
694
|
-
...
|
|
731
|
+
...preparedRequest,
|
|
695
732
|
requestID: key
|
|
696
733
|
});
|
|
697
734
|
break;
|
|
@@ -761,250 +798,284 @@ class WalletPermissionsManager {
|
|
|
761
798
|
return expiry > 0 && expiry < now;
|
|
762
799
|
}
|
|
763
800
|
/** Looks for a DPACP permission token matching origin/domain, privileged, protocol, cpty. */
|
|
764
|
-
async findProtocolToken(originator, privileged, protocolID, counterparty, includeExpired) {
|
|
801
|
+
async findProtocolToken(originator, privileged, protocolID, counterparty, includeExpired, originatorLookupValues) {
|
|
765
802
|
const [secLevel, protoName] = protocolID;
|
|
766
|
-
const
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
basket: BASKET_MAP.protocol,
|
|
777
|
-
tags,
|
|
778
|
-
tagQueryMode: 'all',
|
|
779
|
-
include: 'entire transactions'
|
|
780
|
-
}, this.adminOriginator);
|
|
781
|
-
for (const out of result.outputs) {
|
|
782
|
-
const [txid, outputIndexStr] = out.outpoint.split('.');
|
|
783
|
-
const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
|
|
784
|
-
const dec = sdk_1.PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript);
|
|
785
|
-
if (!dec || !dec.fields || dec.fields.length < 6)
|
|
786
|
-
continue;
|
|
787
|
-
const domainRaw = dec.fields[0];
|
|
788
|
-
const expiryRaw = dec.fields[1];
|
|
789
|
-
const privRaw = dec.fields[2];
|
|
790
|
-
const secLevelRaw = dec.fields[3];
|
|
791
|
-
const protoNameRaw = dec.fields[4];
|
|
792
|
-
const counterpartyRaw = dec.fields[5];
|
|
793
|
-
const domainDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw));
|
|
794
|
-
const expiryDecoded = parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10);
|
|
795
|
-
const privDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(privRaw)) === 'true';
|
|
796
|
-
const secLevelDecoded = parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(secLevelRaw)), 10);
|
|
797
|
-
const protoNameDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(protoNameRaw));
|
|
798
|
-
const cptyDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(counterpartyRaw));
|
|
799
|
-
if (domainDecoded !== originator ||
|
|
800
|
-
privDecoded !== !!privileged ||
|
|
801
|
-
secLevelDecoded !== secLevel ||
|
|
802
|
-
protoNameDecoded !== protoName ||
|
|
803
|
-
(secLevelDecoded === 2 && cptyDecoded !== counterparty)) {
|
|
804
|
-
continue;
|
|
803
|
+
const originsToTry = (originatorLookupValues === null || originatorLookupValues === void 0 ? void 0 : originatorLookupValues.length) ? originatorLookupValues : [originator];
|
|
804
|
+
for (const originTag of originsToTry) {
|
|
805
|
+
const tags = [
|
|
806
|
+
`originator ${originTag}`,
|
|
807
|
+
`privileged ${!!privileged}`,
|
|
808
|
+
`protocolName ${protoName}`,
|
|
809
|
+
`protocolSecurityLevel ${secLevel}`
|
|
810
|
+
];
|
|
811
|
+
if (secLevel === 2) {
|
|
812
|
+
tags.push(`counterparty ${counterparty}`);
|
|
805
813
|
}
|
|
806
|
-
|
|
807
|
-
|
|
814
|
+
const result = await this.underlying.listOutputs({
|
|
815
|
+
basket: BASKET_MAP.protocol,
|
|
816
|
+
tags,
|
|
817
|
+
tagQueryMode: 'all',
|
|
818
|
+
include: 'entire transactions'
|
|
819
|
+
}, this.adminOriginator);
|
|
820
|
+
for (const out of result.outputs) {
|
|
821
|
+
const [txid, outputIndexStr] = out.outpoint.split('.');
|
|
822
|
+
const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
|
|
823
|
+
const dec = sdk_1.PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript);
|
|
824
|
+
if (!dec || !dec.fields || dec.fields.length < 6)
|
|
825
|
+
continue;
|
|
826
|
+
const domainRaw = dec.fields[0];
|
|
827
|
+
const expiryRaw = dec.fields[1];
|
|
828
|
+
const privRaw = dec.fields[2];
|
|
829
|
+
const secLevelRaw = dec.fields[3];
|
|
830
|
+
const protoNameRaw = dec.fields[4];
|
|
831
|
+
const counterpartyRaw = dec.fields[5];
|
|
832
|
+
const domainDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw));
|
|
833
|
+
const normalizedDomain = this.normalizeOriginator(domainDecoded);
|
|
834
|
+
if (normalizedDomain !== originator) {
|
|
835
|
+
continue;
|
|
836
|
+
}
|
|
837
|
+
const expiryDecoded = parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10);
|
|
838
|
+
const privDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(privRaw)) === 'true';
|
|
839
|
+
const secLevelDecoded = parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(secLevelRaw)), 10);
|
|
840
|
+
const protoNameDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(protoNameRaw));
|
|
841
|
+
const cptyDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(counterpartyRaw));
|
|
842
|
+
if (privDecoded !== !!privileged ||
|
|
843
|
+
secLevelDecoded !== secLevel ||
|
|
844
|
+
protoNameDecoded !== protoName ||
|
|
845
|
+
(secLevelDecoded === 2 && cptyDecoded !== counterparty)) {
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
if (!includeExpired && this.isTokenExpired(expiryDecoded)) {
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
return {
|
|
852
|
+
tx: tx.toBEEF(),
|
|
853
|
+
txid: out.outpoint.split('.')[0],
|
|
854
|
+
outputIndex: parseInt(out.outpoint.split('.')[1], 10),
|
|
855
|
+
outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
|
|
856
|
+
satoshis: out.satoshis,
|
|
857
|
+
originator,
|
|
858
|
+
rawOriginator: domainDecoded,
|
|
859
|
+
privileged,
|
|
860
|
+
protocol: protoName,
|
|
861
|
+
securityLevel: secLevel,
|
|
862
|
+
expiry: expiryDecoded,
|
|
863
|
+
counterparty: cptyDecoded
|
|
864
|
+
};
|
|
808
865
|
}
|
|
809
|
-
return {
|
|
810
|
-
tx: tx.toBEEF(),
|
|
811
|
-
txid: out.outpoint.split('.')[0],
|
|
812
|
-
outputIndex: parseInt(out.outpoint.split('.')[1], 10),
|
|
813
|
-
outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
|
|
814
|
-
satoshis: out.satoshis,
|
|
815
|
-
originator,
|
|
816
|
-
privileged,
|
|
817
|
-
protocol: protoName,
|
|
818
|
-
securityLevel: secLevel,
|
|
819
|
-
expiry: expiryDecoded,
|
|
820
|
-
counterparty: cptyDecoded
|
|
821
|
-
};
|
|
822
866
|
}
|
|
823
867
|
return undefined;
|
|
824
868
|
}
|
|
825
869
|
/** Finds ALL DPACP permission tokens matching origin/domain, privileged, protocol, cpty. Never filters by expiry. */
|
|
826
|
-
async findAllProtocolTokens(originator, privileged, protocolID, counterparty) {
|
|
870
|
+
async findAllProtocolTokens(originator, privileged, protocolID, counterparty, originatorLookupValues) {
|
|
827
871
|
const [secLevel, protoName] = protocolID;
|
|
828
|
-
const
|
|
829
|
-
`originator ${originator}`,
|
|
830
|
-
`privileged ${!!privileged}`,
|
|
831
|
-
`protocolName ${protoName}`,
|
|
832
|
-
`protocolSecurityLevel ${secLevel}`
|
|
833
|
-
];
|
|
834
|
-
if (secLevel === 2) {
|
|
835
|
-
tags.push(`counterparty ${counterparty}`);
|
|
836
|
-
}
|
|
837
|
-
const result = await this.underlying.listOutputs({
|
|
838
|
-
basket: BASKET_MAP.protocol,
|
|
839
|
-
tags,
|
|
840
|
-
tagQueryMode: 'all',
|
|
841
|
-
include: 'entire transactions'
|
|
842
|
-
}, this.adminOriginator);
|
|
872
|
+
const originsToTry = (originatorLookupValues === null || originatorLookupValues === void 0 ? void 0 : originatorLookupValues.length) ? originatorLookupValues : [originator];
|
|
843
873
|
const matches = [];
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
const
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
const
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
const
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
874
|
+
const seen = new Set();
|
|
875
|
+
for (const originTag of originsToTry) {
|
|
876
|
+
const tags = [
|
|
877
|
+
`originator ${originTag}`,
|
|
878
|
+
`privileged ${!!privileged}`,
|
|
879
|
+
`protocolName ${protoName}`,
|
|
880
|
+
`protocolSecurityLevel ${secLevel}`
|
|
881
|
+
];
|
|
882
|
+
if (secLevel === 2) {
|
|
883
|
+
tags.push(`counterparty ${counterparty}`);
|
|
884
|
+
}
|
|
885
|
+
const result = await this.underlying.listOutputs({
|
|
886
|
+
basket: BASKET_MAP.protocol,
|
|
887
|
+
tags,
|
|
888
|
+
tagQueryMode: 'all',
|
|
889
|
+
include: 'entire transactions'
|
|
890
|
+
}, this.adminOriginator);
|
|
891
|
+
for (const out of result.outputs) {
|
|
892
|
+
if (seen.has(out.outpoint))
|
|
893
|
+
continue;
|
|
894
|
+
const [txid, outputIndexStr] = out.outpoint.split('.');
|
|
895
|
+
const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
|
|
896
|
+
const vout = Number(outputIndexStr);
|
|
897
|
+
const dec = sdk_1.PushDrop.decode(tx.outputs[vout].lockingScript);
|
|
898
|
+
if (!dec || !dec.fields || dec.fields.length < 6)
|
|
899
|
+
continue;
|
|
900
|
+
const domainRaw = dec.fields[0];
|
|
901
|
+
const expiryRaw = dec.fields[1];
|
|
902
|
+
const privRaw = dec.fields[2];
|
|
903
|
+
const secLevelRaw = dec.fields[3];
|
|
904
|
+
const protoNameRaw = dec.fields[4];
|
|
905
|
+
const counterpartyRaw = dec.fields[5];
|
|
906
|
+
const domainDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw));
|
|
907
|
+
const normalizedDomain = this.normalizeOriginator(domainDecoded);
|
|
908
|
+
if (normalizedDomain !== originator) {
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
const expiryDecoded = parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10);
|
|
912
|
+
const privDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(privRaw)) === 'true';
|
|
913
|
+
const secLevelDecoded = parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(secLevelRaw)), 10);
|
|
914
|
+
const protoNameDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(protoNameRaw));
|
|
915
|
+
const cptyDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(counterpartyRaw));
|
|
916
|
+
if (privDecoded !== !!privileged ||
|
|
917
|
+
secLevelDecoded !== secLevel ||
|
|
918
|
+
protoNameDecoded !== protoName ||
|
|
919
|
+
(secLevelDecoded === 2 && cptyDecoded !== counterparty)) {
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
seen.add(out.outpoint);
|
|
923
|
+
matches.push({
|
|
924
|
+
tx: tx.toBEEF(),
|
|
925
|
+
txid,
|
|
926
|
+
outputIndex: vout,
|
|
927
|
+
outputScript: tx.outputs[vout].lockingScript.toHex(),
|
|
928
|
+
satoshis: out.satoshis,
|
|
929
|
+
originator,
|
|
930
|
+
rawOriginator: domainDecoded,
|
|
931
|
+
privileged,
|
|
932
|
+
protocol: protoName,
|
|
933
|
+
securityLevel: secLevel,
|
|
934
|
+
expiry: expiryDecoded,
|
|
935
|
+
counterparty: cptyDecoded
|
|
936
|
+
});
|
|
871
937
|
}
|
|
872
|
-
matches.push({
|
|
873
|
-
tx: tx.toBEEF(),
|
|
874
|
-
txid,
|
|
875
|
-
outputIndex: vout,
|
|
876
|
-
outputScript: tx.outputs[vout].lockingScript.toHex(),
|
|
877
|
-
satoshis: out.satoshis,
|
|
878
|
-
originator,
|
|
879
|
-
privileged,
|
|
880
|
-
protocol: protoName,
|
|
881
|
-
securityLevel: secLevel,
|
|
882
|
-
expiry: expiryDecoded,
|
|
883
|
-
counterparty: cptyDecoded
|
|
884
|
-
});
|
|
885
938
|
}
|
|
886
939
|
return matches;
|
|
887
940
|
}
|
|
888
941
|
/** Looks for a DBAP token matching (originator, basket). */
|
|
889
|
-
async findBasketToken(originator, basket, includeExpired) {
|
|
890
|
-
const
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
const
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
942
|
+
async findBasketToken(originator, basket, includeExpired, originatorLookupValues) {
|
|
943
|
+
const originsToTry = (originatorLookupValues === null || originatorLookupValues === void 0 ? void 0 : originatorLookupValues.length) ? originatorLookupValues : [originator];
|
|
944
|
+
for (const originTag of originsToTry) {
|
|
945
|
+
const result = await this.underlying.listOutputs({
|
|
946
|
+
basket: BASKET_MAP.basket,
|
|
947
|
+
tags: [`originator ${originTag}`, `basket ${basket}`],
|
|
948
|
+
tagQueryMode: 'all',
|
|
949
|
+
include: 'entire transactions'
|
|
950
|
+
}, this.adminOriginator);
|
|
951
|
+
for (const out of result.outputs) {
|
|
952
|
+
const [txid, outputIndexStr] = out.outpoint.split('.');
|
|
953
|
+
const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
|
|
954
|
+
const dec = sdk_1.PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript);
|
|
955
|
+
if (!(dec === null || dec === void 0 ? void 0 : dec.fields) || dec.fields.length < 3)
|
|
956
|
+
continue;
|
|
957
|
+
const domainRaw = dec.fields[0];
|
|
958
|
+
const expiryRaw = dec.fields[1];
|
|
959
|
+
const basketRaw = dec.fields[2];
|
|
960
|
+
const domainDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw));
|
|
961
|
+
const normalizedDomain = this.normalizeOriginator(domainDecoded);
|
|
962
|
+
if (normalizedDomain !== originator) {
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
const expiryDecoded = parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10);
|
|
966
|
+
const basketDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(basketRaw));
|
|
967
|
+
if (basketDecoded !== basket)
|
|
968
|
+
continue;
|
|
969
|
+
if (!includeExpired && this.isTokenExpired(expiryDecoded))
|
|
970
|
+
continue;
|
|
971
|
+
return {
|
|
972
|
+
tx: tx.toBEEF(),
|
|
973
|
+
txid: out.outpoint.split('.')[0],
|
|
974
|
+
outputIndex: parseInt(out.outpoint.split('.')[1], 10),
|
|
975
|
+
outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
|
|
976
|
+
satoshis: out.satoshis,
|
|
977
|
+
originator,
|
|
978
|
+
rawOriginator: domainDecoded,
|
|
979
|
+
basketName: basketDecoded,
|
|
980
|
+
expiry: expiryDecoded
|
|
981
|
+
};
|
|
982
|
+
}
|
|
922
983
|
}
|
|
923
984
|
return undefined;
|
|
924
985
|
}
|
|
925
986
|
/** Looks for a DCAP token matching (origin, privileged, verifier, certType, fields subset). */
|
|
926
|
-
async findCertificateToken(originator, privileged, verifier, certType, fields, includeExpired) {
|
|
927
|
-
const
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
const
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
privDecoded
|
|
949
|
-
typeDecoded
|
|
950
|
-
verifierDec
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
987
|
+
async findCertificateToken(originator, privileged, verifier, certType, fields, includeExpired, originatorLookupValues) {
|
|
988
|
+
const originsToTry = (originatorLookupValues === null || originatorLookupValues === void 0 ? void 0 : originatorLookupValues.length) ? originatorLookupValues : [originator];
|
|
989
|
+
for (const originTag of originsToTry) {
|
|
990
|
+
const result = await this.underlying.listOutputs({
|
|
991
|
+
basket: BASKET_MAP.certificate,
|
|
992
|
+
tags: [`originator ${originTag}`, `privileged ${!!privileged}`, `type ${certType}`, `verifier ${verifier}`],
|
|
993
|
+
tagQueryMode: 'all',
|
|
994
|
+
include: 'entire transactions'
|
|
995
|
+
}, this.adminOriginator);
|
|
996
|
+
for (const out of result.outputs) {
|
|
997
|
+
const [txid, outputIndexStr] = out.outpoint.split('.');
|
|
998
|
+
const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
|
|
999
|
+
const dec = sdk_1.PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript);
|
|
1000
|
+
if (!(dec === null || dec === void 0 ? void 0 : dec.fields) || dec.fields.length < 6)
|
|
1001
|
+
continue;
|
|
1002
|
+
const [domainRaw, expiryRaw, privRaw, typeRaw, fieldsRaw, verifierRaw] = dec.fields;
|
|
1003
|
+
const domainDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw));
|
|
1004
|
+
const normalizedDomain = this.normalizeOriginator(domainDecoded);
|
|
1005
|
+
if (normalizedDomain !== originator) {
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
const expiryDecoded = parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10);
|
|
1009
|
+
const privDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(privRaw)) === 'true';
|
|
1010
|
+
const typeDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(typeRaw));
|
|
1011
|
+
const verifierDec = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(verifierRaw));
|
|
1012
|
+
const fieldsJson = await this.decryptPermissionTokenField(fieldsRaw);
|
|
1013
|
+
const allFields = JSON.parse(sdk_1.Utils.toUTF8(fieldsJson));
|
|
1014
|
+
if (privDecoded !== !!privileged || typeDecoded !== certType || verifierDec !== verifier) {
|
|
1015
|
+
continue;
|
|
1016
|
+
}
|
|
1017
|
+
// Check if 'fields' is a subset of 'allFields'
|
|
1018
|
+
const setAll = new Set(allFields);
|
|
1019
|
+
if (fields.some(f => !setAll.has(f))) {
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
1022
|
+
if (!includeExpired && this.isTokenExpired(expiryDecoded)) {
|
|
1023
|
+
continue;
|
|
1024
|
+
}
|
|
1025
|
+
return {
|
|
1026
|
+
tx: tx.toBEEF(),
|
|
1027
|
+
txid: out.outpoint.split('.')[0],
|
|
1028
|
+
outputIndex: parseInt(out.outpoint.split('.')[1], 10),
|
|
1029
|
+
outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
|
|
1030
|
+
satoshis: out.satoshis,
|
|
1031
|
+
originator,
|
|
1032
|
+
rawOriginator: domainDecoded,
|
|
1033
|
+
privileged,
|
|
1034
|
+
verifier: verifierDec,
|
|
1035
|
+
certType: typeDecoded,
|
|
1036
|
+
certFields: allFields,
|
|
1037
|
+
expiry: expiryDecoded
|
|
1038
|
+
};
|
|
960
1039
|
}
|
|
961
|
-
return {
|
|
962
|
-
tx: tx.toBEEF(),
|
|
963
|
-
txid: out.outpoint.split('.')[0],
|
|
964
|
-
outputIndex: parseInt(out.outpoint.split('.')[1], 10),
|
|
965
|
-
outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
|
|
966
|
-
satoshis: out.satoshis,
|
|
967
|
-
originator,
|
|
968
|
-
privileged,
|
|
969
|
-
verifier: verifierDec,
|
|
970
|
-
certType: typeDecoded,
|
|
971
|
-
certFields: allFields,
|
|
972
|
-
expiry: expiryDecoded
|
|
973
|
-
};
|
|
974
1040
|
}
|
|
975
1041
|
return undefined;
|
|
976
1042
|
}
|
|
977
1043
|
/** Looks for a DSAP token matching origin, returning the first one found. */
|
|
978
|
-
async findSpendingToken(originator) {
|
|
979
|
-
const
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
const
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1044
|
+
async findSpendingToken(originator, originatorLookupValues) {
|
|
1045
|
+
const originsToTry = (originatorLookupValues === null || originatorLookupValues === void 0 ? void 0 : originatorLookupValues.length) ? originatorLookupValues : [originator];
|
|
1046
|
+
for (const originTag of originsToTry) {
|
|
1047
|
+
const result = await this.underlying.listOutputs({
|
|
1048
|
+
basket: BASKET_MAP.spending,
|
|
1049
|
+
tags: [`originator ${originTag}`],
|
|
1050
|
+
tagQueryMode: 'all',
|
|
1051
|
+
include: 'entire transactions'
|
|
1052
|
+
}, this.adminOriginator);
|
|
1053
|
+
for (const out of result.outputs) {
|
|
1054
|
+
const [txid, outputIndexStr] = out.outpoint.split('.');
|
|
1055
|
+
const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
|
|
1056
|
+
const dec = sdk_1.PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript);
|
|
1057
|
+
if (!(dec === null || dec === void 0 ? void 0 : dec.fields) || dec.fields.length < 2)
|
|
1058
|
+
continue;
|
|
1059
|
+
const domainRaw = dec.fields[0];
|
|
1060
|
+
const amtRaw = dec.fields[1];
|
|
1061
|
+
const domainDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw));
|
|
1062
|
+
const normalizedDomain = this.normalizeOriginator(domainDecoded);
|
|
1063
|
+
if (normalizedDomain !== originator)
|
|
1064
|
+
continue;
|
|
1065
|
+
const amtDecodedStr = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(amtRaw));
|
|
1066
|
+
const authorizedAmount = parseInt(amtDecodedStr, 10);
|
|
1067
|
+
return {
|
|
1068
|
+
tx: tx.toBEEF(),
|
|
1069
|
+
txid: out.outpoint.split('.')[0],
|
|
1070
|
+
outputIndex: parseInt(out.outpoint.split('.')[1], 10),
|
|
1071
|
+
outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
|
|
1072
|
+
satoshis: out.satoshis,
|
|
1073
|
+
originator,
|
|
1074
|
+
rawOriginator: domainDecoded,
|
|
1075
|
+
authorizedAmount,
|
|
1076
|
+
expiry: 0 // Not time-limited, monthly authorization
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1008
1079
|
}
|
|
1009
1080
|
return undefined;
|
|
1010
1081
|
}
|
|
@@ -1023,11 +1094,16 @@ class WalletPermissionsManager {
|
|
|
1023
1094
|
* Returns spending for an originator in the current calendar month.
|
|
1024
1095
|
*/
|
|
1025
1096
|
async querySpentSince(token) {
|
|
1026
|
-
const
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1097
|
+
const labelOrigins = this.buildOriginatorLookupValues(token.rawOriginator, token.originator);
|
|
1098
|
+
let total = 0;
|
|
1099
|
+
for (const labelOrigin of labelOrigins) {
|
|
1100
|
+
const { actions } = await this.underlying.listActions({
|
|
1101
|
+
labels: [`admin originator ${labelOrigin}`, `admin month ${this.getCurrentMonthYearUTC()}`],
|
|
1102
|
+
labelQueryMode: 'all'
|
|
1103
|
+
}, this.adminOriginator);
|
|
1104
|
+
total += actions.reduce((a, e) => a + e.satoshis, 0);
|
|
1105
|
+
}
|
|
1106
|
+
return total;
|
|
1031
1107
|
}
|
|
1032
1108
|
/* ---------------------------------------------------------------------
|
|
1033
1109
|
* 5) CREATE / RENEW / REVOKE PERMISSION TOKENS ON CHAIN
|
|
@@ -1042,6 +1118,8 @@ class WalletPermissionsManager {
|
|
|
1042
1118
|
* @param amount For DSAP, the authorized spending limit
|
|
1043
1119
|
*/
|
|
1044
1120
|
async createPermissionOnChain(r, expiry, amount) {
|
|
1121
|
+
const normalizedOriginator = this.normalizeOriginator(r.originator) || r.originator;
|
|
1122
|
+
r.originator = normalizedOriginator;
|
|
1045
1123
|
const basketName = BASKET_MAP[r.type];
|
|
1046
1124
|
if (!basketName)
|
|
1047
1125
|
return;
|
|
@@ -1131,13 +1209,14 @@ class WalletPermissionsManager {
|
|
|
1131
1209
|
* @param newAmount For DSAP, the new authorized amount
|
|
1132
1210
|
*/
|
|
1133
1211
|
async renewPermissionOnChain(oldToken, r, newExpiry, newAmount) {
|
|
1212
|
+
r.originator = this.normalizeOriginator(r.originator) || r.originator;
|
|
1134
1213
|
// 1) build new fields
|
|
1135
1214
|
const newFields = await this.buildPushdropFields(r, newExpiry, newAmount);
|
|
1136
1215
|
// 2) new script
|
|
1137
1216
|
const newScript = await new sdk_1.PushDrop(this.underlying).lock(newFields, WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL, '1', 'self', true, true);
|
|
1138
1217
|
const tags = this.buildTagsForRequest(r);
|
|
1139
1218
|
// Check if there are multiple old tokens for the same parameters (shouldn't usually happen)
|
|
1140
|
-
const oldTokens = await this.findAllProtocolTokens(oldToken.originator, oldToken.privileged, [oldToken.securityLevel, oldToken.protocol], oldToken.counterparty);
|
|
1219
|
+
const oldTokens = await this.findAllProtocolTokens(oldToken.originator, oldToken.privileged, [oldToken.securityLevel, oldToken.protocol], oldToken.counterparty, this.buildOriginatorLookupValues(oldToken.rawOriginator, oldToken.originator));
|
|
1141
1220
|
// If so, coalesce them into a single token first, to avoid bloat
|
|
1142
1221
|
if (oldTokens.length > 1) {
|
|
1143
1222
|
const txid = await this.coalescePermissionTokens(oldTokens, newScript, {
|
|
@@ -1145,7 +1224,6 @@ class WalletPermissionsManager {
|
|
|
1145
1224
|
basket: BASKET_MAP[r.type],
|
|
1146
1225
|
description: `Coalesce ${r.type} permission tokens`
|
|
1147
1226
|
});
|
|
1148
|
-
console.log('Coalesced permission tokens:', txid);
|
|
1149
1227
|
}
|
|
1150
1228
|
else {
|
|
1151
1229
|
// Otherwise, just proceed with the single-token renewal
|
|
@@ -1245,7 +1323,9 @@ class WalletPermissionsManager {
|
|
|
1245
1323
|
tags.push(`privileged ${!!r.privileged}`);
|
|
1246
1324
|
tags.push(`protocolName ${r.protocolID[1]}`);
|
|
1247
1325
|
tags.push(`protocolSecurityLevel ${r.protocolID[0]}`);
|
|
1248
|
-
|
|
1326
|
+
if (r.protocolID[0] === 2) {
|
|
1327
|
+
tags.push(`counterparty ${r.counterparty}`);
|
|
1328
|
+
}
|
|
1249
1329
|
break;
|
|
1250
1330
|
}
|
|
1251
1331
|
case 'basket': {
|
|
@@ -1279,56 +1359,70 @@ class WalletPermissionsManager {
|
|
|
1279
1359
|
*/
|
|
1280
1360
|
async listProtocolPermissions({ originator, privileged, protocolName, protocolSecurityLevel, counterparty } = {}) {
|
|
1281
1361
|
const basketName = BASKET_MAP.protocol;
|
|
1282
|
-
const
|
|
1283
|
-
if (originator) {
|
|
1284
|
-
tags.push(`originator ${originator}`);
|
|
1285
|
-
}
|
|
1362
|
+
const baseTags = [];
|
|
1286
1363
|
if (privileged !== undefined) {
|
|
1287
|
-
|
|
1364
|
+
baseTags.push(`privileged ${!!privileged}`);
|
|
1288
1365
|
}
|
|
1289
1366
|
if (protocolName) {
|
|
1290
|
-
|
|
1367
|
+
baseTags.push(`protocolName ${protocolName}`);
|
|
1291
1368
|
}
|
|
1292
1369
|
if (protocolSecurityLevel !== undefined) {
|
|
1293
|
-
|
|
1370
|
+
baseTags.push(`protocolSecurityLevel ${protocolSecurityLevel}`);
|
|
1294
1371
|
}
|
|
1295
1372
|
if (counterparty) {
|
|
1296
|
-
|
|
1373
|
+
baseTags.push(`counterparty ${counterparty}`);
|
|
1297
1374
|
}
|
|
1298
|
-
const
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
tagQueryMode: 'all',
|
|
1302
|
-
include: 'entire transactions',
|
|
1303
|
-
limit: 100
|
|
1304
|
-
}, this.adminOriginator);
|
|
1375
|
+
const originFilter = originator ? this.prepareOriginator(originator) : undefined;
|
|
1376
|
+
const originVariants = originFilter ? originFilter.lookupValues : [undefined];
|
|
1377
|
+
const seen = new Set();
|
|
1305
1378
|
const tokens = [];
|
|
1306
|
-
for (const
|
|
1307
|
-
const
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
const
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
txid
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1379
|
+
for (const originTag of originVariants) {
|
|
1380
|
+
const tags = [...baseTags];
|
|
1381
|
+
if (originTag) {
|
|
1382
|
+
tags.push(`originator ${originTag}`);
|
|
1383
|
+
}
|
|
1384
|
+
const result = await this.underlying.listOutputs({
|
|
1385
|
+
basket: basketName,
|
|
1386
|
+
tags,
|
|
1387
|
+
tagQueryMode: 'all',
|
|
1388
|
+
include: 'entire transactions',
|
|
1389
|
+
limit: 100
|
|
1390
|
+
}, this.adminOriginator);
|
|
1391
|
+
for (const out of result.outputs) {
|
|
1392
|
+
if (seen.has(out.outpoint))
|
|
1393
|
+
continue;
|
|
1394
|
+
const [txid, outputIndexStr] = out.outpoint.split('.');
|
|
1395
|
+
const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
|
|
1396
|
+
const dec = sdk_1.PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript);
|
|
1397
|
+
if (!(dec === null || dec === void 0 ? void 0 : dec.fields) || dec.fields.length < 6)
|
|
1398
|
+
continue;
|
|
1399
|
+
const [domainRaw, expiryRaw, privRaw, secRaw, protoRaw, cptyRaw] = dec.fields;
|
|
1400
|
+
const domainDec = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw));
|
|
1401
|
+
const normalizedDomain = this.normalizeOriginator(domainDec);
|
|
1402
|
+
if (originFilter && normalizedDomain !== originFilter.normalized) {
|
|
1403
|
+
continue;
|
|
1404
|
+
}
|
|
1405
|
+
const expiryDec = parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10);
|
|
1406
|
+
const privDec = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(privRaw)) === 'true';
|
|
1407
|
+
const secDec = parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(secRaw)), 10);
|
|
1408
|
+
const protoDec = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(protoRaw));
|
|
1409
|
+
const cptyDec = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(cptyRaw));
|
|
1410
|
+
seen.add(out.outpoint);
|
|
1411
|
+
tokens.push({
|
|
1412
|
+
tx: tx.toBEEF(),
|
|
1413
|
+
txid: out.outpoint.split('.')[0],
|
|
1414
|
+
outputIndex: parseInt(out.outpoint.split('.')[1], 10),
|
|
1415
|
+
outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
|
|
1416
|
+
satoshis: out.satoshis,
|
|
1417
|
+
originator: normalizedDomain,
|
|
1418
|
+
rawOriginator: domainDec,
|
|
1419
|
+
expiry: expiryDec,
|
|
1420
|
+
privileged: privDec,
|
|
1421
|
+
securityLevel: secDec,
|
|
1422
|
+
protocol: protoDec,
|
|
1423
|
+
counterparty: cptyDec
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1332
1426
|
}
|
|
1333
1427
|
return tokens;
|
|
1334
1428
|
}
|
|
@@ -1358,41 +1452,55 @@ class WalletPermissionsManager {
|
|
|
1358
1452
|
*/
|
|
1359
1453
|
async listBasketAccess(params = {}) {
|
|
1360
1454
|
const basketName = BASKET_MAP.basket;
|
|
1361
|
-
const
|
|
1362
|
-
if (params.originator) {
|
|
1363
|
-
tags.push(`originator ${params.originator}`);
|
|
1364
|
-
}
|
|
1455
|
+
const baseTags = [];
|
|
1365
1456
|
if (params.basket) {
|
|
1366
|
-
|
|
1457
|
+
baseTags.push(`basket ${params.basket}`);
|
|
1367
1458
|
}
|
|
1368
|
-
const
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
tagQueryMode: 'all',
|
|
1372
|
-
include: 'entire transactions',
|
|
1373
|
-
limit: 10000
|
|
1374
|
-
}, this.adminOriginator);
|
|
1459
|
+
const originFilter = params.originator ? this.prepareOriginator(params.originator) : undefined;
|
|
1460
|
+
const originVariants = originFilter ? originFilter.lookupValues : [undefined];
|
|
1461
|
+
const seen = new Set();
|
|
1375
1462
|
const tokens = [];
|
|
1376
|
-
for (const
|
|
1377
|
-
const
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1463
|
+
for (const originTag of originVariants) {
|
|
1464
|
+
const tags = [...baseTags];
|
|
1465
|
+
if (originTag) {
|
|
1466
|
+
tags.push(`originator ${originTag}`);
|
|
1467
|
+
}
|
|
1468
|
+
const result = await this.underlying.listOutputs({
|
|
1469
|
+
basket: basketName,
|
|
1470
|
+
tags,
|
|
1471
|
+
tagQueryMode: 'all',
|
|
1472
|
+
include: 'entire transactions',
|
|
1473
|
+
limit: 10000
|
|
1474
|
+
}, this.adminOriginator);
|
|
1475
|
+
for (const out of result.outputs) {
|
|
1476
|
+
if (seen.has(out.outpoint))
|
|
1477
|
+
continue;
|
|
1478
|
+
const [txid, outputIndexStr] = out.outpoint.split('.');
|
|
1479
|
+
const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
|
|
1480
|
+
const dec = sdk_1.PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript);
|
|
1481
|
+
if (!(dec === null || dec === void 0 ? void 0 : dec.fields) || dec.fields.length < 3)
|
|
1482
|
+
continue;
|
|
1483
|
+
const [domainRaw, expiryRaw, basketRaw] = dec.fields;
|
|
1484
|
+
const domainDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw));
|
|
1485
|
+
const normalizedDomain = this.normalizeOriginator(domainDecoded);
|
|
1486
|
+
if (originFilter && normalizedDomain !== originFilter.normalized) {
|
|
1487
|
+
continue;
|
|
1488
|
+
}
|
|
1489
|
+
const expiryDecoded = parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10);
|
|
1490
|
+
const basketDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(basketRaw));
|
|
1491
|
+
seen.add(out.outpoint);
|
|
1492
|
+
tokens.push({
|
|
1493
|
+
tx: tx.toBEEF(),
|
|
1494
|
+
txid: out.outpoint.split('.')[0],
|
|
1495
|
+
outputIndex: parseInt(out.outpoint.split('.')[1], 10),
|
|
1496
|
+
satoshis: out.satoshis,
|
|
1497
|
+
outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
|
|
1498
|
+
originator: normalizedDomain,
|
|
1499
|
+
rawOriginator: domainDecoded,
|
|
1500
|
+
basketName: basketDecoded,
|
|
1501
|
+
expiry: expiryDecoded
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1396
1504
|
}
|
|
1397
1505
|
return tokens;
|
|
1398
1506
|
}
|
|
@@ -1480,54 +1588,68 @@ class WalletPermissionsManager {
|
|
|
1480
1588
|
*/
|
|
1481
1589
|
async listCertificateAccess(params = {}) {
|
|
1482
1590
|
const basketName = BASKET_MAP.certificate;
|
|
1483
|
-
const
|
|
1484
|
-
if (params.originator) {
|
|
1485
|
-
tags.push(`originator ${params.originator}`);
|
|
1486
|
-
}
|
|
1591
|
+
const baseTags = [];
|
|
1487
1592
|
if (params.privileged !== undefined) {
|
|
1488
|
-
|
|
1593
|
+
baseTags.push(`privileged ${!!params.privileged}`);
|
|
1489
1594
|
}
|
|
1490
1595
|
if (params.certType) {
|
|
1491
|
-
|
|
1596
|
+
baseTags.push(`type ${params.certType}`);
|
|
1492
1597
|
}
|
|
1493
1598
|
if (params.verifier) {
|
|
1494
|
-
|
|
1599
|
+
baseTags.push(`verifier ${params.verifier}`);
|
|
1495
1600
|
}
|
|
1496
|
-
const
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
tagQueryMode: 'all',
|
|
1500
|
-
include: 'entire transactions',
|
|
1501
|
-
limit: 10000
|
|
1502
|
-
}, this.adminOriginator);
|
|
1601
|
+
const originFilter = params.originator ? this.prepareOriginator(params.originator) : undefined;
|
|
1602
|
+
const originVariants = originFilter ? originFilter.lookupValues : [undefined];
|
|
1603
|
+
const seen = new Set();
|
|
1503
1604
|
const tokens = [];
|
|
1504
|
-
for (const
|
|
1505
|
-
const
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
const
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1605
|
+
for (const originTag of originVariants) {
|
|
1606
|
+
const tags = [...baseTags];
|
|
1607
|
+
if (originTag) {
|
|
1608
|
+
tags.push(`originator ${originTag}`);
|
|
1609
|
+
}
|
|
1610
|
+
const result = await this.underlying.listOutputs({
|
|
1611
|
+
basket: basketName,
|
|
1612
|
+
tags,
|
|
1613
|
+
tagQueryMode: 'all',
|
|
1614
|
+
include: 'entire transactions',
|
|
1615
|
+
limit: 10000
|
|
1616
|
+
}, this.adminOriginator);
|
|
1617
|
+
for (const out of result.outputs) {
|
|
1618
|
+
if (seen.has(out.outpoint))
|
|
1619
|
+
continue;
|
|
1620
|
+
const [txid, outputIndexStr] = out.outpoint.split('.');
|
|
1621
|
+
const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
|
|
1622
|
+
const dec = sdk_1.PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript);
|
|
1623
|
+
if (!(dec === null || dec === void 0 ? void 0 : dec.fields) || dec.fields.length < 6)
|
|
1624
|
+
continue;
|
|
1625
|
+
const [domainRaw, expiryRaw, privRaw, typeRaw, fieldsRaw, verifierRaw] = dec.fields;
|
|
1626
|
+
const domainDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw));
|
|
1627
|
+
const normalizedDomain = this.normalizeOriginator(domainDecoded);
|
|
1628
|
+
if (originFilter && normalizedDomain !== originFilter.normalized) {
|
|
1629
|
+
continue;
|
|
1630
|
+
}
|
|
1631
|
+
const expiryDecoded = parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10);
|
|
1632
|
+
const privDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(privRaw)) === 'true';
|
|
1633
|
+
const typeDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(typeRaw));
|
|
1634
|
+
const verifierDec = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(verifierRaw));
|
|
1635
|
+
const fieldsJson = await this.decryptPermissionTokenField(fieldsRaw);
|
|
1636
|
+
const allFields = JSON.parse(sdk_1.Utils.toUTF8(fieldsJson));
|
|
1637
|
+
seen.add(out.outpoint);
|
|
1638
|
+
tokens.push({
|
|
1639
|
+
tx: tx.toBEEF(),
|
|
1640
|
+
txid: out.outpoint.split('.')[0],
|
|
1641
|
+
outputIndex: parseInt(out.outpoint.split('.')[1], 10),
|
|
1642
|
+
satoshis: out.satoshis,
|
|
1643
|
+
outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
|
|
1644
|
+
originator: normalizedDomain,
|
|
1645
|
+
rawOriginator: domainDecoded,
|
|
1646
|
+
privileged: privDecoded,
|
|
1647
|
+
certType: typeDecoded,
|
|
1648
|
+
certFields: allFields,
|
|
1649
|
+
verifier: verifierDec,
|
|
1650
|
+
expiry: expiryDecoded
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1531
1653
|
}
|
|
1532
1654
|
return tokens;
|
|
1533
1655
|
}
|
|
@@ -2063,8 +2185,10 @@ class WalletPermissionsManager {
|
|
|
2063
2185
|
}
|
|
2064
2186
|
async waitForAuthentication(...args) {
|
|
2065
2187
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
2066
|
-
|
|
2188
|
+
let [_, originator] = args;
|
|
2067
2189
|
if (this.config.seekGroupedPermission && originator) {
|
|
2190
|
+
const { normalized: normalizedOriginator } = this.prepareOriginator(originator);
|
|
2191
|
+
originator = normalizedOriginator;
|
|
2068
2192
|
// 1. Fetch manifest.json from the originator
|
|
2069
2193
|
let groupPermissions;
|
|
2070
2194
|
try {
|
|
@@ -2146,7 +2270,7 @@ class WalletPermissionsManager {
|
|
|
2146
2270
|
try {
|
|
2147
2271
|
await new Promise(async (resolve, reject) => {
|
|
2148
2272
|
this.activeRequests.set(key, {
|
|
2149
|
-
request: { originator, permissions: permissionsToRequest },
|
|
2273
|
+
request: { originator: originator, permissions: permissionsToRequest },
|
|
2150
2274
|
pending: [{ resolve, reject }]
|
|
2151
2275
|
});
|
|
2152
2276
|
await this.callEvent('onGroupedPermissionRequested', {
|
|
@@ -2184,7 +2308,7 @@ class WalletPermissionsManager {
|
|
|
2184
2308
|
* --------------------------------------------------------------------- */
|
|
2185
2309
|
/** Returns true if the specified origin is the admin originator. */
|
|
2186
2310
|
isAdminOriginator(originator) {
|
|
2187
|
-
return originator === this.adminOriginator;
|
|
2311
|
+
return this.normalizeOriginator(originator) === this.adminOriginator;
|
|
2188
2312
|
}
|
|
2189
2313
|
/**
|
|
2190
2314
|
* Checks if the given protocol is admin-reserved per BRC-100 rules:
|
|
@@ -2252,27 +2376,113 @@ class WalletPermissionsManager {
|
|
|
2252
2376
|
cachePermission(key, expiry) {
|
|
2253
2377
|
this.permissionCache.set(key, { expiry, cachedAt: Date.now() });
|
|
2254
2378
|
}
|
|
2379
|
+
/** Records that a non-spending permission was just granted so we can skip re-prompting briefly. */
|
|
2380
|
+
markRecentGrant(request) {
|
|
2381
|
+
if (request.type === 'spending')
|
|
2382
|
+
return;
|
|
2383
|
+
const key = this.buildRequestKey(request);
|
|
2384
|
+
if (!key)
|
|
2385
|
+
return;
|
|
2386
|
+
this.recentGrants.set(key, Date.now() + WalletPermissionsManager.RECENT_GRANT_COVER_MS);
|
|
2387
|
+
}
|
|
2388
|
+
/** Returns true if we are inside the short "cover window" immediately after granting permission. */
|
|
2389
|
+
isRecentlyGranted(key) {
|
|
2390
|
+
const expiry = this.recentGrants.get(key);
|
|
2391
|
+
if (!expiry)
|
|
2392
|
+
return false;
|
|
2393
|
+
if (Date.now() > expiry) {
|
|
2394
|
+
this.recentGrants.delete(key);
|
|
2395
|
+
return false;
|
|
2396
|
+
}
|
|
2397
|
+
return true;
|
|
2398
|
+
}
|
|
2399
|
+
/** Normalizes and canonicalizes originator domains (e.g., lowercase + drop default ports). */
|
|
2400
|
+
normalizeOriginator(originator) {
|
|
2401
|
+
if (!originator)
|
|
2402
|
+
return '';
|
|
2403
|
+
const trimmed = originator.trim();
|
|
2404
|
+
if (!trimmed) {
|
|
2405
|
+
return '';
|
|
2406
|
+
}
|
|
2407
|
+
try {
|
|
2408
|
+
const hasScheme = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(trimmed);
|
|
2409
|
+
const candidate = hasScheme ? trimmed : `https://${trimmed}`;
|
|
2410
|
+
const url = new URL(candidate);
|
|
2411
|
+
if (!url.hostname) {
|
|
2412
|
+
return trimmed.toLowerCase();
|
|
2413
|
+
}
|
|
2414
|
+
const hostname = url.hostname.toLowerCase();
|
|
2415
|
+
const needsBrackets = hostname.includes(':');
|
|
2416
|
+
const baseHost = needsBrackets ? `[${hostname}]` : hostname;
|
|
2417
|
+
const port = url.port;
|
|
2418
|
+
const defaultPort = WalletPermissionsManager.DEFAULT_PORTS[url.protocol];
|
|
2419
|
+
if (port && defaultPort && port === defaultPort) {
|
|
2420
|
+
return baseHost;
|
|
2421
|
+
}
|
|
2422
|
+
return port ? `${baseHost}:${port}` : baseHost;
|
|
2423
|
+
}
|
|
2424
|
+
catch (_a) {
|
|
2425
|
+
// Fall back to a conservative lowercase trim if URL parsing fails.
|
|
2426
|
+
return trimmed.toLowerCase();
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
/**
|
|
2430
|
+
* Produces a normalized originator value along with the set of legacy
|
|
2431
|
+
* representations that should be considered when searching for existing
|
|
2432
|
+
* permission tokens (for backwards compatibility).
|
|
2433
|
+
*/
|
|
2434
|
+
prepareOriginator(originator) {
|
|
2435
|
+
const trimmed = originator === null || originator === void 0 ? void 0 : originator.trim();
|
|
2436
|
+
if (!trimmed) {
|
|
2437
|
+
throw new Error('Originator is required for permission checks.');
|
|
2438
|
+
}
|
|
2439
|
+
const normalized = this.normalizeOriginator(trimmed) || trimmed.toLowerCase();
|
|
2440
|
+
const lookupValues = Array.from(new Set([trimmed, normalized])).filter(Boolean);
|
|
2441
|
+
return { normalized, lookupValues };
|
|
2442
|
+
}
|
|
2443
|
+
/**
|
|
2444
|
+
* Builds a unique list of originator variants that should be searched when
|
|
2445
|
+
* looking up on-chain tokens (e.g., legacy raw + normalized forms).
|
|
2446
|
+
*/
|
|
2447
|
+
buildOriginatorLookupValues(...origins) {
|
|
2448
|
+
const variants = new Set();
|
|
2449
|
+
for (const origin of origins) {
|
|
2450
|
+
const trimmed = origin === null || origin === void 0 ? void 0 : origin.trim();
|
|
2451
|
+
if (trimmed) {
|
|
2452
|
+
variants.add(trimmed);
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
return Array.from(variants);
|
|
2456
|
+
}
|
|
2255
2457
|
/**
|
|
2256
2458
|
* Builds a "map key" string so that identical requests (e.g. "protocol:domain:true:protoName:counterparty")
|
|
2257
2459
|
* do not produce multiple user prompts.
|
|
2258
2460
|
*/
|
|
2259
2461
|
buildRequestKey(r) {
|
|
2260
2462
|
var _a, _b, _c, _d, _e;
|
|
2463
|
+
const normalizedOriginator = this.normalizeOriginator(r.originator);
|
|
2261
2464
|
switch (r.type) {
|
|
2262
2465
|
case 'protocol':
|
|
2263
|
-
return `proto:${
|
|
2466
|
+
return `proto:${normalizedOriginator}:${!!r.privileged}:${(_a = r.protocolID) === null || _a === void 0 ? void 0 : _a.join(',')}:${r.counterparty}`;
|
|
2264
2467
|
case 'basket':
|
|
2265
|
-
return `basket:${
|
|
2468
|
+
return `basket:${normalizedOriginator}:${r.basket}`;
|
|
2266
2469
|
case 'certificate':
|
|
2267
|
-
return `cert:${
|
|
2470
|
+
return `cert:${normalizedOriginator}:${!!r.privileged}:${(_b = r.certificate) === null || _b === void 0 ? void 0 : _b.verifier}:${(_c = r.certificate) === null || _c === void 0 ? void 0 : _c.certType}:${(_d = r.certificate) === null || _d === void 0 ? void 0 : _d.fields.join('|')}`;
|
|
2268
2471
|
case 'spending':
|
|
2269
|
-
return `spend:${
|
|
2472
|
+
return `spend:${normalizedOriginator}:${(_e = r.spending) === null || _e === void 0 ? void 0 : _e.satoshis}`;
|
|
2270
2473
|
}
|
|
2271
2474
|
}
|
|
2272
2475
|
}
|
|
2273
2476
|
exports.WalletPermissionsManager = WalletPermissionsManager;
|
|
2274
2477
|
/** How long a cached permission remains valid (5 minutes). */
|
|
2275
2478
|
WalletPermissionsManager.CACHE_TTL_MS = 5 * 60 * 1000;
|
|
2479
|
+
/** Window during which freshly granted permissions are auto-allowed (except spending). */
|
|
2480
|
+
WalletPermissionsManager.RECENT_GRANT_COVER_MS = 15 * 1000;
|
|
2481
|
+
/** Default ports used when normalizing originator values. */
|
|
2482
|
+
WalletPermissionsManager.DEFAULT_PORTS = {
|
|
2483
|
+
'http:': '80',
|
|
2484
|
+
'https:': '443'
|
|
2485
|
+
};
|
|
2276
2486
|
/* ---------------------------------------------------------------------
|
|
2277
2487
|
* 4) SEARCH / DECODE / DECRYPT ON-CHAIN TOKENS (PushDrop Scripts)
|
|
2278
2488
|
* --------------------------------------------------------------------- */
|