@unicitylabs/nostr-js-sdk 0.2.5 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5591,6 +5591,8 @@
5591
5591
  const GIFT_WRAP = 1059;
5592
5592
  /** NIP-65: Relay list metadata */
5593
5593
  const RELAY_LIST = 10002;
5594
+ /** NIP-42: Client authentication to relay */
5595
+ const AUTH = 22242;
5594
5596
  /** NIP-78: Application-specific data (parameterized replaceable) */
5595
5597
  const APP_DATA = 30078;
5596
5598
  // ============================================================================
@@ -5606,6 +5608,8 @@
5606
5608
  const FILE_METADATA = 31114;
5607
5609
  /** Unicity: Payment request */
5608
5610
  const PAYMENT_REQUEST = 31115;
5611
+ /** Unicity: Payment request response (accept/decline) */
5612
+ const PAYMENT_REQUEST_RESPONSE = 31116;
5609
5613
  // ============================================================================
5610
5614
  // Event Kind Classification Functions
5611
5615
  // ============================================================================
@@ -5681,6 +5685,8 @@
5681
5685
  return 'File Metadata';
5682
5686
  case PAYMENT_REQUEST:
5683
5687
  return 'Payment Request';
5688
+ case PAYMENT_REQUEST_RESPONSE:
5689
+ return 'Payment Request Response';
5684
5690
  default:
5685
5691
  if (isReplaceable(kind)) {
5686
5692
  return `Replaceable (${kind})`;
@@ -5700,6 +5706,7 @@
5700
5706
  AGENT_LOCATION: AGENT_LOCATION,
5701
5707
  AGENT_PROFILE: AGENT_PROFILE,
5702
5708
  APP_DATA: APP_DATA,
5709
+ AUTH: AUTH,
5703
5710
  CHAT_MESSAGE: CHAT_MESSAGE,
5704
5711
  CONTACTS: CONTACTS,
5705
5712
  DELETION: DELETION,
@@ -5707,6 +5714,7 @@
5707
5714
  FILE_METADATA: FILE_METADATA,
5708
5715
  GIFT_WRAP: GIFT_WRAP,
5709
5716
  PAYMENT_REQUEST: PAYMENT_REQUEST,
5717
+ PAYMENT_REQUEST_RESPONSE: PAYMENT_REQUEST_RESPONSE,
5710
5718
  PROFILE: PROFILE,
5711
5719
  REACTION: REACTION,
5712
5720
  READ_RECEIPT: READ_RECEIPT,
@@ -5999,6 +6007,13 @@
5999
6007
  const DEFAULT_RECONNECT_INTERVAL_MS = 1000;
6000
6008
  const DEFAULT_MAX_RECONNECT_INTERVAL_MS = 30000;
6001
6009
  const DEFAULT_PING_INTERVAL_MS = 30000;
6010
+ /**
6011
+ * Delay before resubscribing after NIP-42 authentication.
6012
+ * This gives the relay time to process the AUTH response before we send
6013
+ * subscription requests. Without this delay, some relays may still reject
6014
+ * the subscriptions as the AUTH hasn't been fully processed yet.
6015
+ */
6016
+ const AUTH_RESUBSCRIBE_DELAY_MS = 100;
6002
6017
  /**
6003
6018
  * NostrClient provides the main interface for Nostr protocol operations.
6004
6019
  */
@@ -6343,6 +6358,9 @@
6343
6358
  case 'CLOSED':
6344
6359
  this.handleClosedMessage(json);
6345
6360
  break;
6361
+ case 'AUTH':
6362
+ this.handleAuthMessage(_url, json);
6363
+ break;
6346
6364
  }
6347
6365
  }
6348
6366
  catch {
@@ -6423,6 +6441,33 @@
6423
6441
  subscription.listener.onError(subscriptionId, `Subscription closed: ${message}`);
6424
6442
  }
6425
6443
  }
6444
+ /**
6445
+ * Handle AUTH message from relay (NIP-42 authentication challenge).
6446
+ */
6447
+ handleAuthMessage(relayUrl, json) {
6448
+ if (json.length < 2)
6449
+ return;
6450
+ const challenge = json[1];
6451
+ const relay = this.relays.get(relayUrl);
6452
+ if (!relay?.socket || !relay.connected)
6453
+ return;
6454
+ // Create and sign the auth event (kind 22242)
6455
+ const authEvent = Event.create(this.keyManager, {
6456
+ kind: AUTH,
6457
+ tags: [
6458
+ ['relay', relayUrl],
6459
+ ['challenge', challenge],
6460
+ ],
6461
+ content: '',
6462
+ });
6463
+ // Send AUTH response
6464
+ const message = JSON.stringify(['AUTH', authEvent.toJSON()]);
6465
+ relay.socket.send(message);
6466
+ // Re-send subscriptions after auth (relay may have ignored pre-auth requests)
6467
+ setTimeout(() => {
6468
+ this.resubscribeAll(relayUrl);
6469
+ }, AUTH_RESUBSCRIBE_DELAY_MS);
6470
+ }
6426
6471
  /**
6427
6472
  * Disconnect from all relays.
6428
6473
  */
