@bsv/wallet-toolbox 1.1.62 → 1.2.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.
Files changed (185) hide show
  1. package/docs/client.md +2339 -182
  2. package/docs/wallet.md +2339 -182
  3. package/out/src/CWIStyleWalletManager.d.ts +417 -0
  4. package/out/src/CWIStyleWalletManager.d.ts.map +1 -0
  5. package/out/src/CWIStyleWalletManager.js +1140 -0
  6. package/out/src/CWIStyleWalletManager.js.map +1 -0
  7. package/out/src/SimpleWalletManager.d.ts +169 -0
  8. package/out/src/SimpleWalletManager.d.ts.map +1 -0
  9. package/out/src/SimpleWalletManager.js +315 -0
  10. package/out/src/SimpleWalletManager.js.map +1 -0
  11. package/out/src/Wallet.d.ts +6 -1
  12. package/out/src/Wallet.d.ts.map +1 -1
  13. package/out/src/Wallet.js +39 -7
  14. package/out/src/Wallet.js.map +1 -1
  15. package/out/src/WalletAuthenticationManager.d.ts +33 -0
  16. package/out/src/WalletAuthenticationManager.d.ts.map +1 -0
  17. package/out/src/WalletAuthenticationManager.js +110 -0
  18. package/out/src/WalletAuthenticationManager.js.map +1 -0
  19. package/out/src/WalletPermissionsManager.d.ts +575 -0
  20. package/out/src/WalletPermissionsManager.d.ts.map +1 -0
  21. package/out/src/WalletPermissionsManager.js +1789 -0
  22. package/out/src/WalletPermissionsManager.js.map +1 -0
  23. package/out/src/WalletSettingsManager.d.ts +59 -0
  24. package/out/src/WalletSettingsManager.d.ts.map +1 -0
  25. package/out/src/WalletSettingsManager.js +189 -0
  26. package/out/src/WalletSettingsManager.js.map +1 -0
  27. package/out/src/__tests/CWIStyleWalletManager.test.d.ts +2 -0
  28. package/out/src/__tests/CWIStyleWalletManager.test.d.ts.map +1 -0
  29. package/out/src/__tests/CWIStyleWalletManager.test.js +471 -0
  30. package/out/src/__tests/CWIStyleWalletManager.test.js.map +1 -0
  31. package/out/src/__tests/WalletPermissionsManager.callbacks.test.d.ts +2 -0
  32. package/out/src/__tests/WalletPermissionsManager.callbacks.test.d.ts.map +1 -0
  33. package/out/src/__tests/WalletPermissionsManager.callbacks.test.js +239 -0
  34. package/out/src/__tests/WalletPermissionsManager.callbacks.test.js.map +1 -0
  35. package/out/src/__tests/WalletPermissionsManager.checks.test.d.ts +2 -0
  36. package/out/src/__tests/WalletPermissionsManager.checks.test.d.ts.map +1 -0
  37. package/out/src/__tests/WalletPermissionsManager.checks.test.js +637 -0
  38. package/out/src/__tests/WalletPermissionsManager.checks.test.js.map +1 -0
  39. package/out/src/__tests/WalletPermissionsManager.encryption.test.d.ts +2 -0
  40. package/out/src/__tests/WalletPermissionsManager.encryption.test.d.ts.map +1 -0
  41. package/out/src/__tests/WalletPermissionsManager.encryption.test.js +295 -0
  42. package/out/src/__tests/WalletPermissionsManager.encryption.test.js.map +1 -0
  43. package/out/src/__tests/WalletPermissionsManager.fixtures.d.ts +83 -0
  44. package/out/src/__tests/WalletPermissionsManager.fixtures.d.ts.map +1 -0
  45. package/out/src/__tests/WalletPermissionsManager.fixtures.js +261 -0
  46. package/out/src/__tests/WalletPermissionsManager.fixtures.js.map +1 -0
  47. package/out/src/__tests/WalletPermissionsManager.flows.test.d.ts +2 -0
  48. package/out/src/__tests/WalletPermissionsManager.flows.test.d.ts.map +1 -0
  49. package/out/src/__tests/WalletPermissionsManager.flows.test.js +377 -0
  50. package/out/src/__tests/WalletPermissionsManager.flows.test.js.map +1 -0
  51. package/out/src/__tests/WalletPermissionsManager.initialization.test.d.ts +2 -0
  52. package/out/src/__tests/WalletPermissionsManager.initialization.test.d.ts.map +1 -0
  53. package/out/src/__tests/WalletPermissionsManager.initialization.test.js +227 -0
  54. package/out/src/__tests/WalletPermissionsManager.initialization.test.js.map +1 -0
  55. package/out/src/__tests/WalletPermissionsManager.proxying.test.d.ts +2 -0
  56. package/out/src/__tests/WalletPermissionsManager.proxying.test.d.ts.map +1 -0
  57. package/out/src/__tests/WalletPermissionsManager.proxying.test.js +566 -0
  58. package/out/src/__tests/WalletPermissionsManager.proxying.test.js.map +1 -0
  59. package/out/src/__tests/WalletPermissionsManager.tokens.test.d.ts +2 -0
  60. package/out/src/__tests/WalletPermissionsManager.tokens.test.d.ts.map +1 -0
  61. package/out/src/__tests/WalletPermissionsManager.tokens.test.js +454 -0
  62. package/out/src/__tests/WalletPermissionsManager.tokens.test.js.map +1 -0
  63. package/out/src/index.all.d.ts +9 -0
  64. package/out/src/index.all.d.ts.map +1 -1
  65. package/out/src/index.all.js +9 -0
  66. package/out/src/index.all.js.map +1 -1
  67. package/out/src/index.client.d.ts +9 -0
  68. package/out/src/index.client.d.ts.map +1 -1
  69. package/out/src/index.client.js +9 -0
  70. package/out/src/index.client.js.map +1 -1
  71. package/out/src/sdk/CertOpsWallet.d.ts +7 -0
  72. package/out/src/sdk/CertOpsWallet.d.ts.map +1 -0
  73. package/out/src/sdk/CertOpsWallet.js +3 -0
  74. package/out/src/sdk/CertOpsWallet.js.map +1 -0
  75. package/out/src/sdk/__test/CertificateLifeCycle.test.js +19 -82
  76. package/out/src/sdk/__test/CertificateLifeCycle.test.js.map +1 -1
  77. package/out/src/sdk/index.d.ts +1 -1
  78. package/out/src/sdk/index.d.ts.map +1 -1
  79. package/out/src/sdk/index.js +1 -1
  80. package/out/src/sdk/index.js.map +1 -1
  81. package/out/src/sdk/validationHelpers.d.ts.map +1 -1
  82. package/out/src/sdk/validationHelpers.js +13 -12
  83. package/out/src/sdk/validationHelpers.js.map +1 -1
  84. package/out/src/services/__tests/bitrails.test.js +7 -2
  85. package/out/src/services/__tests/bitrails.test.js.map +1 -1
  86. package/out/src/services/providers/__tests/WhatsOnChain.test.js +3 -3
  87. package/out/src/services/providers/__tests/WhatsOnChain.test.js.map +1 -1
  88. package/out/src/signer/methods/proveCertificate.d.ts.map +1 -1
  89. package/out/src/signer/methods/proveCertificate.js +3 -19
  90. package/out/src/signer/methods/proveCertificate.js.map +1 -1
  91. package/out/src/storage/__test/WalletStorageManager.test.js +1 -1
  92. package/out/src/storage/__test/WalletStorageManager.test.js.map +1 -1
  93. package/out/src/storage/remoting/StorageClient.d.ts +2 -2
  94. package/out/src/storage/remoting/StorageClient.d.ts.map +1 -1
  95. package/out/src/storage/remoting/StorageClient.js +1 -1
  96. package/out/src/storage/remoting/StorageClient.js.map +1 -1
  97. package/out/src/utility/identityUtils.d.ts +31 -0
  98. package/out/src/utility/identityUtils.d.ts.map +1 -0
  99. package/out/src/utility/identityUtils.js +116 -0
  100. package/out/src/utility/identityUtils.js.map +1 -0
  101. package/out/src/wab-client/WABClient.d.ts +49 -0
  102. package/out/src/wab-client/WABClient.d.ts.map +1 -0
  103. package/out/src/wab-client/WABClient.js +83 -0
  104. package/out/src/wab-client/WABClient.js.map +1 -0
  105. package/out/src/wab-client/__tests/WABClient.man.test.d.ts +2 -0
  106. package/out/src/wab-client/__tests/WABClient.man.test.d.ts.map +1 -0
  107. package/out/src/wab-client/__tests/WABClient.man.test.js +52 -0
  108. package/out/src/wab-client/__tests/WABClient.man.test.js.map +1 -0
  109. package/out/src/wab-client/auth-method-interactors/AuthMethodInteractor.d.ts +34 -0
  110. package/out/src/wab-client/auth-method-interactors/AuthMethodInteractor.d.ts.map +1 -0
  111. package/out/src/wab-client/auth-method-interactors/AuthMethodInteractor.js +16 -0
  112. package/out/src/wab-client/auth-method-interactors/AuthMethodInteractor.js.map +1 -0
  113. package/out/src/wab-client/auth-method-interactors/PersonaIDInteractor.d.ts +7 -0
  114. package/out/src/wab-client/auth-method-interactors/PersonaIDInteractor.d.ts.map +1 -0
  115. package/out/src/wab-client/auth-method-interactors/PersonaIDInteractor.js +36 -0
  116. package/out/src/wab-client/auth-method-interactors/PersonaIDInteractor.js.map +1 -0
  117. package/out/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.d.ts +28 -0
  118. package/out/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.d.ts.map +1 -0
  119. package/out/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.js +69 -0
  120. package/out/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.js.map +1 -0
  121. package/out/test/Wallet/action/internalizeAction.a.test.js +1 -1
  122. package/out/test/Wallet/action/internalizeAction.a.test.js.map +1 -1
  123. package/out/test/Wallet/certificate/acquireCertificate.test.js +26 -29
  124. package/out/test/Wallet/certificate/acquireCertificate.test.js.map +1 -1
  125. package/out/test/storage/KnexMigrations.test.js +1 -1
  126. package/out/test/storage/KnexMigrations.test.js.map +1 -1
  127. package/out/test/storage/update.test.js +1 -1
  128. package/out/test/storage/update.test.js.map +1 -1
  129. package/out/test/utils/TestUtilsWalletStorage.d.ts +9 -5
  130. package/out/test/utils/TestUtilsWalletStorage.d.ts.map +1 -1
  131. package/out/test/utils/TestUtilsWalletStorage.js +15 -9
  132. package/out/test/utils/TestUtilsWalletStorage.js.map +1 -1
  133. package/out/test/wallet/action/internalizeAction.test.js +1 -1
  134. package/out/test/wallet/action/internalizeAction.test.js.map +1 -1
  135. package/out/test/wallet/list/listActions2.test.js +1 -1
  136. package/out/test/wallet/list/listActions2.test.js.map +1 -1
  137. package/out/test/wallet/sync/Wallet.sync.test.js +1 -1
  138. package/out/test/wallet/sync/Wallet.sync.test.js.map +1 -1
  139. package/out/tsconfig.all.tsbuildinfo +1 -1
  140. package/package.json +3 -4
  141. package/src/CWIStyleWalletManager.ts +1724 -0
  142. package/src/SimpleWalletManager.ts +526 -0
  143. package/src/Wallet.ts +70 -7
  144. package/src/WalletAuthenticationManager.ts +150 -0
  145. package/src/WalletPermissionsManager.ts +2424 -0
  146. package/src/WalletSettingsManager.ts +243 -0
  147. package/src/__tests/CWIStyleWalletManager.test.ts +604 -0
  148. package/src/__tests/WalletPermissionsManager.callbacks.test.ts +323 -0
  149. package/src/__tests/WalletPermissionsManager.checks.test.ts +839 -0
  150. package/src/__tests/WalletPermissionsManager.encryption.test.ts +370 -0
  151. package/src/__tests/WalletPermissionsManager.fixtures.ts +284 -0
  152. package/src/__tests/WalletPermissionsManager.flows.test.ts +457 -0
  153. package/src/__tests/WalletPermissionsManager.initialization.test.ts +300 -0
  154. package/src/__tests/WalletPermissionsManager.proxying.test.ts +706 -0
  155. package/src/__tests/WalletPermissionsManager.tokens.test.ts +546 -0
  156. package/src/index.all.ts +9 -0
  157. package/src/index.client.ts +9 -0
  158. package/src/sdk/CertOpsWallet.ts +18 -0
  159. package/src/sdk/__test/CertificateLifeCycle.test.ts +66 -113
  160. package/src/sdk/index.ts +1 -1
  161. package/src/sdk/validationHelpers.ts +12 -11
  162. package/src/services/__tests/bitrails.test.ts +7 -2
  163. package/src/services/providers/__tests/WhatsOnChain.test.ts +3 -3
  164. package/src/signer/methods/proveCertificate.ts +14 -21
  165. package/src/storage/__test/WalletStorageManager.test.ts +1 -1
  166. package/src/storage/remoting/StorageClient.ts +4 -4
  167. package/src/utility/identityUtils.ts +159 -0
  168. package/src/wab-client/WABClient.ts +94 -0
  169. package/src/wab-client/__tests/WABClient.man.test.ts +59 -0
  170. package/src/wab-client/auth-method-interactors/AuthMethodInteractor.ts +47 -0
  171. package/src/wab-client/auth-method-interactors/PersonaIDInteractor.ts +35 -0
  172. package/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.ts +72 -0
  173. package/test/Wallet/action/internalizeAction.a.test.ts +1 -1
  174. package/test/Wallet/certificate/acquireCertificate.test.ts +89 -30
  175. package/test/storage/KnexMigrations.test.ts +1 -1
  176. package/test/storage/update.test.ts +1 -1
  177. package/test/utils/TestUtilsWalletStorage.ts +24 -13
  178. package/test/wallet/action/internalizeAction.test.ts +1 -1
  179. package/test/wallet/list/listActions2.test.ts +1 -1
  180. package/test/wallet/sync/Wallet.sync.test.ts +1 -1
  181. package/out/src/sdk/CertOps.d.ts +0 -66
  182. package/out/src/sdk/CertOps.d.ts.map +0 -1
  183. package/out/src/sdk/CertOps.js +0 -190
  184. package/out/src/sdk/CertOps.js.map +0 -1
  185. package/src/sdk/CertOps.ts +0 -274
