@bsv/wallet-toolbox-client 1.7.18 → 1.7.20
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/out/src/WalletPermissionsManager.d.ts +48 -1
- package/out/src/WalletPermissionsManager.d.ts.map +1 -1
- package/out/src/WalletPermissionsManager.js +775 -124
- package/out/src/WalletPermissionsManager.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/Ingest/BulkIngestorWhatsOnChainCdn.d.ts +1 -1
- package/out/src/services/chaintracker/chaintracks/util/validBulkHeaderFilesByFileHash.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/util/validBulkHeaderFilesByFileHash.js +12 -0
- package/out/src/services/chaintracker/chaintracks/util/validBulkHeaderFilesByFileHash.js.map +1 -1
- package/out/src/storage/StorageProvider.d.ts +16 -2
- package/out/src/storage/StorageProvider.d.ts.map +1 -1
- package/out/src/storage/StorageProvider.js +33 -4
- package/out/src/storage/StorageProvider.js.map +1 -1
- package/out/src/storage/methods/getBeefForTransaction.js +1 -1
- package/out/src/storage/methods/getBeefForTransaction.js.map +1 -1
- package/package.json +1 -1
|
@@ -77,7 +77,8 @@ class WalletPermissionsManager {
|
|
|
77
77
|
onBasketAccessRequested: [],
|
|
78
78
|
onCertificateAccessRequested: [],
|
|
79
79
|
onSpendingAuthorizationRequested: [],
|
|
80
|
-
onGroupedPermissionRequested: []
|
|
80
|
+
onGroupedPermissionRequested: [],
|
|
81
|
+
onCounterpartyPermissionRequested: []
|
|
81
82
|
};
|
|
82
83
|
/**
|
|
83
84
|
* We queue parallel requests for the same resource so that only one
|
|
@@ -93,6 +94,9 @@ class WalletPermissionsManager {
|
|
|
93
94
|
/** Cache recently confirmed permissions to avoid repeated lookups. */
|
|
94
95
|
this.permissionCache = new Map();
|
|
95
96
|
this.recentGrants = new Map();
|
|
97
|
+
this.manifestCache = new Map();
|
|
98
|
+
this.manifestFetchInProgress = new Map();
|
|
99
|
+
this.pactEstablishedCache = new Map();
|
|
96
100
|
this.underlying = underlyingWallet;
|
|
97
101
|
this.adminOriginator = this.normalizeOriginator(adminOriginator) || adminOriginator;
|
|
98
102
|
// Default all config options to true unless specified
|
|
@@ -316,9 +320,8 @@ class WalletPermissionsManager {
|
|
|
316
320
|
const { originator, permissions: requestedPermissions, displayOriginator } = originalRequest;
|
|
317
321
|
const originLookupValues = this.buildOriginatorLookupValues(displayOriginator, originator);
|
|
318
322
|
// --- Validation: Ensure granted permissions are a subset of what was requested ---
|
|
319
|
-
if (params.granted.spendingAuthorization &&
|
|
320
|
-
|
|
321
|
-
throw new Error('Granted spending authorization does not match the original request.');
|
|
323
|
+
if (params.granted.spendingAuthorization && !requestedPermissions.spendingAuthorization) {
|
|
324
|
+
throw new Error('Granted spending authorization was not part of the original request.');
|
|
322
325
|
}
|
|
323
326
|
if ((_a = params.granted.protocolPermissions) === null || _a === void 0 ? void 0 : _a.some(g => { var _a; return !((_a = requestedPermissions.protocolPermissions) === null || _a === void 0 ? void 0 : _a.find(r => deepEqual(r, g))); })) {
|
|
324
327
|
throw new Error('Granted protocol permissions are not a subset of the original request.');
|
|
@@ -331,62 +334,67 @@ class WalletPermissionsManager {
|
|
|
331
334
|
}
|
|
332
335
|
// --- End Validation ---
|
|
333
336
|
const expiry = params.expiry || 0; // default: never expires
|
|
337
|
+
const toCreate = [];
|
|
338
|
+
const toRenew = [];
|
|
334
339
|
if (params.granted.spendingAuthorization) {
|
|
335
|
-
|
|
336
|
-
|
|
340
|
+
toCreate.push({
|
|
341
|
+
request: {
|
|
342
|
+
type: 'spending',
|
|
343
|
+
originator,
|
|
344
|
+
spending: { satoshis: params.granted.spendingAuthorization.amount },
|
|
345
|
+
reason: params.granted.spendingAuthorization.description
|
|
346
|
+
},
|
|
347
|
+
expiry: 0,
|
|
348
|
+
amount: params.granted.spendingAuthorization.amount
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
const grantedProtocols = params.granted.protocolPermissions || [];
|
|
352
|
+
const protocolTokens = await this.mapWithConcurrency(grantedProtocols, 8, async (p) => {
|
|
353
|
+
const token = await this.findProtocolToken(originator, false, p.protocolID, p.counterparty || 'self', true, originLookupValues);
|
|
354
|
+
return { p, token };
|
|
355
|
+
});
|
|
356
|
+
for (const { p, token } of protocolTokens) {
|
|
357
|
+
const request = {
|
|
358
|
+
type: 'protocol',
|
|
337
359
|
originator,
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
for (const p of params.granted.protocolPermissions || []) {
|
|
344
|
-
const token = await this.findProtocolToken(originator, false, // No privileged protocols allowed in groups for added security.
|
|
345
|
-
p.protocolID, p.counterparty || 'self', true, originLookupValues);
|
|
360
|
+
privileged: false,
|
|
361
|
+
protocolID: p.protocolID,
|
|
362
|
+
counterparty: p.counterparty || 'self',
|
|
363
|
+
reason: p.description
|
|
364
|
+
};
|
|
346
365
|
if (token) {
|
|
347
|
-
|
|
348
|
-
type: 'protocol',
|
|
349
|
-
originator,
|
|
350
|
-
privileged: false, // No privileged protocols allowed in groups for added security.
|
|
351
|
-
protocolID: p.protocolID,
|
|
352
|
-
counterparty: p.counterparty || 'self',
|
|
353
|
-
reason: p.description
|
|
354
|
-
};
|
|
355
|
-
await this.renewPermissionOnChain(token, request, expiry);
|
|
356
|
-
this.markRecentGrant(request);
|
|
366
|
+
toRenew.push({ oldToken: token, request, expiry });
|
|
357
367
|
}
|
|
358
368
|
else {
|
|
359
|
-
|
|
360
|
-
type: 'protocol',
|
|
361
|
-
originator,
|
|
362
|
-
privileged: false, // No privileged protocols allowed in groups for added security.
|
|
363
|
-
protocolID: p.protocolID,
|
|
364
|
-
counterparty: p.counterparty || 'self',
|
|
365
|
-
reason: p.description
|
|
366
|
-
};
|
|
367
|
-
await this.createPermissionOnChain(request, expiry);
|
|
368
|
-
this.markRecentGrant(request);
|
|
369
|
+
toCreate.push({ request, expiry });
|
|
369
370
|
}
|
|
370
371
|
}
|
|
371
372
|
for (const b of params.granted.basketAccess || []) {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
373
|
+
toCreate.push({
|
|
374
|
+
request: { type: 'basket', originator, basket: b.basket, reason: b.description },
|
|
375
|
+
expiry
|
|
376
|
+
});
|
|
375
377
|
}
|
|
376
378
|
for (const c of params.granted.certificateAccess || []) {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
379
|
+
toCreate.push({
|
|
380
|
+
request: {
|
|
381
|
+
type: 'certificate',
|
|
382
|
+
originator,
|
|
383
|
+
privileged: false,
|
|
384
|
+
certificate: {
|
|
385
|
+
verifier: c.verifierPublicKey,
|
|
386
|
+
certType: c.type,
|
|
387
|
+
fields: c.fields
|
|
388
|
+
},
|
|
389
|
+
reason: c.description
|
|
385
390
|
},
|
|
386
|
-
|
|
387
|
-
};
|
|
388
|
-
|
|
389
|
-
|
|
391
|
+
expiry
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
const created = await this.createPermissionTokensBestEffort(toCreate);
|
|
395
|
+
const renewed = await this.renewPermissionTokensBestEffort(toRenew);
|
|
396
|
+
for (const req of [...created, ...renewed]) {
|
|
397
|
+
this.markRecentGrant(req);
|
|
390
398
|
}
|
|
391
399
|
// Resolve all pending promises for this request
|
|
392
400
|
for (const p of matching.pending) {
|
|
@@ -410,6 +418,74 @@ class WalletPermissionsManager {
|
|
|
410
418
|
}
|
|
411
419
|
this.activeRequests.delete(requestID);
|
|
412
420
|
}
|
|
421
|
+
async dismissGroupedPermission(requestID) {
|
|
422
|
+
const matching = this.activeRequests.get(requestID);
|
|
423
|
+
if (!matching) {
|
|
424
|
+
throw new Error('Request ID not found.');
|
|
425
|
+
}
|
|
426
|
+
for (const p of matching.pending) {
|
|
427
|
+
p.resolve(true);
|
|
428
|
+
}
|
|
429
|
+
this.activeRequests.delete(requestID);
|
|
430
|
+
}
|
|
431
|
+
async grantCounterpartyPermission(params) {
|
|
432
|
+
var _a;
|
|
433
|
+
const matching = this.activeRequests.get(params.requestID);
|
|
434
|
+
if (!matching) {
|
|
435
|
+
throw new Error('Request ID not found.');
|
|
436
|
+
}
|
|
437
|
+
const originalRequest = matching.request;
|
|
438
|
+
const { originator, counterparty, permissions: requestedPermissions, displayOriginator } = originalRequest;
|
|
439
|
+
const originLookupValues = this.buildOriginatorLookupValues(displayOriginator, originator);
|
|
440
|
+
if ((_a = params.granted.protocols) === null || _a === void 0 ? void 0 : _a.some(g => !requestedPermissions.protocols.find(r => deepEqual(r, g)))) {
|
|
441
|
+
throw new Error('Granted protocol permissions are not a subset of the original request.');
|
|
442
|
+
}
|
|
443
|
+
const expiry = params.expiry || 0;
|
|
444
|
+
const toCreate = [];
|
|
445
|
+
const toRenew = [];
|
|
446
|
+
const grantedProtocols = params.granted.protocols || [];
|
|
447
|
+
const protocolTokens = await this.mapWithConcurrency(grantedProtocols, 8, async (p) => {
|
|
448
|
+
const token = await this.findProtocolToken(originator, false, p.protocolID, counterparty, true, originLookupValues);
|
|
449
|
+
return { p, token };
|
|
450
|
+
});
|
|
451
|
+
for (const { p, token } of protocolTokens) {
|
|
452
|
+
const request = {
|
|
453
|
+
type: 'protocol',
|
|
454
|
+
originator,
|
|
455
|
+
privileged: false,
|
|
456
|
+
protocolID: p.protocolID,
|
|
457
|
+
counterparty,
|
|
458
|
+
reason: p.description
|
|
459
|
+
};
|
|
460
|
+
if (token) {
|
|
461
|
+
toRenew.push({ oldToken: token, request, expiry });
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
toCreate.push({ request, expiry });
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
const created = await this.createPermissionTokensBestEffort(toCreate);
|
|
468
|
+
const renewed = await this.renewPermissionTokensBestEffort(toRenew);
|
|
469
|
+
for (const req of [...created, ...renewed]) {
|
|
470
|
+
this.markRecentGrant(req);
|
|
471
|
+
}
|
|
472
|
+
for (const p of matching.pending) {
|
|
473
|
+
p.resolve(true);
|
|
474
|
+
}
|
|
475
|
+
this.activeRequests.delete(params.requestID);
|
|
476
|
+
}
|
|
477
|
+
async denyCounterpartyPermission(requestID) {
|
|
478
|
+
const matching = this.activeRequests.get(requestID);
|
|
479
|
+
if (!matching) {
|
|
480
|
+
throw new Error('Request ID not found.');
|
|
481
|
+
}
|
|
482
|
+
const err = new Error('The user has denied the request for permission.');
|
|
483
|
+
err.code = 'ERR_PERMISSION_DENIED';
|
|
484
|
+
for (const p of matching.pending) {
|
|
485
|
+
p.reject(err);
|
|
486
|
+
}
|
|
487
|
+
this.activeRequests.delete(requestID);
|
|
488
|
+
}
|
|
413
489
|
/* ---------------------------------------------------------------------
|
|
414
490
|
* 3) THE "ENSURE" METHODS: CHECK IF PERMISSION EXISTS, OTHERWISE PROMPT
|
|
415
491
|
* --------------------------------------------------------------------- */
|
|
@@ -728,6 +804,469 @@ class WalletPermissionsManager {
|
|
|
728
804
|
usageType: 'generic'
|
|
729
805
|
});
|
|
730
806
|
}
|
|
807
|
+
validateCounterpartyPermissions(raw) {
|
|
808
|
+
if (!raw || !Array.isArray(raw.protocols) || raw.protocols.length === 0)
|
|
809
|
+
return null;
|
|
810
|
+
const validProtocols = raw.protocols.filter((p) => {
|
|
811
|
+
return (Array.isArray(p === null || p === void 0 ? void 0 : p.protocolID) &&
|
|
812
|
+
p.protocolID[0] === 2 &&
|
|
813
|
+
typeof p.protocolID[1] === 'string' &&
|
|
814
|
+
typeof (p === null || p === void 0 ? void 0 : p.description) === 'string');
|
|
815
|
+
});
|
|
816
|
+
if (validProtocols.length === 0)
|
|
817
|
+
return null;
|
|
818
|
+
return {
|
|
819
|
+
description: typeof raw.description === 'string' ? raw.description : undefined,
|
|
820
|
+
protocols: validProtocols
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
async fetchManifestPermissions(originator) {
|
|
824
|
+
const cached = this.manifestCache.get(originator);
|
|
825
|
+
if (cached && Date.now() - cached.fetchedAt < WalletPermissionsManager.MANIFEST_CACHE_TTL_MS) {
|
|
826
|
+
return {
|
|
827
|
+
groupPermissions: cached.groupPermissions,
|
|
828
|
+
counterpartyPermissions: cached.counterpartyPermissions
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
const inProgress = this.manifestFetchInProgress.get(originator);
|
|
832
|
+
if (inProgress) {
|
|
833
|
+
return inProgress;
|
|
834
|
+
}
|
|
835
|
+
const fetchPromise = (async () => {
|
|
836
|
+
var _a, _b;
|
|
837
|
+
try {
|
|
838
|
+
const proto = originator.startsWith('localhost:') ? 'http' : 'https';
|
|
839
|
+
const response = await fetch(`${proto}://${originator}/manifest.json`);
|
|
840
|
+
if (response.ok) {
|
|
841
|
+
const manifest = await response.json();
|
|
842
|
+
const groupPermissions = ((_a = manifest === null || manifest === void 0 ? void 0 : manifest.babbage) === null || _a === void 0 ? void 0 : _a.groupPermissions) || null;
|
|
843
|
+
const counterpartyPermissions = this.validateCounterpartyPermissions((_b = manifest === null || manifest === void 0 ? void 0 : manifest.babbage) === null || _b === void 0 ? void 0 : _b.counterpartyPermissions);
|
|
844
|
+
this.manifestCache.set(originator, { groupPermissions, counterpartyPermissions, fetchedAt: Date.now() });
|
|
845
|
+
return { groupPermissions, counterpartyPermissions };
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
catch (e) { }
|
|
849
|
+
const result = { groupPermissions: null, counterpartyPermissions: null };
|
|
850
|
+
this.manifestCache.set(originator, { ...result, fetchedAt: Date.now() });
|
|
851
|
+
return result;
|
|
852
|
+
})();
|
|
853
|
+
this.manifestFetchInProgress.set(originator, fetchPromise);
|
|
854
|
+
try {
|
|
855
|
+
return await fetchPromise;
|
|
856
|
+
}
|
|
857
|
+
finally {
|
|
858
|
+
this.manifestFetchInProgress.delete(originator);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
async fetchManifestGroupPermissions(originator) {
|
|
862
|
+
const { groupPermissions } = await this.fetchManifestPermissions(originator);
|
|
863
|
+
return groupPermissions;
|
|
864
|
+
}
|
|
865
|
+
async filterAlreadyGrantedPermissions(originator, groupPermissions) {
|
|
866
|
+
const permissionsToRequest = {
|
|
867
|
+
description: groupPermissions.description,
|
|
868
|
+
protocolPermissions: [],
|
|
869
|
+
basketAccess: [],
|
|
870
|
+
certificateAccess: []
|
|
871
|
+
};
|
|
872
|
+
if (groupPermissions.spendingAuthorization) {
|
|
873
|
+
const hasAuth = await this.hasSpendingAuthorization({
|
|
874
|
+
originator,
|
|
875
|
+
satoshis: groupPermissions.spendingAuthorization.amount
|
|
876
|
+
});
|
|
877
|
+
if (!hasAuth) {
|
|
878
|
+
permissionsToRequest.spendingAuthorization = groupPermissions.spendingAuthorization;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
for (const p of groupPermissions.protocolPermissions || []) {
|
|
882
|
+
const hasPerm = await this.hasProtocolPermission({
|
|
883
|
+
originator,
|
|
884
|
+
privileged: false,
|
|
885
|
+
protocolID: p.protocolID,
|
|
886
|
+
counterparty: p.counterparty || 'self'
|
|
887
|
+
});
|
|
888
|
+
if (!hasPerm) {
|
|
889
|
+
permissionsToRequest.protocolPermissions.push(p);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
for (const b of groupPermissions.basketAccess || []) {
|
|
893
|
+
const hasAccess = await this.hasBasketAccess({
|
|
894
|
+
originator,
|
|
895
|
+
basket: b.basket
|
|
896
|
+
});
|
|
897
|
+
if (!hasAccess) {
|
|
898
|
+
permissionsToRequest.basketAccess.push(b);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
for (const c of groupPermissions.certificateAccess || []) {
|
|
902
|
+
const hasAccess = await this.hasCertificateAccess({
|
|
903
|
+
originator,
|
|
904
|
+
privileged: false,
|
|
905
|
+
verifier: c.verifierPublicKey,
|
|
906
|
+
certType: c.type,
|
|
907
|
+
fields: c.fields
|
|
908
|
+
});
|
|
909
|
+
if (!hasAccess) {
|
|
910
|
+
permissionsToRequest.certificateAccess.push(c);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
return permissionsToRequest;
|
|
914
|
+
}
|
|
915
|
+
hasAnyPermissionsToRequest(permissions) {
|
|
916
|
+
var _a, _b, _c, _d, _e, _f;
|
|
917
|
+
return !!(permissions.spendingAuthorization ||
|
|
918
|
+
((_b = (_a = permissions.protocolPermissions) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0 ||
|
|
919
|
+
((_d = (_c = permissions.basketAccess) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0) > 0 ||
|
|
920
|
+
((_f = (_e = permissions.certificateAccess) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0) > 0);
|
|
921
|
+
}
|
|
922
|
+
hasGroupedPermissionRequestedHandlers() {
|
|
923
|
+
const handlers = this.callbacks.onGroupedPermissionRequested || [];
|
|
924
|
+
return handlers.some(h => typeof h === 'function');
|
|
925
|
+
}
|
|
926
|
+
hasCounterpartyPermissionRequestedHandlers() {
|
|
927
|
+
const handlers = this.callbacks.onCounterpartyPermissionRequested || [];
|
|
928
|
+
return handlers.some(h => typeof h === 'function');
|
|
929
|
+
}
|
|
930
|
+
async hasPactEstablished(originator, counterparty) {
|
|
931
|
+
var _a;
|
|
932
|
+
if (counterparty === 'self' || counterparty === 'anyone') {
|
|
933
|
+
return true;
|
|
934
|
+
}
|
|
935
|
+
const cacheKey = `${originator}:${counterparty}`;
|
|
936
|
+
if (this.pactEstablishedCache.has(cacheKey)) {
|
|
937
|
+
return true;
|
|
938
|
+
}
|
|
939
|
+
const { counterpartyPermissions } = await this.fetchManifestPermissions(originator);
|
|
940
|
+
if (!((_a = counterpartyPermissions === null || counterpartyPermissions === void 0 ? void 0 : counterpartyPermissions.protocols) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
941
|
+
return true;
|
|
942
|
+
}
|
|
943
|
+
const firstProtocol = counterpartyPermissions.protocols[0];
|
|
944
|
+
const hasToken = await this.hasProtocolPermission({
|
|
945
|
+
originator,
|
|
946
|
+
privileged: false,
|
|
947
|
+
protocolID: firstProtocol.protocolID,
|
|
948
|
+
counterparty
|
|
949
|
+
});
|
|
950
|
+
if (hasToken) {
|
|
951
|
+
this.pactEstablishedCache.set(cacheKey, Date.now());
|
|
952
|
+
return true;
|
|
953
|
+
}
|
|
954
|
+
return false;
|
|
955
|
+
}
|
|
956
|
+
markPactEstablished(originator, counterparty) {
|
|
957
|
+
const cacheKey = `${originator}:${counterparty}`;
|
|
958
|
+
this.pactEstablishedCache.set(cacheKey, Date.now());
|
|
959
|
+
}
|
|
960
|
+
async maybeRequestPact(currentRequest) {
|
|
961
|
+
var _a;
|
|
962
|
+
if (!this.config.seekGroupedPermission) {
|
|
963
|
+
return null;
|
|
964
|
+
}
|
|
965
|
+
if (!this.hasCounterpartyPermissionRequestedHandlers()) {
|
|
966
|
+
return null;
|
|
967
|
+
}
|
|
968
|
+
if (currentRequest.type !== 'protocol') {
|
|
969
|
+
return null;
|
|
970
|
+
}
|
|
971
|
+
if (currentRequest.privileged) {
|
|
972
|
+
return null;
|
|
973
|
+
}
|
|
974
|
+
const [level] = currentRequest.protocolID;
|
|
975
|
+
if (level !== 2) {
|
|
976
|
+
return null;
|
|
977
|
+
}
|
|
978
|
+
const originator = currentRequest.originator;
|
|
979
|
+
const counterparty = currentRequest.counterparty;
|
|
980
|
+
if (!counterparty || counterparty === 'self' || counterparty === 'anyone') {
|
|
981
|
+
return null;
|
|
982
|
+
}
|
|
983
|
+
if (!/^[0-9a-fA-F]{66}$/.test(counterparty)) {
|
|
984
|
+
return null;
|
|
985
|
+
}
|
|
986
|
+
if (await this.hasPactEstablished(originator, counterparty)) {
|
|
987
|
+
return null;
|
|
988
|
+
}
|
|
989
|
+
const { counterpartyPermissions } = await this.fetchManifestPermissions(originator);
|
|
990
|
+
if (!((_a = counterpartyPermissions === null || counterpartyPermissions === void 0 ? void 0 : counterpartyPermissions.protocols) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
991
|
+
return null;
|
|
992
|
+
}
|
|
993
|
+
const protocolsToRequest = [];
|
|
994
|
+
for (const p of counterpartyPermissions.protocols) {
|
|
995
|
+
const hasPerm = await this.hasProtocolPermission({
|
|
996
|
+
originator,
|
|
997
|
+
privileged: false,
|
|
998
|
+
protocolID: p.protocolID,
|
|
999
|
+
counterparty
|
|
1000
|
+
});
|
|
1001
|
+
if (!hasPerm) {
|
|
1002
|
+
protocolsToRequest.push(p);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
if (protocolsToRequest.length === 0) {
|
|
1006
|
+
this.markPactEstablished(originator, counterparty);
|
|
1007
|
+
return null;
|
|
1008
|
+
}
|
|
1009
|
+
const permissionsToRequest = {
|
|
1010
|
+
description: counterpartyPermissions.description,
|
|
1011
|
+
protocols: protocolsToRequest
|
|
1012
|
+
};
|
|
1013
|
+
const key = `pact:${originator}:${counterparty}`;
|
|
1014
|
+
const existing = this.activeRequests.get(key);
|
|
1015
|
+
if (existing) {
|
|
1016
|
+
const existingRequest = existing.request;
|
|
1017
|
+
for (const p of permissionsToRequest.protocols) {
|
|
1018
|
+
if (!existingRequest.permissions.protocols.find(x => deepEqual(x, p))) {
|
|
1019
|
+
existingRequest.permissions.protocols.push(p);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
await new Promise((resolve, reject) => {
|
|
1023
|
+
existing.pending.push({ resolve, reject });
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
else {
|
|
1027
|
+
await new Promise(async (resolve, reject) => {
|
|
1028
|
+
this.activeRequests.set(key, {
|
|
1029
|
+
request: {
|
|
1030
|
+
originator,
|
|
1031
|
+
counterparty,
|
|
1032
|
+
permissions: permissionsToRequest,
|
|
1033
|
+
displayOriginator: currentRequest.displayOriginator
|
|
1034
|
+
},
|
|
1035
|
+
pending: [{ resolve, reject }]
|
|
1036
|
+
});
|
|
1037
|
+
await this.callEvent('onCounterpartyPermissionRequested', {
|
|
1038
|
+
requestID: key,
|
|
1039
|
+
originator,
|
|
1040
|
+
counterparty,
|
|
1041
|
+
permissions: permissionsToRequest
|
|
1042
|
+
});
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
this.markPactEstablished(originator, counterparty);
|
|
1046
|
+
const satisfied = await this.hasProtocolPermission({
|
|
1047
|
+
originator,
|
|
1048
|
+
privileged: false,
|
|
1049
|
+
protocolID: currentRequest.protocolID,
|
|
1050
|
+
counterparty
|
|
1051
|
+
});
|
|
1052
|
+
return satisfied ? true : null;
|
|
1053
|
+
}
|
|
1054
|
+
async maybeRequestPeerGroupedLevel2ProtocolPermissions(currentRequest) {
|
|
1055
|
+
var _a, _b;
|
|
1056
|
+
if (!this.config.seekGroupedPermission) {
|
|
1057
|
+
return null;
|
|
1058
|
+
}
|
|
1059
|
+
if (!this.hasGroupedPermissionRequestedHandlers()) {
|
|
1060
|
+
return null;
|
|
1061
|
+
}
|
|
1062
|
+
if (currentRequest.type !== 'protocol') {
|
|
1063
|
+
return null;
|
|
1064
|
+
}
|
|
1065
|
+
const [level] = currentRequest.protocolID;
|
|
1066
|
+
if (level !== 2) {
|
|
1067
|
+
return null;
|
|
1068
|
+
}
|
|
1069
|
+
const originator = currentRequest.originator;
|
|
1070
|
+
const privileged = (_a = currentRequest.privileged) !== null && _a !== void 0 ? _a : false;
|
|
1071
|
+
const counterparty = (_b = currentRequest.counterparty) !== null && _b !== void 0 ? _b : 'self';
|
|
1072
|
+
const groupPermissions = await this.fetchManifestGroupPermissions(originator);
|
|
1073
|
+
if (!groupPermissions) {
|
|
1074
|
+
return null;
|
|
1075
|
+
}
|
|
1076
|
+
const normalizeManifestCounterparty = (cp) => {
|
|
1077
|
+
if (cp === '')
|
|
1078
|
+
return counterparty;
|
|
1079
|
+
return cp !== null && cp !== void 0 ? cp : 'self';
|
|
1080
|
+
};
|
|
1081
|
+
const manifestLevel2ForThisPeer = (groupPermissions.protocolPermissions || [])
|
|
1082
|
+
.filter(p => { var _a, _b; return ((_b = (_a = p.protocolID) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 0) === 2; })
|
|
1083
|
+
.map(p => ({
|
|
1084
|
+
protocolID: p.protocolID,
|
|
1085
|
+
counterparty: normalizeManifestCounterparty(p.counterparty),
|
|
1086
|
+
description: p.description
|
|
1087
|
+
}))
|
|
1088
|
+
.filter(p => p.counterparty === counterparty);
|
|
1089
|
+
const isCurrentRequestInManifest = manifestLevel2ForThisPeer.some(p => deepEqual(p.protocolID, currentRequest.protocolID));
|
|
1090
|
+
if (!isCurrentRequestInManifest) {
|
|
1091
|
+
return null;
|
|
1092
|
+
}
|
|
1093
|
+
const permissionsToRequest = {
|
|
1094
|
+
protocolPermissions: []
|
|
1095
|
+
};
|
|
1096
|
+
for (const p of manifestLevel2ForThisPeer) {
|
|
1097
|
+
const hasPerm = await this.hasProtocolPermission({
|
|
1098
|
+
originator,
|
|
1099
|
+
privileged,
|
|
1100
|
+
protocolID: p.protocolID,
|
|
1101
|
+
counterparty: p.counterparty
|
|
1102
|
+
});
|
|
1103
|
+
if (!hasPerm) {
|
|
1104
|
+
permissionsToRequest.protocolPermissions.push({
|
|
1105
|
+
protocolID: p.protocolID,
|
|
1106
|
+
counterparty: p.counterparty,
|
|
1107
|
+
description: p.description
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
if (!this.hasAnyPermissionsToRequest(permissionsToRequest)) {
|
|
1112
|
+
return null;
|
|
1113
|
+
}
|
|
1114
|
+
const key = `group-peer:${originator}:${privileged}:${counterparty}`;
|
|
1115
|
+
const existing = this.activeRequests.get(key);
|
|
1116
|
+
if (existing) {
|
|
1117
|
+
const existingRequest = existing.request;
|
|
1118
|
+
if (!existingRequest.permissions.protocolPermissions) {
|
|
1119
|
+
existingRequest.permissions.protocolPermissions = [];
|
|
1120
|
+
}
|
|
1121
|
+
for (const p of permissionsToRequest.protocolPermissions || []) {
|
|
1122
|
+
if (!existingRequest.permissions.protocolPermissions.find(x => deepEqual(x, p))) {
|
|
1123
|
+
existingRequest.permissions.protocolPermissions.push(p);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
await new Promise((resolve, reject) => {
|
|
1127
|
+
existing.pending.push({ resolve, reject });
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
else {
|
|
1131
|
+
await new Promise(async (resolve, reject) => {
|
|
1132
|
+
const permissions = permissionsToRequest;
|
|
1133
|
+
this.activeRequests.set(key, {
|
|
1134
|
+
request: {
|
|
1135
|
+
originator,
|
|
1136
|
+
permissions,
|
|
1137
|
+
displayOriginator: currentRequest.displayOriginator
|
|
1138
|
+
},
|
|
1139
|
+
pending: [{ resolve, reject }]
|
|
1140
|
+
});
|
|
1141
|
+
await this.callEvent('onGroupedPermissionRequested', {
|
|
1142
|
+
requestID: key,
|
|
1143
|
+
originator,
|
|
1144
|
+
permissions
|
|
1145
|
+
});
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
const satisfied = await this.checkSpecificPermissionAfterGroupFlow(currentRequest);
|
|
1149
|
+
return satisfied ? true : null;
|
|
1150
|
+
}
|
|
1151
|
+
async checkSpecificPermissionAfterGroupFlow(request) {
|
|
1152
|
+
var _a, _b, _c;
|
|
1153
|
+
switch (request.type) {
|
|
1154
|
+
case 'protocol':
|
|
1155
|
+
return await this.hasProtocolPermission({
|
|
1156
|
+
originator: request.originator,
|
|
1157
|
+
privileged: (_a = request.privileged) !== null && _a !== void 0 ? _a : false,
|
|
1158
|
+
protocolID: request.protocolID,
|
|
1159
|
+
counterparty: (_b = request.counterparty) !== null && _b !== void 0 ? _b : 'self'
|
|
1160
|
+
});
|
|
1161
|
+
case 'basket':
|
|
1162
|
+
return await this.hasBasketAccess({
|
|
1163
|
+
originator: request.originator,
|
|
1164
|
+
basket: request.basket
|
|
1165
|
+
});
|
|
1166
|
+
case 'certificate':
|
|
1167
|
+
return await this.hasCertificateAccess({
|
|
1168
|
+
originator: request.originator,
|
|
1169
|
+
privileged: (_c = request.privileged) !== null && _c !== void 0 ? _c : false,
|
|
1170
|
+
verifier: request.certificate.verifier,
|
|
1171
|
+
certType: request.certificate.certType,
|
|
1172
|
+
fields: request.certificate.fields
|
|
1173
|
+
});
|
|
1174
|
+
case 'spending':
|
|
1175
|
+
return await this.hasSpendingAuthorization({
|
|
1176
|
+
originator: request.originator,
|
|
1177
|
+
satoshis: request.spending.satoshis
|
|
1178
|
+
});
|
|
1179
|
+
default:
|
|
1180
|
+
return false;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
isRequestIncludedInGroupPermissions(request, groupPermissions) {
|
|
1184
|
+
var _a, _b, _c, _d;
|
|
1185
|
+
switch (request.type) {
|
|
1186
|
+
case 'protocol': {
|
|
1187
|
+
if (request.privileged)
|
|
1188
|
+
return false;
|
|
1189
|
+
const pid = request.protocolID;
|
|
1190
|
+
if (!pid)
|
|
1191
|
+
return false;
|
|
1192
|
+
const cp = (_a = request.counterparty) !== null && _a !== void 0 ? _a : 'self';
|
|
1193
|
+
return !!((_b = groupPermissions.protocolPermissions) === null || _b === void 0 ? void 0 : _b.some(p => {
|
|
1194
|
+
var _a;
|
|
1195
|
+
const manifestCp = p.counterparty === '' ? cp : ((_a = p.counterparty) !== null && _a !== void 0 ? _a : 'self');
|
|
1196
|
+
return deepEqual(p.protocolID, pid) && manifestCp === cp;
|
|
1197
|
+
}));
|
|
1198
|
+
}
|
|
1199
|
+
case 'basket': {
|
|
1200
|
+
const basket = request.basket;
|
|
1201
|
+
if (!basket)
|
|
1202
|
+
return false;
|
|
1203
|
+
return !!((_c = groupPermissions.basketAccess) === null || _c === void 0 ? void 0 : _c.some(b => b.basket === basket));
|
|
1204
|
+
}
|
|
1205
|
+
case 'certificate': {
|
|
1206
|
+
if (request.privileged)
|
|
1207
|
+
return false;
|
|
1208
|
+
const cert = request.certificate;
|
|
1209
|
+
if (!cert)
|
|
1210
|
+
return false;
|
|
1211
|
+
return !!((_d = groupPermissions.certificateAccess) === null || _d === void 0 ? void 0 : _d.some(c => {
|
|
1212
|
+
const fieldsA = new Set(c.fields || []);
|
|
1213
|
+
const fieldsB = new Set(cert.fields || []);
|
|
1214
|
+
if (fieldsA.size !== fieldsB.size)
|
|
1215
|
+
return false;
|
|
1216
|
+
for (const f of fieldsA)
|
|
1217
|
+
if (!fieldsB.has(f))
|
|
1218
|
+
return false;
|
|
1219
|
+
return c.type === cert.certType && c.verifierPublicKey === cert.verifier;
|
|
1220
|
+
}));
|
|
1221
|
+
}
|
|
1222
|
+
case 'spending':
|
|
1223
|
+
return !!groupPermissions.spendingAuthorization;
|
|
1224
|
+
default:
|
|
1225
|
+
return false;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
async maybeRequestGroupedPermissions(currentRequest) {
|
|
1229
|
+
if (!this.config.seekGroupedPermission) {
|
|
1230
|
+
return null;
|
|
1231
|
+
}
|
|
1232
|
+
const originator = currentRequest.originator;
|
|
1233
|
+
const groupPermissions = await this.fetchManifestGroupPermissions(originator);
|
|
1234
|
+
if (!groupPermissions) {
|
|
1235
|
+
return null;
|
|
1236
|
+
}
|
|
1237
|
+
if (!this.isRequestIncludedInGroupPermissions(currentRequest, groupPermissions)) {
|
|
1238
|
+
return null;
|
|
1239
|
+
}
|
|
1240
|
+
const permissionsToRequest = await this.filterAlreadyGrantedPermissions(originator, groupPermissions);
|
|
1241
|
+
if (!this.hasAnyPermissionsToRequest(permissionsToRequest)) {
|
|
1242
|
+
return null;
|
|
1243
|
+
}
|
|
1244
|
+
const key = `group:${originator}`;
|
|
1245
|
+
if (this.activeRequests.has(key)) {
|
|
1246
|
+
await new Promise((resolve, reject) => {
|
|
1247
|
+
this.activeRequests.get(key).pending.push({ resolve, reject });
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
else {
|
|
1251
|
+
await new Promise(async (resolve, reject) => {
|
|
1252
|
+
this.activeRequests.set(key, {
|
|
1253
|
+
request: {
|
|
1254
|
+
originator,
|
|
1255
|
+
permissions: permissionsToRequest,
|
|
1256
|
+
displayOriginator: currentRequest.displayOriginator
|
|
1257
|
+
},
|
|
1258
|
+
pending: [{ resolve, reject }]
|
|
1259
|
+
});
|
|
1260
|
+
await this.callEvent('onGroupedPermissionRequested', {
|
|
1261
|
+
requestID: key,
|
|
1262
|
+
originator,
|
|
1263
|
+
permissions: permissionsToRequest
|
|
1264
|
+
});
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
const satisfied = await this.checkSpecificPermissionAfterGroupFlow(currentRequest);
|
|
1268
|
+
return satisfied ? true : null;
|
|
1269
|
+
}
|
|
731
1270
|
/**
|
|
732
1271
|
* A central method that triggers the permission request flow.
|
|
733
1272
|
* - It checks if there's already an active request for the same key
|
|
@@ -736,13 +1275,25 @@ class WalletPermissionsManager {
|
|
|
736
1275
|
* and return a promise that resolves once permission is granted or rejects if denied.
|
|
737
1276
|
*/
|
|
738
1277
|
async requestPermissionFlow(r) {
|
|
739
|
-
var _a;
|
|
1278
|
+
var _a, _b, _c;
|
|
740
1279
|
const normalizedOriginator = this.normalizeOriginator(r.originator) || r.originator;
|
|
741
1280
|
const preparedRequest = {
|
|
742
1281
|
...r,
|
|
743
1282
|
originator: normalizedOriginator,
|
|
744
|
-
displayOriginator: (_a = r.displayOriginator) !== null && _a !== void 0 ? _a : r.originator
|
|
1283
|
+
displayOriginator: (_c = (_a = r.displayOriginator) !== null && _a !== void 0 ? _a : (_b = r.previousToken) === null || _b === void 0 ? void 0 : _b.rawOriginator) !== null && _c !== void 0 ? _c : r.originator
|
|
745
1284
|
};
|
|
1285
|
+
const pactResult = await this.maybeRequestPact(preparedRequest);
|
|
1286
|
+
if (pactResult !== null) {
|
|
1287
|
+
return pactResult;
|
|
1288
|
+
}
|
|
1289
|
+
const peerGroupResult = await this.maybeRequestPeerGroupedLevel2ProtocolPermissions(preparedRequest);
|
|
1290
|
+
if (peerGroupResult !== null) {
|
|
1291
|
+
return peerGroupResult;
|
|
1292
|
+
}
|
|
1293
|
+
const groupResult = await this.maybeRequestGroupedPermissions(preparedRequest);
|
|
1294
|
+
if (groupResult !== null) {
|
|
1295
|
+
return groupResult;
|
|
1296
|
+
}
|
|
746
1297
|
const key = this.buildRequestKey(preparedRequest);
|
|
747
1298
|
// If there's already a queue for the same resource, we piggyback on it
|
|
748
1299
|
const existingQueue = this.activeRequests.get(key);
|
|
@@ -1196,10 +1747,128 @@ class WalletPermissionsManager {
|
|
|
1196
1747
|
}
|
|
1197
1748
|
],
|
|
1198
1749
|
options: {
|
|
1199
|
-
acceptDelayedBroadcast:
|
|
1750
|
+
acceptDelayedBroadcast: true
|
|
1200
1751
|
}
|
|
1201
1752
|
}, this.adminOriginator);
|
|
1202
1753
|
}
|
|
1754
|
+
async mapWithConcurrency(items, concurrency, fn) {
|
|
1755
|
+
if (!items.length)
|
|
1756
|
+
return [];
|
|
1757
|
+
const results = new Array(items.length);
|
|
1758
|
+
let i = 0;
|
|
1759
|
+
const worker = async () => {
|
|
1760
|
+
while (true) {
|
|
1761
|
+
const idx = i++;
|
|
1762
|
+
if (idx >= items.length)
|
|
1763
|
+
return;
|
|
1764
|
+
results[idx] = await fn(items[idx]);
|
|
1765
|
+
}
|
|
1766
|
+
};
|
|
1767
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()));
|
|
1768
|
+
return results;
|
|
1769
|
+
}
|
|
1770
|
+
async runBestEffortBatches(items, chunkSize, runChunk) {
|
|
1771
|
+
if (!items.length)
|
|
1772
|
+
return [];
|
|
1773
|
+
const out = [];
|
|
1774
|
+
for (let i = 0; i < items.length; i += chunkSize) {
|
|
1775
|
+
const chunk = items.slice(i, i + chunkSize);
|
|
1776
|
+
out.push(...(await this.runBestEffortChunk(chunk, runChunk)));
|
|
1777
|
+
}
|
|
1778
|
+
return out;
|
|
1779
|
+
}
|
|
1780
|
+
async runBestEffortChunk(chunk, runChunk) {
|
|
1781
|
+
try {
|
|
1782
|
+
return await runChunk(chunk);
|
|
1783
|
+
}
|
|
1784
|
+
catch (e) {
|
|
1785
|
+
if (chunk.length <= 1) {
|
|
1786
|
+
console.error('Permission batch failed:', e);
|
|
1787
|
+
return [];
|
|
1788
|
+
}
|
|
1789
|
+
const mid = Math.ceil(chunk.length / 2);
|
|
1790
|
+
const left = await this.runBestEffortChunk(chunk.slice(0, mid), runChunk);
|
|
1791
|
+
const right = await this.runBestEffortChunk(chunk.slice(mid), runChunk);
|
|
1792
|
+
return [...left, ...right];
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
async buildPermissionOutput(r, expiry, amount) {
|
|
1796
|
+
const normalizedOriginator = this.normalizeOriginator(r.originator) || r.originator;
|
|
1797
|
+
r.originator = normalizedOriginator;
|
|
1798
|
+
const basketName = BASKET_MAP[r.type];
|
|
1799
|
+
if (!basketName) {
|
|
1800
|
+
throw new Error(`Unsupported permission type: ${r.type}`);
|
|
1801
|
+
}
|
|
1802
|
+
const fields = await this.buildPushdropFields(r, expiry, amount);
|
|
1803
|
+
const script = await new sdk_1.PushDrop(this.underlying).lock(fields, WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL, '1', 'self', true, true);
|
|
1804
|
+
const tags = this.buildTagsForRequest(r);
|
|
1805
|
+
return {
|
|
1806
|
+
request: r,
|
|
1807
|
+
output: {
|
|
1808
|
+
lockingScript: script.toHex(),
|
|
1809
|
+
satoshis: 1,
|
|
1810
|
+
outputDescription: `${r.type} permission token`,
|
|
1811
|
+
basket: basketName,
|
|
1812
|
+
tags
|
|
1813
|
+
}
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1816
|
+
async createPermissionTokensBestEffort(items) {
|
|
1817
|
+
const CHUNK = 25;
|
|
1818
|
+
return this.runBestEffortBatches(items, CHUNK, async (chunk) => {
|
|
1819
|
+
const built = await this.mapWithConcurrency(chunk, 8, c => this.buildPermissionOutput(c.request, c.expiry, c.amount));
|
|
1820
|
+
await this.createAction({
|
|
1821
|
+
description: `Grant ${built.length} permissions`,
|
|
1822
|
+
outputs: built.map(b => b.output),
|
|
1823
|
+
options: { acceptDelayedBroadcast: true }
|
|
1824
|
+
}, this.adminOriginator);
|
|
1825
|
+
return built.map(b => b.request);
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
async renewPermissionTokensBestEffort(items) {
|
|
1829
|
+
const CHUNK = 15;
|
|
1830
|
+
return this.runBestEffortBatches(items, CHUNK, async (chunk) => {
|
|
1831
|
+
const built = await this.mapWithConcurrency(chunk, 8, c => this.buildPermissionOutput(c.request, c.expiry, c.amount));
|
|
1832
|
+
const inputBeef = new sdk_1.Beef();
|
|
1833
|
+
for (const c of chunk) {
|
|
1834
|
+
inputBeef.mergeBeef(sdk_1.Beef.fromBinary(c.oldToken.tx));
|
|
1835
|
+
}
|
|
1836
|
+
const { signableTransaction } = await this.createAction({
|
|
1837
|
+
description: `Renew ${chunk.length} permissions`,
|
|
1838
|
+
inputBEEF: inputBeef.toBinary(),
|
|
1839
|
+
inputs: chunk.map((c, i) => ({
|
|
1840
|
+
outpoint: `${c.oldToken.txid}.${c.oldToken.outputIndex}`,
|
|
1841
|
+
unlockingScriptLength: 73,
|
|
1842
|
+
inputDescription: `Consume old permission token #${i + 1}`
|
|
1843
|
+
})),
|
|
1844
|
+
outputs: built.map(b => b.output),
|
|
1845
|
+
options: {
|
|
1846
|
+
acceptDelayedBroadcast: true,
|
|
1847
|
+
randomizeOutputs: false,
|
|
1848
|
+
signAndProcess: false
|
|
1849
|
+
}
|
|
1850
|
+
}, this.adminOriginator);
|
|
1851
|
+
if (!(signableTransaction === null || signableTransaction === void 0 ? void 0 : signableTransaction.reference) || !signableTransaction.tx) {
|
|
1852
|
+
throw new Error('Failed to create signable transaction');
|
|
1853
|
+
}
|
|
1854
|
+
const partialTx = sdk_1.Transaction.fromAtomicBEEF(signableTransaction.tx);
|
|
1855
|
+
const pushdrop = new sdk_1.PushDrop(this.underlying);
|
|
1856
|
+
const spends = {};
|
|
1857
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
1858
|
+
const token = chunk[i].oldToken;
|
|
1859
|
+
const unlocker = pushdrop.unlock(WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL, '1', 'self', 'all', false, 1, sdk_1.LockingScript.fromHex(token.outputScript));
|
|
1860
|
+
const unlockingScript = await unlocker.sign(partialTx, i);
|
|
1861
|
+
spends[i] = { unlockingScript: unlockingScript.toHex() };
|
|
1862
|
+
}
|
|
1863
|
+
const { txid } = await this.underlying.signAction({
|
|
1864
|
+
reference: signableTransaction.reference,
|
|
1865
|
+
spends
|
|
1866
|
+
});
|
|
1867
|
+
if (!txid)
|
|
1868
|
+
throw new Error('Failed to finalize renewal transaction');
|
|
1869
|
+
return built.map(b => b.request);
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1203
1872
|
async coalescePermissionTokens(oldTokens, newScript, opts) {
|
|
1204
1873
|
var _a;
|
|
1205
1874
|
if (!(oldTokens === null || oldTokens === void 0 ? void 0 : oldTokens.length))
|
|
@@ -1230,7 +1899,7 @@ class WalletPermissionsManager {
|
|
|
1230
1899
|
}
|
|
1231
1900
|
],
|
|
1232
1901
|
options: {
|
|
1233
|
-
acceptDelayedBroadcast:
|
|
1902
|
+
acceptDelayedBroadcast: true,
|
|
1234
1903
|
randomizeOutputs: false,
|
|
1235
1904
|
signAndProcess: false
|
|
1236
1905
|
}
|
|
@@ -1309,7 +1978,7 @@ class WalletPermissionsManager {
|
|
|
1309
1978
|
}
|
|
1310
1979
|
],
|
|
1311
1980
|
options: {
|
|
1312
|
-
acceptDelayedBroadcast:
|
|
1981
|
+
acceptDelayedBroadcast: true
|
|
1313
1982
|
}
|
|
1314
1983
|
}, this.adminOriginator);
|
|
1315
1984
|
const tx = sdk_1.Transaction.fromBEEF(signableTransaction.tx);
|
|
@@ -1445,7 +2114,7 @@ class WalletPermissionsManager {
|
|
|
1445
2114
|
tags,
|
|
1446
2115
|
tagQueryMode: 'all',
|
|
1447
2116
|
include: 'entire transactions',
|
|
1448
|
-
limit:
|
|
2117
|
+
limit: 10000
|
|
1449
2118
|
}, this.adminOriginator);
|
|
1450
2119
|
for (const out of result.outputs) {
|
|
1451
2120
|
if (seen.has(out.outpoint))
|
|
@@ -1750,16 +2419,60 @@ class WalletPermissionsManager {
|
|
|
1750
2419
|
}
|
|
1751
2420
|
],
|
|
1752
2421
|
options: {
|
|
1753
|
-
acceptDelayedBroadcast:
|
|
2422
|
+
acceptDelayedBroadcast: true
|
|
1754
2423
|
}
|
|
1755
2424
|
}, this.adminOriginator);
|
|
1756
2425
|
const tx = sdk_1.Transaction.fromBEEF(signableTransaction.tx);
|
|
2426
|
+
const normalizeTxid = (txid) => (txid !== null && txid !== void 0 ? txid : '').toLowerCase();
|
|
2427
|
+
const reverseHexTxid = (txid) => {
|
|
2428
|
+
const hex = normalizeTxid(txid);
|
|
2429
|
+
if (!/^[0-9a-f]{64}$/.test(hex))
|
|
2430
|
+
return hex;
|
|
2431
|
+
const bytes = hex.match(/../g);
|
|
2432
|
+
return bytes ? bytes.reverse().join('') : hex;
|
|
2433
|
+
};
|
|
2434
|
+
const matchesOutpointString = (outpoint) => {
|
|
2435
|
+
const dot = outpoint.lastIndexOf('.');
|
|
2436
|
+
const colon = outpoint.lastIndexOf(':');
|
|
2437
|
+
const sep = dot > colon ? dot : colon;
|
|
2438
|
+
if (sep === -1)
|
|
2439
|
+
return false;
|
|
2440
|
+
const txidPart = outpoint.slice(0, sep);
|
|
2441
|
+
const indexPart = outpoint.slice(sep + 1);
|
|
2442
|
+
const vout = Number(indexPart);
|
|
2443
|
+
if (!Number.isFinite(vout))
|
|
2444
|
+
return false;
|
|
2445
|
+
return normalizeTxid(txidPart) === normalizeTxid(oldToken.txid) && vout === oldToken.outputIndex;
|
|
2446
|
+
};
|
|
2447
|
+
let permInputIndex = tx.inputs.findIndex((input) => {
|
|
2448
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
2449
|
+
const txidCandidate = (_g = (_f = (_e = (_d = (_c = (_b = (_a = input === null || input === void 0 ? void 0 : input.sourceTXID) !== null && _a !== void 0 ? _a : input === null || input === void 0 ? void 0 : input.sourceTxid) !== null && _b !== void 0 ? _b : input === null || input === void 0 ? void 0 : input.sourceTxId) !== null && _c !== void 0 ? _c : input === null || input === void 0 ? void 0 : input.prevTxId) !== null && _d !== void 0 ? _d : input === null || input === void 0 ? void 0 : input.prevTxid) !== null && _e !== void 0 ? _e : input === null || input === void 0 ? void 0 : input.prevTXID) !== null && _f !== void 0 ? _f : input === null || input === void 0 ? void 0 : input.txid) !== null && _g !== void 0 ? _g : input === null || input === void 0 ? void 0 : input.txID;
|
|
2450
|
+
const voutCandidate = (_l = (_k = (_j = (_h = input === null || input === void 0 ? void 0 : input.sourceOutputIndex) !== null && _h !== void 0 ? _h : input === null || input === void 0 ? void 0 : input.sourceOutput) !== null && _j !== void 0 ? _j : input === null || input === void 0 ? void 0 : input.outputIndex) !== null && _k !== void 0 ? _k : input === null || input === void 0 ? void 0 : input.vout) !== null && _l !== void 0 ? _l : input === null || input === void 0 ? void 0 : input.prevOutIndex;
|
|
2451
|
+
if (typeof txidCandidate === 'string' && typeof voutCandidate === 'number') {
|
|
2452
|
+
const cand = normalizeTxid(txidCandidate);
|
|
2453
|
+
const target = normalizeTxid(oldToken.txid);
|
|
2454
|
+
if (cand === target && voutCandidate === oldToken.outputIndex)
|
|
2455
|
+
return true;
|
|
2456
|
+
if (cand === reverseHexTxid(oldToken.txid) && voutCandidate === oldToken.outputIndex)
|
|
2457
|
+
return true;
|
|
2458
|
+
}
|
|
2459
|
+
const outpointCandidate = (_o = (_m = input === null || input === void 0 ? void 0 : input.outpoint) !== null && _m !== void 0 ? _m : input === null || input === void 0 ? void 0 : input.sourceOutpoint) !== null && _o !== void 0 ? _o : input === null || input === void 0 ? void 0 : input.prevOutpoint;
|
|
2460
|
+
if (typeof outpointCandidate === 'string' && matchesOutpointString(outpointCandidate))
|
|
2461
|
+
return true;
|
|
2462
|
+
return false;
|
|
2463
|
+
});
|
|
2464
|
+
if (permInputIndex === -1 && tx.inputs.length === 1) {
|
|
2465
|
+
permInputIndex = 0;
|
|
2466
|
+
}
|
|
2467
|
+
if (permInputIndex === -1) {
|
|
2468
|
+
throw new Error('Unable to locate permission token input for revocation.');
|
|
2469
|
+
}
|
|
1757
2470
|
const unlocker = new sdk_1.PushDrop(this.underlying).unlock(WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL, '1', 'self', 'all', false, 1, sdk_1.LockingScript.fromHex(oldToken.outputScript));
|
|
1758
|
-
const unlockingScript = await unlocker.sign(tx,
|
|
2471
|
+
const unlockingScript = await unlocker.sign(tx, permInputIndex);
|
|
1759
2472
|
await this.underlying.signAction({
|
|
1760
2473
|
reference: signableTransaction.reference,
|
|
1761
2474
|
spends: {
|
|
1762
|
-
|
|
2475
|
+
[permInputIndex]: {
|
|
1763
2476
|
unlockingScript: unlockingScript.toHex()
|
|
1764
2477
|
}
|
|
1765
2478
|
}
|
|
@@ -2358,80 +3071,17 @@ class WalletPermissionsManager {
|
|
|
2358
3071
|
return this.underlying.isAuthenticated(...args);
|
|
2359
3072
|
}
|
|
2360
3073
|
async waitForAuthentication(...args) {
|
|
2361
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
2362
3074
|
let [_, originator] = args;
|
|
2363
3075
|
if (this.config.seekGroupedPermission && originator) {
|
|
2364
3076
|
const { normalized: normalizedOriginator } = this.prepareOriginator(originator);
|
|
2365
3077
|
originator = normalizedOriginator;
|
|
2366
3078
|
// 1. Fetch manifest.json from the originator
|
|
2367
|
-
|
|
2368
|
-
try {
|
|
2369
|
-
const proto = originator.startsWith('localhost:') ? 'http' : 'https';
|
|
2370
|
-
const response = await fetch(`${proto}://${originator}/manifest.json`);
|
|
2371
|
-
if (response.ok) {
|
|
2372
|
-
const manifest = await response.json();
|
|
2373
|
-
if ((_a = manifest === null || manifest === void 0 ? void 0 : manifest.babbage) === null || _a === void 0 ? void 0 : _a.groupPermissions) {
|
|
2374
|
-
groupPermissions = manifest.babbage.groupPermissions;
|
|
2375
|
-
}
|
|
2376
|
-
}
|
|
2377
|
-
}
|
|
2378
|
-
catch (e) {
|
|
2379
|
-
// Ignore fetch/parse errors, just proceed without group permissions.
|
|
2380
|
-
}
|
|
3079
|
+
const groupPermissions = await this.fetchManifestGroupPermissions(originator);
|
|
2381
3080
|
if (groupPermissions) {
|
|
2382
3081
|
// 2. Filter out already-granted permissions
|
|
2383
|
-
const permissionsToRequest =
|
|
2384
|
-
protocolPermissions: [],
|
|
2385
|
-
basketAccess: [],
|
|
2386
|
-
certificateAccess: []
|
|
2387
|
-
};
|
|
2388
|
-
if (groupPermissions.spendingAuthorization) {
|
|
2389
|
-
const hasAuth = await this.hasSpendingAuthorization({
|
|
2390
|
-
originator,
|
|
2391
|
-
satoshis: groupPermissions.spendingAuthorization.amount
|
|
2392
|
-
});
|
|
2393
|
-
if (!hasAuth) {
|
|
2394
|
-
permissionsToRequest.spendingAuthorization = groupPermissions.spendingAuthorization;
|
|
2395
|
-
}
|
|
2396
|
-
}
|
|
2397
|
-
for (const p of groupPermissions.protocolPermissions || []) {
|
|
2398
|
-
const hasPerm = await this.hasProtocolPermission({
|
|
2399
|
-
originator,
|
|
2400
|
-
privileged: false, // Privilege is never allowed here
|
|
2401
|
-
protocolID: p.protocolID,
|
|
2402
|
-
counterparty: p.counterparty || 'self'
|
|
2403
|
-
});
|
|
2404
|
-
if (!hasPerm) {
|
|
2405
|
-
permissionsToRequest.protocolPermissions.push(p);
|
|
2406
|
-
}
|
|
2407
|
-
}
|
|
2408
|
-
for (const b of groupPermissions.basketAccess || []) {
|
|
2409
|
-
const hasAccess = await this.hasBasketAccess({
|
|
2410
|
-
originator,
|
|
2411
|
-
basket: b.basket
|
|
2412
|
-
});
|
|
2413
|
-
if (!hasAccess) {
|
|
2414
|
-
permissionsToRequest.basketAccess.push(b);
|
|
2415
|
-
}
|
|
2416
|
-
}
|
|
2417
|
-
for (const c of groupPermissions.certificateAccess || []) {
|
|
2418
|
-
const hasAccess = await this.hasCertificateAccess({
|
|
2419
|
-
originator,
|
|
2420
|
-
privileged: false, // Privilege is never allowed here for security
|
|
2421
|
-
verifier: c.verifierPublicKey,
|
|
2422
|
-
certType: c.type,
|
|
2423
|
-
fields: c.fields
|
|
2424
|
-
});
|
|
2425
|
-
if (!hasAccess) {
|
|
2426
|
-
permissionsToRequest.certificateAccess.push(c);
|
|
2427
|
-
}
|
|
2428
|
-
}
|
|
3082
|
+
const permissionsToRequest = await this.filterAlreadyGrantedPermissions(originator, groupPermissions);
|
|
2429
3083
|
// 3. If any permissions are left to request, start the flow
|
|
2430
|
-
|
|
2431
|
-
((_c = (_b = permissionsToRequest.protocolPermissions) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0) > 0 ||
|
|
2432
|
-
((_e = (_d = permissionsToRequest.basketAccess) === null || _d === void 0 ? void 0 : _d.length) !== null && _e !== void 0 ? _e : 0) > 0 ||
|
|
2433
|
-
((_g = (_f = permissionsToRequest.certificateAccess) === null || _f === void 0 ? void 0 : _f.length) !== null && _g !== void 0 ? _g : 0) > 0;
|
|
2434
|
-
if (hasRequests) {
|
|
3084
|
+
if (this.hasAnyPermissionsToRequest(permissionsToRequest)) {
|
|
2435
3085
|
const key = `group:${originator}`;
|
|
2436
3086
|
if (this.activeRequests.has(key)) {
|
|
2437
3087
|
// Another call is already waiting, piggyback on it
|
|
@@ -2646,6 +3296,7 @@ class WalletPermissionsManager {
|
|
|
2646
3296
|
}
|
|
2647
3297
|
}
|
|
2648
3298
|
exports.WalletPermissionsManager = WalletPermissionsManager;
|
|
3299
|
+
WalletPermissionsManager.MANIFEST_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
2649
3300
|
/** How long a cached permission remains valid (5 minutes). */
|
|
2650
3301
|
WalletPermissionsManager.CACHE_TTL_MS = 5 * 60 * 1000;
|
|
2651
3302
|
/** Window during which freshly granted permissions are auto-allowed (except spending). */
|