@@ -6569,6 +6614,41 @@
6569
6614
  const event = await PaymentRequestProtocol$1.createPaymentRequestEvent(this.keyManager, targetPubkeyHex, request);
6570
6615
  return this.publishEvent(event);
6571
6616
  }
6617
+ /**
6618
+ * Send a payment request response (decline/expiration notification).
6619
+ * @param targetPubkeyHex Original requester's public key
6620
+ * @param response Response details
6621
+ * @returns Promise that resolves with the event ID
6622
+ */
6623
+ async sendPaymentRequestResponse(targetPubkeyHex, response) {
6624
+ const PaymentRequestProtocol$1 = await Promise.resolve().then(function () { return PaymentRequestProtocol; });
6625
+ const event = await PaymentRequestProtocol$1.createPaymentRequestResponseEvent(this.keyManager, targetPubkeyHex, {
6626
+ requestId: response.requestId,
6627
+ originalEventId: response.originalEventId,
6628
+ status: response.status === 'DECLINED'
6629
+ ? PaymentRequestProtocol$1.ResponseStatus.DECLINED
6630
+ : PaymentRequestProtocol$1.ResponseStatus.EXPIRED,
6631
+ reason: response.reason,
6632
+ });
6633
+ return this.publishEvent(event);
6634
+ }
6635
+ /**
6636
+ * Send a payment request decline response.
6637
+ * Convenience method for declining a payment request.
6638
+ * @param originalRequestSenderPubkey Pubkey of who sent the original payment request
6639
+ * @param originalEventId Event ID of the original payment request
6640
+ * @param requestId Request ID from the original payment request
6641
+ * @param reason Optional reason for declining
6642
+ * @returns Promise that resolves with the event ID
6643
+ */
6644
+ async sendPaymentRequestDecline(originalRequestSenderPubkey, originalEventId, requestId, reason) {
6645
+ return this.sendPaymentRequestResponse(originalRequestSenderPubkey, {
6646
+ requestId,
6647
+ originalEventId,
6648
+ status: 'DECLINED',
6649
+ reason,
6650
+ });
6651
+ }
6572
6652
  /**
6573
6653
  * Publish a nametag binding.
6574
6654
  * @param nametagId Nametag identifier
@@ -10204,6 +10284,20 @@
10204
10284
  */
10205
10285
  /** Prefix for payment request messages */
10206
10286
  const MESSAGE_PREFIX = 'payment_request:';
10287
+ /** Prefix for payment request response messages */
10288
+ const RESPONSE_PREFIX = 'payment_request_response:';
10289
+ /** Default deadline duration: 5 minutes in milliseconds */
10290
+ const DEFAULT_DEADLINE_MS = 5 * 60 * 1000;
10291
+ /**
10292
+ * Payment request response status.
10293
+ */
10294
+ var ResponseStatus;
10295
+ (function (ResponseStatus) {
10296
+ /** Payment request was declined by the recipient */
10297
+ ResponseStatus["DECLINED"] = "DECLINED";
10298
+ /** Payment request expired (deadline passed) */
10299
+ ResponseStatus["EXPIRED"] = "EXPIRED";
10300
+ })(ResponseStatus || (ResponseStatus = {}));
10207
10301
  /**
10208
10302
  * Generate a short unique request ID.
10209
10303
  */
@@ -10244,6 +10338,13 @@
10244
10338
  async function createPaymentRequestEvent(keyManager, targetPubkeyHex, request) {
10245
10339
  // Generate request ID if not provided
10246
10340
  const requestId = request.requestId || generateRequestId();
10341
+ // Calculate deadline: use provided value, or default to 5 minutes from now
10342
+ // If explicitly set to null, no deadline
10343
+ const deadline = request.deadline === null
10344
+ ? null
10345
+ : request.deadline !== undefined
10346
+ ? request.deadline
10347
+ : Date.now() + DEFAULT_DEADLINE_MS;
10247
10348
  // Serialize request to JSON
10248
10349
  const requestJson = JSON.stringify({
10249
10350
  amount: String(request.amount), // Convert to string for JSON compatibility with bigint
@@ -10251,6 +10352,7 @@
10251
10352
  message: request.message,
10252
10353
  recipientNametag: request.recipientNametag,
10253
10354
  requestId: requestId,
10355
+ deadline: deadline,
10254
10356
  });
10255
10357
  // Add prefix and encrypt
10256
10358
  const message = MESSAGE_PREFIX + requestJson;
@@ -10322,6 +10424,7 @@
10322
10424
  senderPubkey: event.pubkey,
10323
10425
  timestamp: event.created_at * 1000, // Convert to milliseconds
10324
10426
  eventId: event.id,
10427
+ deadline: parsed.deadline !== undefined ? parsed.deadline : null,
10325
10428
  };
