@bsv/wallet-toolbox 1.7.11 → 1.7.12

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.
Files changed (31) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/docs/client.md +213 -52
  3. package/docs/wallet.md +213 -52
  4. package/mobile/out/src/WalletPermissionsManager.d.ts +60 -0
  5. package/mobile/out/src/WalletPermissionsManager.d.ts.map +1 -1
  6. package/mobile/out/src/WalletPermissionsManager.js +200 -35
  7. package/mobile/out/src/WalletPermissionsManager.js.map +1 -1
  8. package/mobile/package-lock.json +2 -2
  9. package/mobile/package.json +1 -1
  10. package/out/src/WalletPermissionsManager.d.ts +60 -0
  11. package/out/src/WalletPermissionsManager.d.ts.map +1 -1
  12. package/out/src/WalletPermissionsManager.js +200 -35
  13. package/out/src/WalletPermissionsManager.js.map +1 -1
  14. package/out/src/__tests/WalletPermissionsManager.fixtures.d.ts.map +1 -1
  15. package/out/src/__tests/WalletPermissionsManager.fixtures.js.map +1 -1
  16. package/out/src/__tests/WalletPermissionsManager.pmodules.test.d.ts +2 -0
  17. package/out/src/__tests/WalletPermissionsManager.pmodules.test.d.ts.map +1 -0
  18. package/out/src/__tests/WalletPermissionsManager.pmodules.test.js +624 -0
  19. package/out/src/__tests/WalletPermissionsManager.pmodules.test.js.map +1 -0
  20. package/out/src/__tests/WalletPermissionsManager.proxying.test.js.map +1 -1
  21. package/out/src/storage/remoting/StorageServer.d.ts.map +1 -1
  22. package/out/src/storage/remoting/StorageServer.js.map +1 -1
  23. package/out/tsconfig.all.tsbuildinfo +1 -1
  24. package/package.json +1 -1
  25. package/src/Wallet.ts +2 -2
  26. package/src/WalletLogger.ts +1 -1
  27. package/src/WalletPermissionsManager.ts +350 -42
  28. package/src/__tests/WalletPermissionsManager.fixtures.ts +1 -2
  29. package/src/__tests/WalletPermissionsManager.pmodules.test.ts +798 -0
  30. package/src/__tests/WalletPermissionsManager.proxying.test.ts +2 -2
  31. package/src/storage/remoting/StorageServer.ts +0 -2
