@bsv/message-box-client 1.1.10 → 1.2.0

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 (36) hide show
  1. package/dist/cjs/package.json +4 -4
  2. package/dist/cjs/src/MessageBoxClient.js +747 -129
  3. package/dist/cjs/src/MessageBoxClient.js.map +1 -1
  4. package/dist/cjs/src/PeerPayClient.js +61 -28
  5. package/dist/cjs/src/PeerPayClient.js.map +1 -1
  6. package/dist/cjs/src/Utils/logger.js +22 -21
  7. package/dist/cjs/src/Utils/logger.js.map +1 -1
  8. package/dist/cjs/src/types/permissions.js +6 -0
  9. package/dist/cjs/src/types/permissions.js.map +1 -0
  10. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  11. package/dist/esm/src/MessageBoxClient.js +636 -57
  12. package/dist/esm/src/MessageBoxClient.js.map +1 -1
  13. package/dist/esm/src/PeerPayClient.js +1 -1
  14. package/dist/esm/src/PeerPayClient.js.map +1 -1
  15. package/dist/esm/src/Utils/logger.js +17 -19
  16. package/dist/esm/src/Utils/logger.js.map +1 -1
  17. package/dist/esm/src/types/permissions.js +5 -0
  18. package/dist/esm/src/types/permissions.js.map +1 -0
  19. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  20. package/dist/types/src/MessageBoxClient.d.ts +235 -24
  21. package/dist/types/src/MessageBoxClient.d.ts.map +1 -1
  22. package/dist/types/src/PeerPayClient.d.ts.map +1 -1
  23. package/dist/types/src/Utils/logger.d.ts +5 -8
  24. package/dist/types/src/Utils/logger.d.ts.map +1 -1
  25. package/dist/types/src/types/permissions.d.ts +75 -0
  26. package/dist/types/src/types/permissions.d.ts.map +1 -0
  27. package/dist/types/src/types.d.ts +80 -2
  28. package/dist/types/src/types.d.ts.map +1 -1
  29. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  30. package/dist/umd/bundle.js +1 -1
  31. package/package.json +4 -4
  32. package/src/MessageBoxClient.ts +781 -68
  33. package/src/PeerPayClient.ts +11 -11
  34. package/src/Utils/logger.ts +17 -19
  35. package/src/types/permissions.ts +81 -0
  36. package/src/types.ts +87 -2
@@ -22,11 +22,44 @@
22
22
  * @author Project Babbage
23
23
  * @license Open BSV License
24
24
  */
25
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ var desc = Object.getOwnPropertyDescriptor(m, k);
28
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
29
+ desc = { enumerable: true, get: function() { return m[k]; } };
30
+ }
31
+ Object.defineProperty(o, k2, desc);
32
+ }) : (function(o, m, k, k2) {
33
+ if (k2 === undefined) k2 = k;
34
+ o[k2] = m[k];
35
+ }));
36
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
37
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
38
+ }) : function(o, v) {
39
+ o["default"] = v;
40
+ });
41
+ var __importStar = (this && this.__importStar) || (function () {
42
+ var ownKeys = function(o) {
43
+ ownKeys = Object.getOwnPropertyNames || function (o) {
44
+ var ar = [];
45
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
46
+ return ar;
47
+ };
48
+ return ownKeys(o);
49
+ };
50
+ return function (mod) {
51
+ if (mod && mod.__esModule) return mod;
52
+ var result = {};
53
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
54
+ __setModuleDefault(result, mod);
55
+ return result;
56
+ };
57
+ })();
25
58
  Object.defineProperty(exports, "__esModule", { value: true });
26
59
  exports.MessageBoxClient = void 0;
27
60
  const sdk_1 = require("@bsv/sdk");
28
61
  const authsocket_client_1 = require("@bsv/authsocket-client");
29
- const logger_js_1 = require("./Utils/logger.js");
62
+ const Logger = __importStar(require("./Utils/logger.js"));
30
63
  const DEFAULT_MAINNET_HOST = 'https://messagebox.babbage.systems';
31
64
  const DEFAULT_TESTNET_HOST = 'https://staging-messagebox.babbage.systems';
32
65
  /**
@@ -61,7 +94,7 @@ class MessageBoxClient {
61
94
  * @constructor
62
95
  * @param {Object} options - Initialization options for the MessageBoxClient.
63
96
  * @param {string} [options.host] - The base URL of the MessageBox server. If omitted, defaults to mainnet/testnet hosts.
64
- * @param {WalletClient} options.walletClient - Wallet instance used for authentication, signing, and encryption.
97
+ * @param {WalletInterface} options.walletClient - Wallet instance used for authentication, signing, and encryption.
65
98
  * @param {boolean} [options.enableLogging=false] - Whether to enable detailed debug logging to the console.
66
99
  * @param {'local' | 'mainnet' | 'testnet'} [options.networkPreset='mainnet'] - Overlay network preset used for routing and advertisement lookup.
67
100
  *
@@ -85,25 +118,26 @@ class MessageBoxClient {
85
118
  var _a;
86
119
  this.joinedRooms = new Set();
87
120
  this.initialized = false;
88
- const { host, walletClient, enableLogging = false, networkPreset = 'mainnet' } = options;
121
+ const { host, walletClient, enableLogging = false, networkPreset = 'mainnet', originator = undefined } = options;
89
122
  const defaultHost = this.networkPreset === 'testnet'
90
123
  ? DEFAULT_TESTNET_HOST
91
124
  : DEFAULT_MAINNET_HOST;
92
125
  this.host = (_a = host === null || host === void 0 ? void 0 : host.trim()) !== null && _a !== void 0 ? _a : defaultHost;
93
- this.walletClient = walletClient !== null && walletClient !== void 0 ? walletClient : new sdk_1.WalletClient();
126
+ this.walletClient = walletClient !== null && walletClient !== void 0 ? walletClient : new sdk_1.WalletClient('auto', originator);
94
127
  this.authFetch = new sdk_1.AuthFetch(this.walletClient);
95
128
  this.networkPreset = networkPreset;
96
129
  this.lookupResolver = new sdk_1.LookupResolver({
97
130
  networkPreset
98
131
  });
99
132
  if (enableLogging) {
100
- logger_js_1.Logger.enable();
133
+ Logger.enable();
101
134
  }
102
135
  }
103
136
  /**
104
137
  * @method init
105
138
  * @async
106
139
  * @param {string} [targetHost] - Optional host to set or override the default host.
140
+ * @param {string} [originator] - Optional originator to use with walletClient.
107
141
  * @returns {Promise<void>}
108
142
  *
109
143
  * @description
@@ -122,7 +156,7 @@ class MessageBoxClient {
122
156
  * await client.init()
123
157
  * await client.sendMessage({ recipient, messageBox: 'inbox', body: 'Hello' })
124
158
  */