10326
10429
  }
10327
10430
  /**
@@ -10419,18 +10522,166 @@
10419
10522
  const fractionalPart = BigInt(fractionalStr);
10420
10523
  return wholePart * multiplier + fractionalPart;
10421
10524
  }
10525
+ // ============================================================================
10526
+ // Payment Request Response Functions
10527
+ // ============================================================================
10528
+ /**
10529
+ * Check if a parsed payment request has expired.
10530
+ * @param request Parsed payment request
10531
+ * @returns true if the request has a deadline and it has passed
10532
+ */
10533
+ function isExpired(request) {
10534
+ return request.deadline !== null && Date.now() > request.deadline;
10535
+ }
10536
+ /**
10537
+ * Get remaining time until deadline in milliseconds.
10538
+ * @param request Parsed payment request
10539
+ * @returns Remaining time in ms, 0 if expired, null if no deadline
10540
+ */
10541
+ function getRemainingTimeMs(request) {
10542
+ if (request.deadline === null)
10543
+ return null;
10544
+ const remaining = request.deadline - Date.now();
10545
+ return remaining > 0 ? remaining : 0;
10546
+ }
10547
+ /**
10548
+ * Create a payment request response event (for decline/expiration).
10549
+ *
10550
+ * Event structure:
10551
+ * - Kind: 31116 (PAYMENT_REQUEST_RESPONSE)
10552
+ * - Tags:
10553
+ * - ["p", "<target_pubkey_hex>"] - Original requester
10554
+ * - ["type", "payment_request_response"]
10555
+ * - ["status", "DECLINED" | "EXPIRED"]
10556
+ * - ["e", "<original_event_id>", "", "reply"] - Reference to original request
10557
+ * - Content: NIP-04 encrypted response JSON
10558
+ *
10559
+ * @param keyManager Key manager with signing keys
10560
+ * @param targetPubkeyHex Original requester's public key
10561
+ * @param response Response details
10562
+ * @returns Signed event
10563
+ */
10564
+ async function createPaymentRequestResponseEvent(keyManager, targetPubkeyHex, response) {
10565
+ // Serialize response to JSON
10566
+ const responseJson = JSON.stringify({
10567
+ requestId: response.requestId,
10568
+ originalEventId: response.originalEventId,
10569
+ status: response.status,
10570
+ reason: response.reason,
10571
+ });
10572
+ // Add prefix and encrypt
10573
+ const message = RESPONSE_PREFIX + responseJson;
10574
+ const encryptedContent = await keyManager.encryptHex(message, targetPubkeyHex);
10575
+ // Build tags
10576
+ const tags = [
10577
+ ['p', targetPubkeyHex],
10578
+ ['type', 'payment_request_response'],
10579
+ ['status', response.status],
10580
+ ];
10581
+ // Add reference to original event
10582
+ if (response.originalEventId) {
10583
+ tags.push(['e', response.originalEventId, '', 'reply']);
10584
+ }
10585
+ const event = Event.create(keyManager, {
10586
+ kind: PAYMENT_REQUEST_RESPONSE,
10587
+ tags,
10588
+ content: encryptedContent,
10589
+ });
10590
+ return event;
10591
+ }
10592
+ /**
10593
+ * Parse a payment request response event.
10594
+ * Decrypts and parses the response data.
10595
+ *
10596
+ * @param event Payment request response event
10597
+ * @param keyManager Key manager for decryption
10598
+ * @returns Parsed payment request response
10599
+ * @throws Error if the event is not a valid payment request response
10600
+ */
10601
+ async function parsePaymentRequestResponse(event, keyManager) {
10602
+ // Verify event kind
10603
+ if (event.kind !== PAYMENT_REQUEST_RESPONSE) {
10604
+ throw new Error('Event is not a payment request response');
10605
+ }
10606
+ // Determine the peer's public key for decryption
10607
+ let peerPubkeyHex;
10608
+ if (keyManager.isMyPublicKey(event.pubkey)) {
10609
+ // We sent this response, decrypt with target's key
10610
+ const targetPubkey = event.getTagValue('p');
10611
+ if (!targetPubkey) {
10612
+ throw new Error('No target found in event');
10613
+ }
10614
+ peerPubkeyHex = targetPubkey;
10615
+ }
10616
+ else {
10617
+ // We received this response, decrypt with sender's key
10618
+ peerPubkeyHex = event.pubkey;
10619
+ }
10620
+ // Decrypt the content
10621
+ const decrypted = await keyManager.decryptHex(event.content, peerPubkeyHex);
10622
+ // Validate prefix
10623
+ if (!decrypted.startsWith(RESPONSE_PREFIX)) {
10624
+ throw new Error('Invalid payment request response format: missing prefix');
10625
+ }
10626
+ // Parse JSON
10627
+ const responseJson = decrypted.slice(RESPONSE_PREFIX.length);
10628
+ const parsed = JSON.parse(responseJson);
10629
+ return {
10630
+ requestId: parsed.requestId,
10631
+ originalEventId: parsed.originalEventId,
10632
+ status: parsed.status,
10633
+ reason: parsed.reason,
10634
+ senderPubkey: event.pubkey,
10635
+ eventId: event.id,
10636
+ timestamp: event.created_at * 1000,
10637
+ };
10638
+ }
10639
+ /**
10640
+ * Check if an event is a payment request response.
10641
+ * @param event Event to check
10642
+ * @returns true if the event is a payment request response
10643
+ */
10644
+ function isPaymentRequestResponse(event) {
10645
+ return (event.kind === PAYMENT_REQUEST_RESPONSE &&
10646
+ event.getTagValue('type') === 'payment_request_response');
10647
+ }
10648
+ /**
10649
+ * Get the response status from a payment request response event (from unencrypted tag).
10650
+ * @param event Payment request response event
10651
+ * @returns Status string, or undefined if not found
10652
+ */
10653
+ function getResponseStatus(event) {
10654
+ return event.getTagValue('status');
10655
+ }
10656
+ /**
10657
+ * Get the referenced original event ID from the response event.
10658
+ * @param event Payment request response event
10659
+ * @returns Original event ID, or undefined if not found
10660
+ */
10661
+ function getOriginalEventId(event) {
10662
+ return event.getTagValue('e');
10663
+ }
10422
10664
 
