@bsv/wallet-toolbox 1.6.40 → 1.6.42
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 +70 -30
- package/docs/services.md +1 -1
- package/docs/storage.md +34 -8
- package/docs/wallet.md +70 -30
- 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 +597 -379
- package/mobile/out/src/WalletPermissionsManager.js.map +1 -1
- package/mobile/out/src/WalletSettingsManager.d.ts +1 -0
- package/mobile/out/src/WalletSettingsManager.d.ts.map +1 -1
- package/mobile/out/src/WalletSettingsManager.js +2 -1
- package/mobile/out/src/WalletSettingsManager.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 +597 -379
- package/out/src/WalletPermissionsManager.js.map +1 -1
- package/out/src/WalletSettingsManager.d.ts +1 -0
- package/out/src/WalletSettingsManager.d.ts.map +1 -1
- package/out/src/WalletSettingsManager.js +2 -1
- package/out/src/WalletSettingsManager.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 +698 -454
- package/src/WalletSettingsManager.ts +3 -1
- 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,
|
|
@@ -212,20 +213,22 @@ class WalletPermissionsManager {
|
|
|
212
213
|
const request = matching.request;
|
|
213
214
|
if (!request.renewal) {
|
|
214
215
|
// brand-new permission token
|
|
215
|
-
await this.createPermissionOnChain(request, params.expiry ||
|
|
216
|
+
await this.createPermissionOnChain(request, params.expiry || 0, // default: never expires
|
|
216
217
|
params.amount);
|
|
217
218
|
}
|
|
218
219
|
else {
|
|
219
220
|
// renewal => spend the old token, produce a new one
|
|
220
|
-
await this.renewPermissionOnChain(request.previousToken, request, params.expiry ||
|
|
221
|
+
await this.renewPermissionOnChain(request.previousToken, request, params.expiry || 0, // default: never expires
|
|
222
|
+
params.amount);
|
|
221
223
|
}
|
|
222
224
|
}
|
|
223
225
|
// Only cache non-ephemeral permissions
|
|
224
226
|
// Ephemeral permissions should not be cached as they are one-time authorizations
|
|
225
227
|
if (!params.ephemeral) {
|
|
226
|
-
const expiry = params.expiry ||
|
|
228
|
+
const expiry = params.expiry || 0; // default: never expires
|
|
227
229
|
const key = this.buildRequestKey(matching.request);
|
|
228
230
|
this.cachePermission(key, expiry);
|
|
231
|
+
this.markRecentGrant(matching.request);
|
|
229
232
|
}
|
|
230
233
|
}
|
|
231
234
|
/**
|
|
@@ -259,7 +262,8 @@ class WalletPermissionsManager {
|
|
|
259
262
|
throw new Error('Request ID not found.');
|
|
260
263
|
}
|
|
261
264
|
const originalRequest = matching.request;
|
|
262
|
-
const { originator, permissions: requestedPermissions } = originalRequest;
|
|
265
|
+
const { originator, permissions: requestedPermissions, displayOriginator } = originalRequest;
|
|
266
|
+
const originLookupValues = this.buildOriginatorLookupValues(displayOriginator, originator);
|
|
263
267
|
// --- Validation: Ensure granted permissions are a subset of what was requested ---
|
|
264
268
|
if (params.granted.spendingAuthorization &&
|
|
265
269
|
!deepEqual(params.granted.spendingAuthorization, requestedPermissions.spendingAuthorization)) {
|
|
@@ -275,7 +279,7 @@ class WalletPermissionsManager {
|
|
|
275
279
|
throw new Error('Granted certificate access permissions are not a subset of the original request.');
|
|
276
280
|
}
|
|
277
281
|
// --- End Validation ---
|
|
278
|
-
const expiry = params.expiry ||
|
|
282
|
+
const expiry = params.expiry || 0; // default: never expires
|
|
279
283
|
if (params.granted.spendingAuthorization) {
|
|
280
284
|
await this.createPermissionOnChain({
|
|
281
285
|
type: 'spending',
|
|
@@ -287,33 +291,39 @@ class WalletPermissionsManager {
|
|
|
287
291
|
}
|
|
288
292
|
for (const p of params.granted.protocolPermissions || []) {
|
|
289
293
|
const token = await this.findProtocolToken(originator, false, // No privileged protocols allowed in groups for added security.
|
|
290
|
-
p.protocolID, p.counterparty || 'self', true);
|
|
294
|
+
p.protocolID, p.counterparty || 'self', true, originLookupValues);
|
|
291
295
|
if (token) {
|
|
292
|
-
|
|
296
|
+
const request = {
|
|
293
297
|
type: 'protocol',
|
|
294
298
|
originator,
|
|
295
299
|
privileged: false, // No privileged protocols allowed in groups for added security.
|
|
296
300
|
protocolID: p.protocolID,
|
|
297
301
|
counterparty: p.counterparty || 'self',
|
|
298
302
|
reason: p.description
|
|
299
|
-
}
|
|
303
|
+
};
|
|
304
|
+
await this.renewPermissionOnChain(token, request, expiry);
|
|
305
|
+
this.markRecentGrant(request);
|
|
300
306
|
}
|
|
301
307
|
else {
|
|
302
|
-
|
|
308
|
+
const request = {
|
|
303
309
|
type: 'protocol',
|
|
304
310
|
originator,
|
|
305
311
|
privileged: false, // No privileged protocols allowed in groups for added security.
|
|
306
312
|
protocolID: p.protocolID,
|
|
307
313
|
counterparty: p.counterparty || 'self',
|
|
308
314
|
reason: p.description
|
|
309
|
-
}
|
|
315
|
+
};
|
|
316
|
+
await this.createPermissionOnChain(request, expiry);
|
|
317
|
+
this.markRecentGrant(request);
|
|
310
318
|
}
|
|
311
319
|
}
|
|
312
320
|
for (const b of params.granted.basketAccess || []) {
|
|
313
|
-
|
|
321
|
+
const request = { type: 'basket', originator, basket: b.basket, reason: b.description };
|
|
322
|
+
await this.createPermissionOnChain(request, expiry);
|
|
323
|
+
this.markRecentGrant(request);
|
|
314
324
|
}
|
|
315
325
|
for (const c of params.granted.certificateAccess || []) {
|
|
316
|
-
|
|
326
|
+
const request = {
|
|
317
327
|
type: 'certificate',
|
|
318
328
|
originator,
|
|
319
329
|
privileged: false, // No certificates on the privileged identity are allowed as part of groups.
|
|
@@ -323,7 +333,9 @@ class WalletPermissionsManager {
|
|
|
323
333
|
fields: c.fields
|
|
324
334
|
},
|
|
325
335
|
reason: c.description
|
|
326
|
-
}
|
|
336
|
+
};
|
|
337
|
+
await this.createPermissionOnChain(request, expiry);
|
|
338
|
+
this.markRecentGrant(request);
|
|
327
339
|
}
|
|
328
340
|
// Resolve all pending promises for this request
|
|
329
341
|
for (const p of matching.pending) {
|
|
@@ -355,6 +367,8 @@ class WalletPermissionsManager {
|
|
|
355
367
|
* If no valid (unexpired) permission token is found, triggers a permission request flow.
|
|
356
368
|
*/
|
|
357
369
|
async ensureProtocolPermission({ originator, privileged, protocolID, counterparty, reason, seekPermission = true, usageType }) {
|
|
370
|
+
const { normalized: normalizedOriginator, lookupValues } = this.prepareOriginator(originator);
|
|
371
|
+
originator = normalizedOriginator;
|
|
358
372
|
// 1) adminOriginator can do anything
|
|
359
373
|
if (this.isAdminOriginator(originator))
|
|
360
374
|
return true;
|
|
@@ -398,9 +412,12 @@ class WalletPermissionsManager {
|
|
|
398
412
|
if (this.isPermissionCached(cacheKey)) {
|
|
399
413
|
return true;
|
|
400
414
|
}
|
|
415
|
+
if (this.isRecentlyGranted(cacheKey)) {
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
401
418
|
// 4) Attempt to find a valid token in the internal basket
|
|
402
419
|
const token = await this.findProtocolToken(originator, privileged, protocolID, counterparty,
|
|
403
|
-
/*includeExpired=*/ true);
|
|
420
|
+
/*includeExpired=*/ true, lookupValues);
|
|
404
421
|
if (token) {
|
|
405
422
|
if (!this.isTokenExpired(token.expiry)) {
|
|
406
423
|
// valid and unexpired
|
|
@@ -446,6 +463,8 @@ class WalletPermissionsManager {
|
|
|
446
463
|
* If not, triggers a permission request flow.
|
|
447
464
|
*/
|
|
448
465
|
async ensureBasketAccess({ originator, basket, reason, seekPermission = true, usageType }) {
|
|
466
|
+
const { normalized: normalizedOriginator, lookupValues } = this.prepareOriginator(originator);
|
|
467
|
+
originator = normalizedOriginator;
|
|
449
468
|
if (this.isAdminOriginator(originator))
|
|
450
469
|
return true;
|
|
451
470
|
if (this.isAdminBasket(basket)) {
|
|
@@ -461,7 +480,10 @@ class WalletPermissionsManager {
|
|
|
461
480
|
if (this.isPermissionCached(cacheKey)) {
|
|
462
481
|
return true;
|
|
463
482
|
}
|
|
464
|
-
|
|
483
|
+
if (this.isRecentlyGranted(cacheKey)) {
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
486
|
+
const token = await this.findBasketToken(originator, basket, true, lookupValues);
|
|
465
487
|
if (token) {
|
|
466
488
|
if (!this.isTokenExpired(token.expiry)) {
|
|
467
489
|
this.cachePermission(cacheKey, token.expiry);
|
|
@@ -501,6 +523,8 @@ class WalletPermissionsManager {
|
|
|
501
523
|
* This is relevant when revealing certificate fields in DCAP contexts.
|
|
502
524
|
*/
|
|
503
525
|
async ensureCertificateAccess({ originator, privileged, verifier, certType, fields, reason, seekPermission = true, usageType }) {
|
|
526
|
+
const { normalized: normalizedOriginator, lookupValues } = this.prepareOriginator(originator);
|
|
527
|
+
originator = normalizedOriginator;
|
|
504
528
|
if (this.isAdminOriginator(originator))
|
|
505
529
|
return true;
|
|
506
530
|
if (usageType === 'disclosure' && !this.config.seekCertificateDisclosurePermissions) {
|
|
@@ -518,8 +542,11 @@ class WalletPermissionsManager {
|
|
|
518
542
|
if (this.isPermissionCached(cacheKey)) {
|
|
519
543
|
return true;
|
|
520
544
|
}
|
|
545
|
+
if (this.isRecentlyGranted(cacheKey)) {
|
|
546
|
+
return true;
|
|
547
|
+
}
|
|
521
548
|
const token = await this.findCertificateToken(originator, privileged, verifier, certType, fields,
|
|
522
|
-
/*includeExpired=*/ true);
|
|
549
|
+
/*includeExpired=*/ true, lookupValues);
|
|
523
550
|
if (token) {
|
|
524
551
|
if (!this.isTokenExpired(token.expiry)) {
|
|
525
552
|
this.cachePermission(cacheKey, token.expiry);
|
|
@@ -560,6 +587,8 @@ class WalletPermissionsManager {
|
|
|
560
587
|
* If the existing token limit is insufficient, attempts to renew. If no token, attempts to create one.
|
|
561
588
|
*/
|
|
562
589
|
async ensureSpendingAuthorization({ originator, satoshis, lineItems, reason, seekPermission = true }) {
|
|
590
|
+
const { normalized: normalizedOriginator, lookupValues } = this.prepareOriginator(originator);
|
|
591
|
+
originator = normalizedOriginator;
|
|
563
592
|
if (this.isAdminOriginator(originator))
|
|
564
593
|
return true;
|
|
565
594
|
if (!this.config.seekSpendingPermissions) {
|
|
@@ -570,7 +599,7 @@ class WalletPermissionsManager {
|
|
|
570
599
|
if (this.isPermissionCached(cacheKey)) {
|
|
571
600
|
return true;
|
|
572
601
|
}
|
|
573
|
-
const token = await this.findSpendingToken(originator);
|
|
602
|
+
const token = await this.findSpendingToken(originator, lookupValues);
|
|
574
603
|
if (token === null || token === void 0 ? void 0 : token.authorizedAmount) {
|
|
575
604
|
// Check how much has been spent so far
|
|
576
605
|
const spentSoFar = await this.querySpentSince(token);
|
|
@@ -612,6 +641,8 @@ class WalletPermissionsManager {
|
|
|
612
641
|
* If no valid (unexpired) permission token is found, triggers a permission request flow.
|
|
613
642
|
*/
|
|
614
643
|
async ensureLabelAccess({ originator, label, reason, seekPermission = true, usageType }) {
|
|
644
|
+
const { normalized: normalizedOriginator } = this.prepareOriginator(originator);
|
|
645
|
+
originator = normalizedOriginator;
|
|
615
646
|
// 1) adminOriginator can do anything
|
|
616
647
|
if (this.isAdminOriginator(originator))
|
|
617
648
|
return true;
|
|
@@ -654,7 +685,14 @@ class WalletPermissionsManager {
|
|
|
654
685
|
* and return a promise that resolves once permission is granted or rejects if denied.
|
|
655
686
|
*/
|
|
656
687
|
async requestPermissionFlow(r) {
|
|
657
|
-
|
|
688
|
+
var _a;
|
|
689
|
+
const normalizedOriginator = this.normalizeOriginator(r.originator) || r.originator;
|
|
690
|
+
const preparedRequest = {
|
|
691
|
+
...r,
|
|
692
|
+
originator: normalizedOriginator,
|
|
693
|
+
displayOriginator: (_a = r.displayOriginator) !== null && _a !== void 0 ? _a : r.originator
|
|
694
|
+
};
|
|
695
|
+
const key = this.buildRequestKey(preparedRequest);
|
|
658
696
|
// If there's already a queue for the same resource, we piggyback on it
|
|
659
697
|
const existingQueue = this.activeRequests.get(key);
|
|
660
698
|
if (existingQueue && existingQueue.pending.length > 0) {
|
|
@@ -666,32 +704,32 @@ class WalletPermissionsManager {
|
|
|
666
704
|
// Return a promise that resolves or rejects once the user grants/denies
|
|
667
705
|
return new Promise(async (resolve, reject) => {
|
|
668
706
|
this.activeRequests.set(key, {
|
|
669
|
-
request:
|
|
707
|
+
request: preparedRequest,
|
|
670
708
|
pending: [{ resolve, reject }]
|
|
671
709
|
});
|
|
672
710
|
// Fire the relevant onXXXRequested event (which one depends on r.type)
|
|
673
|
-
switch (
|
|
711
|
+
switch (preparedRequest.type) {
|
|
674
712
|
case 'protocol':
|
|
675
713
|
await this.callEvent('onProtocolPermissionRequested', {
|
|
676
|
-
...
|
|
714
|
+
...preparedRequest,
|
|
677
715
|
requestID: key
|
|
678
716
|
});
|
|
679
717
|
break;
|
|
680
718
|
case 'basket':
|
|
681
719
|
await this.callEvent('onBasketAccessRequested', {
|
|
682
|
-
...
|
|
720
|
+
...preparedRequest,
|
|
683
721
|
requestID: key
|
|
684
722
|
});
|
|
685
723
|
break;
|
|
686
724
|
case 'certificate':
|
|
687
725
|
await this.callEvent('onCertificateAccessRequested', {
|
|
688
|
-
...
|
|
726
|
+
...preparedRequest,
|
|
689
727
|
requestID: key
|
|
690
728
|
});
|
|
691
729
|
break;
|
|
692
730
|
case 'spending':
|
|
693
731
|
await this.callEvent('onSpendingAuthorizationRequested', {
|
|
694
|
-
...
|
|
732
|
+
...preparedRequest,
|
|
695
733
|
requestID: key
|
|
696
734
|
});
|
|
697
735
|
break;
|
|
@@ -761,250 +799,284 @@ class WalletPermissionsManager {
|
|
|
761
799
|
return expiry > 0 && expiry < now;
|
|
762
800
|
}
|
|
763
801
|
/** Looks for a DPACP permission token matching origin/domain, privileged, protocol, cpty. */
|
|
764
|
-
async findProtocolToken(originator, privileged, protocolID, counterparty, includeExpired) {
|
|
802
|
+
async findProtocolToken(originator, privileged, protocolID, counterparty, includeExpired, originatorLookupValues) {
|
|
765
803
|
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;
|
|
804
|
+
const originsToTry = (originatorLookupValues === null || originatorLookupValues === void 0 ? void 0 : originatorLookupValues.length) ? originatorLookupValues : [originator];
|
|
805
|
+
for (const originTag of originsToTry) {
|
|
806
|
+
const tags = [
|
|
807
|
+
`originator ${originTag}`,
|
|
808
|
+
`privileged ${!!privileged}`,
|
|
809
|
+
`protocolName ${protoName}`,
|
|
810
|
+
`protocolSecurityLevel ${secLevel}`
|
|
811
|
+
];
|
|
812
|
+
if (secLevel === 2) {
|
|
813
|
+
tags.push(`counterparty ${counterparty}`);
|
|
805
814
|
}
|
|
806
|
-
|
|
807
|
-
|
|
815
|
+
const result = await this.underlying.listOutputs({
|
|
816
|
+
basket: BASKET_MAP.protocol,
|
|
817
|
+
tags,
|
|
818
|
+
tagQueryMode: 'all',
|
|
819
|
+
include: 'entire transactions'
|
|
820
|
+
}, this.adminOriginator);
|
|
821
|
+
for (const out of result.outputs) {
|
|
822
|
+
const [txid, outputIndexStr] = out.outpoint.split('.');
|
|
823
|
+
const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
|
|
824
|
+
const dec = sdk_1.PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript);
|
|
825
|
+
if (!dec || !dec.fields || dec.fields.length < 6)
|
|
826
|
+
continue;
|
|
827
|
+
const domainRaw = dec.fields[0];
|
|
828
|
+
const expiryRaw = dec.fields[1];
|
|
829
|
+
const privRaw = dec.fields[2];
|
|
830
|
+
const secLevelRaw = dec.fields[3];
|
|
831
|
+
const protoNameRaw = dec.fields[4];
|
|
832
|
+
const counterpartyRaw = dec.fields[5];
|
|
833
|
+
const domainDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw));
|
|
834
|
+
const normalizedDomain = this.normalizeOriginator(domainDecoded);
|
|
835
|
+
if (normalizedDomain !== originator) {
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
const expiryDecoded = parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10);
|
|
839
|
+
const privDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(privRaw)) === 'true';
|
|
840
|
+
const secLevelDecoded = parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(secLevelRaw)), 10);
|
|
841
|
+
const protoNameDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(protoNameRaw));
|
|
842
|
+
const cptyDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(counterpartyRaw));
|
|
843
|
+
if (privDecoded !== !!privileged ||
|
|
844
|
+
secLevelDecoded !== secLevel ||
|
|
845
|
+
protoNameDecoded !== protoName ||
|
|
846
|
+
(secLevelDecoded === 2 && cptyDecoded !== counterparty)) {
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
if (!includeExpired && this.isTokenExpired(expiryDecoded)) {
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
return {
|
|
853
|
+
tx: tx.toBEEF(),
|
|
854
|
+
txid: out.outpoint.split('.')[0],
|
|
855
|
+
outputIndex: parseInt(out.outpoint.split('.')[1], 10),
|
|
856
|
+
outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
|
|
857
|
+
satoshis: out.satoshis,
|
|
858
|
+
originator,
|
|
859
|
+
rawOriginator: domainDecoded,
|
|
860
|
+
privileged,
|
|
861
|
+
protocol: protoName,
|
|
862
|
+
securityLevel: secLevel,
|
|
863
|
+
expiry: expiryDecoded,
|
|
864
|
+
counterparty: cptyDecoded
|
|
865
|
+
};
|
|
808
866
|
}
|
|
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
867
|
}
|
|
823
868
|
return undefined;
|
|
824
869
|
}
|
|
825
870
|
/** Finds ALL DPACP permission tokens matching origin/domain, privileged, protocol, cpty. Never filters by expiry. */
|
|
826
|
-
async findAllProtocolTokens(originator, privileged, protocolID, counterparty) {
|
|
871
|
+
async findAllProtocolTokens(originator, privileged, protocolID, counterparty, originatorLookupValues) {
|
|
827
872
|
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);
|
|
873
|
+
const originsToTry = (originatorLookupValues === null || originatorLookupValues === void 0 ? void 0 : originatorLookupValues.length) ? originatorLookupValues : [originator];
|
|
843
874
|
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
|
-
|
|
875
|
+
const seen = new Set();
|
|
876
|
+
for (const originTag of originsToTry) {
|
|
877
|
+
const tags = [
|
|
878
|
+
`originator ${originTag}`,
|
|
879
|
+
`privileged ${!!privileged}`,
|
|
880
|
+
`protocolName ${protoName}`,
|
|
881
|
+
`protocolSecurityLevel ${secLevel}`
|
|
882
|
+
];
|
|
883
|
+
if (secLevel === 2) {
|
|
884
|
+
tags.push(`counterparty ${counterparty}`);
|
|
885
|
+
}
|
|
886
|
+
const result = await this.underlying.listOutputs({
|
|
887
|
+
basket: BASKET_MAP.protocol,
|
|
888
|
+
tags,
|
|
889
|
+
tagQueryMode: 'all',
|
|
890
|
+
include: 'entire transactions'
|
|
891
|
+
}, this.adminOriginator);
|
|
892
|
+
for (const out of result.outputs) {
|
|
893
|
+
if (seen.has(out.outpoint))
|
|
894
|
+
continue;
|
|
895
|
+
const [txid, outputIndexStr] = out.outpoint.split('.');
|
|
896
|
+
const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
|
|
897
|
+
const vout = Number(outputIndexStr);
|
|
898
|
+
const dec = sdk_1.PushDrop.decode(tx.outputs[vout].lockingScript);
|
|
899
|
+
if (!dec || !dec.fields || dec.fields.length < 6)
|
|
900
|
+
continue;
|
|
901
|
+
const domainRaw = dec.fields[0];
|
|
902
|
+
const expiryRaw = dec.fields[1];
|
|
903
|
+
const privRaw = dec.fields[2];
|
|
904
|
+
const secLevelRaw = dec.fields[3];
|
|
905
|
+
const protoNameRaw = dec.fields[4];
|
|
906
|
+
const counterpartyRaw = dec.fields[5];
|
|
907
|
+
const domainDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw));
|
|
908
|
+
const normalizedDomain = this.normalizeOriginator(domainDecoded);
|
|
909
|
+
if (normalizedDomain !== originator) {
|
|
910
|
+
continue;
|
|
911
|
+
}
|
|
912
|
+
const expiryDecoded = parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10);
|
|
913
|
+
const privDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(privRaw)) === 'true';
|
|
914
|
+
const secLevelDecoded = parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(secLevelRaw)), 10);
|
|
915
|
+
const protoNameDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(protoNameRaw));
|
|
916
|
+
const cptyDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(counterpartyRaw));
|
|
917
|
+
if (privDecoded !== !!privileged ||
|
|
918
|
+
secLevelDecoded !== secLevel ||
|
|
919
|
+
protoNameDecoded !== protoName ||
|
|
920
|
+
(secLevelDecoded === 2 && cptyDecoded !== counterparty)) {
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
seen.add(out.outpoint);
|
|
924
|
+
matches.push({
|
|
925
|
+
tx: tx.toBEEF(),
|
|
926
|
+
txid,
|
|
927
|
+
outputIndex: vout,
|
|
928
|
+
outputScript: tx.outputs[vout].lockingScript.toHex(),
|
|
929
|
+
satoshis: out.satoshis,
|
|
930
|
+
originator,
|
|
931
|
+
rawOriginator: domainDecoded,
|
|
932
|
+
privileged,
|
|
933
|
+
protocol: protoName,
|
|
934
|
+
securityLevel: secLevel,
|
|
935
|
+
expiry: expiryDecoded,
|
|
936
|
+
counterparty: cptyDecoded
|
|
937
|
+
});
|
|
871
938
|
}
|
|
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
939
|
}
|
|
886
940
|
return matches;
|
|
887
941
|
}
|
|
888
942
|
/** 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
|
-
|
|
943
|
+
async findBasketToken(originator, basket, includeExpired, originatorLookupValues) {
|
|
944
|
+
const originsToTry = (originatorLookupValues === null || originatorLookupValues === void 0 ? void 0 : originatorLookupValues.length) ? originatorLookupValues : [originator];
|
|
945
|
+
for (const originTag of originsToTry) {
|
|
946
|
+
const result = await this.underlying.listOutputs({
|
|
947
|
+
basket: BASKET_MAP.basket,
|
|
948
|
+
tags: [`originator ${originTag}`, `basket ${basket}`],
|
|
949
|
+
tagQueryMode: 'all',
|
|
950
|
+
include: 'entire transactions'
|
|
951
|
+
}, this.adminOriginator);
|
|
952
|
+
for (const out of result.outputs) {
|
|
953
|
+
const [txid, outputIndexStr] = out.outpoint.split('.');
|
|
954
|
+
const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
|
|
955
|
+
const dec = sdk_1.PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript);
|
|
956
|
+
if (!(dec === null || dec === void 0 ? void 0 : dec.fields) || dec.fields.length < 3)
|
|
957
|
+
continue;
|
|
958
|
+
const domainRaw = dec.fields[0];
|
|
959
|
+
const expiryRaw = dec.fields[1];
|
|
960
|
+
const basketRaw = dec.fields[2];
|
|
961
|
+
const domainDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw));
|
|
962
|
+
const normalizedDomain = this.normalizeOriginator(domainDecoded);
|
|
963
|
+
if (normalizedDomain !== originator) {
|
|
964
|
+
continue;
|
|
965
|
+
}
|
|
966
|
+
const expiryDecoded = parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10);
|
|
967
|
+
const basketDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(basketRaw));
|
|
968
|
+
if (basketDecoded !== basket)
|
|
969
|
+
continue;
|
|
970
|
+
if (!includeExpired && this.isTokenExpired(expiryDecoded))
|
|
971
|
+
continue;
|
|
972
|
+
return {
|
|
973
|
+
tx: tx.toBEEF(),
|
|
974
|
+
txid: out.outpoint.split('.')[0],
|
|
975
|
+
outputIndex: parseInt(out.outpoint.split('.')[1], 10),
|
|
976
|
+
outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
|
|
977
|
+
satoshis: out.satoshis,
|
|
978
|
+
originator,
|
|
979
|
+
rawOriginator: domainDecoded,
|
|
980
|
+
basketName: basketDecoded,
|
|
981
|
+
expiry: expiryDecoded
|
|
982
|
+
};
|
|
983
|
+
}
|
|
922
984
|
}
|
|
923
985
|
return undefined;
|
|
924
986
|
}
|
|
925
987
|
/** 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
|
-
|
|
988
|
+
async findCertificateToken(originator, privileged, verifier, certType, fields, includeExpired, originatorLookupValues) {
|
|
989
|
+
const originsToTry = (originatorLookupValues === null || originatorLookupValues === void 0 ? void 0 : originatorLookupValues.length) ? originatorLookupValues : [originator];
|
|
990
|
+
for (const originTag of originsToTry) {
|
|
991
|
+
const result = await this.underlying.listOutputs({
|
|
992
|
+
basket: BASKET_MAP.certificate,
|
|
993
|
+
tags: [`originator ${originTag}`, `privileged ${!!privileged}`, `type ${certType}`, `verifier ${verifier}`],
|
|
994
|
+
tagQueryMode: 'all',
|
|
995
|
+
include: 'entire transactions'
|
|
996
|
+
}, this.adminOriginator);
|
|
997
|
+
for (const out of result.outputs) {
|
|
998
|
+
const [txid, outputIndexStr] = out.outpoint.split('.');
|
|
999
|
+
const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
|
|
1000
|
+
const dec = sdk_1.PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript);
|
|
1001
|
+
if (!(dec === null || dec === void 0 ? void 0 : dec.fields) || dec.fields.length < 6)
|
|
1002
|
+
continue;
|
|
1003
|
+
const [domainRaw, expiryRaw, privRaw, typeRaw, fieldsRaw, verifierRaw] = dec.fields;
|
|
1004
|
+
const domainDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw));
|
|
1005
|
+
const normalizedDomain = this.normalizeOriginator(domainDecoded);
|
|
1006
|
+
if (normalizedDomain !== originator) {
|
|
1007
|
+
continue;
|
|
1008
|
+
}
|
|
1009
|
+
const expiryDecoded = parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10);
|
|
1010
|
+
const privDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(privRaw)) === 'true';
|
|
1011
|
+
const typeDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(typeRaw));
|
|
1012
|
+
const verifierDec = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(verifierRaw));
|
|
1013
|
+
const fieldsJson = await this.decryptPermissionTokenField(fieldsRaw);
|
|
1014
|
+
const allFields = JSON.parse(sdk_1.Utils.toUTF8(fieldsJson));
|
|
1015
|
+
if (privDecoded !== !!privileged || typeDecoded !== certType || verifierDec !== verifier) {
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
// Check if 'fields' is a subset of 'allFields'
|
|
1019
|
+
const setAll = new Set(allFields);
|
|
1020
|
+
if (fields.some(f => !setAll.has(f))) {
|
|
1021
|
+
continue;
|
|
1022
|
+
}
|
|
1023
|
+
if (!includeExpired && this.isTokenExpired(expiryDecoded)) {
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
return {
|
|
1027
|
+
tx: tx.toBEEF(),
|
|
1028
|
+
txid: out.outpoint.split('.')[0],
|
|
1029
|
+
outputIndex: parseInt(out.outpoint.split('.')[1], 10),
|
|
1030
|
+
outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
|
|
1031
|
+
satoshis: out.satoshis,
|
|
1032
|
+
originator,
|
|
1033
|
+
rawOriginator: domainDecoded,
|
|
1034
|
+
privileged,
|
|
1035
|
+
verifier: verifierDec,
|
|
1036
|
+
certType: typeDecoded,
|
|
1037
|
+
certFields: allFields,
|
|
1038
|
+
expiry: expiryDecoded
|
|
1039
|
+
};
|
|
960
1040
|
}
|
|
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
1041
|
}
|
|
975
1042
|
return undefined;
|
|
976
1043
|
}
|
|
977
1044
|
/** 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
|
-
|
|
1045
|
+
async findSpendingToken(originator, originatorLookupValues) {
|
|
1046
|
+
const originsToTry = (originatorLookupValues === null || originatorLookupValues === void 0 ? void 0 : originatorLookupValues.length) ? originatorLookupValues : [originator];
|
|
1047
|
+
for (const originTag of originsToTry) {
|
|
1048
|
+
const result = await this.underlying.listOutputs({
|
|
1049
|
+
basket: BASKET_MAP.spending,
|
|
1050
|
+
tags: [`originator ${originTag}`],
|
|
1051
|
+
tagQueryMode: 'all',
|
|
1052
|
+
include: 'entire transactions'
|
|
1053
|
+
}, this.adminOriginator);
|
|
1054
|
+
for (const out of result.outputs) {
|
|
1055
|
+
const [txid, outputIndexStr] = out.outpoint.split('.');
|
|
1056
|
+
const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
|
|
1057
|
+
const dec = sdk_1.PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript);
|
|
1058
|
+
if (!(dec === null || dec === void 0 ? void 0 : dec.fields) || dec.fields.length < 2)
|
|
1059
|
+
continue;
|
|
1060
|
+
const domainRaw = dec.fields[0];
|
|
1061
|
+
const amtRaw = dec.fields[1];
|
|
1062
|
+
const domainDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw));
|
|
1063
|
+
const normalizedDomain = this.normalizeOriginator(domainDecoded);
|
|
1064
|
+
if (normalizedDomain !== originator)
|
|
1065
|
+
continue;
|
|
1066
|
+
const amtDecodedStr = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(amtRaw));
|
|
1067
|
+
const authorizedAmount = parseInt(amtDecodedStr, 10);
|
|
1068
|
+
return {
|
|
1069
|
+
tx: tx.toBEEF(),
|
|
1070
|
+
txid: out.outpoint.split('.')[0],
|
|
1071
|
+
outputIndex: parseInt(out.outpoint.split('.')[1], 10),
|
|
1072
|
+
outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
|
|
1073
|
+
satoshis: out.satoshis,
|
|
1074
|
+
originator,
|
|
1075
|
+
rawOriginator: domainDecoded,
|
|
1076
|
+
authorizedAmount,
|
|
1077
|
+
expiry: 0 // Not time-limited, monthly authorization
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1008
1080
|
}
|
|
1009
1081
|
return undefined;
|
|
1010
1082
|
}
|
|
@@ -1023,11 +1095,16 @@ class WalletPermissionsManager {
|
|
|
1023
1095
|
* Returns spending for an originator in the current calendar month.
|
|
1024
1096
|
*/
|
|
1025
1097
|
async querySpentSince(token) {
|
|
1026
|
-
const
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1098
|
+
const labelOrigins = this.buildOriginatorLookupValues(token.rawOriginator, token.originator);
|
|
1099
|
+
let total = 0;
|
|
1100
|
+
for (const labelOrigin of labelOrigins) {
|
|
1101
|
+
const { actions } = await this.underlying.listActions({
|
|
1102
|
+
labels: [`admin originator ${labelOrigin}`, `admin month ${this.getCurrentMonthYearUTC()}`],
|
|
1103
|
+
labelQueryMode: 'all'
|
|
1104
|
+
}, this.adminOriginator);
|
|
1105
|
+
total += actions.reduce((a, e) => a + e.satoshis, 0);
|
|
1106
|
+
}
|
|
1107
|
+
return total;
|
|
1031
1108
|
}
|
|
1032
1109
|
/* ---------------------------------------------------------------------
|
|
1033
1110
|
* 5) CREATE / RENEW / REVOKE PERMISSION TOKENS ON CHAIN
|
|
@@ -1042,6 +1119,8 @@ class WalletPermissionsManager {
|
|
|
1042
1119
|
* @param amount For DSAP, the authorized spending limit
|
|
1043
1120
|
*/
|
|
1044
1121
|
async createPermissionOnChain(r, expiry, amount) {
|
|
1122
|
+
const normalizedOriginator = this.normalizeOriginator(r.originator) || r.originator;
|
|
1123
|
+
r.originator = normalizedOriginator;
|
|
1045
1124
|
const basketName = BASKET_MAP[r.type];
|
|
1046
1125
|
if (!basketName)
|
|
1047
1126
|
return;
|
|
@@ -1077,8 +1156,14 @@ class WalletPermissionsManager {
|
|
|
1077
1156
|
if (oldTokens.length < 2)
|
|
1078
1157
|
throw new Error('Need at least 2 tokens to coalesce');
|
|
1079
1158
|
// 1) Create a signable action with N inputs and a single renewed output
|
|
1159
|
+
// Merge all input token BEEFs into a single BEEF structure
|
|
1160
|
+
const inputBeef = new sdk_1.Beef();
|
|
1161
|
+
for (const token of oldTokens) {
|
|
1162
|
+
inputBeef.mergeBeef(sdk_1.Beef.fromBinary(token.tx));
|
|
1163
|
+
}
|
|
1080
1164
|
const { signableTransaction } = await this.createAction({
|
|
1081
1165
|
description: (_a = opts === null || opts === void 0 ? void 0 : opts.description) !== null && _a !== void 0 ? _a : `Coalesce ${oldTokens.length} permission tokens`,
|
|
1166
|
+
inputBEEF: inputBeef.toBinary(),
|
|
1082
1167
|
inputs: oldTokens.map((t, i) => ({
|
|
1083
1168
|
outpoint: `${t.txid}.${t.outputIndex}`,
|
|
1084
1169
|
unlockingScriptLength: 74,
|
|
@@ -1102,13 +1187,14 @@ class WalletPermissionsManager {
|
|
|
1102
1187
|
if (!(signableTransaction === null || signableTransaction === void 0 ? void 0 : signableTransaction.reference) || !signableTransaction.tx) {
|
|
1103
1188
|
throw new Error('Failed to create signable transaction');
|
|
1104
1189
|
}
|
|
1105
|
-
// 2) Sign each input
|
|
1190
|
+
// 2) Sign each input - each token needs its own unlocker with the correct locking script
|
|
1106
1191
|
const partialTx = sdk_1.Transaction.fromAtomicBEEF(signableTransaction.tx);
|
|
1107
1192
|
const pushdrop = new sdk_1.PushDrop(this.underlying);
|
|
1108
|
-
const unlocker = pushdrop.unlock(WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL, '1', 'self');
|
|
1109
1193
|
const spends = {};
|
|
1110
1194
|
for (let i = 0; i < oldTokens.length; i++) {
|
|
1111
|
-
|
|
1195
|
+
const token = oldTokens[i];
|
|
1196
|
+
// Each token requires its own unlocker with the specific locking script
|
|
1197
|
+
const unlocker = pushdrop.unlock(WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL, '1', 'self', 'all', false, 1, sdk_1.LockingScript.fromHex(token.outputScript));
|
|
1112
1198
|
const unlockingScript = await unlocker.sign(partialTx, i);
|
|
1113
1199
|
spends[i] = { unlockingScript: unlockingScript.toHex() };
|
|
1114
1200
|
}
|
|
@@ -1131,21 +1217,21 @@ class WalletPermissionsManager {
|
|
|
1131
1217
|
* @param newAmount For DSAP, the new authorized amount
|
|
1132
1218
|
*/
|
|
1133
1219
|
async renewPermissionOnChain(oldToken, r, newExpiry, newAmount) {
|
|
1220
|
+
r.originator = this.normalizeOriginator(r.originator) || r.originator;
|
|
1134
1221
|
// 1) build new fields
|
|
1135
1222
|
const newFields = await this.buildPushdropFields(r, newExpiry, newAmount);
|
|
1136
1223
|
// 2) new script
|
|
1137
1224
|
const newScript = await new sdk_1.PushDrop(this.underlying).lock(newFields, WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL, '1', 'self', true, true);
|
|
1138
1225
|
const tags = this.buildTagsForRequest(r);
|
|
1139
1226
|
// 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);
|
|
1227
|
+
const oldTokens = await this.findAllProtocolTokens(oldToken.originator, oldToken.privileged, [oldToken.securityLevel, oldToken.protocol], oldToken.counterparty, this.buildOriginatorLookupValues(oldToken.rawOriginator, oldToken.originator));
|
|
1141
1228
|
// If so, coalesce them into a single token first, to avoid bloat
|
|
1142
1229
|
if (oldTokens.length > 1) {
|
|
1143
|
-
|
|
1230
|
+
await this.coalescePermissionTokens(oldTokens, newScript, {
|
|
1144
1231
|
tags,
|
|
1145
1232
|
basket: BASKET_MAP[r.type],
|
|
1146
1233
|
description: `Coalesce ${r.type} permission tokens`
|
|
1147
1234
|
});
|
|
1148
|
-
console.log('Coalesced permission tokens:', txid);
|
|
1149
1235
|
}
|
|
1150
1236
|
else {
|
|
1151
1237
|
// Otherwise, just proceed with the single-token renewal
|
|
@@ -1245,7 +1331,9 @@ class WalletPermissionsManager {
|
|
|
1245
1331
|
tags.push(`privileged ${!!r.privileged}`);
|
|
1246
1332
|
tags.push(`protocolName ${r.protocolID[1]}`);
|
|
1247
1333
|
tags.push(`protocolSecurityLevel ${r.protocolID[0]}`);
|
|
1248
|
-
|
|
1334
|
+
if (r.protocolID[0] === 2) {
|
|
1335
|
+
tags.push(`counterparty ${r.counterparty}`);
|
|
1336
|
+
}
|
|
1249
1337
|
break;
|
|
1250
1338
|
}
|
|
1251
1339
|
case 'basket': {
|
|
@@ -1279,56 +1367,70 @@ class WalletPermissionsManager {
|
|
|
1279
1367
|
*/
|
|
1280
1368
|
async listProtocolPermissions({ originator, privileged, protocolName, protocolSecurityLevel, counterparty } = {}) {
|
|
1281
1369
|
const basketName = BASKET_MAP.protocol;
|
|
1282
|
-
const
|
|
1283
|
-
if (originator) {
|
|
1284
|
-
tags.push(`originator ${originator}`);
|
|
1285
|
-
}
|
|
1370
|
+
const baseTags = [];
|
|
1286
1371
|
if (privileged !== undefined) {
|
|
1287
|
-
|
|
1372
|
+
baseTags.push(`privileged ${!!privileged}`);
|
|
1288
1373
|
}
|
|
1289
1374
|
if (protocolName) {
|
|
1290
|
-
|
|
1375
|
+
baseTags.push(`protocolName ${protocolName}`);
|
|
1291
1376
|
}
|
|
1292
1377
|
if (protocolSecurityLevel !== undefined) {
|
|
1293
|
-
|
|
1378
|
+
baseTags.push(`protocolSecurityLevel ${protocolSecurityLevel}`);
|
|
1294
1379
|
}
|
|
1295
1380
|
if (counterparty) {
|
|
1296
|
-
|
|
1381
|
+
baseTags.push(`counterparty ${counterparty}`);
|
|
1297
1382
|
}
|
|
1298
|
-
const
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
tagQueryMode: 'all',
|
|
1302
|
-
include: 'entire transactions',
|
|
1303
|
-
limit: 100
|
|
1304
|
-
}, this.adminOriginator);
|
|
1383
|
+
const originFilter = originator ? this.prepareOriginator(originator) : undefined;
|
|
1384
|
+
const originVariants = originFilter ? originFilter.lookupValues : [undefined];
|
|
1385
|
+
const seen = new Set();
|
|
1305
1386
|
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
|
-
|
|
1387
|
+
for (const originTag of originVariants) {
|
|
1388
|
+
const tags = [...baseTags];
|
|
1389
|
+
if (originTag) {
|
|
1390
|
+
tags.push(`originator ${originTag}`);
|
|
1391
|
+
}
|
|
1392
|
+
const result = await this.underlying.listOutputs({
|
|
1393
|
+
basket: basketName,
|
|
1394
|
+
tags,
|
|
1395
|
+
tagQueryMode: 'all',
|
|
1396
|
+
include: 'entire transactions',
|
|
1397
|
+
limit: 100
|
|
1398
|
+
}, this.adminOriginator);
|
|
1399
|
+
for (const out of result.outputs) {
|
|
1400
|
+
if (seen.has(out.outpoint))
|
|
1401
|
+
continue;
|
|
1402
|
+
const [txid, outputIndexStr] = out.outpoint.split('.');
|
|
1403
|
+
const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
|
|
1404
|
+
const dec = sdk_1.PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript);
|
|
1405
|
+
if (!(dec === null || dec === void 0 ? void 0 : dec.fields) || dec.fields.length < 6)
|
|
1406
|
+
continue;
|
|
1407
|
+
const [domainRaw, expiryRaw, privRaw, secRaw, protoRaw, cptyRaw] = dec.fields;
|
|
1408
|
+
const domainDec = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw));
|
|
1409
|
+
const normalizedDomain = this.normalizeOriginator(domainDec);
|
|
1410
|
+
if (originFilter && normalizedDomain !== originFilter.normalized) {
|
|
1411
|
+
continue;
|
|
1412
|
+
}
|
|
1413
|
+
const expiryDec = parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10);
|
|
1414
|
+
const privDec = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(privRaw)) === 'true';
|
|
1415
|
+
const secDec = parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(secRaw)), 10);
|
|
1416
|
+
const protoDec = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(protoRaw));
|
|
1417
|
+
const cptyDec = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(cptyRaw));
|
|
1418
|
+
seen.add(out.outpoint);
|
|
1419
|
+
tokens.push({
|
|
1420
|
+
tx: tx.toBEEF(),
|
|
1421
|
+
txid: out.outpoint.split('.')[0],
|
|
1422
|
+
outputIndex: parseInt(out.outpoint.split('.')[1], 10),
|
|
1423
|
+
outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
|
|
1424
|
+
satoshis: out.satoshis,
|
|
1425
|
+
originator: normalizedDomain,
|
|
1426
|
+
rawOriginator: domainDec,
|
|
1427
|
+
expiry: expiryDec,
|
|
1428
|
+
privileged: privDec,
|
|
1429
|
+
securityLevel: secDec,
|
|
1430
|
+
protocol: protoDec,
|
|
1431
|
+
counterparty: cptyDec
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1332
1434
|
}
|
|
1333
1435
|
return tokens;
|
|
1334
1436
|
}
|
|
@@ -1358,41 +1460,55 @@ class WalletPermissionsManager {
|
|
|
1358
1460
|
*/
|
|
1359
1461
|
async listBasketAccess(params = {}) {
|
|
1360
1462
|
const basketName = BASKET_MAP.basket;
|
|
1361
|
-
const
|
|
1362
|
-
if (params.originator) {
|
|
1363
|
-
tags.push(`originator ${params.originator}`);
|
|
1364
|
-
}
|
|
1463
|
+
const baseTags = [];
|
|
1365
1464
|
if (params.basket) {
|
|
1366
|
-
|
|
1465
|
+
baseTags.push(`basket ${params.basket}`);
|
|
1367
1466
|
}
|
|
1368
|
-
const
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
tagQueryMode: 'all',
|
|
1372
|
-
include: 'entire transactions',
|
|
1373
|
-
limit: 10000
|
|
1374
|
-
}, this.adminOriginator);
|
|
1467
|
+
const originFilter = params.originator ? this.prepareOriginator(params.originator) : undefined;
|
|
1468
|
+
const originVariants = originFilter ? originFilter.lookupValues : [undefined];
|
|
1469
|
+
const seen = new Set();
|
|
1375
1470
|
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
|
-
|
|
1471
|
+
for (const originTag of originVariants) {
|
|
1472
|
+
const tags = [...baseTags];
|
|
1473
|
+
if (originTag) {
|
|
1474
|
+
tags.push(`originator ${originTag}`);
|
|
1475
|
+
}
|
|
1476
|
+
const result = await this.underlying.listOutputs({
|
|
1477
|
+
basket: basketName,
|
|
1478
|
+
tags,
|
|
1479
|
+
tagQueryMode: 'all',
|
|
1480
|
+
include: 'entire transactions',
|
|
1481
|
+
limit: 10000
|
|
1482
|
+
}, this.adminOriginator);
|
|
1483
|
+
for (const out of result.outputs) {
|
|
1484
|
+
if (seen.has(out.outpoint))
|
|
1485
|
+
continue;
|
|
1486
|
+
const [txid, outputIndexStr] = out.outpoint.split('.');
|
|
1487
|
+
const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
|
|
1488
|
+
const dec = sdk_1.PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript);
|
|
1489
|
+
if (!(dec === null || dec === void 0 ? void 0 : dec.fields) || dec.fields.length < 3)
|
|
1490
|
+
continue;
|
|
1491
|
+
const [domainRaw, expiryRaw, basketRaw] = dec.fields;
|
|
1492
|
+
const domainDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw));
|
|
1493
|
+
const normalizedDomain = this.normalizeOriginator(domainDecoded);
|
|
1494
|
+
if (originFilter && normalizedDomain !== originFilter.normalized) {
|
|
1495
|
+
continue;
|
|
1496
|
+
}
|
|
1497
|
+
const expiryDecoded = parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10);
|
|
1498
|
+
const basketDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(basketRaw));
|
|
1499
|
+
seen.add(out.outpoint);
|
|
1500
|
+
tokens.push({
|
|
1501
|
+
tx: tx.toBEEF(),
|
|
1502
|
+
txid: out.outpoint.split('.')[0],
|
|
1503
|
+
outputIndex: parseInt(out.outpoint.split('.')[1], 10),
|
|
1504
|
+
satoshis: out.satoshis,
|
|
1505
|
+
outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
|
|
1506
|
+
originator: normalizedDomain,
|
|
1507
|
+
rawOriginator: domainDecoded,
|
|
1508
|
+
basketName: basketDecoded,
|
|
1509
|
+
expiry: expiryDecoded
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1396
1512
|
}
|
|
1397
1513
|
return tokens;
|
|
1398
1514
|
}
|
|
@@ -1480,54 +1596,68 @@ class WalletPermissionsManager {
|
|
|
1480
1596
|
*/
|
|
1481
1597
|
async listCertificateAccess(params = {}) {
|
|
1482
1598
|
const basketName = BASKET_MAP.certificate;
|
|
1483
|
-
const
|
|
1484
|
-
if (params.originator) {
|
|
1485
|
-
tags.push(`originator ${params.originator}`);
|
|
1486
|
-
}
|
|
1599
|
+
const baseTags = [];
|
|
1487
1600
|
if (params.privileged !== undefined) {
|
|
1488
|
-
|
|
1601
|
+
baseTags.push(`privileged ${!!params.privileged}`);
|
|
1489
1602
|
}
|
|
1490
1603
|
if (params.certType) {
|
|
1491
|
-
|
|
1604
|
+
baseTags.push(`type ${params.certType}`);
|
|
1492
1605
|
}
|
|
1493
1606
|
if (params.verifier) {
|
|
1494
|
-
|
|
1607
|
+
baseTags.push(`verifier ${params.verifier}`);
|
|
1495
1608
|
}
|
|
1496
|
-
const
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
tagQueryMode: 'all',
|
|
1500
|
-
include: 'entire transactions',
|
|
1501
|
-
limit: 10000
|
|
1502
|
-
}, this.adminOriginator);
|
|
1609
|
+
const originFilter = params.originator ? this.prepareOriginator(params.originator) : undefined;
|
|
1610
|
+
const originVariants = originFilter ? originFilter.lookupValues : [undefined];
|
|
1611
|
+
const seen = new Set();
|
|
1503
1612
|
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
|
-
|
|
1613
|
+
for (const originTag of originVariants) {
|
|
1614
|
+
const tags = [...baseTags];
|
|
1615
|
+
if (originTag) {
|
|
1616
|
+
tags.push(`originator ${originTag}`);
|
|
1617
|
+
}
|
|
1618
|
+
const result = await this.underlying.listOutputs({
|
|
1619
|
+
basket: basketName,
|
|
1620
|
+
tags,
|
|
1621
|
+
tagQueryMode: 'all',
|
|
1622
|
+
include: 'entire transactions',
|
|
1623
|
+
limit: 10000
|
|
1624
|
+
}, this.adminOriginator);
|
|
1625
|
+
for (const out of result.outputs) {
|
|
1626
|
+
if (seen.has(out.outpoint))
|
|
1627
|
+
continue;
|
|
1628
|
+
const [txid, outputIndexStr] = out.outpoint.split('.');
|
|
1629
|
+
const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
|
|
1630
|
+
const dec = sdk_1.PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript);
|
|
1631
|
+
if (!(dec === null || dec === void 0 ? void 0 : dec.fields) || dec.fields.length < 6)
|
|
1632
|
+
continue;
|
|
1633
|
+
const [domainRaw, expiryRaw, privRaw, typeRaw, fieldsRaw, verifierRaw] = dec.fields;
|
|
1634
|
+
const domainDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw));
|
|
1635
|
+
const normalizedDomain = this.normalizeOriginator(domainDecoded);
|
|
1636
|
+
if (originFilter && normalizedDomain !== originFilter.normalized) {
|
|
1637
|
+
continue;
|
|
1638
|
+
}
|
|
1639
|
+
const expiryDecoded = parseInt(sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10);
|
|
1640
|
+
const privDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(privRaw)) === 'true';
|
|
1641
|
+
const typeDecoded = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(typeRaw));
|
|
1642
|
+
const verifierDec = sdk_1.Utils.toUTF8(await this.decryptPermissionTokenField(verifierRaw));
|
|
1643
|
+
const fieldsJson = await this.decryptPermissionTokenField(fieldsRaw);
|
|
1644
|
+
const allFields = JSON.parse(sdk_1.Utils.toUTF8(fieldsJson));
|
|
1645
|
+
seen.add(out.outpoint);
|
|
1646
|
+
tokens.push({
|
|
1647
|
+
tx: tx.toBEEF(),
|
|
1648
|
+
txid: out.outpoint.split('.')[0],
|
|
1649
|
+
outputIndex: parseInt(out.outpoint.split('.')[1], 10),
|
|
1650
|
+
satoshis: out.satoshis,
|
|
1651
|
+
outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
|
|
1652
|
+
originator: normalizedDomain,
|
|
1653
|
+
rawOriginator: domainDecoded,
|
|
1654
|
+
privileged: privDecoded,
|
|
1655
|
+
certType: typeDecoded,
|
|
1656
|
+
certFields: allFields,
|
|
1657
|
+
verifier: verifierDec,
|
|
1658
|
+
expiry: expiryDecoded
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1531
1661
|
}
|
|
1532
1662
|
return tokens;
|
|
1533
1663
|
}
|
|
@@ -2063,8 +2193,10 @@ class WalletPermissionsManager {
|
|
|
2063
2193
|
}
|
|
2064
2194
|
async waitForAuthentication(...args) {
|
|
2065
2195
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
2066
|
-
|
|
2196
|
+
let [_, originator] = args;
|
|
2067
2197
|
if (this.config.seekGroupedPermission && originator) {
|
|
2198
|
+
const { normalized: normalizedOriginator } = this.prepareOriginator(originator);
|
|
2199
|
+
originator = normalizedOriginator;
|
|
2068
2200
|
// 1. Fetch manifest.json from the originator
|
|
2069
2201
|
let groupPermissions;
|
|
2070
2202
|
try {
|
|
@@ -2146,7 +2278,7 @@ class WalletPermissionsManager {
|
|
|
2146
2278
|
try {
|
|
2147
2279
|
await new Promise(async (resolve, reject) => {
|
|
2148
2280
|
this.activeRequests.set(key, {
|
|
2149
|
-
request: { originator, permissions: permissionsToRequest },
|
|
2281
|
+
request: { originator: originator, permissions: permissionsToRequest },
|
|
2150
2282
|
pending: [{ resolve, reject }]
|
|
2151
2283
|
});
|
|
2152
2284
|
await this.callEvent('onGroupedPermissionRequested', {
|
|
@@ -2184,7 +2316,7 @@ class WalletPermissionsManager {
|
|
|
2184
2316
|
* --------------------------------------------------------------------- */
|
|
2185
2317
|
/** Returns true if the specified origin is the admin originator. */
|
|
2186
2318
|
isAdminOriginator(originator) {
|
|
2187
|
-
return originator === this.adminOriginator;
|
|
2319
|
+
return this.normalizeOriginator(originator) === this.adminOriginator;
|
|
2188
2320
|
}
|
|
2189
2321
|
/**
|
|
2190
2322
|
* Checks if the given protocol is admin-reserved per BRC-100 rules:
|
|
@@ -2252,27 +2384,113 @@ class WalletPermissionsManager {
|
|
|
2252
2384
|
cachePermission(key, expiry) {
|
|
2253
2385
|
this.permissionCache.set(key, { expiry, cachedAt: Date.now() });
|
|
2254
2386
|
}
|
|
2387
|
+
/** Records that a non-spending permission was just granted so we can skip re-prompting briefly. */
|
|
2388
|
+
markRecentGrant(request) {
|
|
2389
|
+
if (request.type === 'spending')
|
|
2390
|
+
return;
|
|
2391
|
+
const key = this.buildRequestKey(request);
|
|
2392
|
+
if (!key)
|
|
2393
|
+
return;
|
|
2394
|
+
this.recentGrants.set(key, Date.now() + WalletPermissionsManager.RECENT_GRANT_COVER_MS);
|
|
2395
|
+
}
|
|
2396
|
+
/** Returns true if we are inside the short "cover window" immediately after granting permission. */
|
|
2397
|
+
isRecentlyGranted(key) {
|
|
2398
|
+
const expiry = this.recentGrants.get(key);
|
|
2399
|
+
if (!expiry)
|
|
2400
|
+
return false;
|
|
2401
|
+
if (Date.now() > expiry) {
|
|
2402
|
+
this.recentGrants.delete(key);
|
|
2403
|
+
return false;
|
|
2404
|
+
}
|
|
2405
|
+
return true;
|
|
2406
|
+
}
|
|
2407
|
+
/** Normalizes and canonicalizes originator domains (e.g., lowercase + drop default ports). */
|
|
2408
|
+
normalizeOriginator(originator) {
|
|
2409
|
+
if (!originator)
|
|
2410
|
+
return '';
|
|
2411
|
+
const trimmed = originator.trim();
|
|
2412
|
+
if (!trimmed) {
|
|
2413
|
+
return '';
|
|
2414
|
+
}
|
|
2415
|
+
try {
|
|
2416
|
+
const hasScheme = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(trimmed);
|
|
2417
|
+
const candidate = hasScheme ? trimmed : `https://${trimmed}`;
|
|
2418
|
+
const url = new URL(candidate);
|
|
2419
|
+
if (!url.hostname) {
|
|
2420
|
+
return trimmed.toLowerCase();
|
|
2421
|
+
}
|
|
2422
|
+
const hostname = url.hostname.toLowerCase();
|
|
2423
|
+
const needsBrackets = hostname.includes(':');
|
|
2424
|
+
const baseHost = needsBrackets ? `[${hostname}]` : hostname;
|
|
2425
|
+
const port = url.port;
|
|
2426
|
+
const defaultPort = WalletPermissionsManager.DEFAULT_PORTS[url.protocol];
|
|
2427
|
+
if (port && defaultPort && port === defaultPort) {
|
|
2428
|
+
return baseHost;
|
|
2429
|
+
}
|
|
2430
|
+
return port ? `${baseHost}:${port}` : baseHost;
|
|
2431
|
+
}
|
|
2432
|
+
catch (_a) {
|
|
2433
|
+
// Fall back to a conservative lowercase trim if URL parsing fails.
|
|
2434
|
+
return trimmed.toLowerCase();
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
/**
|
|
2438
|
+
* Produces a normalized originator value along with the set of legacy
|
|
2439
|
+
* representations that should be considered when searching for existing
|
|
2440
|
+
* permission tokens (for backwards compatibility).
|
|
2441
|
+
*/
|
|
2442
|
+
prepareOriginator(originator) {
|
|
2443
|
+
const trimmed = originator === null || originator === void 0 ? void 0 : originator.trim();
|
|
2444
|
+
if (!trimmed) {
|
|
2445
|
+
throw new Error('Originator is required for permission checks.');
|
|
2446
|
+
}
|
|
2447
|
+
const normalized = this.normalizeOriginator(trimmed) || trimmed.toLowerCase();
|
|
2448
|
+
const lookupValues = Array.from(new Set([trimmed, normalized])).filter(Boolean);
|
|
2449
|
+
return { normalized, lookupValues };
|
|
2450
|
+
}
|
|
2451
|
+
/**
|
|
2452
|
+
* Builds a unique list of originator variants that should be searched when
|
|
2453
|
+
* looking up on-chain tokens (e.g., legacy raw + normalized forms).
|
|
2454
|
+
*/
|
|
2455
|
+
buildOriginatorLookupValues(...origins) {
|
|
2456
|
+
const variants = new Set();
|
|
2457
|
+
for (const origin of origins) {
|
|
2458
|
+
const trimmed = origin === null || origin === void 0 ? void 0 : origin.trim();
|
|
2459
|
+
if (trimmed) {
|
|
2460
|
+
variants.add(trimmed);
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
return Array.from(variants);
|
|
2464
|
+
}
|
|
2255
2465
|
/**
|
|
2256
2466
|
* Builds a "map key" string so that identical requests (e.g. "protocol:domain:true:protoName:counterparty")
|
|
2257
2467
|
* do not produce multiple user prompts.
|
|
2258
2468
|
*/
|
|
2259
2469
|
buildRequestKey(r) {
|
|
2260
2470
|
var _a, _b, _c, _d, _e;
|
|
2471
|
+
const normalizedOriginator = this.normalizeOriginator(r.originator);
|
|
2261
2472
|
switch (r.type) {
|
|
2262
2473
|
case 'protocol':
|
|
2263
|
-
return `proto:${
|
|
2474
|
+
return `proto:${normalizedOriginator}:${!!r.privileged}:${(_a = r.protocolID) === null || _a === void 0 ? void 0 : _a.join(',')}:${r.counterparty}`;
|
|
2264
2475
|
case 'basket':
|
|
2265
|
-
return `basket:${
|
|
2476
|
+
return `basket:${normalizedOriginator}:${r.basket}`;
|
|
2266
2477
|
case 'certificate':
|
|
2267
|
-
return `cert:${
|
|
2478
|
+
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
2479
|
case 'spending':
|
|
2269
|
-
return `spend:${
|
|
2480
|
+
return `spend:${normalizedOriginator}:${(_e = r.spending) === null || _e === void 0 ? void 0 : _e.satoshis}`;
|
|
2270
2481
|
}
|
|
2271
2482
|
}
|
|
2272
2483
|
}
|
|
2273
2484
|
exports.WalletPermissionsManager = WalletPermissionsManager;
|
|
2274
2485
|
/** How long a cached permission remains valid (5 minutes). */
|
|
2275
2486
|
WalletPermissionsManager.CACHE_TTL_MS = 5 * 60 * 1000;
|
|
2487
|
+
/** Window during which freshly granted permissions are auto-allowed (except spending). */
|
|
2488
|
+
WalletPermissionsManager.RECENT_GRANT_COVER_MS = 15 * 1000;
|
|
2489
|
+
/** Default ports used when normalizing originator values. */
|
|
2490
|
+
WalletPermissionsManager.DEFAULT_PORTS = {
|
|
2491
|
+
'http:': '80',
|
|
2492
|
+
'https:': '443'
|
|
2493
|
+
};
|
|
2276
2494
|
/* ---------------------------------------------------------------------
|
|
2277
2495
|
* 4) SEARCH / DECODE / DECRYPT ON-CHAIN TOKENS (PushDrop Scripts)
|
|
2278
2496
|
* --------------------------------------------------------------------- */
|