125
- async init(targetHost = this.host) {
159
+ async init(targetHost = this.host, originator) {
126
160
  var _a;
127
161
  const normalizedHost = targetHost === null || targetHost === void 0 ? void 0 : targetHost.trim();
128
162
  if (normalizedHost === '') {
@@ -136,13 +170,13 @@ class MessageBoxClient {
136
170
  if (this.initialized)
137
171
  return;
138
172
  // 1. Get our identity key
139
- const identityKey = await this.getIdentityKey();
173
+ const identityKey = await this.getIdentityKey(originator);
140
174
  // 2. Check for any matching advertisements for the given host
141
- const [firstAdvertisement] = await this.queryAdvertisements(identityKey, normalizedHost);
175
+ const [firstAdvertisement] = await this.queryAdvertisements(identityKey, normalizedHost, originator);
142
176
  // 3. If none our found, anoint this host
143
177
  if (firstAdvertisement == null || ((_a = firstAdvertisement === null || firstAdvertisement === void 0 ? void 0 : firstAdvertisement.host) === null || _a === void 0 ? void 0 : _a.trim()) === '' || (firstAdvertisement === null || firstAdvertisement === void 0 ? void 0 : firstAdvertisement.host) !== normalizedHost) {
144
- logger_js_1.Logger.log('[MB CLIENT] Anointing host:', normalizedHost);
145
- const { txid } = await this.anointHost(normalizedHost);
178
+ Logger.log('[MB CLIENT] Anointing host:', normalizedHost);
179
+ const { txid } = await this.anointHost(normalizedHost, originator);
146
180
  if (txid == null || txid.trim() === '') {
147
181
  throw new Error('Failed to anoint host: No transaction ID returned');
148
182
  }
@@ -177,24 +211,25 @@ class MessageBoxClient {
177
211
  }
178
212
  /**
179
213
  * @method getIdentityKey
214
+ * @param {string} [originator] - Optional originator to use for identity key lookup
180
215
  * @returns {Promise<string>} The identity public key of the user
181
216
  * @description
182
217
  * Returns the client's identity key, used for signing, encryption, and addressing.
183
218
  * If not already loaded, it will fetch and cache it.
184
219
  */
185
- async getIdentityKey() {
220
+ async getIdentityKey(originator) {
186
221
  if (this.myIdentityKey != null && this.myIdentityKey.trim() !== '') {
187
222
  return this.myIdentityKey;
188
223
  }
189
- logger_js_1.Logger.log('[MB CLIENT] Fetching identity key...');
224
+ Logger.log('[MB CLIENT] Fetching identity key...');
190
225
  try {
191
- const keyResult = await this.walletClient.getPublicKey({ identityKey: true });
226
+ const keyResult = await this.walletClient.getPublicKey({ identityKey: true }, originator);
192
227
  this.myIdentityKey = keyResult.publicKey;
193
- logger_js_1.Logger.log(`[MB CLIENT] Identity key fetched: ${this.myIdentityKey}`);
228
+ Logger.log(`[MB CLIENT] Identity key fetched: ${this.myIdentityKey}`);
194
229
  return this.myIdentityKey;
195
230
  }
196
231
  catch (error) {
197
- logger_js_1.Logger.error('[MB CLIENT ERROR] Failed to fetch identity key:', error);
232
+ Logger.error('[MB CLIENT ERROR] Failed to fetch identity key:', error);
198
233
  throw new Error('Identity key retrieval failed');
199
234
  }
200
235
  }
@@ -214,6 +249,7 @@ class MessageBoxClient {
214
249
  }
215
250
  /**
216
251
  * @method initializeConnection
252
+ * @param {string} [originator] - Optional originator to use for authentication.
217
253
  * @async
218
254
  * @returns {Promise<void>}
219
255
  * @description
@@ -235,26 +271,17 @@ class MessageBoxClient {
235
271
  * await mb.initializeConnection()
236
272
  * // WebSocket is now ready for use
237
273
  */
238
- async initializeConnection() {
274
+ async initializeConnection(originator) {
239
275
  await this.assertInitialized();
240
- logger_js_1.Logger.log('[MB CLIENT] initializeConnection() STARTED');
276
+ Logger.log('[MB CLIENT] initializeConnection() STARTED');
241
277
  if (this.myIdentityKey == null || this.myIdentityKey.trim() === '') {
242
- logger_js_1.Logger.log('[MB CLIENT] Fetching identity key...');
243
- try {
244
- const keyResult = await this.walletClient.getPublicKey({ identityKey: true });
245
- this.myIdentityKey = keyResult.publicKey;
246
- logger_js_1.Logger.log(`[MB CLIENT] Identity key fetched successfully: ${this.myIdentityKey}`);
247
- }
248
- catch (error) {
249
- logger_js_1.Logger.error('[MB CLIENT ERROR] Failed to fetch identity key:', error);
250
- throw new Error('Identity key retrieval failed');
251
- }
278
+ await this.getIdentityKey(originator);
252
279
  }
253
280
  if (this.myIdentityKey == null || this.myIdentityKey.trim() === '') {
254
- logger_js_1.Logger.error('[MB CLIENT ERROR] Identity key is still missing after retrieval!');
281
+ Logger.error('[MB CLIENT ERROR] Identity key is still missing after retrieval!');
255
282
  throw new Error('Identity key is missing');
256
283
  }
257
- logger_js_1.Logger.log('[MB CLIENT] Setting up WebSocket connection...');
284
+ Logger.log('[MB CLIENT] Setting up WebSocket connection...');
258
285
  if (this.socket == null) {
259
286
  if (typeof this.host !== 'string' || this.host.trim() === '') {
260
287
  throw new Error('Cannot initialize WebSocket: Host is not set');
@@ -264,11 +291,11 @@ class MessageBoxClient {
264
291
  let authenticated = false;
265
292
  this.socket.on('connect', () => {
266
293
  var _a;
267
- logger_js_1.Logger.log('[MB CLIENT] Connected to WebSocket.');
294
+ Logger.log('[MB CLIENT] Connected to WebSocket.');
268
295
  if (!identitySent) {
269
- logger_js_1.Logger.log('[MB CLIENT] Sending authentication data:', this.myIdentityKey);
296
+ Logger.log('[MB CLIENT] Sending authentication data:', this.myIdentityKey);
270
297
  if (this.myIdentityKey == null || this.myIdentityKey.trim() === '') {
271
- logger_js_1.Logger.error('[MB CLIENT ERROR] Cannot send authentication: Identity key is missing!');
298
+ Logger.error('[MB CLIENT ERROR] Cannot send authentication: Identity key is missing!');
272
299
  }
273
300
  else {
274
301
  (_a = this.socket) === null || _a === void 0 ? void 0 : _a.emit('authenticated', { identityKey: this.myIdentityKey });
@@ -278,28 +305,28 @@ class MessageBoxClient {
278
305
  });
279
306
  // Listen for authentication success from the server
280
307
  this.socket.on('authenticationSuccess', (data) => {
281
- logger_js_1.Logger.log(`[MB CLIENT] WebSocket authentication successful: ${JSON.stringify(data)}`);
308
+ Logger.log(`[MB CLIENT] WebSocket authentication successful: ${JSON.stringify(data)}`);
282
309
  authenticated = true;
283
310
  });
284
311
  // Handle authentication failures
285
312
  this.socket.on('authenticationFailed', (data) => {
286
- logger_js_1.Logger.error(`[MB CLIENT ERROR] WebSocket authentication failed: ${JSON.stringify(data)}`);
313
+ Logger.error(`[MB CLIENT ERROR] WebSocket authentication failed: ${JSON.stringify(data)}`);
287
314
  authenticated = false;
288
315
  });
289
316
  this.socket.on('disconnect', () => {
290
- logger_js_1.Logger.log('[MB CLIENT] Disconnected from MessageBox server');
317
+ Logger.log('[MB CLIENT] Disconnected from MessageBox server');
291
318
  this.socket = undefined;
292
319
  identitySent = false;
293
320
  authenticated = false;
294
321
  });
295
322
  this.socket.on('error', (error) => {
296
- logger_js_1.Logger.error('[MB CLIENT ERROR] WebSocket error:', error);
323
+ Logger.error('[MB CLIENT ERROR] WebSocket error:', error);
297
324
  });
298
325
  // Wait for authentication confirmation before proceeding
299
326
  await new Promise((resolve, reject) => {
300
327
  setTimeout(() => {
301
328
  if (authenticated) {
302
- logger_js_1.Logger.log('[MB CLIENT] WebSocket fully authenticated and ready!');
329
+ Logger.log('[MB CLIENT] WebSocket fully authenticated and ready!');
303
330
  resolve();
304
331
  }
305
332
  else {
@@ -313,6 +340,7 @@ class MessageBoxClient {
313
340
  * @method resolveHostForRecipient
314
341
  * @async
315
342
  * @param {string} identityKey - The public identity key of the intended recipient.
343
+ * @param {string} [originator] - The originator to use for the WalletClient.
316
344
  * @returns {Promise<string>} - A fully qualified host URL for the recipient's MessageBox server.
317
345
  *
318
346
  * @description
@@ -327,10 +355,10 @@ class MessageBoxClient {
327
355
  * @example
328
356
  * const host = await resolveHostForRecipient('028d...') // → returns either overlay host or this.host
329
357
  */
330
- async resolveHostForRecipient(identityKey) {
331
- const advertisementTokens = await this.queryAdvertisements(identityKey);
358
+ async resolveHostForRecipient(identityKey, originator) {
359
+ const advertisementTokens = await this.queryAdvertisements(identityKey, undefined, originator);
332
360
  if (advertisementTokens.length === 0) {
333
- logger_js_1.Logger.warn(`[MB CLIENT] No advertisements for ${identityKey}, using default host ${this.host}`);
361
+ Logger.warn(`[MB CLIENT] No advertisements for ${identityKey}, using default host ${this.host}`);
334
362
  return this.host;
335
363
  }
336
364
  // Return the first host found
@@ -344,10 +372,10 @@ class MessageBoxClient {
344
372
  * @param host? if passed, only look for adverts anointed at that host
345
373
  * @returns 0-length array if nothing valid was found
346
374
  */
347
- async queryAdvertisements(identityKey, host) {
375
+ async queryAdvertisements(identityKey, host, originator) {
348
376
  const hosts = [];
349
377
  try {
350
- const query = { identityKey: identityKey !== null && identityKey !== void 0 ? identityKey : await this.getIdentityKey() };
378
+ const query = { identityKey: identityKey !== null && identityKey !== void 0 ? identityKey : await this.getIdentityKey(originator) };
351
379
  if (host != null && host.trim() !== '')
352
380
  query.host = host;
353
381
  const result = await this.lookupResolver.query({
@@ -355,7 +383,7 @@ class MessageBoxClient {
355
383
  query
356
384
  });
357
385
  if (result.type !== 'output-list') {
358
- throw new Error(`Unexpected result type: ${result.type}`);
386
+ throw new Error(`Unexpected result type: ${String(result.type)}`);
359
387
  }
360
388
  for (const output of result.outputs) {
361
389
  try {
@@ -380,7 +408,7 @@ class MessageBoxClient {
380
408
  }
381
409
  }
382
410
  catch (err) {
383
- logger_js_1.Logger.error('[MB CLIENT ERROR] _queryAdvertisements failed:', err);
411
+ Logger.error('[MB CLIENT ERROR] _queryAdvertisements failed:', err);
384
412
  }
385
413
  return hosts;
386
414
  }
@@ -406,10 +434,10 @@ class MessageBoxClient {
406
434
  async joinRoom(messageBox) {
407
435
  var _a, _b;
408
436
  await this.assertInitialized();
409
- logger_js_1.Logger.log(`[MB CLIENT] Attempting to join WebSocket room: ${messageBox}`);
437
+ Logger.log(`[MB CLIENT] Attempting to join WebSocket room: ${messageBox}`);
410
438
  // Ensure WebSocket connection is established first
411
439
  if (this.socket == null) {
412
- logger_js_1.Logger.log('[MB CLIENT] No WebSocket connection. Initializing...');
440
+ Logger.log('[MB CLIENT] No WebSocket connection. Initializing...');
413
441
  await this.initializeConnection();
414
442
  }
415
443
  if (this.myIdentityKey == null || this.myIdentityKey.trim() === '') {
@@ -417,17 +445,17 @@ class MessageBoxClient {
417
445
  }
418
446
  const roomId = `${(_a = this.myIdentityKey) !== null && _a !== void 0 ? _a : ''}-${messageBox}`;
419
447
  if (this.joinedRooms.has(roomId)) {
420
- logger_js_1.Logger.log(`[MB CLIENT] Already joined WebSocket room: ${roomId}`);
448
+ Logger.log(`[MB CLIENT] Already joined WebSocket room: ${roomId}`);
421
449
  return;
422
450
  }
423
451
  try {
424
- logger_js_1.Logger.log(`[MB CLIENT] Joining WebSocket room: ${roomId}`);
452
+ Logger.log(`[MB CLIENT] Joining WebSocket room: ${roomId}`);
425
453
  await ((_b = this.socket) === null || _b === void 0 ? void 0 : _b.emit('joinRoom', roomId));
426
454
  this.joinedRooms.add(roomId);
427
- logger_js_1.Logger.log(`[MB CLIENT] Successfully joined room: ${roomId}`);
455
+ Logger.log(`[MB CLIENT] Successfully joined room: ${roomId}`);
428
456
  }
429
457
  catch (error) {
430
- logger_js_1.Logger.error(`[MB CLIENT ERROR] Failed to join WebSocket room: ${roomId}`, error);
458
+ Logger.error(`[MB CLIENT ERROR] Failed to join WebSocket room: ${roomId}`, error);
431
459
  }
432
460
  }
433
461
  /**
@@ -458,10 +486,10 @@ class MessageBoxClient {
458
486
  * onMessage: (msg) => console.log('Received live message:', msg)
459
487
  * })
460
488
  */
461
- async listenForLiveMessages({ onMessage, messageBox }) {
489
+ async listenForLiveMessages({ onMessage, messageBox, originator }) {
462
490
  var _a;
463
491
  await this.assertInitialized();
464
- logger_js_1.Logger.log(`[MB CLIENT] Setting up listener for WebSocket room: ${messageBox}`);
492
+ Logger.log(`[MB CLIENT] Setting up listener for WebSocket room: ${messageBox}`);
465
493
  // Ensure WebSocket connection and room join
466
494
  await this.joinRoom(messageBox);
467
495
  // Ensure identity key is available before creating roomId
@@ -469,10 +497,10 @@ class MessageBoxClient {
469
497
  throw new Error('[MB CLIENT ERROR] Identity key is missing. Cannot construct room ID.');
470
498
  }
471
499
  const roomId = `${this.myIdentityKey}-${messageBox}`;
472
- logger_js_1.Logger.log(`[MB CLIENT] Listening for messages in room: ${roomId}`);
500
+ Logger.log(`[MB CLIENT] Listening for messages in room: ${roomId}`);
473
501
  (_a = this.socket) === null || _a === void 0 ? void 0 : _a.on(`sendMessage-${roomId}`, (message) => {
474
502
  void (async () => {
475
- logger_js_1.Logger.log(`[MB CLIENT] Received message in room ${roomId}:`, message);
503
+ Logger.log(`[MB CLIENT] Received message in room ${roomId}:`, message);
476
504
  try {
477
505
  let parsedBody = message.body;
478
506
  if (typeof parsedBody === 'string') {
@@ -486,17 +514,17 @@ class MessageBoxClient {
486
514
  if (parsedBody != null &&
487
515
  typeof parsedBody === 'object' &&
488
516
  typeof parsedBody.encryptedMessage === 'string') {
489
- logger_js_1.Logger.log(`[MB CLIENT] Decrypting message from ${String(message.sender)}...`);
517
+ Logger.log(`[MB CLIENT] Decrypting message from ${String(message.sender)}...`);
490
518
  const decrypted = await this.walletClient.decrypt({
491
519
  protocolID: [1, 'messagebox'],
492
520
  keyID: '1',
493
521
  counterparty: message.sender,
494
522
  ciphertext: sdk_1.Utils.toArray(parsedBody.encryptedMessage, 'base64')
495
- });
523
+ }, originator);
496
524
  message.body = sdk_1.Utils.toUTF8(decrypted.plaintext);
497
525
  }
498
526
  else {
499
- logger_js_1.Logger.log('[MB CLIENT] Message is not encrypted.');
527
+ Logger.log('[MB CLIENT] Message is not encrypted.');
500
528
  message.body = typeof parsedBody === 'string'
501
529
  ? parsedBody
502
530
  : (() => { try {
@@ -508,7 +536,7 @@ class MessageBoxClient {
508
536
  }
509
537
  }
510
538
  catch (err) {
511
- logger_js_1.Logger.error('[MB CLIENT ERROR] Failed to parse or decrypt live message:', err);
539
+ Logger.error('[MB CLIENT ERROR] Failed to parse or decrypt live message:', err);
512
540
  message.body = '[Error: Failed to decrypt or parse message]';
513
541
  }
514
542
  onMessage(message);
@@ -544,7 +572,7 @@ class MessageBoxClient {
544
572
  * body: { amount: 1000 }
545
573
  * })
546
574
  */
547
- async sendLiveMessage({ recipient, messageBox, body, messageId, skipEncryption }) {
575
+ async sendLiveMessage({ recipient, messageBox, body, messageId, skipEncryption, checkPermissions, originator }) {
548
576
  await this.assertInitialized();
549
577
  if (recipient == null || recipient.trim() === '') {
550
578
  throw new Error('[MB CLIENT ERROR] Recipient identity key is required');
@@ -559,7 +587,7 @@ class MessageBoxClient {
559
587
  await this.joinRoom(messageBox);
560
588
  // Fallback to HTTP if WebSocket is not connected
561
589
  if (this.socket == null || !this.socket.connected) {
562
- logger_js_1.Logger.warn('[MB CLIENT WARNING] WebSocket not connected, falling back to HTTP');
590
+ Logger.warn('[MB CLIENT WARNING] WebSocket not connected, falling back to HTTP');
563
591
  const targetHost = await this.resolveHostForRecipient(recipient);
564
592
  return await this.sendMessage({ recipient, messageBox, body }, targetHost);
565
593
  }
@@ -570,15 +598,15 @@ class MessageBoxClient {
570
598
  protocolID: [1, 'messagebox'],
571
599
  keyID: '1',
572
600
  counterparty: recipient
573
- });
601
+ }, originator);
574
602
  finalMessageId = messageId !== null && messageId !== void 0 ? messageId : Array.from(hmac.hmac).map(b => b.toString(16).padStart(2, '0')).join('');
575
603
  }
576
604
  catch (error) {
577
- logger_js_1.Logger.error('[MB CLIENT ERROR] Failed to generate HMAC:', error);
605
+ Logger.error('[MB CLIENT ERROR] Failed to generate HMAC:', error);
578
606
  throw new Error('Failed to generate message identifier.');
579
607
  }
580
608
  const roomId = `${recipient}-${messageBox}`;
581
- logger_js_1.Logger.log(`[MB CLIENT] Sending WebSocket message to room: ${roomId}`);
609
+ Logger.log(`[MB CLIENT] Sending WebSocket message to room: ${roomId}`);
582
610
  let outgoingBody;
583
611
  if (skipEncryption === true) {
584
612
  outgoingBody = typeof body === 'string' ? body : JSON.stringify(body);
@@ -589,7 +617,7 @@ class MessageBoxClient {
589
617
  keyID: '1',
590
618
  counterparty: recipient,
591
619
  plaintext: sdk_1.Utils.toArray(typeof body === 'string' ? body : JSON.stringify(body), 'utf8')
592
- });
620
+ }, originator);
593
621
  outgoingBody = JSON.stringify({
594
622
  encryptedMessage: sdk_1.Utils.toBase64(encryptedMessage.ciphertext)
595
623
  });
@@ -606,15 +634,16 @@ class MessageBoxClient {
606
634
  if (typeof (socketAny === null || socketAny === void 0 ? void 0 : socketAny.off) === 'function') {
607
635
  socketAny.off(ackEvent, ackHandler);
608
636
  }
609
- logger_js_1.Logger.log('[MB CLIENT] Received WebSocket acknowledgment:', response);
637
+ Logger.log('[MB CLIENT] Received WebSocket acknowledgment:', response);
610
638
  if (response == null || response.status !== 'success') {
611
- logger_js_1.Logger.warn('[MB CLIENT] WebSocket message failed or returned unexpected response. Falling back to HTTP.');
639
+ Logger.warn('[MB CLIENT] WebSocket message failed or returned unexpected response. Falling back to HTTP.');
612
640
  const fallbackMessage = {
613
641
  recipient,
614
642
  messageBox,
615
643
  body,
616
644
  messageId: finalMessageId,
617
- skipEncryption
645
+ skipEncryption,
646
+ checkPermissions
618
647
  };
619
648
  this.resolveHostForRecipient(recipient)
620
649
  .then(async (host) => {
@@ -624,7 +653,7 @@ class MessageBoxClient {
624
653
  .catch(reject);
625
654
  }
626
655
  else {
627
- logger_js_1.Logger.log('[MB CLIENT] Message sent successfully via WebSocket:', response);
656
+ Logger.log('[MB CLIENT] Message sent successfully via WebSocket:', response);
628
657
  resolve(response);
629
658
  }
630
659
  };
@@ -647,13 +676,14 @@ class MessageBoxClient {
647
676
  if (typeof (socketAny === null || socketAny === void 0 ? void 0 : socketAny.off) === 'function') {
648
677
  socketAny.off(ackEvent, ackHandler);
649
678
  }
650
- logger_js_1.Logger.warn('[CLIENT] WebSocket acknowledgment timed out, falling back to HTTP');
679
+ Logger.warn('[CLIENT] WebSocket acknowledgment timed out, falling back to HTTP');
651
680
  const fallbackMessage = {
652
681
  recipient,
653
682
  messageBox,
654
683
  body,
655
684
  messageId: finalMessageId,
656
- skipEncryption
685
+ skipEncryption,
686
+ checkPermissions
657
687
  };
658
688
  this.resolveHostForRecipient(recipient)
659
689
  .then(async (host) => {
@@ -683,14 +713,14 @@ class MessageBoxClient {
683
713
  async leaveRoom(messageBox) {
684
714
  await this.assertInitialized();
685
715
  if (this.socket == null) {
686
- logger_js_1.Logger.warn('[MB CLIENT] Attempted to leave a room but WebSocket is not connected.');
716
+ Logger.warn('[MB CLIENT] Attempted to leave a room but WebSocket is not connected.');
687
717
  return;
688
718
  }
689
719
  if (this.myIdentityKey == null || this.myIdentityKey.trim() === '') {
690
720
  throw new Error('[MB CLIENT ERROR] Identity key is not defined');
691
721
  }
692
722
  const roomId = `${this.myIdentityKey}-${messageBox}`;
693
- logger_js_1.Logger.log(`[MB CLIENT] Leaving WebSocket room: ${roomId}`);
723
+ Logger.log(`[MB CLIENT] Leaving WebSocket room: ${roomId}`);
694
724
  this.socket.emit('leaveRoom', roomId);
695
725
  // Ensure the room is removed from tracking
696
726
  this.joinedRooms.delete(roomId);
@@ -711,12 +741,12 @@ class MessageBoxClient {
711
741
  async disconnectWebSocket() {
712
742
  await this.assertInitialized();
713
743
  if (this.socket != null) {
714
- logger_js_1.Logger.log('[MB CLIENT] Closing WebSocket connection...');
744
+ Logger.log('[MB CLIENT] Closing WebSocket connection...');
715
745
  this.socket.disconnect();
716
746
  this.socket = undefined;
717
747
  }
718
748
  else {
719
- logger_js_1.Logger.log('[MB CLIENT] No active WebSocket connection to close.');
749
+ Logger.log('[MB CLIENT] No active WebSocket connection to close.');
720
750
  }
721
751
  }
722
752
  /**
@@ -746,7 +776,7 @@ class MessageBoxClient {
746
776
  * body: { type: 'ping' }
747
777
  * })
748
778
  */
749
- async sendMessage(message, overrideHost) {
779
+ async sendMessage(message, overrideHost, originator) {
750
780
  var _a, _b;
751
781
  await this.assertInitialized();
752
782
  if (message.recipient == null || message.recipient.trim() === '') {
@@ -758,6 +788,33 @@ class MessageBoxClient {
758
788
  if (message.body == null || (typeof message.body === 'string' && message.body.trim().length === 0)) {
759
789
  throw new Error('Every message must have a body!');
760
790
  }
791
+ // Optional permission checking for backwards compatibility
792
+ let paymentData;
793
+ if (message.checkPermissions === true) {
794
+ try {
795
+ Logger.log('[MB CLIENT] Checking permissions and fees for message...');
796
+ // Get quote to check if payment is required
797
+ const quote = await this.getMessageBoxQuote({
798
+ recipient: message.recipient,
799
+ messageBox: message.messageBox
800
+ });
801
+ if (quote.recipientFee === -1) {
802
+ throw new Error('You have been blocked from sending messages to this recipient.');
803
+ }
804
+ if (quote.recipientFee > 0 || quote.deliveryFee > 0) {
805
+ const requiredPayment = quote.recipientFee + quote.deliveryFee;
806
+ if (requiredPayment > 0) {
807
+ Logger.log(`[MB CLIENT] Creating payment of ${requiredPayment} sats for message...`);
808
+ // Create payment using helper method
809
+ paymentData = await this.createMessagePayment(message.recipient, quote, overrideHost);
810
+ Logger.log('[MB CLIENT] Payment data prepared:', paymentData);
811
+ }
812
+ }
813
+ }
814
+ catch (error) {
815
+ throw new Error(`Permission check failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
816
+ }
817
+ }
761
818
  let messageId;
762
819
  try {
763
820
  const hmac = await this.walletClient.createHmac({
@@ -765,11 +822,11 @@ class MessageBoxClient {
765
822
  protocolID: [1, 'messagebox'],
766
823
  keyID: '1',
767
824
  counterparty: message.recipient
768
- });
825
+ }, originator);
769
826
  messageId = (_a = message.messageId) !== null && _a !== void 0 ? _a : Array.from(hmac.hmac).map(b => b.toString(16).padStart(2, '0')).join('');
770
827
  }
771
828
  catch (error) {
772
- logger_js_1.Logger.error('[MB CLIENT ERROR] Failed to generate HMAC:', error);
829
+ Logger.error('[MB CLIENT ERROR] Failed to generate HMAC:', error);
773
830
  throw new Error('Failed to generate message identifier.');
774
831
  }
775
832
  let finalBody;
@@ -782,7 +839,7 @@ class MessageBoxClient {
782
839
  keyID: '1',
783
840
  counterparty: message.recipient,
784
841
  plaintext: sdk_1.Utils.toArray(typeof message.body === 'string' ? message.body : JSON.stringify(message.body), 'utf8')
785
- });
842
+ }, originator);
786
843
  finalBody = JSON.stringify({ encryptedMessage: sdk_1.Utils.toBase64(encryptedMessage.ciphertext) });
787
844
  }
788
845
  const requestBody = {
@@ -790,20 +847,21 @@ class MessageBoxClient {
790
847
  ...message,
791
848
  messageId,
792
849
  body: finalBody
793
- }
850
+ },
851
+ ...(paymentData != null && { payment: paymentData })
794
852
  };
795
853
  try {
796
854
  const finalHost = overrideHost !== null && overrideHost !== void 0 ? overrideHost : await this.resolveHostForRecipient(message.recipient);
797
- logger_js_1.Logger.log('[MB CLIENT] Sending HTTP request to:', `${finalHost}/sendMessage`);
798
- logger_js_1.Logger.log('[MB CLIENT] Request Body:', JSON.stringify(requestBody, null, 2));
855
+ Logger.log('[MB CLIENT] Sending HTTP request to:', `${finalHost}/sendMessage`);
856
+ Logger.log('[MB CLIENT] Request Body:', JSON.stringify(requestBody, null, 2));
799
857
  if (this.myIdentityKey == null || this.myIdentityKey === '') {
800
858
  try {
801
- const keyResult = await this.walletClient.getPublicKey({ identityKey: true });
859
+ const keyResult = await this.walletClient.getPublicKey({ identityKey: true }, originator);
802
860
  this.myIdentityKey = keyResult.publicKey;
803
- logger_js_1.Logger.log(`[MB CLIENT] Fetched identity key before sending request: ${this.myIdentityKey}`);
861
+ Logger.log(`[MB CLIENT] Fetched identity key before sending request: ${this.myIdentityKey}`);
804
862
  }
805
863
  catch (error) {
806
- logger_js_1.Logger.error('[MB CLIENT ERROR] Failed to fetch identity key:', error);
864
+ Logger.error('[MB CLIENT ERROR] Failed to fetch identity key:', error);
807
865
  throw new Error('Identity key retrieval failed');
808
866
  }
809
867
  }
@@ -818,20 +876,20 @@ class MessageBoxClient {
818
876
  throw new Error('[MB CLIENT ERROR] Response body has already been used!');
819
877
  }
820
878
  const parsedResponse = await response.json();
821
- logger_js_1.Logger.log('[MB CLIENT] Raw Response Body:', parsedResponse);
879
+ Logger.log('[MB CLIENT] Raw Response Body:', parsedResponse);
822
880
  if (!response.ok) {
823
- logger_js_1.Logger.error(`[MB CLIENT ERROR] Failed to send message. HTTP ${response.status}: ${response.statusText}`);
881
+ Logger.error(`[MB CLIENT ERROR] Failed to send message. HTTP ${response.status}: ${response.statusText}`);
824
882
  throw new Error(`Message sending failed: HTTP ${response.status} - ${response.statusText}`);
825
883
  }
826
884
  if (parsedResponse.status !== 'success') {
827
- logger_js_1.Logger.error(`[MB CLIENT ERROR] Server returned an error: ${String(parsedResponse.description)}`);
885
+ Logger.error(`[MB CLIENT ERROR] Server returned an error: ${String(parsedResponse.description)}`);
828
886
  throw new Error((_b = parsedResponse.description) !== null && _b !== void 0 ? _b : 'Unknown error from server.');
829
887
  }
830
- logger_js_1.Logger.log('[MB CLIENT] Message successfully sent.');
888
+ Logger.log('[MB CLIENT] Message successfully sent.');
831
889
  return { ...parsedResponse, messageId };
832
890
  }
833
891
  catch (error) {
834
- logger_js_1.Logger.error('[MB CLIENT ERROR] Network or timeout error:', error);
892
+ Logger.error('[MB CLIENT ERROR] Network or timeout error:', error);
835
893
  const errorMessage = error instanceof Error ? error.message : 'Unknown error';
836
894
  throw new Error(`Failed to send message: ${errorMessage}`);
837
895
  }
@@ -859,27 +917,27 @@ class MessageBoxClient {
859
917
  * @example
860
918
  * const { txid } = await client.anointHost('https://my-messagebox.io')
861
919
  */
862
- async anointHost(host) {
863
- logger_js_1.Logger.log('[MB CLIENT] Starting anointHost...');
920
+ async anointHost(host, originator) {
921
+ Logger.log('[MB CLIENT] Starting anointHost...');
864
922
  try {
865
923
  if (!host.startsWith('http')) {
866
924
  throw new Error('Invalid host URL');
867
925
  }
868
- const identityKey = await this.getIdentityKey();
869
- logger_js_1.Logger.log('[MB CLIENT] Fields - Identity:', identityKey, 'Host:', host);
926
+ const identityKey = await this.getIdentityKey(originator);
927
+ Logger.log('[MB CLIENT] Fields - Identity:', identityKey, 'Host:', host);
870
928
  const fields = [
871
929
  sdk_1.Utils.toArray(identityKey, 'hex'),
872
930
  sdk_1.Utils.toArray(host, 'utf8')
873
931
  ];
874
- const pushdrop = new sdk_1.PushDrop(this.walletClient);
875
- logger_js_1.Logger.log('Fields:', fields.map(a => sdk_1.Utils.toHex(a)));
876
- logger_js_1.Logger.log('ProtocolID:', [1, 'messagebox advertisement']);
877
- logger_js_1.Logger.log('KeyID:', '1');
878
- logger_js_1.Logger.log('SignAs:', 'self');
879
- logger_js_1.Logger.log('anyoneCanSpend:', false);
880
- logger_js_1.Logger.log('forSelf:', true);
932
+ const pushdrop = new sdk_1.PushDrop(this.walletClient, originator);
933
+ Logger.log('Fields:', fields.map(a => sdk_1.Utils.toHex(a)));
934
+ Logger.log('ProtocolID:', [1, 'messagebox advertisement']);
935
+ Logger.log('KeyID:', '1');
936
+ Logger.log('SignAs:', 'self');
937
+ Logger.log('anyoneCanSpend:', false);
938
+ Logger.log('forSelf:', true);
881
939
  const script = await pushdrop.lock(fields, [1, 'messagebox advertisement'], '1', 'anyone', true);
882
- logger_js_1.Logger.log('[MB CLIENT] PushDrop script:', script.toASM());
940
+ Logger.log('[MB CLIENT] PushDrop script:', script.toASM());
883
941
  const { tx, txid } = await this.walletClient.createAction({
884
942
  description: 'Anoint host for overlay routing',
885
943
  outputs: [{
@@ -889,14 +947,14 @@ class MessageBoxClient {
889
947
  outputDescription: 'Overlay advertisement output'
890
948
  }],
891
949
  options: { randomizeOutputs: false, acceptDelayedBroadcast: false }
892
- });
893
- logger_js_1.Logger.log('[MB CLIENT] Transaction created:', txid);
950
+ }, originator);
951
+ Logger.log('[MB CLIENT] Transaction created:', txid);
894
952
  if (tx !== undefined) {
895
953
  const broadcaster = new sdk_1.TopicBroadcaster(['tm_messagebox'], {
896
954
  networkPreset: this.networkPreset
897
955
  });
898
956
  const result = await broadcaster.broadcast(sdk_1.Transaction.fromAtomicBEEF(tx));
899
- logger_js_1.Logger.log('[MB CLIENT] Advertisement broadcast succeeded. TXID:', result.txid);
957
+ Logger.log('[MB CLIENT] Advertisement broadcast succeeded. TXID:', result.txid);
900
958
  if (typeof result.txid !== 'string') {
901
959
  throw new Error('Anoint failed: broadcast did not return a txid');
902
960
  }
@@ -905,7 +963,7 @@ class MessageBoxClient {
905
963
  throw new Error('Anoint failed: failed to create action!');
906
964
  }
907
965
  catch (err) {
908
- logger_js_1.Logger.error('[MB CLIENT ERROR] anointHost threw:', err);
966
+ Logger.error('[MB CLIENT ERROR] anointHost threw:', err);
909
967
  throw err;
910
968
  }
911
969
  }
@@ -913,6 +971,7 @@ class MessageBoxClient {
913
971
  * @method revokeHostAdvertisement
914
972
  * @async
915
973
  * @param {AdvertisementToken} advertisementToken - The advertisement token containing the messagebox host to revoke.
974
+ * @param {string} [originator] - Optional originator to use with walletClient.
916
975
  * @returns {Promise<{ txid: string }>} - The transaction ID of the revocation broadcast to the overlay network.
917
976
  *
918
977
  * @description
@@ -922,8 +981,8 @@ class MessageBoxClient {
922
981
  * @example
923
982
  * const { txid } = await client.revokeHost('https://my-messagebox.io')
924
983
  */
925
- async revokeHostAdvertisement(advertisementToken) {
926
- logger_js_1.Logger.log('[MB CLIENT] Starting revokeHost...');
984
+ async revokeHostAdvertisement(advertisementToken, originator) {
985
+ Logger.log('[MB CLIENT] Starting revokeHost...');
927
986
  const outpoint = `${advertisementToken.txid}.${advertisementToken.outputIndex}`;
928
987
  try {
929
988
  const { signableTransaction } = await this.walletClient.createAction({
@@ -936,13 +995,13 @@ class MessageBoxClient {
936
995
  inputDescription: 'Revoking host advertisement token'
937
996
  }
938
997
  ]
939
- });
998
+ }, originator);
940
999
  if (signableTransaction === undefined) {
941
1000
  throw new Error('Failed to create signable transaction.');
942
1001
  }
943
1002
  const partialTx = sdk_1.Transaction.fromBEEF(signableTransaction.tx);
944
1003
  // Prepare the unlocker
945
- const pushdrop = new sdk_1.PushDrop(this.walletClient);
1004
+ const pushdrop = new sdk_1.PushDrop(this.walletClient, originator);
946
1005
  const unlocker = await pushdrop.unlock([1, 'messagebox advertisement'], '1', 'anyone', 'all', false, advertisementToken.outputIndex, advertisementToken.lockingScript);
947
1006
  // Convert to Transaction, apply signature
948
1007
  const finalUnlockScript = await unlocker.sign(partialTx, advertisementToken.outputIndex);
@@ -957,7 +1016,7 @@ class MessageBoxClient {
957
1016
  options: {
958
1017
  acceptDelayedBroadcast: false
959
1018
  }
960
- });
1019
+ }, originator);
961
1020
  if (signedTx === undefined) {
962
1021
  throw new Error('Failed to finalize the transaction signature.');
963
1022
  }
@@ -965,14 +1024,14 @@ class MessageBoxClient {
965
1024
  networkPreset: this.networkPreset
966
1025
  });
967
1026
  const result = await broadcaster.broadcast(sdk_1.Transaction.fromAtomicBEEF(signedTx));
968
- logger_js_1.Logger.log('[MB CLIENT] Revocation broadcast succeeded. TXID:', result.txid);
1027
+ Logger.log('[MB CLIENT] Revocation broadcast succeeded. TXID:', result.txid);
969
1028
  if (typeof result.txid !== 'string') {
970
1029
  throw new Error('Revoke failed: broadcast did not return a txid');
971
1030
  }
972
1031
  return { txid: result.txid };
973
1032
  }
974
1033
  catch (err) {
975
- logger_js_1.Logger.error('[MB CLIENT ERROR] revokeHost threw:', err);
1034
+ Logger.error('[MB CLIENT ERROR] revokeHost threw:', err);
976
1035
  throw err;
977
1036
  }
978
1037
  }
@@ -988,9 +1047,16 @@ class MessageBoxClient {
988
1047
  *
989
1048
  * Each message is:
990
1049
  * - Parsed and, if encrypted, decrypted using AES-256-GCM via BRC-2-compliant ECDH key derivation and symmetric encryption.
1050
+ * - Automatically processed for payments: if the message includes recipient fee payments, they are internalized using `walletClient.internalizeAction()`.
991
1051
  * - Returned as a normalized `PeerMessage` with readable string body content.
992
1052
  *
993
- * Decryption automatically derives a shared secret using the sender’s identity key and the receiver’s child private key.
1053
+ * Payment Processing:
1054
+ * - Detects messages that include payment data (from paid message delivery).
1055
+ * - Automatically internalizes recipient payment outputs, allowing you to receive payments without additional API calls.
1056
+ * - Only recipient payments are stored with messages - delivery fees are already processed by the server.
1057
+ * - Continues processing messages even if payment internalization fails.
1058
+ *
1059
+ * Decryption automatically derives a shared secret using the sender's identity key and the receiver's child private key.
994
1060
  * If the sender is the same as the recipient, the `counterparty` is set to `'self'`.
995
1061
  *
996
1062
  * @throws {Error} If no messageBox is specified, the request fails, or the server returns an error.
@@ -998,22 +1064,24 @@ class MessageBoxClient {
998
1064
  * @example
999
1065
  * const messages = await client.listMessages({ messageBox: 'inbox' })
1000
1066
  * messages.forEach(msg => console.log(msg.sender, msg.body))
1067
+ * // Payments included with messages are automatically received
1001
1068
  */
1002
- async listMessages({ messageBox, host }) {
1069
+ async listMessages({ messageBox, host, originator }) {
1070
+ var _a;
1003
1071
  await this.assertInitialized();
1004
1072
  if (messageBox.trim() === '') {
1005
1073
  throw new Error('MessageBox cannot be empty');
1006
1074
  }
1007
1075
  let hosts = host != null ? [host] : [];
1008
1076
  if (hosts.length === 0) {
1009
- const advertisedHosts = await this.queryAdvertisements(await this.getIdentityKey());
1077
+ const advertisedHosts = await this.queryAdvertisements(await this.getIdentityKey(originator), originator);
1010
1078
  hosts = Array.from(new Set([this.host, ...advertisedHosts.map(h => h.host)]));
1011
1079
  }
1012
1080
  // Query each host in parallel
1013
1081
  const fetchFromHost = async (host) => {
1014
1082
  var _a;
1015
1083
  try {
1016
- logger_js_1.Logger.log(`[MB CLIENT] Listing messages from ${host}…`);
1084
+ Logger.log(`[MB CLIENT] Listing messages from ${host}…`);
1017
1085
  const res = await this.authFetch.fetch(`${host}/listMessages`, {
1018
1086
  method: 'POST',
1019
1087
  headers: { 'Content-Type': 'application/json' },
@@ -1027,7 +1095,7 @@ class MessageBoxClient {
1027
1095
  return data.messages;
1028
1096
  }
1029
1097
  catch (err) {
1030
- logger_js_1.Logger.log(`[MB CLIENT DEBUG] listMessages failed for ${host}:`, err);
1098
+ Logger.log(`[MB CLIENT DEBUG] listMessages failed for ${host}:`, err);
1031
1099
  throw err; // re-throw to be caught in the settled promise
1032
1100
  }
1033
1101
  };
@@ -1070,25 +1138,69 @@ class MessageBoxClient {
1070
1138
  for (const message of messages) {
1071
1139
  try {
1072
1140
  const parsedBody = typeof message.body === 'string' ? tryParse(message.body) : message.body;
1141
+ let messageContent = parsedBody;
1142
+ let paymentData;
1073
1143
  if (parsedBody != null &&
1074
1144
  typeof parsedBody === 'object' &&
1075
- typeof parsedBody.encryptedMessage === 'string') {
1076
- logger_js_1.Logger.log(`[MB CLIENT] Decrypting message from ${String(message.sender)}…`);
1145
+ 'message' in parsedBody) {
1146
+ // Handle wrapped message format (with payment data)
1147
+ const wrappedMessage = parsedBody.message;
1148
+ messageContent = typeof wrappedMessage === 'string'
1149
+ ? tryParse(wrappedMessage)
1150
+ : wrappedMessage;
1151
+ paymentData = parsedBody.payment;
1152
+ }
1153
+ // Process payment if present - server now only stores recipient payments
1154
+ if ((paymentData === null || paymentData === void 0 ? void 0 : paymentData.tx) != null && paymentData.outputs != null) {
1155
+ try {
1156
+ Logger.log(`[MB CLIENT] Processing recipient payment in message from ${String(message.sender)}…`);
1157
+ // All outputs in the stored payment data are for the recipient
1158
+ // (delivery fees are already processed by the server)
1159
+ const recipientOutputs = paymentData.outputs.filter(output => output.protocol === 'wallet payment');
1160
+ if (recipientOutputs.length > 0) {
1161
+ Logger.log(`[MB CLIENT] Internalizing ${recipientOutputs.length} recipient payment output(s)…`);
1162
+ const internalizeResult = await this.walletClient.internalizeAction({
1163
+ tx: paymentData.tx,
1164
+ outputs: recipientOutputs,
1165
+ description: (_a = paymentData.description) !== null && _a !== void 0 ? _a : 'MessageBox recipient payment'
1166
+ }, originator);
1167
+ if (internalizeResult.accepted) {
1168
+ Logger.log('[MB CLIENT] Successfully internalized recipient payment');
1169
+ }
1170
+ else {
1171
+ Logger.warn('[MB CLIENT] Recipient payment internalization was not accepted');
1172
+ }
1173
+ }
1174
+ else {
1175
+ Logger.log('[MB CLIENT] No wallet payment outputs found in payment data');
1176
+ }
1177
+ }
1178
+ catch (paymentError) {
1179
+ Logger.error('[MB CLIENT ERROR] Failed to internalize recipient payment:', paymentError);
1180
+ // Continue processing the message even if payment fails
1181
+ }
1182
+ }
1183
+ // Handle message decryption
1184
+ if (messageContent != null &&
1185
+ typeof messageContent === 'object' &&
1186
+ typeof messageContent.encryptedMessage === 'string') {
1187
+ Logger.log(`[MB CLIENT] Decrypting message from ${String(message.sender)}…`);
1077
1188
  const decrypted = await this.walletClient.decrypt({
1078
1189
  protocolID: [1, 'messagebox'],
1079
1190
  keyID: '1',
1080
1191
  counterparty: message.sender,
1081
- ciphertext: sdk_1.Utils.toArray(parsedBody.encryptedMessage, 'base64')
1082
- });
1192
+ ciphertext: sdk_1.Utils.toArray(messageContent.encryptedMessage, 'base64')
1193
+ }, originator);
1083
1194
  const decryptedText = sdk_1.Utils.toUTF8(decrypted.plaintext);
1084
1195
  message.body = tryParse(decryptedText);
1085
1196
  }
1086
1197
  else {
1087
- message.body = parsedBody;
1198
+ // For non-encrypted messages, use the processed content
1199
+ message.body = messageContent !== null && messageContent !== void 0 ? messageContent : parsedBody;
1088
1200
  }
1089
1201
  }
1090
1202
  catch (err) {
1091
- logger_js_1.Logger.error('[MB CLIENT ERROR] Failed to parse or decrypt message in list:', err);
1203
+ Logger.error('[MB CLIENT ERROR] Failed to parse or decrypt message in list:', err);
1092
1204
  message.body = '[Error: Failed to decrypt or parse message]';
1093
1205
  }
1094
1206
  }
@@ -1117,18 +1229,18 @@ class MessageBoxClient {
1117
1229
  * @example
1118
1230
  * await client.acknowledgeMessage({ messageIds: ['msg123', 'msg456'] })
1119
1231
  */
1120
- async acknowledgeMessage({ messageIds, host }) {
1232
+ async acknowledgeMessage({ messageIds, host, originator }) {
1121
1233
  var _a;
1122
1234
  await this.assertInitialized();
1123
1235
  if (!Array.isArray(messageIds) || messageIds.length === 0) {
1124
1236
  throw new Error('Message IDs array cannot be empty');
1125
1237
  }
1126
- logger_js_1.Logger.log(`[MB CLIENT] Acknowledging messages ${JSON.stringify(messageIds)}…`);
1238
+ Logger.log(`[MB CLIENT] Acknowledging messages ${JSON.stringify(messageIds)}…`);
1127
1239
  let hosts = host != null ? [host] : [];
1128
1240
  if (hosts.length === 0) {
1129
1241
  // 1. Determine all hosts (advertised + default)
1130
- const identityKey = await this.getIdentityKey();
1131
- const advertisedHosts = await this.queryAdvertisements(identityKey);
1242
+ const identityKey = await this.getIdentityKey(originator);
1243
+ const advertisedHosts = await this.queryAdvertisements(identityKey, undefined, originator);
1132
1244
  hosts = Array.from(new Set([this.host, ...advertisedHosts.map(h => h.host)]));
1133
1245
  }
1134
1246
  // 2. Dispatch parallel acknowledge requests
@@ -1144,11 +1256,11 @@ class MessageBoxClient {
1144
1256
  const data = await res.json();
1145
1257
  if (data.status === 'error')
1146
1258
  throw new Error(data.description);
1147
- logger_js_1.Logger.log(`[MB CLIENT] Acknowledged on ${host}`);
1259
+ Logger.log(`[MB CLIENT] Acknowledged on ${host}`);
1148
1260
  return data.status;
1149
1261
  }
1150
1262
  catch (err) {
1151
- logger_js_1.Logger.warn(`[MB CLIENT WARN] acknowledgeMessage failed for ${host}:`, err);
1263
+ Logger.warn(`[MB CLIENT WARN] acknowledgeMessage failed for ${host}:`, err);
1152
1264
  return null;
1153
1265
  }
1154
1266
  };
@@ -1166,6 +1278,512 @@ class MessageBoxClient {
1166
1278
  }
1167
1279
  throw new Error(`Failed to acknowledge messages on all hosts: ${errs.map(e => String(e)).join('; ')}`);
1168
1280
  }
1281
+ // ===========================
1282
+ // PERMISSION MANAGEMENT METHODS
1283
+ // ===========================
1284
+ /**
1285
+ * @method setMessageBoxPermission
1286
+ * @async
1287
+ * @param {SetMessageBoxPermissionParams} params - Permission configuration
1288
+ * @param {string} [overrideHost] - Optional host override
1289
+ * @returns {Promise<void>} Permission status after setting
1290
+ *
1291
+ * @description
1292
+ * Sets permission for receiving messages in a specific messageBox.
1293
+ * Can set sender-specific permissions or box-wide defaults.
1294
+ *
1295
+ * @example
1296
+ * // Set box-wide default: allow notifications for 10 sats
1297
+ * await client.setMessageBoxPermission({ messageBox: 'notifications', recipientFee: 10 })
1298
+ *
1299
+ * // Block specific sender
1300
+ * await client.setMessageBoxPermission({
1301
+ * messageBox: 'notifications',
1302
+ * sender: '03abc123...',
1303
+ * recipientFee: -1
1304
+ * })
1305
+ */
1306
+ async setMessageBoxPermission(params, overrideHost) {
1307
+ await this.assertInitialized();
1308
+ const finalHost = overrideHost !== null && overrideHost !== void 0 ? overrideHost : this.host;
1309
+ Logger.log('[MB CLIENT] Setting messageBox permission...');
1310
+ const response = await this.authFetch.fetch(`${finalHost}/permissions/set`, {
1311
+ method: 'POST',
1312
+ headers: { 'Content-Type': 'application/json' },
1313
+ body: JSON.stringify({
1314
+ messageBox: params.messageBox,
1315
+ recipientFee: params.recipientFee,
1316
+ ...(params.sender != null && { sender: params.sender })
1317
+ })
1318
+ });
1319
+ if (!response.ok) {
1320
+ const errorData = await response.json().catch(() => ({}));
1321
+ throw new Error(`Failed to set permission: HTTP ${response.status} - ${String(errorData.description) !== '' ? String(errorData.description) : response.statusText}`);
1322
+ }
1323
+ const { status, description } = await response.json();
1324
+ if (status === 'error') {
1325
+ throw new Error(description !== null && description !== void 0 ? description : 'Failed to set permission');
1326
+ }
1327
+ }
1328
+ /**
1329
+ * @method getMessageBoxPermission
1330
+ * @async
1331
+ * @param {GetMessageBoxPermissionParams} params - Permission query parameters
1332
+ * @param {string} [overrideHost] - Optional host override
1333
+ * @returns {Promise<MessageBoxPermission | null>} Permission data (null if not set)
1334
+ *
1335
+ * @description
1336
+ * Gets current permission data for a sender/messageBox combination.
1337
+ * Returns null if no permission is set.
1338
+ *
1339
+ * @example
1340
+ * const status = await client.getMessageBoxPermission({
1341
+ * recipient: '03def456...',
1342
+ * messageBox: 'notifications',
1343
+ * sender: '03abc123...'
1344
+ * })
1345
+ */
1346
+ async getMessageBoxPermission(params, overrideHost) {
1347
+ var _a;
1348
+ await this.assertInitialized();
1349
+ const finalHost = overrideHost !== null && overrideHost !== void 0 ? overrideHost : await this.resolveHostForRecipient(params.recipient);
1350
+ const queryParams = new URLSearchParams({
1351
+ recipient: params.recipient,
1352
+ messageBox: params.messageBox,
1353
+ ...(params.sender != null && { sender: params.sender })
1354
+ });
1355
+ Logger.log('[MB CLIENT] Getting messageBox permission...');
1356
+ const response = await this.authFetch.fetch(`${finalHost}/permissions/get?${queryParams.toString()}`, {
1357
+ method: 'GET'
1358
+ });
1359
+ if (!response.ok) {
1360
+ const errorData = await response.json().catch(() => ({}));
1361
+ throw new Error(`Failed to get permission: HTTP ${response.status} - ${String(errorData.description) !== '' ? String(errorData.description) : response.statusText}`);
1362
+ }
1363
+ const data = await response.json();
1364
+ if (data.status === 'error') {
1365
+ throw new Error((_a = data.description) !== null && _a !== void 0 ? _a : 'Failed to get permission');
1366
+ }
1367
+ return data.permission;
1368
+ }
1369
+ /**
1370
+ * @method getMessageBoxQuote
1371
+ * @async
1372
+ * @param {GetQuoteParams} params - Quote request parameters
1373
+ * @returns {Promise<MessageBoxQuote>} Fee quote and permission status
1374
+ *
1375
+ * @description
1376
+ * Gets a fee quote for sending a message, including delivery and recipient fees.
1377
+ *
1378
+ * @example
1379
+ * const quote = await client.getMessageBoxQuote({
1380
+ * recipient: '03def456...',
1381
+ * messageBox: 'notifications'
1382
+ * })
1383
+ */
1384
+ async getMessageBoxQuote(params, overrideHost) {
1385
+ var _a;
1386
+ await this.assertInitialized();
1387
+ const finalHost = overrideHost !== null && overrideHost !== void 0 ? overrideHost : await this.resolveHostForRecipient(params.recipient);
1388
+ const queryParams = new URLSearchParams({
1389
+ recipient: params.recipient,
1390
+ messageBox: params.messageBox
1391
+ });
1392
+ Logger.log('[MB CLIENT] Getting messageBox quote...');
1393
+ const response = await this.authFetch.fetch(`${finalHost}/permissions/quote?${queryParams.toString()}`, {
1394
+ method: 'GET'
1395
+ });
1396
+ if (!response.ok) {
1397
+ const errorData = await response.json().catch(() => ({}));
1398
+ throw new Error(`Failed to get quote: HTTP ${response.status} - ${(_a = String(errorData.description)) !== null && _a !== void 0 ? _a : response.statusText}`);
1399
+ }
1400
+ const { status, description, quote } = await response.json();
1401
+ if (status === 'error') {
1402
+ throw new Error(description !== null && description !== void 0 ? description : 'Failed to get quote');
1403
+ }
1404
+ const deliveryAgentIdentityKey = response.headers.get('x-bsv-auth-identity-key');
1405
+ if (deliveryAgentIdentityKey == null) {
1406
+ throw new Error('Failed to get quote: Delivery agent did not provide their identity key');
1407
+ }
1408
+ return {
1409
+ recipientFee: quote.recipientFee,
1410
+ deliveryFee: quote.deliveryFee,
1411
+ deliveryAgentIdentityKey
1412
+ };
1413
+ }
1414
+ /**
1415
+ * @method listMessageBoxPermissions
1416
+ * @async
1417
+ * @param {ListPermissionsParams} [params] - Optional filtering and pagination parameters
1418
+ * @returns {Promise<MessageBoxPermission[]>} List of current permissions
1419
+ *
1420
+ * @description
1421
+ * Lists permissions for the authenticated user's messageBoxes with optional pagination.
1422
+ *
1423
+ * @example
1424
+ * // List all permissions
1425
+ * const all = await client.listMessageBoxPermissions()
1426
+ *
1427
+ * // List only notification permissions with pagination
1428
+ * const notifications = await client.listMessageBoxPermissions({
1429
+ * messageBox: 'notifications',
1430
+ * limit: 50,
1431
+ * offset: 0
1432
+ * })
1433
+ */
1434
+ async listMessageBoxPermissions(params, overrideHost) {
1435
+ var _a;
1436
+ await this.assertInitialized();
1437
+ const finalHost = overrideHost !== null && overrideHost !== void 0 ? overrideHost : this.host;
1438
+ const queryParams = new URLSearchParams();
1439
+ if ((params === null || params === void 0 ? void 0 : params.messageBox) != null) {
1440
+ queryParams.set('message_box', params.messageBox);
1441
+ }
1442
+ if ((params === null || params === void 0 ? void 0 : params.limit) !== undefined) {
1443
+ queryParams.set('limit', params.limit.toString());
1444
+ }
1445
+ if ((params === null || params === void 0 ? void 0 : params.offset) !== undefined) {
1446
+ queryParams.set('offset', params.offset.toString());
1447
+ }
1448
+ Logger.log('[MB CLIENT] Listing messageBox permissions with params:', queryParams.toString());
1449
+ const response = await this.authFetch.fetch(`${finalHost}/permissions/list?${queryParams.toString()}`, {
1450
+ method: 'GET'
1451
+ });
1452
+ if (!response.ok) {
1453
+ const errorData = await response.json().catch(() => ({}));
1454
+ throw new Error(`Failed to list permissions: HTTP ${response.status} - ${String(errorData.description) !== '' ? String(errorData.description) : response.statusText}`);
1455
+ }
1456
+ const data = await response.json();
1457
+ if (data.status === 'error') {
1458
+ throw new Error((_a = data.description) !== null && _a !== void 0 ? _a : 'Failed to list permissions');
1459
+ }
1460
+ return data.permissions.map((p) => ({
1461
+ sender: p.sender,
1462
+ messageBox: p.message_box,
1463
+ recipientFee: p.recipient_fee,
1464
+ status: MessageBoxClient.getStatusFromFee(p.recipient_fee),
1465
+ createdAt: p.created_at,
1466
+ updatedAt: p.updated_at
1467
+ }));
1468
+ }
1469
+ // ===========================
1470
+ // NOTIFICATION CONVENIENCE METHODS
1471
+ // ===========================
1472
+ /**
1473
+ * @method allowNotificationsFromPeer
1474
+ * @async
1475
+ * @param {PubKeyHex} identityKey - Sender's identity key to allow
1476
+ * @param {number} [recipientFee=0] - Fee to charge (0 for always allow)
1477
+ * @param {string} [overrideHost] - Optional host override
1478
+ * @returns {Promise<void>} Permission status after allowing
1479
+ *
1480
+ * @description
1481
+ * Convenience method to allow notifications from a specific peer.
1482
+ *
1483
+ * @example
1484
+ * await client.allowNotificationsFromPeer('03abc123...') // Always allow
1485
+ * await client.allowNotificationsFromPeer('03def456...', 5) // Allow for 5 sats
1486
+ */
1487
+ async allowNotificationsFromPeer(identityKey, recipientFee = 0, overrideHost) {
1488
+ await this.setMessageBoxPermission({
1489
+ messageBox: 'notifications',
1490
+ sender: identityKey,
1491
+ recipientFee
1492
+ }, overrideHost);
1493
+ }
1494
+ /**
1495
+ * @method denyNotificationsFromPeer
1496
+ * @async
1497
+ * @param {PubKeyHex} identityKey - Sender's identity key to block
1498
+ * @returns {Promise<void>} Permission status after denying
1499
+ *
1500
+ * @description
1501
+ * Convenience method to block notifications from a specific peer.
1502
+ *
1503
+ * @example
1504
+ * await client.denyNotificationsFromPeer('03spam123...')
1505
+ */
1506
+ async denyNotificationsFromPeer(identityKey, overrideHost) {
1507
+ await this.setMessageBoxPermission({
1508
+ messageBox: 'notifications',
1509
+ sender: identityKey,
1510
+ recipientFee: -1
1511
+ }, overrideHost);
1512
+ }
1513
+ /**
1514
+ * @method checkPeerNotificationStatus
1515
+ * @async
1516
+ * @param {PubKeyHex} identityKey - Sender's identity key to check
1517
+ * @returns {Promise<MessageBoxPermission>} Current permission status
1518
+ *
1519
+ * @description
1520
+ * Convenience method to check notification permission for a specific peer.
1521
+ *
1522
+ * @example
1523
+ * const status = await client.checkPeerNotificationStatus('03abc123...')
1524
+ * console.log(status.allowed) // true/false
1525
+ */
1526
+ async checkPeerNotificationStatus(identityKey, overrideHost) {
1527
+ const myIdentityKey = await this.getIdentityKey();
1528
+ return await this.getMessageBoxPermission({
1529
+ recipient: myIdentityKey,
1530
+ messageBox: 'notifications',
1531
+ sender: identityKey
1532
+ }, overrideHost);
1533
+ }
1534
+ /**
1535
+ * @method listPeerNotifications
1536
+ * @async
1537
+ * @returns {Promise<MessageBoxPermission[]>} List of notification permissions
1538
+ *
1539
+ * @description
1540
+ * Convenience method to list all notification permissions.
1541
+ *
1542
+ * @example
1543
+ * const notifications = await client.listPeerNotifications()
1544
+ */
1545
+ async listPeerNotifications(overrideHost) {
1546
+ return await this.listMessageBoxPermissions({ messageBox: 'notifications' }, overrideHost);
1547
+ }
1548
+ /**
1549
+ * @method sendNotification
1550
+ * @async
1551
+ * @param {PubKeyHex} recipient - Recipient's identity key
1552
+ * @param {string | object} body - Notification content
1553
+ * @param {string} [overrideHost] - Optional host override
1554
+ * @returns {Promise<SendMessageResponse>} Send result
1555
+ *
1556
+ * @description
1557
+ * Convenience method to send a notification with automatic quote fetching and payment handling.
1558
+ * Automatically determines the required payment amount and creates the payment if needed.
1559
+ *
1560
+ * @example
1561
+ * // Send notification (auto-determines payment needed)
1562
+ * await client.sendNotification('03def456...', 'Hello!')
1563
+ *
1564
+ * // Send with maximum payment limit for safety
1565
+ * await client.sendNotification('03def456...', { title: 'Alert', body: 'Important update' }, 50)
1566
+ */
1567
+ async sendNotification(recipient, body, overrideHost) {
1568
+ await this.assertInitialized();
1569
+ // Use sendMessage with permission checking enabled
1570
+ // This eliminates duplication of quote fetching and payment logic
1571
+ return await this.sendMessage({
1572
+ recipient,
1573
+ messageBox: 'notifications',
1574
+ body,
1575
+ checkPermissions: true
1576
+ }, overrideHost);
1577
+ }
1578
+ /**
1579
+ * Register a device for FCM push notifications.
1580
+ *
1581
+ * @async
1582
+ * @param {DeviceRegistrationParams} params - Device registration parameters
1583
+ * @param {string} [overrideHost] - Optional host override
1584
+ * @returns {Promise<DeviceRegistrationResponse>} Registration response
1585
+ *
1586
+ * @description
1587
+ * Registers a device with the message box server to receive FCM push notifications.
1588
+ * The FCM token is obtained from Firebase SDK on the client side.
1589
+ *
1590
+ * @example
1591
+ * const result = await client.registerDevice({
1592
+ * fcmToken: 'eBo8F...',
1593
+ * platform: 'ios',
1594
+ * deviceId: 'iPhone15Pro'
1595
+ * })
1596
+ */
1597
+ async registerDevice(params, overrideHost) {
1598
+ var _a, _b, _c, _d, _e;
1599
+ await this.assertInitialized();
1600
+ if (params.fcmToken == null || params.fcmToken.trim() === '') {
1601
+ throw new Error('fcmToken is required and must be a non-empty string');
1602
+ }
1603
+ // Validate platform if provided
1604
+ const validPlatforms = ['ios', 'android', 'web'];
1605
+ if (params.platform != null && !validPlatforms.includes(params.platform)) {
1606
+ throw new Error('platform must be one of: ios, android, web');
1607
+ }
1608
+ const finalHost = overrideHost !== null && overrideHost !== void 0 ? overrideHost : this.host;
1609
+ Logger.log('[MB CLIENT] Registering device for FCM notifications...');
1610
+ const response = await this.authFetch.fetch(`${finalHost}/registerDevice`, {
1611
+ method: 'POST',
1612
+ headers: { 'Content-Type': 'application/json' },
1613
+ body: JSON.stringify({
1614
+ fcmToken: params.fcmToken.trim(),
1615
+ deviceId: (_b = (_a = params.deviceId) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : undefined,
1616
+ platform: (_c = params.platform) !== null && _c !== void 0 ? _c : undefined
1617
+ })
1618
+ });
1619
+ if (!response.ok) {
1620
+ const errorData = await response.json().catch(() => ({}));
1621
+ const description = (_d = String(errorData.description)) !== null && _d !== void 0 ? _d : response.statusText;
1622
+ throw new Error(`Failed to register device: HTTP ${response.status} - ${description}`);
1623
+ }
1624
+ const data = await response.json();
1625
+ if (data.status === 'error') {
1626
+ throw new Error((_e = data.description) !== null && _e !== void 0 ? _e : 'Failed to register device');
1627
+ }
1628
+ Logger.log('[MB CLIENT] Device registered successfully');
1629
+ return {
1630
+ status: data.status,
1631
+ message: data.message,
1632
+ deviceId: data.deviceId
1633
+ };
1634
+ }
1635
+ /**
1636
+ * List all registered devices for push notifications.
1637
+ *
1638
+ * @async
1639
+ * @param {string} [overrideHost] - Optional host override
1640
+ * @returns {Promise<RegisteredDevice[]>} Array of registered devices
1641
+ *
1642
+ * @description
1643
+ * Retrieves all devices registered by the authenticated user for FCM push notifications.
1644
+ * Only shows devices belonging to the current user (authenticated via AuthFetch).
1645
+ *
1646
+ * @example
1647
+ * const devices = await client.listRegisteredDevices()
1648
+ * console.log(`Found ${devices.length} registered devices`)
1649
+ * devices.forEach(device => {
1650
+ * console.log(`Device: ${device.platform} - ${device.fcmToken}`)
1651
+ * })
1652
+ */
1653
+ async listRegisteredDevices(overrideHost) {
1654
+ var _a, _b;
1655
+ await this.assertInitialized();
1656
+ const finalHost = overrideHost !== null && overrideHost !== void 0 ? overrideHost : this.host;
1657
+ Logger.log('[MB CLIENT] Listing registered devices...');
1658
+ const response = await this.authFetch.fetch(`${finalHost}/devices`, {
1659
+ method: 'GET'
1660
+ });
1661
+ if (!response.ok) {
1662
+ const errorData = await response.json().catch(() => ({}));
1663
+ const description = (_a = String(errorData.description)) !== null && _a !== void 0 ? _a : response.statusText;
1664
+ throw new Error(`Failed to list devices: HTTP ${response.status} - ${description}`);
1665
+ }
1666
+ const data = await response.json();
1667
+ if (data.status === 'error') {
1668
+ throw new Error((_b = data.description) !== null && _b !== void 0 ? _b : 'Failed to list devices');
1669
+ }
1670
+ Logger.log(`[MB CLIENT] Found ${data.devices.length} registered devices`);
1671
+ return data.devices;
1672
+ }
1673
+ // ===========================
1674
+ // PRIVATE HELPER METHODS
1675
+ // ===========================
1676
+ static getStatusFromFee(fee) {
1677
+ if (fee === -1)
1678
+ return 'blocked';
1679
+ if (fee === 0)
1680
+ return 'always_allow';
1681
+ return 'payment_required';
1682
+ }
1683
+ /**
1684
+ * Creates payment transaction for message delivery fees
1685
+ * TODO: Consider consolidating payment generating logic with a util PeerPayClient can use as well.
1686
+ * @private
1687
+ * @param {string} recipient - Recipient identity key
1688
+ * @param {MessageBoxQuote} quote - Fee quote with delivery and recipient fees
1689
+ * @param {string} description - Description for the payment transaction
1690
+ * @returns {Promise<Payment>} Payment transaction data
1691
+ */
1692
+ async createMessagePayment(recipient, quote, description = 'MessageBox delivery payment', originator) {
1693
+ if (quote.recipientFee <= 0 && quote.deliveryFee <= 0) {
1694
+ throw new Error('No payment required');
1695
+ }
1696
+ Logger.log(`[MB CLIENT] Creating payment transaction for ${quote.recipientFee} sats (delivery: ${quote.deliveryFee}, recipient: ${quote.recipientFee})`);
1697
+ const outputs = [];
1698
+ const createActionOutputs = [];
1699
+ // Get sender identity key for remittance data
1700
+ const senderIdentityKey = await this.getIdentityKey();
1701
+ // Add server delivery fee output if > 0
1702
+ let outputIndex = 0;
1703
+ if (quote.deliveryFee > 0) {
1704
+ const derivationPrefix = sdk_1.Utils.toBase64((0, sdk_1.Random)(32));
1705
+ const derivationSuffix = sdk_1.Utils.toBase64((0, sdk_1.Random)(32));
1706
+ // Get host's derived public key
1707
+ const { publicKey: derivedKeyResult } = await this.walletClient.getPublicKey({
1708
+ protocolID: [2, '3241645161d8'],
1709
+ keyID: `${derivationPrefix} ${derivationSuffix}`,
1710
+ counterparty: quote.deliveryAgentIdentityKey
1711
+ }, originator);
1712
+ // Create locking script using host's public key
1713
+ const lockingScript = new sdk_1.P2PKH().lock(sdk_1.PublicKey.fromString(derivedKeyResult).toAddress()).toHex();
1714
+ // Add to createAction outputs
1715
+ createActionOutputs.push({
1716
+ satoshis: quote.deliveryFee,
1717
+ lockingScript,
1718
+ outputDescription: 'MessageBox server delivery fee',
1719
+ customInstructions: JSON.stringify({
1720
+ derivationPrefix,
1721
+ derivationSuffix,
1722
+ recipientIdentityKey: quote.deliveryAgentIdentityKey
1723
+ })
1724
+ });
1725
+ outputs.push({
1726
+ outputIndex: outputIndex++,
1727
+ protocol: 'wallet payment',
1728
+ paymentRemittance: {
1729
+ derivationPrefix,
1730
+ derivationSuffix,
1731
+ senderIdentityKey
1732
+ }
1733
+ });
1734
+ }
1735
+ // Add recipient fee output if > 0
1736
+ if (quote.recipientFee > 0) {
1737
+ const derivationPrefix = sdk_1.Utils.toBase64((0, sdk_1.Random)(32));
1738
+ const derivationSuffix = sdk_1.Utils.toBase64((0, sdk_1.Random)(32));
1739
+ // Get a derived public key for the recipient that "anyone" can verify
1740
+ const anyoneWallet = new sdk_1.ProtoWallet('anyone');
1741
+ const { publicKey: derivedKeyResult } = await anyoneWallet.getPublicKey({
1742
+ protocolID: [2, '3241645161d8'],
1743
+ keyID: `${derivationPrefix} ${derivationSuffix}`,
1744
+ counterparty: recipient
1745
+ });
1746
+ if (derivedKeyResult == null || derivedKeyResult.trim() === '') {
1747
+ throw new Error('Failed to derive recipient\'s public key');
1748
+ }
1749
+ // Create locking script using recipient's public key
1750
+ const lockingScript = new sdk_1.P2PKH().lock(sdk_1.PublicKey.fromString(derivedKeyResult).toAddress()).toHex();
1751
+ // Add to createAction outputs
1752
+ createActionOutputs.push({
1753
+ satoshis: quote.recipientFee,
1754
+ lockingScript,
1755
+ outputDescription: 'Recipient message fee',
1756
+ customInstructions: JSON.stringify({
1757
+ derivationPrefix,
1758
+ derivationSuffix,
1759
+ recipientIdentityKey: recipient
1760
+ })
1761
+ });
1762
+ outputs.push({
1763
+ outputIndex: outputIndex++,
1764
+ protocol: 'wallet payment',
1765
+ paymentRemittance: {
1766
+ derivationPrefix,
1767
+ derivationSuffix,
1768
+ senderIdentityKey: (await anyoneWallet.getPublicKey({ identityKey: true })).publicKey
1769
+ }
1770
+ });
1771
+ }
1772
+ const { tx } = await this.walletClient.createAction({
1773
+ description,
1774
+ outputs: createActionOutputs,
1775
+ options: { randomizeOutputs: false, acceptDelayedBroadcast: false }
1776
+ }, originator);
1777
+ if (tx == null) {
1778
+ throw new Error('Failed to create payment transaction');
1779
+ }
1780
+ return {
1781
+ tx,
1782
+ outputs,
1783
+ description
1784
+ // labels
1785
+ };
1786
+ }
1169
1787
  }
1170
1788
  exports.MessageBoxClient = MessageBoxClient;
1171
1789
  //# sourceMappingURL=MessageBoxClient.js.map