10423
10665
  var PaymentRequestProtocol = /*#__PURE__*/Object.freeze({
10424
10666
  __proto__: null,
10667
+ DEFAULT_DEADLINE_MS: DEFAULT_DEADLINE_MS,
10668
+ get ResponseStatus () { return ResponseStatus; },
10425
10669
  createPaymentRequestEvent: createPaymentRequestEvent,
10670
+ createPaymentRequestResponseEvent: createPaymentRequestResponseEvent,
10426
10671
  formatAmount: formatAmount,
10427
10672
  getAmount: getAmount,
10673
+ getOriginalEventId: getOriginalEventId,
10428
10674
  getRecipientNametag: getRecipientNametag,
10675
+ getRemainingTimeMs: getRemainingTimeMs,
10676
+ getResponseStatus: getResponseStatus,
10429
10677
  getSender: getSender,
10430
10678
  getTarget: getTarget,
10679
+ isExpired: isExpired,
10431
10680
  isPaymentRequest: isPaymentRequest,
10681
+ isPaymentRequestResponse: isPaymentRequestResponse,
10432
10682
  parseAmount: parseAmount,
10433
- parsePaymentRequest: parsePaymentRequest
10683
+ parsePaymentRequest: parsePaymentRequest,
10684
+ parsePaymentRequestResponse: parsePaymentRequestResponse
10434
10685
  });
10435
10686
 
10436
10687
  /**
@@ -10452,6 +10703,7 @@
10452
10703
  exports.AGENT_LOCATION = AGENT_LOCATION;
10453
10704
  exports.AGENT_PROFILE = AGENT_PROFILE;
10454
10705
  exports.APP_DATA = APP_DATA;
10706
+ exports.AUTH = AUTH;
10455
10707
  exports.Bech32 = bech32;
10456
10708
  exports.CHAT_MESSAGE = CHAT_MESSAGE;
10457
10709
  exports.CLOSED = CLOSED;
@@ -10476,6 +10728,7 @@
10476
10728
  exports.NostrKeyManager = NostrKeyManager;
10477
10729
  exports.OPEN = OPEN;
10478
10730
  exports.PAYMENT_REQUEST = PAYMENT_REQUEST;
10731
+ exports.PAYMENT_REQUEST_RESPONSE = PAYMENT_REQUEST_RESPONSE;
10479
10732
  exports.PROFILE = PROFILE;
10480
10733
  exports.PaymentRequestProtocol = PaymentRequestProtocol;
10481
10734
  exports.REACTION = REACTION;