@@ -0,0 +1,637 @@
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
+ describe('WalletPermissionsManager - Permission Checks', () => {
7
+ let underlying;
8
+ let manager;
9
+ beforeEach(() => {
10
+ // Fresh mock wallet before each test
11
+ underlying = (0, WalletPermissionsManager_fixtures_1.mockUnderlyingWallet)();
12
+ });
13
+ afterEach(() => {
14
+ jest.clearAllMocks();
15
+ });
16
+ /* ------------------------------------------------------
17
+ * 5) PROTOCOL USAGE (DPACP) TESTS
18
+ * ------------------------------------------------------ */
19
+ describe('Protocol Usage (DPACP)', () => {
20
+ it('should skip permission prompt if secLevel=0 (open usage)', async () => {
21
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com', {
22
+ seekProtocolPermissionsForSigning: true // Typically enforced
23
+ });
24
+ // Attempt createSignature with protocolID=[0, "someProtocol"]
25
+ // Because securityLevel=0, the manager should skip checks
26
+ await expect(manager.createSignature({
27
+ protocolID: [0, 'open-protocol'],
28
+ data: [0x01, 0x02],
29
+ keyID: '1'
30
+ }, 'some-user.com')).resolves.not.toThrow();
31
+ // No permission request
32
+ const activeRequests = manager.activeRequests;
33
+ expect(activeRequests.size).toBe(0);
34
+ // Underlying createSignature called once
35
+ expect(underlying.createSignature).toHaveBeenCalledTimes(1);
36
+ });
37
+ it('should prompt for protocol usage if securityLevel=1 and no existing token', async () => {
38
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com', {
39
+ seekProtocolPermissionsForSigning: true
40
+ });
41
+ // We'll bind a callback that grants ephemeral permission automatically
42
+ manager.bindCallback('onProtocolPermissionRequested', async (request) => {
43
+ // For tests, automatically grant ephemeral permission
44
+ await manager.grantPermission({
45
+ requestID: request.requestID,
46
+ ephemeral: true
47
+ });
48
+ });
49
+ // Because secLevel=1, we need a valid DPACP token
50
+ // We have no token => manager triggers a request => callback grants ephemeral => passes
51
+ await expect(manager.createSignature({
52
+ protocolID: [1, 'test-protocol'],
53
+ data: [0x99, 0xaa],
54
+ keyID: '1'
55
+ }, 'some-nonadmin.com')).resolves.not.toThrow();
56
+ // The underlying signature should succeed
57
+ expect(underlying.createSignature).toHaveBeenCalledTimes(1);
58
+ });
59
+ it('should deny protocol usage if user denies permission', async () => {
60
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com', {});
61
+ // The callback denies the request
62
+ manager.bindCallback('onProtocolPermissionRequested', request => {
63
+ manager.denyPermission(request.requestID);
64
+ });
65
+ // Attempt an operation that requires protocol permission
66
+ await expect(manager.encrypt({
67
+ protocolID: [1, 'needs-perm'],
68
+ plaintext: [1, 2, 3],
69
+ keyID: 'xyz'
70
+ }, 'external-app.com')).rejects.toThrow(/Permission denied/);
71
+ // Underlying encrypt was never called
72
+ expect(underlying.encrypt).toHaveBeenCalledTimes(0);
73
+ });
74
+ it('should enforce privileged token if differentiatePrivilegedOperations=true', async () => {
75
+ // By default, differentiatePrivilegedOperations is true.
76
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com', {
77
+ seekProtocolPermissionsForSigning: true
78
+ });
79
+ manager.bindCallback('onProtocolPermissionRequested', async (req) => {
80
+ // The request has `privileged=true`, so the resulting token must also be privileged.
81
+ // We'll grant ephemeral to simulate success quickly.
82
+ await manager.grantPermission({
83
+ requestID: req.requestID,
84
+ ephemeral: true
85
+ });
86
+ });
87
+ // Attempt a privileged signature
88
+ await expect(manager.createSignature({
89
+ protocolID: [1, 'high-level-crypto'],
90
+ privileged: true,
91
+ data: [0xc0, 0xff, 0xee],
92
+ keyID: '1'
93
+ }, 'nonadmin.app')).resolves.not.toThrow();
94
+ // Confirm underlying was ultimately called
95
+ expect(underlying.createSignature).toHaveBeenCalledTimes(1);
96
+ });
97
+ it('should ignore `privileged=true` if differentiatePrivilegedOperations=false', async () => {
98
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com', {
99
+ differentiatePrivilegedOperations: false, // Forces privileged usage to be treated as non-privileged
100
+ seekProtocolPermissionsForSigning: true
101
+ });
102
+ // Because we treat privileged as false, the permission request does not need privileged credentials.
103
+ manager.bindCallback('onProtocolPermissionRequested', async (req) => {
104
+ await manager.grantPermission({
105
+ requestID: req.requestID,
106
+ ephemeral: true
107
+ });
108
+ });
109
+ await expect(manager.createSignature({
110
+ protocolID: [1, 'some-protocol'],
111
+ privileged: true, // This flag will be ignored
112
+ data: [0x99],
113
+ keyID: 'keyXYZ'
114
+ }, 'nonadmin.com')).resolves.not.toThrow();
115
+ });
116
+ it('should fail if protocol name is admin-reserved and caller is not admin', async () => {
117
+ // admin-reserved means protocol name starts with "admin" or "p ".
118
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'secure.admin.com');
119
+ // Non-admin tries to do e.g. `createHmac` with protocol name "admin super-secret"
120
+ await expect(manager.createHmac({
121
+ protocolID: [1, 'admin super-secret'],
122
+ data: [0x01, 0x02],
123
+ keyID: '1'
124
+ }, 'not-an-admin.com')).rejects.toThrow(/admin-only/i);
125
+ // Underlying call never invoked
126
+ expect(underlying.createHmac).toHaveBeenCalledTimes(0);
127
+ });
128
+ it('should prompt for renewal if token is found but expired', async () => {
129
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com', {});
130
+ // Suppose the user already had a token but it’s expired. We mock `findProtocolToken` so that
131
+ // it returns an expired token, forcing a renewal request.
132
+ const expiredToken = {
133
+ txid: 'oldtxid123',
134
+ outputIndex: 0,
135
+ outputScript: 'deadbeef',
136
+ satoshis: 1,
137
+ originator: 'some-nonadmin.com',
138
+ expiry: 1, // definitely in the past
139
+ privileged: false,
140
+ securityLevel: 1,
141
+ protocol: 'test-protocol',
142
+ counterparty: 'self'
143
+ };
144
+ jest.spyOn(manager, 'findProtocolToken').mockResolvedValue(expiredToken);
145
+ // We'll bind a callback that grants a renewal ephemeral
146
+ manager.bindCallback('onProtocolPermissionRequested', async (req) => {
147
+ expect(req.renewal).toBe(true);
148
+ expect(req.previousToken).toEqual(expiredToken);
149
+ await manager.grantPermission({
150
+ requestID: req.requestID,
151
+ ephemeral: true
152
+ });
153
+ });
154
+ // Now call an operation that requires protocol usage
155
+ await manager.createSignature({
156
+ protocolID: [1, 'test-protocol'],
157
+ data: [0xfe],
158
+ keyID: '1'
159
+ }, 'some-nonadmin.com');
160
+ // Should succeed after renewal
161
+ expect(underlying.createSignature).toHaveBeenCalledTimes(1);
162
+ });
163
+ });
164
+ /* ------------------------------------------------------
165
+ * 6) BASKET USAGE (DBAP) TESTS
166
+ * ------------------------------------------------------ */
167
+ describe('Basket Usage (DBAP)', () => {
168
+ it('should fail immediately if using an admin-only basket as non-admin', async () => {
169
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com');
170
+ // Attempt to createAction to insert into "admin secret-basket" from a non-admin origin
171
+ await expect(manager.createAction({
172
+ description: 'Insert into admin basket',
173
+ outputs: [
174
+ {
175
+ lockingScript: 'abcd',
176
+ satoshis: 100,
177
+ basket: 'admin secret-basket',
178
+ outputDescription: 'Nothing to see here'
179
+ }
180
+ ]
181
+ }, 'non-admin.com')).rejects.toThrow(/admin-only/i);
182
+ // Underlying createAction never called
183
+ expect(underlying.createAction).toHaveBeenCalledTimes(0);
184
+ });
185
+ it('should fail immediately if using the reserved basket "default" as non-admin', async () => {
186
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com');
187
+ await expect(manager.createAction({
188
+ description: 'Insert to default basket',
189
+ outputs: [
190
+ {
191
+ lockingScript: '0x1234',
192
+ satoshis: 1,
193
+ basket: 'default',
194
+ outputDescription: 'Nothing to see here'
195
+ }
196
+ ]
197
+ }, 'some-nonadmin.com')).rejects.toThrow(/admin-only/i);
198
+ });
199
+ it('should prompt for insertion permission if seekBasketInsertionPermissions=true', async () => {
200
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com', {
201
+ seekBasketInsertionPermissions: true
202
+ });
203
+ // auto-grant ephemeral
204
+ manager.bindCallback('onBasketAccessRequested', async (req) => {
205
+ await manager.grantPermission({
206
+ requestID: req.requestID,
207
+ ephemeral: true
208
+ });
209
+ });
210
+ // Also auto-grant unrelated spending authorization (since this is createAction)
211
+ manager.bindCallback('onSpendingAuthorizationRequested', async (req) => {
212
+ await manager.grantPermission({
213
+ requestID: req.requestID,
214
+ ephemeral: true
215
+ });
216
+ });
217
+ await expect(manager.createAction({
218
+ description: 'Insert to user-basket',
219
+ outputs: [
220
+ {
221
+ lockingScript: '7812',
222
+ satoshis: 1,
223
+ basket: 'user-basket',
224
+ outputDescription: 'Nothing to see here'
225
+ }
226
+ ]
227
+ }, 'some-nonadmin.com')).resolves.not.toThrow();
228
+ // Confirm underlying createAction was eventually invoked
229
+ expect(underlying.createAction).toHaveBeenCalledTimes(1);
230
+ });
231
+ it('should skip insertion permission if seekBasketInsertionPermissions=false', async () => {
232
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com', {
233
+ seekBasketInsertionPermissions: false
234
+ });
235
+ // Auto-grant unrelated spending authorization (since this is createAction)
236
+ manager.bindCallback('onSpendingAuthorizationRequested', async (req) => {
237
+ await manager.grantPermission({
238
+ requestID: req.requestID,
239
+ ephemeral: true
240
+ });
241
+ });
242
+ await manager.createAction({
243
+ description: 'Insert to user-basket',
244
+ outputs: [
245
+ {
246
+ lockingScript: '1234',
247
+ satoshis: 1,
248
+ basket: 'some-basket',
249
+ outputDescription: 'Nothing to see here'
250
+ }
251
+ ]
252
+ }, 'nonadmin.com');
253
+ // No requests queued, underlying is called
254
+ const activeRequests = manager.activeRequests;
255
+ expect(activeRequests.size).toBe(0);
256
+ expect(underlying.createAction).toHaveBeenCalledTimes(1);
257
+ });
258
+ it('should require listing permission if seekBasketListingPermissions=true and no token', async () => {
259
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com', {
260
+ seekBasketListingPermissions: true
261
+ });
262
+ manager.bindCallback('onBasketAccessRequested', async (req) => {
263
+ // Deny for test
264
+ manager.denyPermission(req.requestID);
265
+ });
266
+ // Attempt to list a user basket
267
+ await expect(manager.listOutputs({ basket: 'user-basket' }, 'some-user.com')).rejects.toThrow(/Permission denied/);
268
+ // There is one underlying call: internally, we called listOutputs to check if we had permission
269
+ // (we did not, we sought it, and the user denied). So we see this call here, but we DO NOT see
270
+ // the actual proxied call (for listing outputs in user-basket), since it was denied.
271
+ expect(underlying.listOutputs).toHaveBeenCalledTimes(1);
272
+ expect(underlying.listOutputs).toHaveBeenLastCalledWith({
273
+ basket: 'admin basket-access',
274
+ include: 'locking scripts',
275
+ tagQueryMode: 'all',
276
+ tags: ['originator some-user.com', 'basket user-basket']
277
+ }, 'admin.com');
278
+ });
279
+ it('should prompt for removal permission if seekBasketRemovalPermissions=true', async () => {
280
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com', {
281
+ seekBasketRemovalPermissions: true
282
+ });
283
+ manager.bindCallback('onBasketAccessRequested', async (req) => {
284
+ // auto-grant ephemeral
285
+ await manager.grantPermission({
286
+ requestID: req.requestID,
287
+ ephemeral: true
288
+ });
289
+ });
290
+ await expect(manager.relinquishOutput({
291
+ output: 'someTxid.1',
292
+ basket: 'user-basket'
293
+ }, 'some-user.com')).resolves.not.toThrow();
294
+ expect(underlying.relinquishOutput).toHaveBeenCalledTimes(1);
295
+ });
296
+ });
297
+ /* ------------------------------------------------------
298
+ * 7) CERTIFICATE USAGE (DCAP) TESTS
299
+ * ------------------------------------------------------ */
300
+ describe('Certificate Usage (DCAP)', () => {
301
+ it('should skip certificate disclosure permission if config.seekCertificateDisclosurePermissions=false', async () => {
302
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com', {
303
+ seekCertificateDisclosurePermissions: false
304
+ });
305
+ // Directly call proveCertificate with no token => no prompt => immediate success
306
+ await expect(manager.proveCertificate({
307
+ certificate: {
308
+ type: 'KYC',
309
+ subject: '02abcdef...',
310
+ serialNumber: '123',
311
+ certifier: '02ccc...',
312
+ fields: { name: 'Alice', dob: '2000-01-01' }
313
+ },
314
+ fieldsToReveal: ['name'],
315
+ verifier: '02xyz...',
316
+ privileged: false
317
+ }, 'nonadmin.com')).resolves.not.toThrow();
318
+ expect(underlying.proveCertificate).toHaveBeenCalledTimes(1);
319
+ });
320
+ it('should require permission if seekCertificateDisclosurePermissions=true, no valid token', async () => {
321
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com', {
322
+ seekCertificateDisclosurePermissions: true
323
+ });
324
+ // Auto-grant ephemeral for test
325
+ manager.bindCallback('onCertificateAccessRequested', async (req) => {
326
+ await manager.grantPermission({
327
+ requestID: req.requestID,
328
+ ephemeral: true
329
+ });
330
+ });
331
+ // Because we don't have a stored token, it triggers request -> ephemeral granted -> success
332
+ await manager.proveCertificate({
333
+ certificate: {
334
+ type: 'KYC',
335
+ subject: '02abc..',
336
+ serialNumber: 'xyz',
337
+ certifier: '02dddd...',
338
+ fields: { name: 'Bob', nationality: 'Mars' }
339
+ },
340
+ fieldsToReveal: ['name'],
341
+ verifier: '02xxxx..',
342
+ privileged: false
343
+ }, 'some-user.com');
344
+ expect(underlying.proveCertificate).toHaveBeenCalledTimes(1);
345
+ });
346
+ it('should check that requested fields are a subset of the token’s fields', async () => {
347
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com', {
348
+ seekCertificateDisclosurePermissions: true
349
+ });
350
+ // Suppose we find an existing token that covers fields: ['name', 'dob', 'nationality']
351
+ const existingToken = {
352
+ txid: 'aabbcc',
353
+ outputIndex: 0,
354
+ outputScript: 'scriptHex',
355
+ satoshis: 1,
356
+ originator: 'some-user.com',
357
+ expiry: 9999999999, // not expired
358
+ privileged: false,
359
+ certType: 'KYC',
360
+ certFields: ['name', 'dob', 'nationality'],
361
+ verifier: '02eeee...'
362
+ };
363
+ jest
364
+ .spyOn(manager, 'findCertificateToken')
365
+ .mockImplementation(async (orig, priv, verif, ct, requestedFields) => {
366
+ // if requestedFields includes "someMissingField", return undefined
367
+ // else return the existingToken
368
+ if (requestedFields.includes('someMissingField')) {
369
+ return undefined; // forces a request
370
+ }
371
+ return existingToken; // forces immediate success
372
+ });
373
+ // Attempt to prove certificate revealing only 'name' -> Should pass without prompt
374
+ await manager.proveCertificate({
375
+ certificate: {
376
+ type: 'KYC',
377
+ certifier: '02eeee...',
378
+ subject: '02some...',
379
+ serialNumber: '',
380
+ fields: { name: 'Charlie', dob: '1999-01-01', nationality: 'EU' }
381
+ },
382
+ fieldsToReveal: ['name'],
383
+ verifier: '02eeee...',
384
+ privileged: false
385
+ }, 'some-user.com');
386
+ expect(underlying.proveCertificate).toHaveBeenCalledTimes(1);
387
+ // Attempt to reveal a field the token does NOT cover -> triggers request
388
+ // Since the existing token does not cover 'someMissingField', we expect a prompt. Let’s deny it:
389
+ manager.bindCallback('onCertificateAccessRequested', async (req) => {
390
+ manager.denyPermission(req.requestID);
391
+ });
392
+ const secondAttempt = manager.proveCertificate({
393
+ certificate: {
394
+ type: 'KYC',
395
+ certifier: '02eeee...',
396
+ fields: { name: 'Charlie', dob: '1999-01-01', nationality: 'EU' }
397
+ },
398
+ fieldsToReveal: ['dob', 'someMissingField'],
399
+ verifier: '02eeee...',
400
+ privileged: false
401
+ }, 'some-user.com');
402
+ await expect(secondAttempt).rejects.toThrow(/Permission denied/);
403
+ // Underlying proveCertificate not called for second attempt
404
+ expect(underlying.proveCertificate).toHaveBeenCalledTimes(1);
405
+ });
406
+ it('should prompt for renewal if token is expired', async () => {
407
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com', {
408
+ seekCertificateDisclosurePermissions: true
409
+ });
410
+ // Mock an expired token
411
+ const expiredCertToken = {
412
+ txid: 'old-expired',
413
+ outputIndex: 0,
414
+ outputScript: 'deadbeef',
415
+ satoshis: 1,
416
+ originator: 'app.com',
417
+ expiry: 1,
418
+ privileged: false,
419
+ certType: 'KYC',
420
+ certFields: ['name', 'dob'],
421
+ verifier: '02verifier'
422
+ };
423
+ jest.spyOn(manager, 'findCertificateToken').mockResolvedValue(expiredCertToken);
424
+ // Callback that grants renewal ephemeral
425
+ manager.bindCallback('onCertificateAccessRequested', async (req) => {
426
+ expect(req.renewal).toBe(true);
427
+ await manager.grantPermission({
428
+ requestID: req.requestID,
429
+ ephemeral: true
430
+ });
431
+ });
432
+ await manager.proveCertificate({
433
+ certificate: {
434
+ type: 'KYC',
435
+ fields: { name: 'Bob', dob: '1970' },
436
+ certifier: '02verifier'
437
+ },
438
+ fieldsToReveal: ['name'],
439
+ verifier: '02verifier',
440
+ privileged: false
441
+ }, 'app.com');
442
+ // Succeeds after ephemeral renewal
443
+ expect(underlying.proveCertificate).toHaveBeenCalledTimes(1);
444
+ });
445
+ });
446
+ /* ------------------------------------------------------
447
+ * 8) SPENDING AUTHORIZATION (DSAP) TESTS
448
+ * ------------------------------------------------------ */
449
+ describe('Spending Authorization (DSAP)', () => {
450
+ it('should skip if seekSpendingPermissions=false', async () => {
451
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com', {
452
+ seekSpendingPermissions: false
453
+ });
454
+ // createAction that tries to net spend 200 sats
455
+ const result = await manager.createAction({
456
+ description: 'Some spend transaction',
457
+ outputs: [
458
+ {
459
+ lockingScript: '1321',
460
+ satoshis: 200,
461
+ outputDescription: 'Nothing to see here'
462
+ }
463
+ ]
464
+ }, 'user.com');
465
+ // No prompt triggered
466
+ const activeRequests = manager.activeRequests;
467
+ expect(activeRequests.size).toBe(0);
468
+ // Underlying createAction definitely called
469
+ expect(underlying.createAction).toHaveBeenCalledTimes(1);
470
+ // If seekSpendingPermissions=false, the result should NOT? contain the signableTransaction
471
+ expect(result.signableTransaction).not.toBeDefined();
472
+ });
473
+ it('should require spending token if netSpent > 0 and seekSpendingPermissions=true', async () => {
474
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com', {
475
+ seekSpendingPermissions: true
476
+ });
477
+ // We’ll also mock the signableTransaction return to help manager compute netSpent
478
+ underlying.createAction.mockResolvedValueOnce({
479
+ signableTransaction: {
480
+ tx: [0x00], // minimal
481
+ reference: 'ref1'
482
+ }
483
+ });
484
+ // The manager tries to parse the transaction to find netSpent.
485
+ // By default, netSpent = totalOutput + fee - totalExplicitInputs
486
+ // We haven't provided any explicit inputs in the createAction call, so netSpent = 200 + fee
487
+ // Auto-grant ephemeral for test
488
+ manager.bindCallback('onSpendingAuthorizationRequested', async (req) => {
489
+ await manager.grantPermission({
490
+ requestID: req.requestID,
491
+ ephemeral: true,
492
+ amount: 1000
493
+ });
494
+ });
495
+ await expect(manager.createAction({
496
+ description: 'Spend 200 sats with no input from user',
497
+ outputs: [
498
+ {
499
+ outputDescription: 'Nothing to see here',
500
+ lockingScript: '1abc',
501
+ satoshis: 200
502
+ }
503
+ ]
504
+ }, 'some-user.com')).resolves.not.toThrow();
505
+ // underlying createAction called
506
+ expect(underlying.createAction).toHaveBeenCalledTimes(1);
507
+ });
508
+ it('should check monthly limit usage and prompt renewal if insufficient', async () => {
509
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com');
510
+ // Suppose we find an existing DSAP token with authorizedAmount=500
511
+ // manager.findSpendingToken() is used internally, so let's mock it
512
+ const existingSpendingToken = {
513
+ txid: 'dsap-old',
514
+ outputIndex: 0,
515
+ outputScript: 'scriptHex',
516
+ satoshis: 1,
517
+ originator: 'shopper.com',
518
+ authorizedAmount: 500,
519
+ expiry: 0 // indefinite
520
+ };
521
+ jest.spyOn(manager, 'findSpendingToken').mockResolvedValue(existingSpendingToken);
522
+ // Next, manager.querySpentSince(token) sums the user’s monthly spending from labeled actions
523
+ // Let’s stub that to say they've already spent 400.
524
+ jest.spyOn(manager, 'querySpentSince').mockResolvedValue(400);
525
+ // Attempt spending 200 => total usage would be 600 which exceeds 500 => prompt renewal
526
+ // We'll auto-deny for test
527
+ manager.bindCallback('onSpendingAuthorizationRequested', req => {
528
+ manager.denyPermission(req.requestID);
529
+ });
530
+ await expect(manager.createAction({
531
+ description: 'Buy something for 200 sats',
532
+ outputs: [
533
+ {
534
+ outputDescription: 'Nothing to see here',
535
+ lockingScript: 'op_return',
536
+ satoshis: 200
537
+ }
538
+ ]
539
+ }, 'shopper.com')).rejects.toThrow(/Permission denied/);
540
+ // The underlying createAction call was started but the manager calls abortAction upon denial
541
+ expect(underlying.abortAction).toHaveBeenCalledTimes(1);
542
+ });
543
+ it('should pass if usage plus new spend is within the monthly limit', async () => {
544
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com', {});
545
+ // existing DSAP token with authorizedAmount=1000
546
+ const dsapToken = {
547
+ txid: 'dsap123',
548
+ outputIndex: 0,
549
+ outputScript: '9218',
550
+ satoshis: 1,
551
+ originator: 'shopper.com',
552
+ authorizedAmount: 1000,
553
+ expiry: 0
554
+ };
555
+ jest.spyOn(manager, 'findSpendingToken').mockResolvedValue(dsapToken);
556
+ // Suppose they've spent 200 so far
557
+ jest.spyOn(manager, 'querySpentSince').mockResolvedValue(200);
558
+ // Attempt new spending of 500 => total=700 which is <= 1000 => no prompt
559
+ await manager.createAction({
560
+ description: 'Spend 500 sats',
561
+ outputs: [
562
+ {
563
+ outputDescription: 'Nothing to see here',
564
+ lockingScript: '0abc',
565
+ satoshis: 500
566
+ }
567
+ ]
568
+ }, 'shopper.com');
569
+ // Success, no new permission requested
570
+ const activeRequests = manager.activeRequests;
571
+ expect(activeRequests.size).toBe(0);
572
+ expect(underlying.createAction).toHaveBeenCalledTimes(1);
573
+ });
574
+ });
575
+ /* ------------------------------------------------------
576
+ * 9) LABEL USAGE PERMISSION TESTS
577
+ * ------------------------------------------------------ */
578
+ describe('Label Usage Permission', () => {
579
+ it('should fail if label starts with "admin" and caller is not admin', async () => {
580
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com');
581
+ // Attempt to createAction with a label "admin secret-stuff"
582
+ await expect(manager.createAction({
583
+ description: 'Applying admin label?',
584
+ labels: ['admin secret-stuff']
585
+ }, 'nonadmin.com')).rejects.toThrow(/admin-only/);
586
+ // Underlying createAction never called
587
+ expect(underlying.createAction).toHaveBeenCalledTimes(0);
588
+ });
589
+ it('should skip label permission if seekPermissionWhenApplyingActionLabels=false', async () => {
590
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com', {
591
+ seekPermissionWhenApplyingActionLabels: false
592
+ });
593
+ // Non-admin applies label "my-app-label"
594
+ await expect(manager.createAction({ description: 'Add label', labels: ['my-app-label'] }, 'some-app.com')).resolves.not.toThrow();
595
+ // No prompt
596
+ const activeRequests = manager.activeRequests;
597
+ expect(activeRequests.size).toBe(0);
598
+ // Called underlying
599
+ expect(underlying.createAction).toHaveBeenCalledTimes(1);
600
+ });
601
+ it('should prompt for label usage if seekPermissionWhenApplyingActionLabels=true', async () => {
602
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com', {
603
+ seekPermissionWhenApplyingActionLabels: true
604
+ });
605
+ manager.bindCallback('onProtocolPermissionRequested', async (req) => {
606
+ // This request will have protocolID=[1, "action label <label>"], etc.
607
+ await manager.grantPermission({
608
+ requestID: req.requestID,
609
+ ephemeral: true
610
+ });
611
+ });
612
+ await manager.createAction({
613
+ description: 'Add label "user-label-123"',
614
+ labels: ['user-label-123']
615
+ }, 'nonadmin.com');
616
+ // Underlying is called
617
+ expect(underlying.createAction).toHaveBeenCalledTimes(1);
618
+ });
619
+ it('should also prompt for listing actions by label if seekPermissionWhenListingActionsByLabel=true', async () => {
620
+ manager = new WalletPermissionsManager_1.WalletPermissionsManager(underlying, 'admin.com', {
621
+ seekPermissionWhenListingActionsByLabel: true
622
+ });
623
+ manager.bindCallback('onProtocolPermissionRequested', async (req) => {
624
+ // auto-grant ephemeral
625
+ await manager.grantPermission({
626
+ requestID: req.requestID,
627
+ ephemeral: true
628
+ });
629
+ });
630
+ await expect(manager.listActions({
631
+ labels: ['search-this-label']
632
+ }, 'external-app.com')).resolves.not.toThrow();
633
+ expect(underlying.listActions).toHaveBeenCalledTimes(1);
634
+ });
635
+ });
636
+ });
637
+ //# sourceMappingURL=WalletPermissionsManager.checks.test.js.map