nostr-tools 1.4.1 → 1.5.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.
package/README.md CHANGED
@@ -4,6 +4,13 @@ Tools for developing [Nostr](https://github.com/fiatjaf/nostr) clients.
4
4
 
5
5
  Only depends on _@scure_ and _@noble_ packages.
6
6
 
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install nostr-tools # or yarn add nostr-tools
11
+ ```
12
+
13
+
7
14
  ## Usage
8
15
 
9
16
  ### Generating a private key and a public key
@@ -53,8 +60,6 @@ import {
53
60
  } from 'nostr-tools'
54
61
 
55
62
  const relay = relayInit('wss://relay.example.com')
56
- await relay.connect()
57
-
58
63
  relay.on('connect', () => {
59
64
  console.log(`connected to ${relay.url}`)
60
65
  })
@@ -62,6 +67,8 @@ relay.on('error', () => {
62
67
  console.log(`failed to connect to ${relay.url}`)
63
68
  })
64
69
 
70
+ await relay.connect()
71
+
65
72
  // let's query for an event that exists
66
73
  let sub = relay.sub([
67
74
  {
@@ -104,9 +111,6 @@ let pub = relay.publish(event)
104
111
  pub.on('ok', () => {
105
112
  console.log(`${relay.url} has accepted our event`)
106
113
  })
107
- pub.on('seen', () => {
108
- console.log(`we saw the event on ${relay.url}`)
109
- })
110
114
  pub.on('failed', reason => {
111
115
  console.log(`failed to publish to ${relay.url}: ${reason}`)
112
116
  })
package/event.ts CHANGED
@@ -2,6 +2,7 @@ import * as secp256k1 from '@noble/secp256k1'
2
2
  import {sha256} from '@noble/hashes/sha256'
3
3
 
4
4
  import {utf8Encoder} from './utils'
5
+ import {getPublicKey} from './keys'
5
6
 
6
7
  /* eslint-disable no-unused-vars */
7
8
  export enum Kind {
@@ -16,30 +17,49 @@ export enum Kind {
16
17
  ChannelMetadata = 41,
17
18
  ChannelMessage = 42,
18
19
  ChannelHideMessage = 43,
19
- ChannelMuteUser = 44
20
+ ChannelMuteUser = 44,
21
+ Report = 1984,
22
+ ZapRequest = 9734,
23
+ Zap = 9735,
24
+ RelayList = 10002,
25
+ ClientAuth = 22242,
26
+ Article = 30023
20
27
  }
21
28
 
22
- export type Event = {
23
- id?: string
24
- sig?: string
29
+ export type EventTemplate = {
25
30
  kind: Kind
26
31
  tags: string[][]
27
- pubkey: string
28
32
  content: string
29
33
  created_at: number
30
34
  }
31
35
 
32
- export function getBlankEvent(): Event {
36
+ export type UnsignedEvent = EventTemplate & {
37
+ pubkey: string
38
+ }
39
+
40
+ export type Event = UnsignedEvent & {
41
+ id: string
42
+ sig: string
43
+ }
44
+
45
+ export function getBlankEvent(): EventTemplate {
33
46
  return {
34
47
  kind: 255,
35
- pubkey: '',
36
48
  content: '',
37
49
  tags: [],
38
50
  created_at: 0
39
51
  }
40
52
  }
41
53
 
42
- export function serializeEvent(evt: Event): string {
54
+ export function finishEvent(t: EventTemplate, privateKey: string): Event {
55
+ let event = t as Event
56
+ event.pubkey = getPublicKey(privateKey)
57
+ event.id = getEventHash(event)
58
+ event.sig = signEvent(event, privateKey)
59
+ return event
60
+ }
61
+
62
+ export function serializeEvent(evt: UnsignedEvent): string {
43
63
  if (!validateEvent(evt))
44
64
  throw new Error("can't serialize event with wrong or missing properties")
45
65
 
@@ -53,12 +73,13 @@ export function serializeEvent(evt: Event): string {
53
73
  ])
54
74
  }
55
75
 
56
- export function getEventHash(event: Event): string {
76
+ export function getEventHash(event: UnsignedEvent): string {
57
77
  let eventHash = sha256(utf8Encoder.encode(serializeEvent(event)))
58
78
  return secp256k1.utils.bytesToHex(eventHash)
59
79
  }
60
80
 
61
- export function validateEvent(event: Event): boolean {
81
+ export function validateEvent(event: UnsignedEvent): boolean {
82
+ if (typeof event !== 'object') return false
62
83
  if (typeof event.content !== 'string') return false
63
84
  if (typeof event.created_at !== 'number') return false
64
85
  if (typeof event.pubkey !== 'string') return false
@@ -84,7 +105,7 @@ export function verifySignature(event: Event & {sig: string}): boolean {
84
105
  )
85
106
  }
86
107
 
87
- export function signEvent(event: Event, key: string): string {
108
+ export function signEvent(event: UnsignedEvent, key: string): string {
88
109
  return secp256k1.utils.bytesToHex(
89
110
  secp256k1.schnorr.signSync(getEventHash(event), key)
90
111
  )
package/index.ts CHANGED
@@ -9,6 +9,7 @@ export * as nip05 from './nip05'
9
9
  export * as nip06 from './nip06'
10
10
  export * as nip19 from './nip19'
11
11
  export * as nip26 from './nip26'
12
+ export * as nip57 from './nip57'
12
13
 
13
14
  export * as fj from './fakejson'
14
15
  export * as utils from './utils'
@@ -3605,6 +3605,7 @@ zoo`.split("\n");
3605
3605
  __export(nostr_tools_exports, {
3606
3606
  Kind: () => Kind,
3607
3607
  SimplePool: () => SimplePool,
3608
+ finishEvent: () => finishEvent,
3608
3609
  fj: () => fakejson_exports,
3609
3610
  generatePrivateKey: () => generatePrivateKey,
3610
3611
  getBlankEvent: () => getBlankEvent,
@@ -3617,6 +3618,7 @@ zoo`.split("\n");
3617
3618
  nip06: () => nip06_exports,
3618
3619
  nip19: () => nip19_exports,
3619
3620
  nip26: () => nip26_exports,
3621
+ nip57: () => nip57_exports,
3620
3622
  relayInit: () => relayInit,
3621
3623
  serializeEvent: () => serializeEvent,
3622
3624
  signEvent: () => signEvent,
@@ -5100,17 +5102,29 @@ zoo`.split("\n");
5100
5102
  Kind2[Kind2["ChannelMessage"] = 42] = "ChannelMessage";
5101
5103
  Kind2[Kind2["ChannelHideMessage"] = 43] = "ChannelHideMessage";
5102
5104
  Kind2[Kind2["ChannelMuteUser"] = 44] = "ChannelMuteUser";
5105
+ Kind2[Kind2["Report"] = 1984] = "Report";
5106
+ Kind2[Kind2["ZapRequest"] = 9734] = "ZapRequest";
5107
+ Kind2[Kind2["Zap"] = 9735] = "Zap";
5108
+ Kind2[Kind2["RelayList"] = 10002] = "RelayList";
5109
+ Kind2[Kind2["ClientAuth"] = 22242] = "ClientAuth";
5110
+ Kind2[Kind2["Article"] = 30023] = "Article";
5103
5111
  return Kind2;
5104
5112
  })(Kind || {});
5105
5113
  function getBlankEvent() {
5106
5114
  return {
5107
5115
  kind: 255,
5108
- pubkey: "",
5109
5116
  content: "",
5110
5117
  tags: [],
5111
5118
  created_at: 0
5112
5119
  };
5113
5120
  }
5121
+ function finishEvent(t, privateKey) {
5122
+ let event = t;
5123
+ event.pubkey = getPublicKey(privateKey);
5124
+ event.id = getEventHash(event);
5125
+ event.sig = signEvent(event, privateKey);
5126
+ return event;
5127
+ }
5114
5128
  function serializeEvent(evt) {
5115
5129
  if (!validateEvent(evt))
5116
5130
  throw new Error("can't serialize event with wrong or missing properties");
@@ -5128,6 +5142,8 @@ zoo`.split("\n");
5128
5142
  return utils.bytesToHex(eventHash);
5129
5143
  }
5130
5144
  function validateEvent(event) {
5145
+ if (typeof event !== "object")
5146
+ return false;
5131
5147
  if (typeof event.content !== "string")
5132
5148
  return false;
5133
5149
  if (typeof event.created_at !== "number")
@@ -5308,17 +5324,24 @@ zoo`.split("\n");
5308
5324
  return;
5309
5325
  case "EOSE": {
5310
5326
  let id2 = data[1];
5311
- (subListeners[id2]?.eose || []).forEach((cb) => cb());
5327
+ if (id2 in subListeners) {
5328
+ subListeners[id2].eose.forEach((cb) => cb());
5329
+ subListeners[id2].eose = [];
5330
+ }
5312
5331
  return;
5313
5332
  }
5314
5333
  case "OK": {
5315
5334
  let id2 = data[1];
5316
5335
  let ok = data[2];
5317
5336
  let reason = data[3] || "";
5318
- if (ok)
5319
- pubListeners[id2]?.ok.forEach((cb) => cb());
5320
- else
5321
- pubListeners[id2]?.failed.forEach((cb) => cb(reason));
5337
+ if (id2 in pubListeners) {
5338
+ if (ok)
5339
+ pubListeners[id2].ok.forEach((cb) => cb());
5340
+ else
5341
+ pubListeners[id2].failed.forEach((cb) => cb(reason));
5342
+ pubListeners[id2].ok = [];
5343
+ pubListeners[id2].failed = [];
5344
+ }
5322
5345
  return;
5323
5346
  }
5324
5347
  case "NOTICE":
@@ -5430,46 +5453,14 @@ zoo`.split("\n");
5430
5453
  if (!event.id)
5431
5454
  throw new Error(`event ${event} has no id`);
5432
5455
  let id = event.id;
5433
- var sent = false;
5434
- var mustMonitor = false;
5435
- trySend(["EVENT", event]).then(() => {
5436
- sent = true;
5437
- if (mustMonitor) {
5438
- startMonitoring();
5439
- mustMonitor = false;
5440
- }
5441
- }).catch(() => {
5442
- });
5443
- const startMonitoring = () => {
5444
- let monitor = sub([{ ids: [id] }], {
5445
- id: `monitor-${id.slice(0, 5)}`
5446
- });
5447
- let willUnsub = setTimeout(() => {
5448
- ;
5449
- (pubListeners[id]?.failed || []).forEach(
5450
- (cb) => cb("event not seen after 5 seconds")
5451
- );
5452
- monitor.unsub();
5453
- }, 5e3);
5454
- monitor.on("event", () => {
5455
- clearTimeout(willUnsub);
5456
- (pubListeners[id]?.seen || []).forEach((cb) => cb());
5457
- });
5458
- };
5456
+ trySend(["EVENT", event]);
5459
5457
  return {
5460
5458
  on: (type, cb) => {
5461
5459
  pubListeners[id] = pubListeners[id] || {
5462
5460
  ok: [],
5463
- seen: [],
5464
5461
  failed: []
5465
5462
  };
5466
5463
  pubListeners[id][type].push(cb);
5467
- if (type === "seen") {
5468
- if (sent)
5469
- startMonitoring();
5470
- else
5471
- mustMonitor = true;
5472
- }
5473
5464
  },
5474
5465
  off: (type, cb) => {
5475
5466
  let listeners2 = pubListeners[id];
@@ -5483,6 +5474,9 @@ zoo`.split("\n");
5483
5474
  },
5484
5475
  connect,
5485
5476
  close() {
5477
+ listeners = { connect: [], disconnect: [], error: [], notice: [] };
5478
+ subListeners = {};
5479
+ pubListeners = {};
5486
5480
  if (ws.readyState > 1)
5487
5481
  return Promise.resolve();
5488
5482
  ws.close();
@@ -8267,6 +8261,7 @@ zoo`.split("\n");
8267
8261
  var nip19_exports = {};
8268
8262
  __export(nip19_exports, {
8269
8263
  decode: () => decode,
8264
+ naddrEncode: () => naddrEncode,
8270
8265
  neventEncode: () => neventEncode,
8271
8266
  noteEncode: () => noteEncode,
8272
8267
  nprofileEncode: () => nprofileEncode,
@@ -8278,38 +8273,64 @@ zoo`.split("\n");
8278
8273
  function decode(nip19) {
8279
8274
  let { prefix, words } = bech32.decode(nip19, Bech32MaxSize);
8280
8275
  let data = new Uint8Array(bech32.fromWords(words));
8281
- if (prefix === "nprofile") {
8282
- let tlv = parseTLV(data);
8283
- if (!tlv[0]?.[0])
8284
- throw new Error("missing TLV 0 for nprofile");
8285
- if (tlv[0][0].length !== 32)
8286
- throw new Error("TLV 0 should be 32 bytes");
8287
- return {
8288
- type: "nprofile",
8289
- data: {
8290
- pubkey: utils.bytesToHex(tlv[0][0]),
8291
- relays: tlv[1].map((d) => utf8Decoder.decode(d))
8292
- }
8293
- };
8294
- }
8295
- if (prefix === "nevent") {
8296
- let tlv = parseTLV(data);
8297
- if (!tlv[0]?.[0])
8298
- throw new Error("missing TLV 0 for nevent");
8299
- if (tlv[0][0].length !== 32)
8300
- throw new Error("TLV 0 should be 32 bytes");
8301
- return {
8302
- type: "nevent",
8303
- data: {
8304
- id: utils.bytesToHex(tlv[0][0]),
8305
- relays: tlv[1].map((d) => utf8Decoder.decode(d))
8306
- }
8307
- };
8308
- }
8309
- if (prefix === "nsec" || prefix === "npub" || prefix === "note") {
8310
- return { type: prefix, data: utils.bytesToHex(data) };
8276
+ switch (prefix) {
8277
+ case "nprofile": {
8278
+ let tlv = parseTLV(data);
8279
+ if (!tlv[0]?.[0])
8280
+ throw new Error("missing TLV 0 for nprofile");
8281
+ if (tlv[0][0].length !== 32)
8282
+ throw new Error("TLV 0 should be 32 bytes");
8283
+ return {
8284
+ type: "nprofile",
8285
+ data: {
8286
+ pubkey: utils.bytesToHex(tlv[0][0]),
8287
+ relays: tlv[1].map((d) => utf8Decoder.decode(d))
8288
+ }
8289
+ };
8290
+ }
8291
+ case "nevent": {
8292
+ let tlv = parseTLV(data);
8293
+ if (!tlv[0]?.[0])
8294
+ throw new Error("missing TLV 0 for nevent");
8295
+ if (tlv[0][0].length !== 32)
8296
+ throw new Error("TLV 0 should be 32 bytes");
8297
+ return {
8298
+ type: "nevent",
8299
+ data: {
8300
+ id: utils.bytesToHex(tlv[0][0]),
8301
+ relays: tlv[1].map((d) => utf8Decoder.decode(d))
8302
+ }
8303
+ };
8304
+ }
8305
+ case "naddr": {
8306
+ let tlv = parseTLV(data);
8307
+ if (!tlv[0]?.[0])
8308
+ throw new Error("missing TLV 0 for naddr");
8309
+ if (!tlv[2]?.[0])
8310
+ throw new Error("missing TLV 2 for naddr");
8311
+ if (tlv[2][0].length !== 32)
8312
+ throw new Error("TLV 2 should be 32 bytes");
8313
+ if (!tlv[3]?.[0])
8314
+ throw new Error("missing TLV 3 for naddr");
8315
+ if (tlv[3][0].length !== 4)
8316
+ throw new Error("TLV 3 should be 4 bytes");
8317
+ return {
8318
+ type: "naddr",
8319
+ data: {
8320
+ identifier: utf8Decoder.decode(tlv[0][0]),
8321
+ pubkey: utils.bytesToHex(tlv[2][0]),
8322
+ kind: parseInt(utils.bytesToHex(tlv[3][0]), 16),
8323
+ relays: tlv[1].map((d) => utf8Decoder.decode(d))
8324
+ }
8325
+ };
8326
+ }
8327
+ case "nsec":
8328
+ case "npub":
8329
+ case "note":
8330
+ return { type: prefix, data: utils.bytesToHex(data) };
8331
+ default:
8332
+ throw new Error(`unknown prefix ${prefix}`);
8311
8333
  }
8312
- throw new Error(`unknown prefix ${prefix}`);
8313
8334
  }
8314
8335
  function parseTLV(data) {
8315
8336
  let result = {};
@@ -8356,6 +8377,18 @@ zoo`.split("\n");
8356
8377
  let words = bech32.toWords(data);
8357
8378
  return bech32.encode("nevent", words, Bech32MaxSize);
8358
8379
  }
8380
+ function naddrEncode(addr) {
8381
+ let kind = new ArrayBuffer(4);
8382
+ new DataView(kind).setUint32(0, addr.kind, false);
8383
+ let data = encodeTLV({
8384
+ 0: [utf8Encoder.encode(addr.identifier)],
8385
+ 1: (addr.relays || []).map((url) => utf8Encoder.encode(url)),
8386
+ 2: [utils.hexToBytes(addr.pubkey)],
8387
+ 3: [new Uint8Array(kind)]
8388
+ });
8389
+ let words = bech32.toWords(data);
8390
+ return bech32.encode("naddr", words, Bech32MaxSize);
8391
+ }
8359
8392
  function encodeTLV(tlv) {
8360
8393
  let entries = [];
8361
8394
  Object.entries(tlv).forEach(([t, vs]) => {
@@ -8428,6 +8461,123 @@ zoo`.split("\n");
8428
8461
  return pubkey;
8429
8462
  }
8430
8463
 
8464
+ // nip57.ts
8465
+ var nip57_exports = {};
8466
+ __export(nip57_exports, {
8467
+ getZapEndpoint: () => getZapEndpoint,
8468
+ makeZapReceipt: () => makeZapReceipt,
8469
+ makeZapRequest: () => makeZapRequest,
8470
+ useFetchImplementation: () => useFetchImplementation2,
8471
+ validateZapRequest: () => validateZapRequest
8472
+ });
8473
+ init_define_process();
8474
+ var _fetch2;
8475
+ try {
8476
+ _fetch2 = fetch;
8477
+ } catch {
8478
+ }
8479
+ function useFetchImplementation2(fetchImplementation) {
8480
+ _fetch2 = fetchImplementation;
8481
+ }
8482
+ async function getZapEndpoint(metadata) {
8483
+ try {
8484
+ let lnurl = "";
8485
+ let { lud06, lud16 } = JSON.parse(metadata.content);
8486
+ if (lud06) {
8487
+ let { words } = bech32.decode(lud06, 1e3);
8488
+ let data = bech32.fromWords(words);
8489
+ lnurl = utf8Decoder.decode(data);
8490
+ } else if (lud16) {
8491
+ let [name, domain] = lud16.split("@");
8492
+ lnurl = `https://${domain}/.well-known/lnurlp/${name}`;
8493
+ } else {
8494
+ return null;
8495
+ }
8496
+ let res = await _fetch2(lnurl);
8497
+ let body = await res.json();
8498
+ if (body.allowsNostr && body.nostrPubkey) {
8499
+ return body.callback;
8500
+ }
8501
+ } catch (err) {
8502
+ }
8503
+ return null;
8504
+ }
8505
+ function makeZapRequest({
8506
+ profile,
8507
+ event,
8508
+ amount,
8509
+ relays,
8510
+ comment = ""
8511
+ }) {
8512
+ if (!amount)
8513
+ throw new Error("amount not given");
8514
+ if (!profile)
8515
+ throw new Error("profile not given");
8516
+ let zr = {
8517
+ kind: 9734,
8518
+ created_at: Math.round(Date.now() / 1e3),
8519
+ content: comment,
8520
+ tags: [
8521
+ ["p", profile],
8522
+ ["amount", amount.toString()],
8523
+ ["relays", ...relays]
8524
+ ]
8525
+ };
8526
+ if (event) {
8527
+ zr.tags.push(["e", event]);
8528
+ }
8529
+ return zr;
8530
+ }
8531
+ function validateZapRequest(zapRequestString) {
8532
+ let zapRequest;
8533
+ try {
8534
+ zapRequest = JSON.parse(zapRequestString);
8535
+ } catch (err) {
8536
+ return "Invalid zap request JSON.";
8537
+ }
8538
+ if (!validateEvent(zapRequest))
8539
+ return "Zap request is not a valid Nostr event.";
8540
+ if (!verifySignature(zapRequest))
8541
+ return "Invalid signature on zap request.";
8542
+ let p = zapRequest.tags.find(([t, v]) => t === "p" && v);
8543
+ if (!p)
8544
+ return "Zap request doesn't have a 'p' tag.";
8545
+ if (!p[1].match(/^[a-f0-9]{64}$/))
8546
+ return "Zap request 'p' tag is not valid hex.";
8547
+ let e = zapRequest.tags.find(([t, v]) => t === "e" && v);
8548
+ if (e && !e[1].match(/^[a-f0-9]{64}$/))
8549
+ return "Zap request 'e' tag is not valid hex.";
8550
+ let relays = zapRequest.tags.find(([t, v]) => t === "relays" && v);
8551
+ if (!relays)
8552
+ return "Zap request doesn't have a 'relays' tag.";
8553
+ return null;
8554
+ }
8555
+ function makeZapReceipt({
8556
+ zapRequest,
8557
+ preimage,
8558
+ bolt11,
8559
+ paidAt
8560
+ }) {
8561
+ let zr = JSON.parse(zapRequest);
8562
+ let tagsFromZapRequest = zr.tags.filter(
8563
+ ([t]) => t === "e" || t === "p" || t === "a"
8564
+ );
8565
+ let zap = {
8566
+ kind: 9735,
8567
+ created_at: Math.round(paidAt.getTime() / 1e3),
8568
+ content: "",
8569
+ tags: [
8570
+ ...tagsFromZapRequest,
8571
+ ["bolt11", bolt11],
8572
+ ["description", zapRequest]
8573
+ ]
8574
+ };
8575
+ if (preimage) {
8576
+ zap.tags.push(["preimage", preimage]);
8577
+ }
8578
+ return zap;
8579
+ }
8580
+
8431
8581
  // node_modules/@noble/hashes/esm/hmac.js
8432
8582
  init_define_process();
8433
8583
  var HMAC2 = class extends Hash {