@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
@@ -120,6 +120,58 @@ class WalletPermissionsManager {
120
120
  ...config // override with user-specified config
121
121
  };
122
122
  }
123
+ /* ---------------------------------------------------------------------
124
+ * HELPER METHODS FOR P-MODULE DELEGATION
125
+ * --------------------------------------------------------------------- */
126
+ /**
127
+ * Delegates a wallet method call to a P-module if the basket or protocol name uses a P-scheme.
128
+ * Handles the full request/response transformation flow.
129
+ *
130
+ * @param basketOrProtocolName - The basket or protocol name to check for p-module delegation
131
+ * @param method - The wallet method name being called
132
+ * @param args - The original args passed to the method
133
+ * @param originator - The originator of the request
134
+ * @param underlyingCall - Callback that executes the underlying wallet method with transformed args
135
+ * @returns The transformed response, or null if not a P-basket/protocol (caller should continue normal flow)
136
+ */
137
+ async delegateToPModuleIfNeeded(basketOrProtocolName, method, args, originator, underlyingCall) {
138
+ var _a;
139
+ // Check if this is a P-protocol/basket
140
+ if (!basketOrProtocolName.startsWith('p ')) {
141
+ return null; // If not, caller should continue normal flow
142
+ }
143
+ const schemeID = basketOrProtocolName.split(' ')[1];
144
+ const module = (_a = this.config.permissionModules) === null || _a === void 0 ? void 0 : _a[schemeID];
145
+ if (!module) {
146
+ throw new Error(`Unsupported P-module scheme: p ${schemeID}`);
147
+ }
148
+ // Transform request with module
149
+ const transformedReq = await module.onRequest({
150
+ method,
151
+ args,
152
+ originator
153
+ });
154
+ // Call underlying method with transformed request
155
+ const results = await underlyingCall(transformedReq.args, originator);
156
+ // Transform response with module
157
+ return await module.onResponse(results, {
158
+ method,
159
+ originator
160
+ });
161
+ }
162
+ /**
163
+ * Decrypts custom instructions in listOutputs results if encryption is configured.
164
+ */
165
+ async decryptListOutputsMetadata(results) {
166
+ if (results.outputs) {
167
+ for (let i = 0; i < results.outputs.length; i++) {
168
+ if (results.outputs[i].customInstructions) {
169
+ results.outputs[i].customInstructions = await this.maybeDecryptMetadata(results.outputs[i].customInstructions);
170
+ }
171
+ }
172
+ }
173
+ return results;
174
+ }
123
175
  /* ---------------------------------------------------------------------
124
176
  * 1) PUBLIC API FOR REGISTERING CALLBACKS (UI PROMPTS, LOGGING, ETC.)
125
177
  * --------------------------------------------------------------------- */
@@ -1717,19 +1769,39 @@ class WalletPermissionsManager {
1717
1769
  * 7) BRC-100 WALLET INTERFACE FORWARDING WITH PERMISSION CHECKS
1718
1770
  * --------------------------------------------------------------------- */
