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