@@ -0,0 +1,624 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const WalletPermissionsManager_fixtures_1 = require("./WalletPermissionsManager.fixtures");
4
+ const WalletPermissionsManager_1 = require("../WalletPermissionsManager");
5
+ jest.mock('@bsv/sdk', () => WalletPermissionsManager_fixtures_1.MockedBSV_SDK);
6
+ /**
7
+ * Test suite for Permission Modules
8
+ * Tests both P-basket and P-protocol delegation to custom permission modules.
9
+ */
10
+ describe('WalletPermissionsManager - Permission Module Support', () => {
11
+ let underlying;
12
+ beforeEach(() => {
13
+ underlying = (0, WalletPermissionsManager_fixtures_1.mockUnderlyingWallet)();
14
+ });
15
+ afterEach(() => {
16
+ jest.clearAllMocks();
17
+ });
18
+ describe('Permission Module Registration', () => {
19
+ it('should accept permissionModules in config', () => {
20
+ var _a;
21
+ const testModule = {
22
+ onRequest: jest.fn(async (req) => req),
23
+ onResponse: jest.fn(async (res) => res)
24
+ };
25
+ const config = {
26
+ permissionModules: {
27
+ 'test-scheme': testModule
28
+ }
29
+ };
30
+ const manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'customToken.domain.com', config);
31
+ // Verify module is stored in config
32
+ const storedConfig = manager.config;
33
+ expect(storedConfig.permissionModules).toBeDefined();
34
+ expect((_a = storedConfig.permissionModules) === null || _a === void 0 ? void 0 : _a['test-scheme']).toBe(testModule);
35
+ });
36
+ it('should support multiple permission modules for different schemes', () => {
37
+ var _a, _b;
38
+ const module1 = {
39
+ onRequest: jest.fn(async (req) => req),
40
+ onResponse: jest.fn(async (res) => res)
41
+ };
42
+ const module2 = {
43
+ onRequest: jest.fn(async (req) => req),
44
+ onResponse: jest.fn(async (res) => res)
45
+ };
46
+ const config = {
47
+ permissionModules: {
48
+ scheme1: module1,
49
+ scheme2: module2
50
+ }
51
+ };
52
+ const manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'customToken.domain.com', config);
53
+ const storedConfig = manager.config;
54
+ expect(Object.keys(storedConfig.permissionModules || {}).length).toBe(2);
55
+ expect((_a = storedConfig.permissionModules) === null || _a === void 0 ? void 0 : _a['scheme1']).toBe(module1);
56
+ expect((_b = storedConfig.permissionModules) === null || _b === void 0 ? void 0 : _b['scheme2']).toBe(module2);
57
+ });
58
+ });
59
+ describe('P-Basket Delegation - listOutputs', () => {
60
+ it('should delegate to permission when basket starts with "p "', async () => {
61
+ const testModule = {
62
+ onRequest: jest.fn(async (req) => {
63
+ // Module can inspect and transform the request
64
+ expect(req.method).toBe('listOutputs');
65
+ expect(req.args.basket).toBe('p myscheme some-data');
66
+ // Transform basket for underlying call
67
+ return {
68
+ ...req,
69
+ args: { ...req.args, basket: 'transformed-basket' }
70
+ };
71
+ }),
72
+ onResponse: jest.fn(async (res) => {
73
+ // Module can transform the response
74
+ return { ...res, transformedByModule: true };
75
+ })
76
+ };
77
+ const config = {
78
+ permissionModules: {
79
+ myscheme: testModule
80
+ }
81
+ };
82
+ const manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'customToken.domain.com', config);
83
+ // Mock underlying response
84
+ underlying.listOutputs.mockResolvedValue({ outputs: [] });
85
+ const result = await manager.listOutputs({ basket: 'p myscheme some-data' }, 'app.com');
86
+ // Verify module's onRequest was called
87
+ expect(testModule.onRequest).toHaveBeenCalledTimes(1);
88
+ // Note: args array includes both the request args and originator
89
+ expect(testModule.onRequest).toHaveBeenCalledWith({
90
+ method: 'listOutputs',
91
+ args: { basket: 'p myscheme some-data' },
92
+ originator: 'app.com'
93
+ });
94
+ // Verify underlying was called with transformed basket
95
+ // Note: originator stays as 'app.com' not changed to admin
96
+ expect(underlying.listOutputs).toHaveBeenCalledWith({ basket: 'transformed-basket' }, 'app.com');
97
+ // Verify module's onResponse was called
98
+ expect(testModule.onResponse).toHaveBeenCalledTimes(1);
99
+ // Verify response was transformed
100
+ expect(result).toHaveProperty('transformedByModule', true);
101
+ });
102
+ it('should throw error if P-basket scheme has no registered module', async () => {
103
+ const manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'customToken.domain.com');
104
+ await expect(manager.listOutputs({ basket: 'p unregistered-scheme data' }, 'app.com')).rejects.toThrow(/Unsupported P-module scheme: p unregistered-scheme/);
105
+ });
106
+ it('should handle normal baskets without delegation', async () => {
107
+ const testModule = {
108
+ onRequest: jest.fn(async (req) => req),
109
+ onResponse: jest.fn(async (res) => res)
110
+ };
111
+ const config = {
112
+ permissionModules: {
113
+ myscheme: testModule
114
+ },
115
+ seekBasketListingPermissions: false
116
+ };
117
+ const manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'customToken.domain.com', config);
118
+ underlying.listOutputs.mockResolvedValue({ outputs: [] });
119
+ await manager.listOutputs({ basket: 'normal-basket' }, 'app.com');
120
+ // Module should NOT be called for normal baskets
121
+ expect(testModule.onRequest).not.toHaveBeenCalled();
122
+ expect(testModule.onResponse).not.toHaveBeenCalled();
123
+ // Underlying should be called with original basket
124
+ expect(underlying.listOutputs).toHaveBeenCalledWith({ basket: 'normal-basket' }, 'app.com');
125
+ });
126
+ it('should decrypt metadata in P-basket responses', async () => {
127
+ const testModule = {
128
+ onRequest: jest.fn(async (req) => req),
129
+ onResponse: jest.fn(async (res) => res)
130
+ };
131
+ const config = {
132
+ permissionModules: {
133
+ myscheme: testModule
134
+ },
135
+ encryptWalletMetadata: true
136
+ };
137
+ const manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'customToken.domain.com', config);
138
+ // Mock decryption
139
+ const decryptMetadata = jest.spyOn(manager, 'decryptListOutputsMetadata');
140
+ decryptMetadata.mockImplementation(async (outputs) => outputs);
141
+ underlying.listOutputs.mockResolvedValue({
142
+ outputs: [{ outpoint: 'txid.0', satoshis: 100 }]
143
+ });
144
+ await manager.listOutputs({ basket: 'p myscheme data' }, 'app.com');
145
+ // Verify metadata decryption was called
146
+ expect(decryptMetadata).toHaveBeenCalled();
147
+ });
148
+ });
149
+ describe('P-Basket Delegation - relinquishOutput', () => {
150
+ it('should delegate to P-module when basket starts with "p "', async () => {
151
+ const testModule = {
152
+ onRequest: jest.fn(async (req) => {
153
+ expect(req.method).toBe('relinquishOutput');
154
+ expect(req.args.basket).toBe('p token admin-basket-data');
155
+ return {
156
+ ...req,
157
+ args: { ...req.args, basket: 'admin-real-basket' }
158
+ };
159
+ }),
160
+ onResponse: jest.fn(async (res) => res)
161
+ };
162
+ const config = {
163
+ permissionModules: {
164
+ token: testModule
165
+ }
166
+ };
167
+ const manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'customToken.domain.com', config);
168
+ underlying.relinquishOutput.mockResolvedValue(undefined);
169
+ await manager.relinquishOutput({ basket: 'p token admin-basket-data', output: 'txid.0' }, 'app.com');
170
+ expect(testModule.onRequest).toHaveBeenCalledTimes(1);
171
+ // Note: originator remains 'app.com' - only basket is transformed
172
+ expect(underlying.relinquishOutput).toHaveBeenCalledWith({ basket: 'admin-real-basket', output: 'txid.0' }, 'app.com');
173
+ });
174
+ });
175
+ describe('P-Basket Delegation - createAction', () => {
176
+ it('should delegate to P-module for outputs with P-baskets', async () => {
177
+ const testModule = {
178
+ onRequest: jest.fn(async (req) => {
179
+ // Verify we receive createAction request
180
+ expect(req.method).toBe('createAction');
181
+ return req;
182
+ }),
183
+ onResponse: jest.fn(async (res) => {
184
+ // Module can inspect the transaction result
185
+ return res;
186
+ })
187
+ };
188
+ const config = {
189
+ permissionModules: {
190
+ bottle: testModule
191
+ },
192
+ seekSpendingPermissions: false,
193
+ seekBasketInsertionPermissions: false
194
+ };
195
+ const manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'customToken.domain.com', config);
196
+ underlying.createAction.mockResolvedValue({
197
+ txid: 'abc123',
198
+ tx: []
199
+ });
200
+ await manager.createAction({
201
+ description: 'Test action',
202
+ outputs: [
203
+ {
204
+ lockingScript: 'abcd',
205
+ satoshis: 1000,
206
+ basket: 'p bottle token-data',
207
+ outputDescription: 'Test output'
208
+ }
209
+ ]
210
+ }, 'app.com');
211
+ // Verify P-module was invoked
212
+ expect(testModule.onRequest).toHaveBeenCalled();
213
+ expect(testModule.onResponse).toHaveBeenCalled();
214
+ });
215
+ it('should chain multiple P-modules in correct order: req1->req2->req3 then res3->res2->res1', async () => {
216
+ const callOrder = [];
217
+ const module1 = {
218
+ onRequest: jest.fn(async (req) => {
219
+ callOrder.push('req1');
220
+ // First module receives original args without any processing markers
221
+ expect(req.args.req1Processed).toBeUndefined();
222
+ expect(req.args.req2Processed).toBeUndefined();
223
+ // Transform args - add marker to track this module processed them
224
+ return {
225
+ ...req,
226
+ args: { ...req.args, req1Processed: true }
227
+ };
228
+ }),
229
+ onResponse: jest.fn(async (res) => {
230
+ callOrder.push('res1');
231
+ // Last module in response chain should see transformations from res2 and res3
232
+ expect(res.processedBy).toBe('module2');
233
+ return { ...res, finalProcessedBy: 'module1' };
234
+ })
235
+ };
236
+ const module2 = {
237
+ onRequest: jest.fn(async (req) => {
238
+ callOrder.push('req2');
239
+ // Second module receives args transformed by module1
240
+ // (each module gets fresh request object, but args are chained)
241
+ expect(req.args.req1Processed).toBe(true);
242
+ expect(req.args.req2Processed).toBeUndefined();
243
+ return {
244
+ ...req,
245
+ args: { ...req.args, req2Processed: true }
246
+ };
247
+ }),
248
+ onResponse: jest.fn(async (res) => {
249
+ callOrder.push('res2');
250
+ // Second-to-last in response chain should see transformation from res3
251
+ expect(res.processedBy).toBe('module3');
252
+ return { ...res, processedBy: 'module2' };
253
+ })
254
+ };
255
+ const module3 = {
256
+ onRequest: jest.fn(async (req) => {
257
+ callOrder.push('req3');
258
+ // Third module receives args with transformations from both module1 and module2
259
+ expect(req.args.req1Processed).toBe(true);
260
+ expect(req.args.req2Processed).toBe(true);
261
+ expect(req.args.req3Processed).toBeUndefined();
262
+ return {
263
+ ...req,
264
+ args: { ...req.args, req3Processed: true }
265
+ };
266
+ }),
267
+ onResponse: jest.fn(async (res) => {
268
+ callOrder.push('res3');
269
+ // First module in response chain receives raw response from underlying wallet
270
+ expect(res.processedBy).toBeUndefined();
271
+ return { ...res, processedBy: 'module3' };
272
+ })
273
+ };
274
+ const config = {
275
+ permissionModules: {
276
+ scheme1: module1,
277
+ scheme2: module2,
278
+ scheme3: module3
279
+ },
280
+ seekSpendingPermissions: false,
281
+ seekBasketInsertionPermissions: false
282
+ };
283
+ const manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'customToken.domain.com', config);
284
+ underlying.createAction.mockResolvedValue({ txid: 'abc', tx: [] });
285
+ const result = await manager.createAction({
286
+ description: 'Multi-module chain test',
287
+ outputs: [
288
+ { lockingScript: '1234', satoshis: 100, basket: 'p scheme1 data1', outputDescription: 'Output 1' },
289
+ { lockingScript: '5678', satoshis: 200, basket: 'p scheme2 data2', outputDescription: 'Output 2' },
290
+ { lockingScript: '9abc', satoshis: 300, basket: 'p scheme3 data3', outputDescription: 'Output 3' }
291
+ ]
292
+ }, 'app.com');
293
+ // Verify all modules were called
294
+ expect(module1.onRequest).toHaveBeenCalledTimes(1);
295
+ expect(module2.onRequest).toHaveBeenCalledTimes(1);
296
+ expect(module3.onRequest).toHaveBeenCalledTimes(1);
297
+ expect(module1.onResponse).toHaveBeenCalledTimes(1);
298
+ expect(module2.onResponse).toHaveBeenCalledTimes(1);
299
+ expect(module3.onResponse).toHaveBeenCalledTimes(1);
300
+ // Verify correct order: req1 -> req2 -> req3 then res3 -> res2 -> res1
301
+ expect(callOrder).toEqual(['req1', 'req2', 'req3', 'res3', 'res2', 'res1']);
302
+ // Verify final result has the complete chain of transformations
303
+ expect(result.finalProcessedBy).toBe('module1');
304
+ });
305
+ });
306
+ describe('P-Basket Delegation - internalizeAction', () => {
307
+ it('should delegate to P-module when P-basket is specified in insertionRemittance', async () => {
308
+ const testModule = {
309
+ onRequest: jest.fn(async (req) => {
310
+ expect(req.method).toBe('internalizeAction');
311
+ return req;
312
+ }),
313
+ onResponse: jest.fn(async (res) => res)
314
+ };
315
+ const config = {
316
+ permissionModules: {
317
+ myscheme: testModule
318
+ },
319
+ encryptWalletMetadata: false
320
+ };
321
+ const manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'customToken.domain.com', config);
322
+ underlying.internalizeAction.mockResolvedValue(undefined);
323
+ await manager.internalizeAction({
324
+ tx: [],
325
+ description: 'Test internalize action',
326
+ outputs: [
327
+ {
328
+ outputIndex: 0,
329
+ protocol: 'basket insertion',
330
+ paymentRemittance: { derivationPrefix: '', derivationSuffix: '', senderIdentityKey: '' },
331
+ insertionRemittance: {
332
+ basket: 'p myscheme data',
333
+ customInstructions: ''
334
+ }
335
+ }
336
+ ] // Use 'as any' to avoid strict type checking in test
337
+ }, 'app.com');
338
+ expect(testModule.onRequest).toHaveBeenCalledTimes(1);
339
+ expect(testModule.onResponse).toHaveBeenCalledTimes(1);
340
+ });
341
+ });
342
+ describe('P-Protocol Delegation', () => {
343
+ it('should delegate getPublicKey to P-protocol module', async () => {
344
+ const testModule = {
345
+ onRequest: jest.fn(async (req) => {
346
+ expect(req.method).toBe('getPublicKey');
347
+ expect(req.args.protocolID).toEqual([0, 'p bottle test']);
348
+ return req;
349
+ }),
350
+ onResponse: jest.fn(async (res) => {
351
+ // Module can verify the key or add metadata
352
+ return { ...res, verifiedByModule: true };
353
+ })
354
+ };
355
+ const config = {
356
+ permissionModules: {
357
+ bottle: testModule
358
+ },
359
+ seekPermissionsForPublicKeyRevelation: false
360
+ };
361
+ const manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'customToken.domain.com', config);
362
+ underlying.getPublicKey.mockResolvedValue({ publicKey: '02abc...' });
363
+ const result = await manager.getPublicKey({
364
+ protocolID: [0, 'p bottle test'],
365
+ keyID: '1',
366
+ counterparty: 'self'
367
+ }, 'app.com');
368
+ expect(testModule.onRequest).toHaveBeenCalledTimes(1);
369
+ expect(testModule.onResponse).toHaveBeenCalledTimes(1);
370
+ expect(result).toHaveProperty('verifiedByModule', true);
371
+ });
372
+ it('should delegate createSignature to P-protocol module', async () => {
373
+ const testModule = {
374
+ onRequest: jest.fn(async (req) => {
375
+ expect(req.method).toBe('createSignature');
376
+ expect(req.args.protocolID).toEqual([1, 'p token spend']);
377
+ // Module can validate spend amounts, check limits, etc.
378
+ return req;
379
+ }),
380
+ onResponse: jest.fn(async (res) => res)
381
+ };
382
+ const config = {
383
+ permissionModules: {
384
+ token: testModule
385
+ },
386
+ seekProtocolPermissionsForSigning: false
387
+ };
388
+ const manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'customToken.domain.com', config);
389
+ underlying.createSignature.mockResolvedValue({ signature: 'abc123' });
390
+ await manager.createSignature({
391
+ protocolID: [1, 'p token spend'],
392
+ keyID: '1',
393
+ data: [0x01, 0x02]
394
+ }, 'app.com');
395
+ expect(testModule.onRequest).toHaveBeenCalledTimes(1);
396
+ expect(testModule.onResponse).toHaveBeenCalledTimes(1);
397
+ });
398
+ it('should delegate verifySignature to P-protocol module', async () => {
399
+ const testModule = {
400
+ onRequest: jest.fn(async (req) => {
401
+ expect(req.method).toBe('verifySignature');
402
+ return req;
403
+ }),
404
+ onResponse: jest.fn(async (res) => res)
405
+ };
406
+ const config = {
407
+ permissionModules: {
408
+ secure: testModule
409
+ }
410
+ };
411
+ const manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'customToken.domain.com', config);
412
+ underlying.verifySignature.mockResolvedValue(true);
413
+ await manager.verifySignature({
414
+ protocolID: [1, 'p secure verify'],
415
+ keyID: '1',
416
+ data: [0x01],
417
+ signature: [0x02]
418
+ }, 'app.com');
419
+ expect(testModule.onRequest).toHaveBeenCalledTimes(1);
420
+ });
421
+ it('should delegate encrypt to P-protocol module', async () => {
422
+ const testModule = {
423
+ onRequest: jest.fn(async (req) => {
424
+ expect(req.method).toBe('encrypt');
425
+ expect(req.args.protocolID).toEqual([2, 'p secure encrypt']);
426
+ return req;
427
+ }),
428
+ onResponse: jest.fn(async (res) => res)
429
+ };
430
+ const config = {
431
+ permissionModules: {
432
+ secure: testModule
433
+ },
434
+ seekProtocolPermissionsForEncrypting: false
435
+ };
436
+ const manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'customToken.domain.com', config);
437
+ underlying.encrypt.mockResolvedValue({ ciphertext: [0x01, 0x02] });
438
+ await manager.encrypt({
439
+ protocolID: [2, 'p secure encrypt'],
440
+ keyID: '1',
441
+ plaintext: [0x48, 0x65, 0x6c, 0x6c, 0x6f]
442
+ }, 'app.com');
443
+ expect(testModule.onRequest).toHaveBeenCalledTimes(1);
444
+ });
445
+ it('should delegate decrypt to P-protocol module', async () => {
446
+ const testModule = {
447
+ onRequest: jest.fn(async (req) => {
448
+ expect(req.method).toBe('decrypt');
449
+ return req;
450
+ }),
451
+ onResponse: jest.fn(async (res) => {
452
+ // Module could verify decrypted content
453
+ return res;
454
+ })
455
+ };
456
+ const config = {
457
+ permissionModules: {
458
+ secure: testModule
459
+ },
460
+ seekProtocolPermissionsForEncrypting: false
461
+ };
462
+ const manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'customToken.domain.com', config);
463
+ underlying.decrypt.mockResolvedValue({ plaintext: [0x48, 0x65] });
464
+ await manager.decrypt({
465
+ protocolID: [2, 'p secure decrypt'],
466
+ keyID: '1',
467
+ ciphertext: [0x01, 0x02]
468
+ }, 'app.com');
469
+ expect(testModule.onRequest).toHaveBeenCalledTimes(1);
470
+ expect(testModule.onResponse).toHaveBeenCalledTimes(1);
471
+ });
472
+ it('should delegate createHmac to P-protocol module', async () => {
473
+ const testModule = {
474
+ onRequest: jest.fn(async (req) => {
475
+ expect(req.method).toBe('createHmac');
476
+ return req;
477
+ }),
478
+ onResponse: jest.fn(async (res) => res)
479
+ };
480
+ const config = {
481
+ permissionModules: {
482
+ hmac: testModule
483
+ },
484
+ seekProtocolPermissionsForHMAC: false
485
+ };
486
+ const manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'customToken.domain.com', config);
487
+ underlying.createHmac.mockResolvedValue({ hmac: [0x01] });
488
+ await manager.createHmac({
489
+ protocolID: [2, 'p hmac test'],
490
+ keyID: '1',
491
+ data: [0x48]
492
+ }, 'app.com');
493
+ expect(testModule.onRequest).toHaveBeenCalledTimes(1);
494
+ });
495
+ it('should delegate verifyHmac to P-protocol module', async () => {
496
+ const testModule = {
497
+ onRequest: jest.fn(async (req) => {
498
+ expect(req.method).toBe('verifyHmac');
499
+ return req;
500
+ }),
501
+ onResponse: jest.fn(async (res) => res)
502
+ };
503
+ const config = {
504
+ permissionModules: {
505
+ hmac: testModule
506
+ }
507
+ };
508
+ const manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'customToken.domain.com', config);
509
+ underlying.verifyHmac.mockResolvedValue(true);
510
+ await manager.verifyHmac({
511
+ protocolID: [2, 'p hmac verify'],
512
+ keyID: '1',
513
+ data: [0x48],
514
+ hmac: [0x01]
515
+ }, 'app.com');
516
+ expect(testModule.onRequest).toHaveBeenCalledTimes(1);
517
+ });
518
+ });
519
+ describe('P-Module Error Handling', () => {
520
+ it('should throw if P-module onRequest throws', async () => {
521
+ const testModule = {
522
+ onRequest: jest.fn(async () => {
523
+ throw new Error('Module validation failed');
524
+ }),
525
+ onResponse: jest.fn(async (res) => res)
526
+ };
527
+ const config = {
528
+ permissionModules: {
529
+ failing: testModule
530
+ }
531
+ };
532
+ const manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'customToken.domain.com', config);
533
+ await expect(manager.listOutputs({ basket: 'p failing data' }, 'app.com')).rejects.toThrow('Module validation failed');
534
+ // Underlying should not be called if module fails
535
+ expect(underlying.listOutputs).not.toHaveBeenCalled();
536
+ });
537
+ it('should throw if P-module onResponse throws', async () => {
538
+ const testModule = {
539
+ onRequest: jest.fn(async (req) => req),
540
+ onResponse: jest.fn(async () => {
541
+ throw new Error('Module response processing failed');
542
+ })
543
+ };
544
+ const config = {
545
+ permissionModules: {
546
+ failing: testModule
547
+ }
548
+ };
549
+ const manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'customToken.domain.com', config);
550
+ underlying.listOutputs.mockResolvedValue({ outputs: [] });
551
+ await expect(manager.listOutputs({ basket: 'p failing data' }, 'app.com')).rejects.toThrow('Module response processing failed');
552
+ // Underlying was called, but response processing failed
553
+ expect(underlying.listOutputs).toHaveBeenCalled();
554
+ });
555
+ it('should call P-module onRequest even when permission checks are enabled', async () => {
556
+ const testModule = {
557
+ onRequest: jest.fn(async (req) => {
558
+ // Module validation happens before permission checks
559
+ return req;
560
+ }),
561
+ onResponse: jest.fn(async (res) => res)
562
+ };
563
+ const config = {
564
+ permissionModules: {
565
+ myscheme: testModule
566
+ },
567
+ seekProtocolPermissionsForSigning: false // Disable to simplify test
568
+ };
569
+ const manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'customToken.domain.com', config);
570
+ underlying.createSignature.mockResolvedValue({ signature: [0x01] });
571
+ // Create a signature request with P-protocol
572
+ await manager.createSignature({
573
+ protocolID: [1, 'p myscheme sign'],
574
+ keyID: '1',
575
+ data: [0x01]
576
+ }, 'app.com');
577
+ // Module should have been called
578
+ expect(testModule.onRequest).toHaveBeenCalled();
579
+ expect(testModule.onResponse).toHaveBeenCalled();
580
+ });
581
+ });
582
+ describe('P-Module Admin Bypass', () => {
583
+ it('should still use P-module even for admin originator', async () => {
584
+ const testModule = {
585
+ onRequest: jest.fn(async (req) => {
586
+ // Module should be called even for admin
587
+ expect(req.originator).toBe('admin.com');
588
+ return req;
589
+ }),
590
+ onResponse: jest.fn(async (res) => res)
591
+ };
592
+ const config = {
593
+ permissionModules: {
594
+ myscheme: testModule
595
+ }
596
+ };
597
+ const manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com', config);
598
+ underlying.listOutputs.mockResolvedValue({ outputs: [] });
599
+ await manager.listOutputs({ basket: 'p myscheme data' }, 'admin.com');
600
+ // Module should be invoked even for admin calls
601
+ expect(testModule.onRequest).toHaveBeenCalledTimes(1);
602
+ expect(testModule.onResponse).toHaveBeenCalledTimes(1);
603
+ });
604
+ it('should still block non-admin access to admin baskets even with P-module', async () => {
605
+ const testModule = {
606
+ onRequest: jest.fn(async (req) => req),
607
+ onResponse: jest.fn(async (res) => res)
608
+ };
609
+ const config = {
610
+ permissionModules: {
611
+ myscheme: testModule
612
+ },
613
+ seekBasketListingPermissions: false
614
+ };
615
+ const manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'customToken.domain.com', config);
616
+ // Try to access admin basket with non-admin originator
617
+ // Admin baskets are prefixed with 'admin_' by convention
618
+ await expect(manager.listOutputs({ basket: 'admin_someAdminBasket' }, 'app.com')).rejects.toThrow(/admin-only/);
619
+ // P-module should NOT have been called since admin check happens before P-module delegation
620
+ expect(testModule.onRequest).not.toHaveBeenCalled();
621
+ });
622
+ });
623
+ });
624
+ //# sourceMappingURL=WalletPermissionsManager.pmodules.test.js.map