1719
1771
  async createAction(args, originator) {
1720
- // 1) Ensure basket and label permissions
1772
+ var _a;
1773
+ // 1) Identify unique P-modules involved (one per schemeID)
1774
+ const pModulesByScheme = new Map();
1775
+ const nonPBaskets = [];
1721
1776
  if (args.outputs) {
1722
1777
  for (const out of args.outputs) {
1723
1778
  if (out.basket) {
1724
- await this.ensureBasketAccess({
1725
- originator: originator,
1726
- basket: out.basket,
1727
- reason: args.description,
1728
- usageType: 'insertion'
1729
- });
1779
+ if (out.basket.startsWith('p ')) {
1780
+ const schemeID = out.basket.split(' ')[1];
1781
+ if (!pModulesByScheme.has(schemeID)) {
1782
+ const module = (_a = this.config.permissionModules) === null || _a === void 0 ? void 0 : _a[schemeID];
1783
+ if (!module) {
1784
+ throw new Error(`Unsupported P-basket scheme: p ${schemeID}`);
1785
+ }
1786
+ pModulesByScheme.set(schemeID, module);
1787
+ }
1788
+ }
1789
+ else {
1790
+ // Track non-P baskets for normal permission checks
1791
+ nonPBaskets.push(out.basket);
1792
+ }
1730
1793
  }
1731
1794
  }
1732
1795
  }
1796
+ // 2) Check permissions for non-P baskets
1797
+ for (const basket of nonPBaskets) {
1798
+ await this.ensureBasketAccess({
1799
+ originator: originator,
1800
+ basket,
1801
+ reason: args.description,
1802
+ usageType: 'insertion'
1803
+ });
1804
+ }
1733
1805
  if (args.labels) {
1734
1806
  for (const lbl of args.labels) {
1735
1807
  await this.ensureLabelAccess({
@@ -1741,7 +1813,7 @@ class WalletPermissionsManager {
1741
1813
  }
1742
1814
  }
1743
1815
  /**
1744
- * 2) Force signAndProcess=false unless the originator is admin and explicitly sets it to true.
1816
+ * 4) Force signAndProcess=false unless the originator is admin and explicitly sets it to true.
1745
1817
  * This ensures the underlying wallet returns a signableTransaction, letting us parse the transaction
1746
1818
  * to determine net spending and request authorization if needed.
1747
1819
  */
@@ -1752,7 +1824,7 @@ class WalletPermissionsManager {
1752
1824
  else if (!this.isAdminOriginator(originator)) {
1753
1825
  throw new Error('Only the admin originator can set signAndProcess=true explicitly.');
1754
1826
  }
1755
- // 3) Encrypt transaction metadata, saving originals for use in permissions and line items.
1827
+ // 5) Encrypt transaction metadata, saving originals for use in permissions and line items.
1756
1828
  const originalDescription = args.description;
1757
1829
  const originalInputDescriptions = {};
1758
1830
  const originalOutputDescriptions = {};
@@ -1773,26 +1845,50 @@ class WalletPermissionsManager {
1773
1845
  }
1774
1846
  }
1775
1847
  /**
1776
- * 4) Call the underlying wallets createAction. We add two “admin” labels:
1777
- * - "admin originator <domain>"
1778
- * - "admin month YYYY-MM"
1779
- * These labels help track the originator’s monthly spending.
1848
+ * 6) Call the underlying wallet's createAction.
1849
+ * - If P-modules are involved, chain request transformations through them first
1850
+ * - Add two "admin" labels for tracking: "admin originator <domain>" and "admin month YYYY-MM"
1851
+ * - If P-modules are involved, chain response transformations back through them
1780
1852
  */
1781
- const createResult = await this.underlying.createAction({
1853
+ const finalArgs = {
1782
1854
  ...args,
1783
1855
  options: modifiedOptions,
1784
- labels: [
1785
- ...(args.labels || []),
1786
- `admin originator ${originator}`,
1787
- `admin month ${this.getCurrentMonthYearUTC()}`
1788
- ]
1789
- }, originator);
1856
+ labels: [...(args.labels || []), `admin originator ${originator}`, `admin month ${this.getCurrentMonthYearUTC()}`]
1857
+ };
1858
+ let createResult;
1859
+ if (pModulesByScheme.size > 0) {
1860
+ // P-modules are involved - chain transformations
1861
+ const pModules = Array.from(pModulesByScheme.values());
1862
+ // Chain onRequest calls through all modules
1863
+ let transformedArgs = finalArgs;
1864
+ for (const module of pModules) {
1865
+ const transformed = await module.onRequest({
1866
+ method: 'createAction',
1867
+ args: transformedArgs,
1868
+ originator: originator
1869
+ });
1870
+ transformedArgs = transformed.args;
1871
+ }
1872
+ // Call underlying wallet with transformed args
1873
+ createResult = await this.underlying.createAction(transformedArgs, originator);
1874
+ // Chain onResponse calls in reverse order
1875
+ for (let i = pModules.length - 1; i >= 0; i--) {
1876
+ createResult = await pModules[i].onResponse(createResult, {
1877
+ method: 'createAction',
1878
+ originator: originator
1879
+ });
1880
+ }
1881
+ }
1882
+ else {
1883
+ // No P-modules - call underlying wallet directly
1884
+ createResult = await this.underlying.createAction(finalArgs, originator);
1885
+ }
1790
1886
  // If there's no signableTransaction, the underlying wallet must have fully finalized it. Return as is.
1791
1887
  if (!createResult.signableTransaction) {
1792
1888
  return createResult;
1793
1889
  }
1794
1890
  /**
1795
- * 5) We have a signable transaction. Parse it to determine how much the originator is actually spending.
1891
+ * 7) We have a signable transaction. Parse it to determine how much the originator is actually spending.
1796
1892
  * We only consider inputs the originator explicitly listed in args.inputs.
1797
1893
  * netSpent = (sum of originator-requested outputs) - (sum of matching originator inputs).
1798
1894
  * If netSpent > 0, we need spending authorization.
@@ -1852,7 +1948,7 @@ class WalletPermissionsManager {
1852
1948
  * minus total foreign inflows. Fees are also considered.
1853
1949
  */
1854
1950
  netSpent = totalOutputSatoshis + tx.getFee() - totalInputSatoshis;
1855
- // 6) If netSpent > 0, require spending authorization. Abort if denied.
1951
+ // 8) If netSpent > 0, require spending authorization. Abort if denied.
1856
1952
  if (netSpent > 0) {
1857
1953
  try {
1858
1954
  await this.ensureSpendingAuthorization({
@@ -1868,7 +1964,7 @@ class WalletPermissionsManager {
1868
1964
  }
1869
1965
  }
1870
1966
  /**
1871
- * 7) Decide whether to finalize the transaction automatically or return the signableTransaction:
1967
+ * 9) Decide whether to finalize the transaction automatically or return the signableTransaction:
1872
1968
  * - If the user originally wanted signAndProcess (the default when undefined), we forcibly set it to false earlier, so check if we should now finalize it.
1873
1969
  * - If the transaction still needs more signatures, we must return the signableTransaction.
1874
1970
  */
@@ -1937,6 +2033,18 @@ class WalletPermissionsManager {
1937
2033
  for (const outIndex in requestArgs.outputs) {
1938
2034
  const out = requestArgs.outputs[outIndex];
1939
2035
  if (out.protocol === 'basket insertion') {
2036
+ // Delegate to permission module if needed
2037
+ const pModuleResult = await this.delegateToPModuleIfNeeded(out.insertionRemittance.basket, 'internalizeAction', requestArgs, originator, async (transformedArgs) => {
2038
+ if (out.insertionRemittance.customInstructions) {
2039
+ ;
2040
+ transformedArgs.outputs[outIndex].insertionRemittance.customInstructions =
2041
+ await this.maybeEncryptMetadata(out.insertionRemittance.customInstructions);
2042
+ }
2043
+ return await this.underlying.internalizeAction(transformedArgs, originator);
2044
+ });
2045
+ if (pModuleResult !== null) {
2046
+ return pModuleResult;
2047
+ }
1940
2048
  await this.ensureBasketAccess({
1941
2049
  originator: originator,
1942
2050
  basket: out.insertionRemittance.basket,
@@ -1952,6 +2060,15 @@ class WalletPermissionsManager {
1952
2060
  }
1953
2061
  async listOutputs(...args) {
1954
2062
  const [requestArgs, originator] = args;
2063
+ // Delegate to permission module if needed
2064
+ const pModuleResult = await this.delegateToPModuleIfNeeded(requestArgs.basket, 'listOutputs', requestArgs, originator, async (transformedArgs) => {
2065
+ const result = await this.underlying.listOutputs(transformedArgs, originator);
2066
+ // Apply metadata decryption to permission module response
2067
+ return await this.decryptListOutputsMetadata(result);
2068
+ });
2069
+ if (pModuleResult !== null) {
2070
+ return pModuleResult;
2071
+ }
1955
2072
  // Ensure the originator has permission for the basket.
1956
2073
  await this.ensureBasketAccess({
1957
2074
  originator: originator,
@@ -1960,18 +2077,18 @@ class WalletPermissionsManager {
1960
2077
  usageType: 'listing'
1961
2078
  });
1962
2079
  const results = await this.underlying.listOutputs(...args);
1963
- // Transparently decrypt transaction metadata, if configured to do so.
1964
- if (results.outputs) {
1965
- for (let i = 0; i < results.outputs.length; i++) {
1966
- if (results.outputs[i].customInstructions) {
1967
- results.outputs[i].customInstructions = await this.maybeDecryptMetadata(results.outputs[i].customInstructions);
1968
- }
1969
- }
1970
- }
1971
- return results;
2080
+ // Apply metadata decryption to regular response
2081
+ return await this.decryptListOutputsMetadata(results);
1972
2082
  }
1973
2083
  async relinquishOutput(...args) {
1974
2084
  const [requestArgs, originator] = args;
2085
+ // Delegate to permission module if needed
2086
+ const pModuleResult = await this.delegateToPModuleIfNeeded(requestArgs.basket, 'relinquishOutput', requestArgs, originator, async (transformedArgs) => {
2087
+ return await this.underlying.relinquishOutput(transformedArgs, originator);
2088
+ });
2089
+ if (pModuleResult !== null) {
2090
+ return pModuleResult;
2091
+ }
1975
2092
  await this.ensureBasketAccess({
1976
2093
  originator: originator,
1977
2094
  basket: requestArgs.basket,
@@ -1983,6 +2100,14 @@ class WalletPermissionsManager {
1983
2100
  async getPublicKey(...args) {
1984
2101
  const [requestArgs, originator] = args;
1985
2102
  if (requestArgs.protocolID) {
2103
+ // Delegate to permission module if needed
2104
+ const pModuleResult = await this.delegateToPModuleIfNeeded(requestArgs.protocolID[1], 'getPublicKey', requestArgs, originator, async (transformedArgs) => {
2105
+ return await this.underlying.getPublicKey(transformedArgs, originator);
2106
+ });
2107
+ if (pModuleResult !== null) {
2108
+ return pModuleResult;
2109
+ }
2110
+ // Not a P-protocol, continue with normal permission flow
1986
2111
  await this.ensureProtocolPermission({
1987
2112
  originator: originator,
1988
2113
  privileged: requestArgs.privileged,
@@ -2034,6 +2159,13 @@ class WalletPermissionsManager {
2034
2159
  }
2035
2160
  async encrypt(...args) {
2036
2161
  const [requestArgs, originator] = args;
2162
+ // Delegate to permission module if needed
2163
+ const pModuleResult = await this.delegateToPModuleIfNeeded(requestArgs.protocolID[1], 'encrypt', requestArgs, originator, async (transformedArgs) => {
2164
+ return await this.underlying.encrypt(transformedArgs, originator);
2165
+ });
2166
+ if (pModuleResult !== null) {
2167
+ return pModuleResult;
2168
+ }
2037
2169
  await this.ensureProtocolPermission({
2038
2170
  originator: originator,
2039
2171
  protocolID: requestArgs.protocolID,
@@ -2046,6 +2178,13 @@ class WalletPermissionsManager {
2046
2178
  }
2047
2179
  async decrypt(...args) {
2048
2180
  const [requestArgs, originator] = args;
2181
+ // Delegate to permission module if needed
2182
+ const pModuleResult = await this.delegateToPModuleIfNeeded(requestArgs.protocolID[1], 'decrypt', requestArgs, originator, async (transformedArgs) => {
2183
+ return await this.underlying.decrypt(transformedArgs, originator);
2184
+ });
2185
+ if (pModuleResult !== null) {
2186
+ return pModuleResult;
2187
+ }
2049
2188
  await this.ensureProtocolPermission({
2050
2189
  originator: originator,
2051
2190
  privileged: requestArgs.privileged,
@@ -2058,6 +2197,13 @@ class WalletPermissionsManager {
2058
2197
  }
2059
2198
  async createHmac(...args) {
2060
2199
  const [requestArgs, originator] = args;
2200
+ // Delegate to permission module if needed
2201
+ const pModuleResult = await this.delegateToPModuleIfNeeded(requestArgs.protocolID[1], 'createHmac', requestArgs, originator, async (transformedArgs) => {
2202
+ return await this.underlying.createHmac(transformedArgs, originator);
2203
+ });
2204
+ if (pModuleResult !== null) {
2205
+ return pModuleResult;
2206
+ }
2061
2207
  await this.ensureProtocolPermission({
2062
2208
  originator: originator,
2063
2209
  privileged: requestArgs.privileged,
@@ -2070,6 +2216,13 @@ class WalletPermissionsManager {
2070
2216
  }
2071
2217
  async verifyHmac(...args) {
2072
2218
  const [requestArgs, originator] = args;
2219
+ // Delegate to permission module if needed
2220
+ const pModuleResult = await this.delegateToPModuleIfNeeded(requestArgs.protocolID[1], 'verifyHmac', requestArgs, originator, async (transformedArgs) => {
2221
+ return await this.underlying.verifyHmac(transformedArgs, originator);
2222
+ });
2223
+ if (pModuleResult !== null) {
2224
+ return pModuleResult;
2225
+ }
2073
2226
  await this.ensureProtocolPermission({
2074
2227
  originator: originator,
2075
2228
  privileged: requestArgs.privileged,
@@ -2082,6 +2235,13 @@ class WalletPermissionsManager {
2082
2235
  }
2083
2236
  async createSignature(...args) {
2084
2237
  const [requestArgs, originator] = args;
2238
+ // Delegate to permission module if needed
2239
+ const pModuleResult = await this.delegateToPModuleIfNeeded(requestArgs.protocolID[1], 'createSignature', requestArgs, originator, async (transformedArgs) => {
2240
+ return await this.underlying.createSignature(transformedArgs, originator);
2241
+ });
2242
+ if (pModuleResult !== null) {
2243
+ return pModuleResult;
2244
+ }
2085
2245
  await this.ensureProtocolPermission({
2086
2246
  originator: originator,
2087
2247
  privileged: requestArgs.privileged,
@@ -2094,6 +2254,13 @@ class WalletPermissionsManager {
2094
2254
  }
2095
2255
  async verifySignature(...args) {
2096
2256
  const [requestArgs, originator] = args;
2257
+ // Delegate to permission module if needed
2258
+ const pModuleResult = await this.delegateToPModuleIfNeeded(requestArgs.protocolID[1], 'verifySignature', requestArgs, originator, async (transformedArgs) => {
2259
+ return await this.underlying.verifySignature(transformedArgs, originator);
2260
+ });
2261
+ if (pModuleResult !== null) {
2262
+ return pModuleResult;
2263
+ }
2097
2264
  await this.ensureProtocolPermission({
2098
2265
  originator: originator,
2099
2266
  privileged: requestArgs.privileged,
@@ -2327,7 +2494,7 @@ class WalletPermissionsManager {
2327
2494
  */
2328
2495
  isAdminProtocol(proto) {
2329
2496
  const protocolName = proto[1];
2330
- if (protocolName.startsWith('admin') || protocolName.startsWith('p ')) {
2497
+ if (protocolName.startsWith('admin')) {
2331
2498
  return true;
2332
2499
  }
2333
2500
  return false;
@@ -2357,8 +2524,6 @@ class WalletPermissionsManager {
2357
2524
  return true;
2358
2525
  if (basket.startsWith('admin'))
2359
2526
  return true;
2360
- if (basket.startsWith('p '))
2361
- return true;
2362
2527
  return false;
2363
2528
  }
2364
2529
  /**