@bsv/wallet-toolbox-client 1.7.22 → 1.8.1
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 +27 -0
- package/out/src/WalletPermissionsManager.d.ts.map +1 -1
- package/out/src/WalletPermissionsManager.js +308 -147
- package/out/src/WalletPermissionsManager.js.map +1 -1
- package/out/src/sdk/WalletServices.interfaces.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageBase.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageBase.js +4 -1
- package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageBase.js.map +1 -1
- package/out/src/wab-client/WABClient.d.ts +65 -0
- package/out/src/wab-client/WABClient.d.ts.map +1 -1
- package/out/src/wab-client/WABClient.js +107 -0
- package/out/src/wab-client/WABClient.js.map +1 -1
- package/package.json +1 -1
|
@@ -163,6 +163,68 @@ class WalletPermissionsManager {
|
|
|
163
163
|
originator
|
|
164
164
|
});
|
|
165
165
|
}
|
|
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
|
+
}
|
|
166
228
|
/**
|
|
167
229
|
* Decrypts custom instructions in listOutputs results if encryption is configured.
|
|
168
230
|
*/
|
|
@@ -176,6 +238,36 @@ class WalletPermissionsManager {
|
|
|
176
238
|
}
|
|
177
239
|
return results;
|
|
178
240
|
}
|
|
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
|
+
}
|
|
179
271
|
/* ---------------------------------------------------------------------
|
|
180
272
|
* 1) PUBLIC API FOR REGISTERING CALLBACKS (UI PROMPTS, LOGGING, ETC.)
|
|
181
273
|
* --------------------------------------------------------------------- */
|
|
@@ -316,91 +408,103 @@ class WalletPermissionsManager {
|
|
|
316
408
|
if (!matching) {
|
|
317
409
|
throw new Error('Request ID not found.');
|
|
318
410
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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))); })) {
|
|
327
|
-
throw new Error('Granted protocol permissions are not a subset of the original request.');
|
|
328
|
-
}
|
|
329
|
-
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))); })) {
|
|
330
|
-
throw new Error('Granted basket access permissions are not a subset of the original request.');
|
|
331
|
-
}
|
|
332
|
-
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))); })) {
|
|
333
|
-
throw new Error('Granted certificate access permissions are not a subset of the original request.');
|
|
334
|
-
}
|
|
335
|
-
// --- End Validation ---
|
|
336
|
-
const expiry = params.expiry || 0; // default: never expires
|
|
337
|
-
const toCreate = [];
|
|
338
|
-
const toRenew = [];
|
|
339
|
-
if (params.granted.spendingAuthorization) {
|
|
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',
|
|
359
|
-
originator,
|
|
360
|
-
privileged: false,
|
|
361
|
-
protocolID: p.protocolID,
|
|
362
|
-
counterparty: p.counterparty || 'self',
|
|
363
|
-
reason: p.description
|
|
364
|
-
};
|
|
365
|
-
if (token) {
|
|
366
|
-
toRenew.push({ oldToken: token, request, expiry });
|
|
411
|
+
try {
|
|
412
|
+
const originalRequest = matching.request;
|
|
413
|
+
const { originator, permissions: requestedPermissions, displayOriginator } = originalRequest;
|
|
414
|
+
const originLookupValues = this.buildOriginatorLookupValues(displayOriginator, originator);
|
|
415
|
+
// --- Validation: Ensure granted permissions are a subset of what was requested ---
|
|
416
|
+
if (params.granted.spendingAuthorization && !requestedPermissions.spendingAuthorization) {
|
|
417
|
+
throw new Error('Granted spending authorization was not part of the original request.');
|
|
367
418
|
}
|
|
368
|
-
|
|
369
|
-
|
|
419
|
+
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))); })) {
|
|
420
|
+
throw new Error('Granted protocol permissions are not a subset of the original request.');
|
|
370
421
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
422
|
+
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))); })) {
|
|
423
|
+
throw new Error('Granted basket access permissions are not a subset of the original request.');
|
|
424
|
+
}
|
|
425
|
+
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))); })) {
|
|
426
|
+
throw new Error('Granted certificate access permissions are not a subset of the original request.');
|
|
427
|
+
}
|
|
428
|
+
// --- End Validation ---
|
|
429
|
+
const expiry = params.expiry || 0; // default: never expires
|
|
430
|
+
const toCreate = [];
|
|
431
|
+
const toRenew = [];
|
|
432
|
+
if (params.granted.spendingAuthorization) {
|
|
433
|
+
toCreate.push({
|
|
434
|
+
request: {
|
|
435
|
+
type: 'spending',
|
|
436
|
+
originator,
|
|
437
|
+
spending: { satoshis: params.granted.spendingAuthorization.amount },
|
|
438
|
+
reason: params.granted.spendingAuthorization.description
|
|
439
|
+
},
|
|
440
|
+
expiry: 0,
|
|
441
|
+
amount: params.granted.spendingAuthorization.amount
|
|
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 };
|
|
376
448
|
});
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
request: {
|
|
381
|
-
type: 'certificate',
|
|
449
|
+
for (const { p, token } of protocolTokens) {
|
|
450
|
+
const request = {
|
|
451
|
+
type: 'protocol',
|
|
382
452
|
originator,
|
|
383
453
|
privileged: false,
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
454
|
+
protocolID: p.protocolID,
|
|
455
|
+
counterparty: p.counterparty || 'self',
|
|
456
|
+
reason: p.description
|
|
457
|
+
};
|
|
458
|
+
if (token) {
|
|
459
|
+
toRenew.push({ oldToken: token, request, expiry });
|
|
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
|
|
388
483
|
},
|
|
389
|
-
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
|
|
484
|
+
expiry
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
const created = await this.createPermissionTokensBestEffort(toCreate);
|
|
488
|
+
const renewed = await this.renewPermissionTokensBestEffort(toRenew);
|
|
489
|
+
for (const req of [...created, ...renewed]) {
|
|
490
|
+
this.markRecentGrant(req);
|
|
491
|
+
}
|
|
492
|
+
// Success - resolve all pending promises for this request
|
|
493
|
+
for (const p of matching.pending) {
|
|
494
|
+
p.resolve(true);
|
|
495
|
+
}
|
|
393
496
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
497
|
+
catch (error) {
|
|
498
|
+
// Failure - reject all pending promises so callers don't hang forever
|
|
499
|
+
for (const p of matching.pending) {
|
|
500
|
+
p.reject(error);
|
|
501
|
+
}
|
|
502
|
+
throw error;
|
|
398
503
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
504
|
+
finally {
|
|
505
|
+
// Always clean up the request entry
|
|
506
|
+
this.activeRequests.delete(params.requestID);
|
|
402
507
|
}
|
|
403
|
-
this.activeRequests.delete(params.requestID);
|
|
404
508
|
}
|
|
405
509
|
/**
|
|
406
510
|
* Denies a previously requested grouped permission.
|
|
@@ -2482,22 +2586,16 @@ class WalletPermissionsManager {
|
|
|
2482
2586
|
* 7) BRC-100 WALLET INTERFACE FORWARDING WITH PERMISSION CHECKS
|
|
2483
2587
|
* --------------------------------------------------------------------- */
|
|
2484
2588
|
async createAction(args, originator) {
|
|
2485
|
-
|
|
2486
|
-
// 1) Identify unique P-modules involved (one per schemeID)
|
|
2589
|
+
// 1) Identify unique P-modules involved (one per schemeID) from both baskets and labels
|
|
2487
2590
|
const pModulesByScheme = new Map();
|
|
2488
2591
|
const nonPBaskets = [];
|
|
2592
|
+
// Check baskets for p modules
|
|
2489
2593
|
if (args.outputs) {
|
|
2490
2594
|
for (const out of args.outputs) {
|
|
2491
2595
|
if (out.basket) {
|
|
2492
2596
|
if (out.basket.startsWith('p ')) {
|
|
2493
2597
|
const schemeID = out.basket.split(' ')[1];
|
|
2494
|
-
|
|
2495
|
-
const module = (_a = this.config.permissionModules) === null || _a === void 0 ? void 0 : _a[schemeID];
|
|
2496
|
-
if (!module) {
|
|
2497
|
-
throw new Error(`Unsupported P-basket scheme: p ${schemeID}`);
|
|
2498
|
-
}
|
|
2499
|
-
pModulesByScheme.set(schemeID, module);
|
|
2500
|
-
}
|
|
2598
|
+
this.addPModuleByScheme(schemeID, 'basket', pModulesByScheme);
|
|
2501
2599
|
}
|
|
2502
2600
|
else {
|
|
2503
2601
|
// Track non-P baskets for normal permission checks
|
|
@@ -2506,6 +2604,8 @@ class WalletPermissionsManager {
|
|
|
2506
2604
|
}
|
|
2507
2605
|
}
|
|
2508
2606
|
}
|
|
2607
|
+
// Check labels for p modules
|
|
2608
|
+
const nonPLabels = this.splitLabelsByPermissionModule(args.labels, pModulesByScheme);
|
|
2509
2609
|
// 2) Check permissions for non-P baskets
|
|
2510
2610
|
for (const basket of nonPBaskets) {
|
|
2511
2611
|
await this.ensureBasketAccess({
|
|
@@ -2515,15 +2615,14 @@ class WalletPermissionsManager {
|
|
|
2515
2615
|
usageType: 'insertion'
|
|
2516
2616
|
});
|
|
2517
2617
|
}
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
}
|
|
2618
|
+
// 3) Check permissions for non-P labels
|
|
2619
|
+
for (const lbl of nonPLabels) {
|
|
2620
|
+
await this.ensureLabelAccess({
|
|
2621
|
+
originator: originator,
|
|
2622
|
+
label: lbl,
|
|
2623
|
+
reason: args.description,
|
|
2624
|
+
usageType: 'apply'
|
|
2625
|
+
});
|
|
2527
2626
|
}
|
|
2528
2627
|
/**
|
|
2529
2628
|
* 4) Force signAndProcess=false unless the originator is admin and explicitly sets it to true.
|
|
@@ -2701,74 +2800,132 @@ class WalletPermissionsManager {
|
|
|
2701
2800
|
}
|
|
2702
2801
|
async listActions(...args) {
|
|
2703
2802
|
const [requestArgs, originator] = args;
|
|
2704
|
-
//
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2803
|
+
// 1) Identify unique P-modules involved (one per schemeID, preserving label order)
|
|
2804
|
+
const pModulesByScheme = new Map();
|
|
2805
|
+
const nonPLabels = this.splitLabelsByPermissionModule(requestArgs.labels, pModulesByScheme);
|
|
2806
|
+
// 2) Check permissions for non-P labels
|
|
2807
|
+
for (const lbl of nonPLabels) {
|
|
2808
|
+
await this.ensureLabelAccess({
|
|
2809
|
+
originator: originator,
|
|
2810
|
+
label: lbl,
|
|
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
|
|
2712
2827
|
});
|
|
2828
|
+
transformedArgs = transformed.args;
|
|
2713
2829
|
}
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
}
|
|
2722
|
-
if (results.actions[i].inputs) {
|
|
2723
|
-
for (let j = 0; j < results.actions[i].inputs.length; j++) {
|
|
2724
|
-
if (results.actions[i].inputs[j].inputDescription) {
|
|
2725
|
-
results.actions[i].inputs[j].inputDescription = await this.maybeDecryptMetadata(results.actions[i].inputs[j].inputDescription);
|
|
2726
|
-
}
|
|
2727
|
-
}
|
|
2728
|
-
}
|
|
2729
|
-
if (results.actions[i].outputs) {
|
|
2730
|
-
for (let j = 0; j < results.actions[i].outputs.length; j++) {
|
|
2731
|
-
if (results.actions[i].outputs[j].outputDescription) {
|
|
2732
|
-
results.actions[i].outputs[j].outputDescription = await this.maybeDecryptMetadata(results.actions[i].outputs[j].outputDescription);
|
|
2733
|
-
}
|
|
2734
|
-
if (results.actions[i].outputs[j].customInstructions) {
|
|
2735
|
-
results.actions[i].outputs[j].customInstructions = await this.maybeDecryptMetadata(results.actions[i].outputs[j].customInstructions);
|
|
2736
|
-
}
|
|
2737
|
-
}
|
|
2738
|
-
}
|
|
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
|
|
2837
|
+
});
|
|
2739
2838
|
}
|
|
2740
2839
|
}
|
|
2741
|
-
|
|
2840
|
+
else {
|
|
2841
|
+
// No P-modules - call underlying wallet directly
|
|
2842
|
+
results = await this.underlying.listActions(...args);
|
|
2843
|
+
}
|
|
2844
|
+
// 4) Transparently decrypt transaction metadata, if configured to do so.
|
|
2845
|
+
return await this.decryptListActionsMetadata(results);
|
|
2742
2846
|
}
|
|
2743
2847
|
async internalizeAction(...args) {
|
|
2848
|
+
var _a;
|
|
2744
2849
|
const [requestArgs, originator] = args;
|
|
2745
|
-
//
|
|
2850
|
+
// 1) Identify unique P-modules involved (one per schemeID) from both baskets and labels
|
|
2851
|
+
const pModulesByScheme = new Map();
|
|
2852
|
+
const nonPBaskets = [];
|
|
2853
|
+
// Check baskets for p modules
|
|
2746
2854
|
for (const outIndex in requestArgs.outputs) {
|
|
2747
2855
|
const out = requestArgs.outputs[outIndex];
|
|
2748
2856
|
if (out.protocol === 'basket insertion') {
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
transformedArgs.outputs[outIndex].insertionRemittance.customInstructions =
|
|
2754
|
-
await this.maybeEncryptMetadata(out.insertionRemittance.customInstructions);
|
|
2755
|
-
}
|
|
2756
|
-
return await this.underlying.internalizeAction(transformedArgs, originator);
|
|
2757
|
-
});
|
|
2758
|
-
if (pModuleResult !== null) {
|
|
2759
|
-
return pModuleResult;
|
|
2857
|
+
const basket = out.insertionRemittance.basket;
|
|
2858
|
+
if (basket.startsWith('p ')) {
|
|
2859
|
+
const schemeID = basket.split(' ')[1];
|
|
2860
|
+
this.addPModuleByScheme(schemeID, 'basket', pModulesByScheme);
|
|
2760
2861
|
}
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2862
|
+
else {
|
|
2863
|
+
// Track non-P baskets for normal permission checks
|
|
2864
|
+
nonPBaskets.push({
|
|
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
|
|
2766
2907
|
});
|
|
2767
|
-
|
|
2768
|
-
|
|
2908
|
+
transformedArgs = transformed.args;
|
|
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);
|
|
2769
2915
|
}
|
|
2770
2916
|
}
|
|
2917
|
+
// Call underlying wallet with transformed args
|
|
2918
|
+
let results = await this.underlying.internalizeAction(transformedArgs, originator);
|
|
2919
|
+
// Chain onResponse calls in reverse order
|
|
2920
|
+
for (let i = pModules.length - 1; i >= 0; i--) {
|
|
2921
|
+
results = await pModules[i].onResponse(results, {
|
|
2922
|
+
method: 'internalizeAction',
|
|
2923
|
+
originator: originator
|
|
2924
|
+
});
|
|
2925
|
+
}
|
|
2926
|
+
return results;
|
|
2771
2927
|
}
|
|
2928
|
+
// No P-modules - call underlying wallet directly
|
|
2772
2929
|
return this.underlying.internalizeAction(...args);
|
|
2773
2930
|
}
|
|
2774
2931
|
async listOutputs(...args) {
|
|
@@ -3153,6 +3310,7 @@ class WalletPermissionsManager {
|
|
|
3153
3310
|
* Checks if the given label is admin-reserved per BRC-100 rules:
|
|
3154
3311
|
*
|
|
3155
3312
|
* - Must not start with `admin` (admin-reserved)
|
|
3313
|
+
* - Must not start with `p ` (permissioned labels requiring a permission module)
|
|
3156
3314
|
*
|
|
3157
3315
|
* If it violates these rules and the caller is not admin, we consider it "admin-only."
|
|
3158
3316
|
*/
|
|
@@ -3160,6 +3318,9 @@ class WalletPermissionsManager {
|
|
|
3160
3318
|
if (label.startsWith('admin')) {
|
|
3161
3319
|
return true;
|
|
3162
3320
|
}
|
|
3321
|
+
if (label.startsWith('p ')) {
|
|
3322
|
+
return true;
|
|
3323
|
+
}
|
|
3163
3324
|
return false;
|
|
3164
3325
|
}
|
|
3165
3326
|
/**
|