nostr-tools 1.5.0 → 1.6.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.
- package/README.md +7 -9
- package/build.js +8 -2
- package/lib/esm/nostr.mjs +1144 -0
- package/lib/esm/nostr.mjs.map +7 -0
- package/lib/esm/package.json +1 -0
- package/lib/nostr.bundle.js +29 -23
- package/lib/nostr.bundle.js.map +2 -2
- package/lib/nostr.cjs.js +29 -23
- package/lib/nostr.cjs.js.map +2 -2
- package/nip19.test.js +12 -0
- package/nip19.ts +3 -3
- package/package.json +6 -2
- package/pool.test.js +2 -2
- package/pool.ts +17 -10
- package/relay.test.js +2 -2
- package/relay.ts +11 -11
|
@@ -0,0 +1,1144 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// keys.ts
|
|
8
|
+
import * as secp256k1 from "@noble/secp256k1";
|
|
9
|
+
function generatePrivateKey() {
|
|
10
|
+
return secp256k1.utils.bytesToHex(secp256k1.utils.randomPrivateKey());
|
|
11
|
+
}
|
|
12
|
+
function getPublicKey(privateKey) {
|
|
13
|
+
return secp256k1.utils.bytesToHex(secp256k1.schnorr.getPublicKey(privateKey));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// event.ts
|
|
17
|
+
import * as secp256k12 from "@noble/secp256k1";
|
|
18
|
+
import { sha256 } from "@noble/hashes/sha256";
|
|
19
|
+
|
|
20
|
+
// utils.ts
|
|
21
|
+
var utils_exports = {};
|
|
22
|
+
__export(utils_exports, {
|
|
23
|
+
insertEventIntoAscendingList: () => insertEventIntoAscendingList,
|
|
24
|
+
insertEventIntoDescendingList: () => insertEventIntoDescendingList,
|
|
25
|
+
normalizeURL: () => normalizeURL,
|
|
26
|
+
utf8Decoder: () => utf8Decoder,
|
|
27
|
+
utf8Encoder: () => utf8Encoder
|
|
28
|
+
});
|
|
29
|
+
var utf8Decoder = new TextDecoder("utf-8");
|
|
30
|
+
var utf8Encoder = new TextEncoder();
|
|
31
|
+
function normalizeURL(url) {
|
|
32
|
+
let p = new URL(url);
|
|
33
|
+
p.pathname = p.pathname.replace(/\/+/g, "/");
|
|
34
|
+
if (p.pathname.endsWith("/"))
|
|
35
|
+
p.pathname = p.pathname.slice(0, -1);
|
|
36
|
+
if (p.port === "80" && p.protocol === "ws:" || p.port === "443" && p.protocol === "wss:")
|
|
37
|
+
p.port = "";
|
|
38
|
+
p.searchParams.sort();
|
|
39
|
+
p.hash = "";
|
|
40
|
+
return p.toString();
|
|
41
|
+
}
|
|
42
|
+
function insertEventIntoDescendingList(sortedArray, event) {
|
|
43
|
+
let start = 0;
|
|
44
|
+
let end = sortedArray.length - 1;
|
|
45
|
+
let midPoint;
|
|
46
|
+
let position = start;
|
|
47
|
+
if (end < 0) {
|
|
48
|
+
position = 0;
|
|
49
|
+
} else if (event.created_at < sortedArray[end].created_at) {
|
|
50
|
+
position = end + 1;
|
|
51
|
+
} else if (event.created_at >= sortedArray[start].created_at) {
|
|
52
|
+
position = start;
|
|
53
|
+
} else
|
|
54
|
+
while (true) {
|
|
55
|
+
if (end <= start + 1) {
|
|
56
|
+
position = end;
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
midPoint = Math.floor(start + (end - start) / 2);
|
|
60
|
+
if (sortedArray[midPoint].created_at > event.created_at) {
|
|
61
|
+
start = midPoint;
|
|
62
|
+
} else if (sortedArray[midPoint].created_at < event.created_at) {
|
|
63
|
+
end = midPoint;
|
|
64
|
+
} else {
|
|
65
|
+
position = midPoint;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (sortedArray[position]?.id !== event.id) {
|
|
70
|
+
return [
|
|
71
|
+
...sortedArray.slice(0, position),
|
|
72
|
+
event,
|
|
73
|
+
...sortedArray.slice(position)
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
return sortedArray;
|
|
77
|
+
}
|
|
78
|
+
function insertEventIntoAscendingList(sortedArray, event) {
|
|
79
|
+
let start = 0;
|
|
80
|
+
let end = sortedArray.length - 1;
|
|
81
|
+
let midPoint;
|
|
82
|
+
let position = start;
|
|
83
|
+
if (end < 0) {
|
|
84
|
+
position = 0;
|
|
85
|
+
} else if (event.created_at > sortedArray[end].created_at) {
|
|
86
|
+
position = end + 1;
|
|
87
|
+
} else if (event.created_at <= sortedArray[start].created_at) {
|
|
88
|
+
position = start;
|
|
89
|
+
} else
|
|
90
|
+
while (true) {
|
|
91
|
+
if (end <= start + 1) {
|
|
92
|
+
position = end;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
midPoint = Math.floor(start + (end - start) / 2);
|
|
96
|
+
if (sortedArray[midPoint].created_at < event.created_at) {
|
|
97
|
+
start = midPoint;
|
|
98
|
+
} else if (sortedArray[midPoint].created_at > event.created_at) {
|
|
99
|
+
end = midPoint;
|
|
100
|
+
} else {
|
|
101
|
+
position = midPoint;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (sortedArray[position]?.id !== event.id) {
|
|
106
|
+
return [
|
|
107
|
+
...sortedArray.slice(0, position),
|
|
108
|
+
event,
|
|
109
|
+
...sortedArray.slice(position)
|
|
110
|
+
];
|
|
111
|
+
}
|
|
112
|
+
return sortedArray;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// event.ts
|
|
116
|
+
var Kind = /* @__PURE__ */ ((Kind2) => {
|
|
117
|
+
Kind2[Kind2["Metadata"] = 0] = "Metadata";
|
|
118
|
+
Kind2[Kind2["Text"] = 1] = "Text";
|
|
119
|
+
Kind2[Kind2["RecommendRelay"] = 2] = "RecommendRelay";
|
|
120
|
+
Kind2[Kind2["Contacts"] = 3] = "Contacts";
|
|
121
|
+
Kind2[Kind2["EncryptedDirectMessage"] = 4] = "EncryptedDirectMessage";
|
|
122
|
+
Kind2[Kind2["EventDeletion"] = 5] = "EventDeletion";
|
|
123
|
+
Kind2[Kind2["Reaction"] = 7] = "Reaction";
|
|
124
|
+
Kind2[Kind2["ChannelCreation"] = 40] = "ChannelCreation";
|
|
125
|
+
Kind2[Kind2["ChannelMetadata"] = 41] = "ChannelMetadata";
|
|
126
|
+
Kind2[Kind2["ChannelMessage"] = 42] = "ChannelMessage";
|
|
127
|
+
Kind2[Kind2["ChannelHideMessage"] = 43] = "ChannelHideMessage";
|
|
128
|
+
Kind2[Kind2["ChannelMuteUser"] = 44] = "ChannelMuteUser";
|
|
129
|
+
Kind2[Kind2["Report"] = 1984] = "Report";
|
|
130
|
+
Kind2[Kind2["ZapRequest"] = 9734] = "ZapRequest";
|
|
131
|
+
Kind2[Kind2["Zap"] = 9735] = "Zap";
|
|
132
|
+
Kind2[Kind2["RelayList"] = 10002] = "RelayList";
|
|
133
|
+
Kind2[Kind2["ClientAuth"] = 22242] = "ClientAuth";
|
|
134
|
+
Kind2[Kind2["Article"] = 30023] = "Article";
|
|
135
|
+
return Kind2;
|
|
136
|
+
})(Kind || {});
|
|
137
|
+
function getBlankEvent() {
|
|
138
|
+
return {
|
|
139
|
+
kind: 255,
|
|
140
|
+
content: "",
|
|
141
|
+
tags: [],
|
|
142
|
+
created_at: 0
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function finishEvent(t, privateKey) {
|
|
146
|
+
let event = t;
|
|
147
|
+
event.pubkey = getPublicKey(privateKey);
|
|
148
|
+
event.id = getEventHash(event);
|
|
149
|
+
event.sig = signEvent(event, privateKey);
|
|
150
|
+
return event;
|
|
151
|
+
}
|
|
152
|
+
function serializeEvent(evt) {
|
|
153
|
+
if (!validateEvent(evt))
|
|
154
|
+
throw new Error("can't serialize event with wrong or missing properties");
|
|
155
|
+
return JSON.stringify([
|
|
156
|
+
0,
|
|
157
|
+
evt.pubkey,
|
|
158
|
+
evt.created_at,
|
|
159
|
+
evt.kind,
|
|
160
|
+
evt.tags,
|
|
161
|
+
evt.content
|
|
162
|
+
]);
|
|
163
|
+
}
|
|
164
|
+
function getEventHash(event) {
|
|
165
|
+
let eventHash = sha256(utf8Encoder.encode(serializeEvent(event)));
|
|
166
|
+
return secp256k12.utils.bytesToHex(eventHash);
|
|
167
|
+
}
|
|
168
|
+
function validateEvent(event) {
|
|
169
|
+
if (typeof event !== "object")
|
|
170
|
+
return false;
|
|
171
|
+
if (typeof event.content !== "string")
|
|
172
|
+
return false;
|
|
173
|
+
if (typeof event.created_at !== "number")
|
|
174
|
+
return false;
|
|
175
|
+
if (typeof event.pubkey !== "string")
|
|
176
|
+
return false;
|
|
177
|
+
if (!event.pubkey.match(/^[a-f0-9]{64}$/))
|
|
178
|
+
return false;
|
|
179
|
+
if (!Array.isArray(event.tags))
|
|
180
|
+
return false;
|
|
181
|
+
for (let i = 0; i < event.tags.length; i++) {
|
|
182
|
+
let tag = event.tags[i];
|
|
183
|
+
if (!Array.isArray(tag))
|
|
184
|
+
return false;
|
|
185
|
+
for (let j = 0; j < tag.length; j++) {
|
|
186
|
+
if (typeof tag[j] === "object")
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
function verifySignature(event) {
|
|
193
|
+
return secp256k12.schnorr.verifySync(
|
|
194
|
+
event.sig,
|
|
195
|
+
getEventHash(event),
|
|
196
|
+
event.pubkey
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
function signEvent(event, key) {
|
|
200
|
+
return secp256k12.utils.bytesToHex(
|
|
201
|
+
secp256k12.schnorr.signSync(getEventHash(event), key)
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// filter.ts
|
|
206
|
+
function matchFilter(filter, event) {
|
|
207
|
+
if (filter.ids && filter.ids.indexOf(event.id) === -1)
|
|
208
|
+
return false;
|
|
209
|
+
if (filter.kinds && filter.kinds.indexOf(event.kind) === -1)
|
|
210
|
+
return false;
|
|
211
|
+
if (filter.authors && filter.authors.indexOf(event.pubkey) === -1)
|
|
212
|
+
return false;
|
|
213
|
+
for (let f in filter) {
|
|
214
|
+
if (f[0] === "#") {
|
|
215
|
+
let tagName = f.slice(1);
|
|
216
|
+
let values = filter[`#${tagName}`];
|
|
217
|
+
if (values && !event.tags.find(
|
|
218
|
+
([t, v]) => t === f.slice(1) && values.indexOf(v) !== -1
|
|
219
|
+
))
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (filter.since && event.created_at < filter.since)
|
|
224
|
+
return false;
|
|
225
|
+
if (filter.until && event.created_at >= filter.until)
|
|
226
|
+
return false;
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
function matchFilters(filters, event) {
|
|
230
|
+
for (let i = 0; i < filters.length; i++) {
|
|
231
|
+
if (matchFilter(filters[i], event))
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// fakejson.ts
|
|
238
|
+
var fakejson_exports = {};
|
|
239
|
+
__export(fakejson_exports, {
|
|
240
|
+
getHex64: () => getHex64,
|
|
241
|
+
getInt: () => getInt,
|
|
242
|
+
getSubscriptionId: () => getSubscriptionId,
|
|
243
|
+
matchEventId: () => matchEventId,
|
|
244
|
+
matchEventKind: () => matchEventKind,
|
|
245
|
+
matchEventPubkey: () => matchEventPubkey
|
|
246
|
+
});
|
|
247
|
+
function getHex64(json, field) {
|
|
248
|
+
let len = field.length + 3;
|
|
249
|
+
let idx = json.indexOf(`"${field}":`) + len;
|
|
250
|
+
let s = json.slice(idx).indexOf(`"`) + idx + 1;
|
|
251
|
+
return json.slice(s, s + 64);
|
|
252
|
+
}
|
|
253
|
+
function getInt(json, field) {
|
|
254
|
+
let len = field.length;
|
|
255
|
+
let idx = json.indexOf(`"${field}":`) + len + 3;
|
|
256
|
+
let sliced = json.slice(idx);
|
|
257
|
+
let end = Math.min(sliced.indexOf(","), sliced.indexOf("}"));
|
|
258
|
+
return parseInt(sliced.slice(0, end), 10);
|
|
259
|
+
}
|
|
260
|
+
function getSubscriptionId(json) {
|
|
261
|
+
let idx = json.slice(0, 22).indexOf(`"EVENT"`);
|
|
262
|
+
if (idx === -1)
|
|
263
|
+
return null;
|
|
264
|
+
let pstart = json.slice(idx + 7 + 1).indexOf(`"`);
|
|
265
|
+
if (pstart === -1)
|
|
266
|
+
return null;
|
|
267
|
+
let start = idx + 7 + 1 + pstart;
|
|
268
|
+
let pend = json.slice(start + 1, 80).indexOf(`"`);
|
|
269
|
+
if (pend === -1)
|
|
270
|
+
return null;
|
|
271
|
+
let end = start + 1 + pend;
|
|
272
|
+
return json.slice(start + 1, end);
|
|
273
|
+
}
|
|
274
|
+
function matchEventId(json, id) {
|
|
275
|
+
return id === getHex64(json, "id");
|
|
276
|
+
}
|
|
277
|
+
function matchEventPubkey(json, pubkey) {
|
|
278
|
+
return pubkey === getHex64(json, "pubkey");
|
|
279
|
+
}
|
|
280
|
+
function matchEventKind(json, kind) {
|
|
281
|
+
return kind === getInt(json, "kind");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// relay.ts
|
|
285
|
+
function relayInit(url) {
|
|
286
|
+
var ws;
|
|
287
|
+
var openSubs = {};
|
|
288
|
+
var listeners = {
|
|
289
|
+
connect: [],
|
|
290
|
+
disconnect: [],
|
|
291
|
+
error: [],
|
|
292
|
+
notice: []
|
|
293
|
+
};
|
|
294
|
+
var subListeners = {};
|
|
295
|
+
var pubListeners = {};
|
|
296
|
+
async function connectRelay() {
|
|
297
|
+
return new Promise((resolve, reject) => {
|
|
298
|
+
ws = new WebSocket(url);
|
|
299
|
+
ws.onopen = () => {
|
|
300
|
+
listeners.connect.forEach((cb) => cb());
|
|
301
|
+
resolve();
|
|
302
|
+
};
|
|
303
|
+
ws.onerror = () => {
|
|
304
|
+
listeners.error.forEach((cb) => cb());
|
|
305
|
+
reject();
|
|
306
|
+
};
|
|
307
|
+
ws.onclose = async () => {
|
|
308
|
+
listeners.disconnect.forEach((cb) => cb());
|
|
309
|
+
};
|
|
310
|
+
let incomingMessageQueue = [];
|
|
311
|
+
let handleNextInterval;
|
|
312
|
+
ws.onmessage = (e) => {
|
|
313
|
+
incomingMessageQueue.push(e.data);
|
|
314
|
+
if (!handleNextInterval) {
|
|
315
|
+
handleNextInterval = setInterval(handleNext, 0);
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
function handleNext() {
|
|
319
|
+
if (incomingMessageQueue.length === 0) {
|
|
320
|
+
clearInterval(handleNextInterval);
|
|
321
|
+
handleNextInterval = null;
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
var json = incomingMessageQueue.shift();
|
|
325
|
+
if (!json)
|
|
326
|
+
return;
|
|
327
|
+
let subid = getSubscriptionId(json);
|
|
328
|
+
if (subid) {
|
|
329
|
+
let so = openSubs[subid];
|
|
330
|
+
if (so && so.alreadyHaveEvent && so.alreadyHaveEvent(getHex64(json, "id"), url)) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
let data = JSON.parse(json);
|
|
336
|
+
switch (data[0]) {
|
|
337
|
+
case "EVENT":
|
|
338
|
+
let id = data[1];
|
|
339
|
+
let event = data[2];
|
|
340
|
+
if (validateEvent(event) && openSubs[id] && (openSubs[id].skipVerification || verifySignature(event)) && matchFilters(openSubs[id].filters, event)) {
|
|
341
|
+
openSubs[id];
|
|
342
|
+
(subListeners[id]?.event || []).forEach((cb) => cb(event));
|
|
343
|
+
}
|
|
344
|
+
return;
|
|
345
|
+
case "EOSE": {
|
|
346
|
+
let id2 = data[1];
|
|
347
|
+
if (id2 in subListeners) {
|
|
348
|
+
subListeners[id2].eose.forEach((cb) => cb());
|
|
349
|
+
subListeners[id2].eose = [];
|
|
350
|
+
}
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
case "OK": {
|
|
354
|
+
let id2 = data[1];
|
|
355
|
+
let ok = data[2];
|
|
356
|
+
let reason = data[3] || "";
|
|
357
|
+
if (id2 in pubListeners) {
|
|
358
|
+
if (ok)
|
|
359
|
+
pubListeners[id2].ok.forEach((cb) => cb());
|
|
360
|
+
else
|
|
361
|
+
pubListeners[id2].failed.forEach((cb) => cb(reason));
|
|
362
|
+
pubListeners[id2].ok = [];
|
|
363
|
+
pubListeners[id2].failed = [];
|
|
364
|
+
}
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
case "NOTICE":
|
|
368
|
+
let notice = data[1];
|
|
369
|
+
listeners.notice.forEach((cb) => cb(notice));
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
} catch (err) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
function connected() {
|
|
379
|
+
return ws?.readyState === 1;
|
|
380
|
+
}
|
|
381
|
+
async function connect() {
|
|
382
|
+
if (connected())
|
|
383
|
+
return;
|
|
384
|
+
await connectRelay();
|
|
385
|
+
}
|
|
386
|
+
async function trySend(params) {
|
|
387
|
+
let msg = JSON.stringify(params);
|
|
388
|
+
if (!connected()) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
ws.send(msg);
|
|
393
|
+
} catch (err) {
|
|
394
|
+
console.log(err);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
const sub = (filters, {
|
|
398
|
+
skipVerification = false,
|
|
399
|
+
alreadyHaveEvent = null,
|
|
400
|
+
id = Math.random().toString().slice(2)
|
|
401
|
+
} = {}) => {
|
|
402
|
+
let subid = id;
|
|
403
|
+
openSubs[subid] = {
|
|
404
|
+
id: subid,
|
|
405
|
+
filters,
|
|
406
|
+
skipVerification,
|
|
407
|
+
alreadyHaveEvent
|
|
408
|
+
};
|
|
409
|
+
trySend(["REQ", subid, ...filters]);
|
|
410
|
+
return {
|
|
411
|
+
sub: (newFilters, newOpts = {}) => sub(newFilters || filters, {
|
|
412
|
+
skipVerification: newOpts.skipVerification || skipVerification,
|
|
413
|
+
alreadyHaveEvent: newOpts.alreadyHaveEvent || alreadyHaveEvent,
|
|
414
|
+
id: subid
|
|
415
|
+
}),
|
|
416
|
+
unsub: () => {
|
|
417
|
+
delete openSubs[subid];
|
|
418
|
+
delete subListeners[subid];
|
|
419
|
+
trySend(["CLOSE", subid]);
|
|
420
|
+
},
|
|
421
|
+
on: (type, cb) => {
|
|
422
|
+
subListeners[subid] = subListeners[subid] || {
|
|
423
|
+
event: [],
|
|
424
|
+
eose: []
|
|
425
|
+
};
|
|
426
|
+
subListeners[subid][type].push(cb);
|
|
427
|
+
},
|
|
428
|
+
off: (type, cb) => {
|
|
429
|
+
let listeners2 = subListeners[subid];
|
|
430
|
+
let idx = listeners2[type].indexOf(cb);
|
|
431
|
+
if (idx >= 0)
|
|
432
|
+
listeners2[type].splice(idx, 1);
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
};
|
|
436
|
+
return {
|
|
437
|
+
url,
|
|
438
|
+
sub,
|
|
439
|
+
on: (type, cb) => {
|
|
440
|
+
listeners[type].push(cb);
|
|
441
|
+
if (type === "connect" && ws?.readyState === 1) {
|
|
442
|
+
cb();
|
|
443
|
+
}
|
|
444
|
+
},
|
|
445
|
+
off: (type, cb) => {
|
|
446
|
+
let index = listeners[type].indexOf(cb);
|
|
447
|
+
if (index !== -1)
|
|
448
|
+
listeners[type].splice(index, 1);
|
|
449
|
+
},
|
|
450
|
+
list: (filters, opts) => new Promise((resolve) => {
|
|
451
|
+
let s = sub(filters, opts);
|
|
452
|
+
let events = [];
|
|
453
|
+
let timeout = setTimeout(() => {
|
|
454
|
+
s.unsub();
|
|
455
|
+
resolve(events);
|
|
456
|
+
}, 1500);
|
|
457
|
+
s.on("eose", () => {
|
|
458
|
+
s.unsub();
|
|
459
|
+
clearTimeout(timeout);
|
|
460
|
+
resolve(events);
|
|
461
|
+
});
|
|
462
|
+
s.on("event", (event) => {
|
|
463
|
+
events.push(event);
|
|
464
|
+
});
|
|
465
|
+
}),
|
|
466
|
+
get: (filter, opts) => new Promise((resolve) => {
|
|
467
|
+
let s = sub([filter], opts);
|
|
468
|
+
let timeout = setTimeout(() => {
|
|
469
|
+
s.unsub();
|
|
470
|
+
resolve(null);
|
|
471
|
+
}, 1500);
|
|
472
|
+
s.on("event", (event) => {
|
|
473
|
+
s.unsub();
|
|
474
|
+
clearTimeout(timeout);
|
|
475
|
+
resolve(event);
|
|
476
|
+
});
|
|
477
|
+
}),
|
|
478
|
+
publish(event) {
|
|
479
|
+
if (!event.id)
|
|
480
|
+
throw new Error(`event ${event} has no id`);
|
|
481
|
+
let id = event.id;
|
|
482
|
+
trySend(["EVENT", event]);
|
|
483
|
+
return {
|
|
484
|
+
on: (type, cb) => {
|
|
485
|
+
pubListeners[id] = pubListeners[id] || {
|
|
486
|
+
ok: [],
|
|
487
|
+
failed: []
|
|
488
|
+
};
|
|
489
|
+
pubListeners[id][type].push(cb);
|
|
490
|
+
},
|
|
491
|
+
off: (type, cb) => {
|
|
492
|
+
let listeners2 = pubListeners[id];
|
|
493
|
+
if (!listeners2)
|
|
494
|
+
return;
|
|
495
|
+
let idx = listeners2[type].indexOf(cb);
|
|
496
|
+
if (idx >= 0)
|
|
497
|
+
listeners2[type].splice(idx, 1);
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
},
|
|
501
|
+
connect,
|
|
502
|
+
close() {
|
|
503
|
+
listeners = { connect: [], disconnect: [], error: [], notice: [] };
|
|
504
|
+
subListeners = {};
|
|
505
|
+
pubListeners = {};
|
|
506
|
+
ws?.close();
|
|
507
|
+
},
|
|
508
|
+
get status() {
|
|
509
|
+
return ws?.readyState ?? 3;
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// pool.ts
|
|
515
|
+
var SimplePool = class {
|
|
516
|
+
_conn;
|
|
517
|
+
_seenOn = {};
|
|
518
|
+
constructor() {
|
|
519
|
+
this._conn = {};
|
|
520
|
+
}
|
|
521
|
+
close(relays) {
|
|
522
|
+
relays.forEach((url) => {
|
|
523
|
+
let relay = this._conn[normalizeURL(url)];
|
|
524
|
+
if (relay)
|
|
525
|
+
relay.close();
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
async ensureRelay(url) {
|
|
529
|
+
const nm = normalizeURL(url);
|
|
530
|
+
const existing = this._conn[nm];
|
|
531
|
+
if (existing)
|
|
532
|
+
return existing;
|
|
533
|
+
const relay = relayInit(nm);
|
|
534
|
+
this._conn[nm] = relay;
|
|
535
|
+
await relay.connect();
|
|
536
|
+
return relay;
|
|
537
|
+
}
|
|
538
|
+
sub(relays, filters, opts) {
|
|
539
|
+
let _knownIds = /* @__PURE__ */ new Set();
|
|
540
|
+
let modifiedOpts = opts || {};
|
|
541
|
+
modifiedOpts.alreadyHaveEvent = (id, url) => {
|
|
542
|
+
let set = this._seenOn[id] || /* @__PURE__ */ new Set();
|
|
543
|
+
set.add(url);
|
|
544
|
+
this._seenOn[id] = set;
|
|
545
|
+
return _knownIds.has(id);
|
|
546
|
+
};
|
|
547
|
+
let subs = [];
|
|
548
|
+
let eventListeners = /* @__PURE__ */ new Set();
|
|
549
|
+
let eoseListeners = /* @__PURE__ */ new Set();
|
|
550
|
+
let eosesMissing = relays.length;
|
|
551
|
+
let eoseSent = false;
|
|
552
|
+
let eoseTimeout = setTimeout(() => {
|
|
553
|
+
eoseSent = true;
|
|
554
|
+
for (let cb of eoseListeners.values())
|
|
555
|
+
cb();
|
|
556
|
+
}, 2400);
|
|
557
|
+
relays.forEach(async (relay) => {
|
|
558
|
+
let r;
|
|
559
|
+
try {
|
|
560
|
+
r = await this.ensureRelay(relay);
|
|
561
|
+
} catch (err) {
|
|
562
|
+
handleEose();
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (!r)
|
|
566
|
+
return;
|
|
567
|
+
let s = r.sub(filters, modifiedOpts);
|
|
568
|
+
s.on("event", (event) => {
|
|
569
|
+
_knownIds.add(event.id);
|
|
570
|
+
for (let cb of eventListeners.values())
|
|
571
|
+
cb(event);
|
|
572
|
+
});
|
|
573
|
+
s.on("eose", () => {
|
|
574
|
+
if (eoseSent)
|
|
575
|
+
return;
|
|
576
|
+
handleEose();
|
|
577
|
+
});
|
|
578
|
+
subs.push(s);
|
|
579
|
+
function handleEose() {
|
|
580
|
+
eosesMissing--;
|
|
581
|
+
if (eosesMissing === 0) {
|
|
582
|
+
clearTimeout(eoseTimeout);
|
|
583
|
+
for (let cb of eoseListeners.values())
|
|
584
|
+
cb();
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
let greaterSub = {
|
|
589
|
+
sub(filters2, opts2) {
|
|
590
|
+
subs.forEach((sub) => sub.sub(filters2, opts2));
|
|
591
|
+
return greaterSub;
|
|
592
|
+
},
|
|
593
|
+
unsub() {
|
|
594
|
+
subs.forEach((sub) => sub.unsub());
|
|
595
|
+
},
|
|
596
|
+
on(type, cb) {
|
|
597
|
+
switch (type) {
|
|
598
|
+
case "event":
|
|
599
|
+
eventListeners.add(cb);
|
|
600
|
+
break;
|
|
601
|
+
case "eose":
|
|
602
|
+
eoseListeners.add(cb);
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
off(type, cb) {
|
|
607
|
+
if (type === "event") {
|
|
608
|
+
eventListeners.delete(cb);
|
|
609
|
+
} else if (type === "eose")
|
|
610
|
+
eoseListeners.delete(cb);
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
return greaterSub;
|
|
614
|
+
}
|
|
615
|
+
get(relays, filter, opts) {
|
|
616
|
+
return new Promise((resolve) => {
|
|
617
|
+
let sub = this.sub(relays, [filter], opts);
|
|
618
|
+
let timeout = setTimeout(() => {
|
|
619
|
+
sub.unsub();
|
|
620
|
+
resolve(null);
|
|
621
|
+
}, 1500);
|
|
622
|
+
sub.on("event", (event) => {
|
|
623
|
+
resolve(event);
|
|
624
|
+
clearTimeout(timeout);
|
|
625
|
+
sub.unsub();
|
|
626
|
+
});
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
list(relays, filters, opts) {
|
|
630
|
+
return new Promise((resolve) => {
|
|
631
|
+
let events = [];
|
|
632
|
+
let sub = this.sub(relays, filters, opts);
|
|
633
|
+
sub.on("event", (event) => {
|
|
634
|
+
events.push(event);
|
|
635
|
+
});
|
|
636
|
+
sub.on("eose", () => {
|
|
637
|
+
sub.unsub();
|
|
638
|
+
resolve(events);
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
publish(relays, event) {
|
|
643
|
+
return relays.map((relay) => {
|
|
644
|
+
let r = this._conn[normalizeURL(relay)];
|
|
645
|
+
if (!r)
|
|
646
|
+
return badPub(relay);
|
|
647
|
+
let s = r.publish(event);
|
|
648
|
+
return s;
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
seenOn(id) {
|
|
652
|
+
return Array.from(this._seenOn[id]?.values?.() || []);
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
function badPub(relay) {
|
|
656
|
+
return {
|
|
657
|
+
on(typ, cb) {
|
|
658
|
+
if (typ === "failed")
|
|
659
|
+
cb(`relay ${relay} not connected`);
|
|
660
|
+
},
|
|
661
|
+
off() {
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// nip04.ts
|
|
667
|
+
var nip04_exports = {};
|
|
668
|
+
__export(nip04_exports, {
|
|
669
|
+
decrypt: () => decrypt,
|
|
670
|
+
encrypt: () => encrypt
|
|
671
|
+
});
|
|
672
|
+
import { randomBytes } from "@noble/hashes/utils";
|
|
673
|
+
import * as secp256k13 from "@noble/secp256k1";
|
|
674
|
+
import { base64 } from "@scure/base";
|
|
675
|
+
async function encrypt(privkey, pubkey, text) {
|
|
676
|
+
const key = secp256k13.getSharedSecret(privkey, "02" + pubkey);
|
|
677
|
+
const normalizedKey = getNormalizedX(key);
|
|
678
|
+
let iv = Uint8Array.from(randomBytes(16));
|
|
679
|
+
let plaintext = utf8Encoder.encode(text);
|
|
680
|
+
let cryptoKey = await crypto.subtle.importKey(
|
|
681
|
+
"raw",
|
|
682
|
+
normalizedKey,
|
|
683
|
+
{ name: "AES-CBC" },
|
|
684
|
+
false,
|
|
685
|
+
["encrypt"]
|
|
686
|
+
);
|
|
687
|
+
let ciphertext = await crypto.subtle.encrypt(
|
|
688
|
+
{ name: "AES-CBC", iv },
|
|
689
|
+
cryptoKey,
|
|
690
|
+
plaintext
|
|
691
|
+
);
|
|
692
|
+
let ctb64 = base64.encode(new Uint8Array(ciphertext));
|
|
693
|
+
let ivb64 = base64.encode(new Uint8Array(iv.buffer));
|
|
694
|
+
return `${ctb64}?iv=${ivb64}`;
|
|
695
|
+
}
|
|
696
|
+
async function decrypt(privkey, pubkey, data) {
|
|
697
|
+
let [ctb64, ivb64] = data.split("?iv=");
|
|
698
|
+
let key = secp256k13.getSharedSecret(privkey, "02" + pubkey);
|
|
699
|
+
let normalizedKey = getNormalizedX(key);
|
|
700
|
+
let cryptoKey = await crypto.subtle.importKey(
|
|
701
|
+
"raw",
|
|
702
|
+
normalizedKey,
|
|
703
|
+
{ name: "AES-CBC" },
|
|
704
|
+
false,
|
|
705
|
+
["decrypt"]
|
|
706
|
+
);
|
|
707
|
+
let ciphertext = base64.decode(ctb64);
|
|
708
|
+
let iv = base64.decode(ivb64);
|
|
709
|
+
let plaintext = await crypto.subtle.decrypt(
|
|
710
|
+
{ name: "AES-CBC", iv },
|
|
711
|
+
cryptoKey,
|
|
712
|
+
ciphertext
|
|
713
|
+
);
|
|
714
|
+
let text = utf8Decoder.decode(plaintext);
|
|
715
|
+
return text;
|
|
716
|
+
}
|
|
717
|
+
function getNormalizedX(key) {
|
|
718
|
+
return key.slice(1, 33);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// nip05.ts
|
|
722
|
+
var nip05_exports = {};
|
|
723
|
+
__export(nip05_exports, {
|
|
724
|
+
queryProfile: () => queryProfile,
|
|
725
|
+
searchDomain: () => searchDomain,
|
|
726
|
+
useFetchImplementation: () => useFetchImplementation
|
|
727
|
+
});
|
|
728
|
+
var _fetch;
|
|
729
|
+
try {
|
|
730
|
+
_fetch = fetch;
|
|
731
|
+
} catch {
|
|
732
|
+
}
|
|
733
|
+
function useFetchImplementation(fetchImplementation) {
|
|
734
|
+
_fetch = fetchImplementation;
|
|
735
|
+
}
|
|
736
|
+
async function searchDomain(domain, query = "") {
|
|
737
|
+
try {
|
|
738
|
+
let res = await (await _fetch(`https://${domain}/.well-known/nostr.json?name=${query}`)).json();
|
|
739
|
+
return res.names;
|
|
740
|
+
} catch (_) {
|
|
741
|
+
return {};
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
async function queryProfile(fullname) {
|
|
745
|
+
let [name, domain] = fullname.split("@");
|
|
746
|
+
if (!domain) {
|
|
747
|
+
domain = name;
|
|
748
|
+
name = "_";
|
|
749
|
+
}
|
|
750
|
+
if (!name.match(/^[A-Za-z0-9-_]+$/))
|
|
751
|
+
return null;
|
|
752
|
+
let res = await (await _fetch(`https://${domain}/.well-known/nostr.json?name=${name}`)).json();
|
|
753
|
+
if (!res?.names?.[name])
|
|
754
|
+
return null;
|
|
755
|
+
let pubkey = res.names[name];
|
|
756
|
+
let relays = res.relays?.[pubkey] || [];
|
|
757
|
+
return {
|
|
758
|
+
pubkey,
|
|
759
|
+
relays
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// nip06.ts
|
|
764
|
+
var nip06_exports = {};
|
|
765
|
+
__export(nip06_exports, {
|
|
766
|
+
generateSeedWords: () => generateSeedWords,
|
|
767
|
+
privateKeyFromSeedWords: () => privateKeyFromSeedWords,
|
|
768
|
+
validateWords: () => validateWords
|
|
769
|
+
});
|
|
770
|
+
import * as secp256k14 from "@noble/secp256k1";
|
|
771
|
+
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
772
|
+
import {
|
|
773
|
+
generateMnemonic,
|
|
774
|
+
mnemonicToSeedSync,
|
|
775
|
+
validateMnemonic
|
|
776
|
+
} from "@scure/bip39";
|
|
777
|
+
import { HDKey } from "@scure/bip32";
|
|
778
|
+
function privateKeyFromSeedWords(mnemonic, passphrase) {
|
|
779
|
+
let root = HDKey.fromMasterSeed(mnemonicToSeedSync(mnemonic, passphrase));
|
|
780
|
+
let privateKey = root.derive(`m/44'/1237'/0'/0/0`).privateKey;
|
|
781
|
+
if (!privateKey)
|
|
782
|
+
throw new Error("could not derive private key");
|
|
783
|
+
return secp256k14.utils.bytesToHex(privateKey);
|
|
784
|
+
}
|
|
785
|
+
function generateSeedWords() {
|
|
786
|
+
return generateMnemonic(wordlist);
|
|
787
|
+
}
|
|
788
|
+
function validateWords(words) {
|
|
789
|
+
return validateMnemonic(words, wordlist);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// nip19.ts
|
|
793
|
+
var nip19_exports = {};
|
|
794
|
+
__export(nip19_exports, {
|
|
795
|
+
decode: () => decode,
|
|
796
|
+
naddrEncode: () => naddrEncode,
|
|
797
|
+
neventEncode: () => neventEncode,
|
|
798
|
+
noteEncode: () => noteEncode,
|
|
799
|
+
nprofileEncode: () => nprofileEncode,
|
|
800
|
+
npubEncode: () => npubEncode,
|
|
801
|
+
nsecEncode: () => nsecEncode
|
|
802
|
+
});
|
|
803
|
+
import * as secp256k15 from "@noble/secp256k1";
|
|
804
|
+
import { bech32 } from "@scure/base";
|
|
805
|
+
var Bech32MaxSize = 5e3;
|
|
806
|
+
function decode(nip19) {
|
|
807
|
+
let { prefix, words } = bech32.decode(nip19, Bech32MaxSize);
|
|
808
|
+
let data = new Uint8Array(bech32.fromWords(words));
|
|
809
|
+
switch (prefix) {
|
|
810
|
+
case "nprofile": {
|
|
811
|
+
let tlv = parseTLV(data);
|
|
812
|
+
if (!tlv[0]?.[0])
|
|
813
|
+
throw new Error("missing TLV 0 for nprofile");
|
|
814
|
+
if (tlv[0][0].length !== 32)
|
|
815
|
+
throw new Error("TLV 0 should be 32 bytes");
|
|
816
|
+
return {
|
|
817
|
+
type: "nprofile",
|
|
818
|
+
data: {
|
|
819
|
+
pubkey: secp256k15.utils.bytesToHex(tlv[0][0]),
|
|
820
|
+
relays: tlv[1] ? tlv[1].map((d) => utf8Decoder.decode(d)) : []
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
case "nevent": {
|
|
825
|
+
let tlv = parseTLV(data);
|
|
826
|
+
if (!tlv[0]?.[0])
|
|
827
|
+
throw new Error("missing TLV 0 for nevent");
|
|
828
|
+
if (tlv[0][0].length !== 32)
|
|
829
|
+
throw new Error("TLV 0 should be 32 bytes");
|
|
830
|
+
return {
|
|
831
|
+
type: "nevent",
|
|
832
|
+
data: {
|
|
833
|
+
id: secp256k15.utils.bytesToHex(tlv[0][0]),
|
|
834
|
+
relays: tlv[1] ? tlv[1].map((d) => utf8Decoder.decode(d)) : []
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
case "naddr": {
|
|
839
|
+
let tlv = parseTLV(data);
|
|
840
|
+
if (!tlv[0]?.[0])
|
|
841
|
+
throw new Error("missing TLV 0 for naddr");
|
|
842
|
+
if (!tlv[2]?.[0])
|
|
843
|
+
throw new Error("missing TLV 2 for naddr");
|
|
844
|
+
if (tlv[2][0].length !== 32)
|
|
845
|
+
throw new Error("TLV 2 should be 32 bytes");
|
|
846
|
+
if (!tlv[3]?.[0])
|
|
847
|
+
throw new Error("missing TLV 3 for naddr");
|
|
848
|
+
if (tlv[3][0].length !== 4)
|
|
849
|
+
throw new Error("TLV 3 should be 4 bytes");
|
|
850
|
+
return {
|
|
851
|
+
type: "naddr",
|
|
852
|
+
data: {
|
|
853
|
+
identifier: utf8Decoder.decode(tlv[0][0]),
|
|
854
|
+
pubkey: secp256k15.utils.bytesToHex(tlv[2][0]),
|
|
855
|
+
kind: parseInt(secp256k15.utils.bytesToHex(tlv[3][0]), 16),
|
|
856
|
+
relays: tlv[1] ? tlv[1].map((d) => utf8Decoder.decode(d)) : []
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
case "nsec":
|
|
861
|
+
case "npub":
|
|
862
|
+
case "note":
|
|
863
|
+
return { type: prefix, data: secp256k15.utils.bytesToHex(data) };
|
|
864
|
+
default:
|
|
865
|
+
throw new Error(`unknown prefix ${prefix}`);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
function parseTLV(data) {
|
|
869
|
+
let result = {};
|
|
870
|
+
let rest = data;
|
|
871
|
+
while (rest.length > 0) {
|
|
872
|
+
let t = rest[0];
|
|
873
|
+
let l = rest[1];
|
|
874
|
+
let v = rest.slice(2, 2 + l);
|
|
875
|
+
rest = rest.slice(2 + l);
|
|
876
|
+
if (v.length < l)
|
|
877
|
+
continue;
|
|
878
|
+
result[t] = result[t] || [];
|
|
879
|
+
result[t].push(v);
|
|
880
|
+
}
|
|
881
|
+
return result;
|
|
882
|
+
}
|
|
883
|
+
function nsecEncode(hex) {
|
|
884
|
+
return encodeBytes("nsec", hex);
|
|
885
|
+
}
|
|
886
|
+
function npubEncode(hex) {
|
|
887
|
+
return encodeBytes("npub", hex);
|
|
888
|
+
}
|
|
889
|
+
function noteEncode(hex) {
|
|
890
|
+
return encodeBytes("note", hex);
|
|
891
|
+
}
|
|
892
|
+
function encodeBytes(prefix, hex) {
|
|
893
|
+
let data = secp256k15.utils.hexToBytes(hex);
|
|
894
|
+
let words = bech32.toWords(data);
|
|
895
|
+
return bech32.encode(prefix, words, Bech32MaxSize);
|
|
896
|
+
}
|
|
897
|
+
function nprofileEncode(profile) {
|
|
898
|
+
let data = encodeTLV({
|
|
899
|
+
0: [secp256k15.utils.hexToBytes(profile.pubkey)],
|
|
900
|
+
1: (profile.relays || []).map((url) => utf8Encoder.encode(url))
|
|
901
|
+
});
|
|
902
|
+
let words = bech32.toWords(data);
|
|
903
|
+
return bech32.encode("nprofile", words, Bech32MaxSize);
|
|
904
|
+
}
|
|
905
|
+
function neventEncode(event) {
|
|
906
|
+
let data = encodeTLV({
|
|
907
|
+
0: [secp256k15.utils.hexToBytes(event.id)],
|
|
908
|
+
1: (event.relays || []).map((url) => utf8Encoder.encode(url))
|
|
909
|
+
});
|
|
910
|
+
let words = bech32.toWords(data);
|
|
911
|
+
return bech32.encode("nevent", words, Bech32MaxSize);
|
|
912
|
+
}
|
|
913
|
+
function naddrEncode(addr) {
|
|
914
|
+
let kind = new ArrayBuffer(4);
|
|
915
|
+
new DataView(kind).setUint32(0, addr.kind, false);
|
|
916
|
+
let data = encodeTLV({
|
|
917
|
+
0: [utf8Encoder.encode(addr.identifier)],
|
|
918
|
+
1: (addr.relays || []).map((url) => utf8Encoder.encode(url)),
|
|
919
|
+
2: [secp256k15.utils.hexToBytes(addr.pubkey)],
|
|
920
|
+
3: [new Uint8Array(kind)]
|
|
921
|
+
});
|
|
922
|
+
let words = bech32.toWords(data);
|
|
923
|
+
return bech32.encode("naddr", words, Bech32MaxSize);
|
|
924
|
+
}
|
|
925
|
+
function encodeTLV(tlv) {
|
|
926
|
+
let entries = [];
|
|
927
|
+
Object.entries(tlv).forEach(([t, vs]) => {
|
|
928
|
+
vs.forEach((v) => {
|
|
929
|
+
let entry = new Uint8Array(v.length + 2);
|
|
930
|
+
entry.set([parseInt(t)], 0);
|
|
931
|
+
entry.set([v.length], 1);
|
|
932
|
+
entry.set(v, 2);
|
|
933
|
+
entries.push(entry);
|
|
934
|
+
});
|
|
935
|
+
});
|
|
936
|
+
return secp256k15.utils.concatBytes(...entries);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// nip26.ts
|
|
940
|
+
var nip26_exports = {};
|
|
941
|
+
__export(nip26_exports, {
|
|
942
|
+
createDelegation: () => createDelegation,
|
|
943
|
+
getDelegator: () => getDelegator
|
|
944
|
+
});
|
|
945
|
+
import * as secp256k16 from "@noble/secp256k1";
|
|
946
|
+
import { sha256 as sha2562 } from "@noble/hashes/sha256";
|
|
947
|
+
function createDelegation(privateKey, parameters) {
|
|
948
|
+
let conditions = [];
|
|
949
|
+
if ((parameters.kind || -1) >= 0)
|
|
950
|
+
conditions.push(`kind=${parameters.kind}`);
|
|
951
|
+
if (parameters.until)
|
|
952
|
+
conditions.push(`created_at<${parameters.until}`);
|
|
953
|
+
if (parameters.since)
|
|
954
|
+
conditions.push(`created_at>${parameters.since}`);
|
|
955
|
+
let cond = conditions.join("&");
|
|
956
|
+
if (cond === "")
|
|
957
|
+
throw new Error("refusing to create a delegation without any conditions");
|
|
958
|
+
let sighash = sha2562(
|
|
959
|
+
utf8Encoder.encode(`nostr:delegation:${parameters.pubkey}:${cond}`)
|
|
960
|
+
);
|
|
961
|
+
let sig = secp256k16.utils.bytesToHex(
|
|
962
|
+
secp256k16.schnorr.signSync(sighash, privateKey)
|
|
963
|
+
);
|
|
964
|
+
return {
|
|
965
|
+
from: getPublicKey(privateKey),
|
|
966
|
+
to: parameters.pubkey,
|
|
967
|
+
cond,
|
|
968
|
+
sig
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
function getDelegator(event) {
|
|
972
|
+
let tag = event.tags.find((tag2) => tag2[0] === "delegation" && tag2.length >= 4);
|
|
973
|
+
if (!tag)
|
|
974
|
+
return null;
|
|
975
|
+
let pubkey = tag[1];
|
|
976
|
+
let cond = tag[2];
|
|
977
|
+
let sig = tag[3];
|
|
978
|
+
let conditions = cond.split("&");
|
|
979
|
+
for (let i = 0; i < conditions.length; i++) {
|
|
980
|
+
let [key, operator, value] = conditions[i].split(/\b/);
|
|
981
|
+
if (key === "kind" && operator === "=" && event.kind === parseInt(value))
|
|
982
|
+
continue;
|
|
983
|
+
else if (key === "created_at" && operator === "<" && event.created_at < parseInt(value))
|
|
984
|
+
continue;
|
|
985
|
+
else if (key === "created_at" && operator === ">" && event.created_at > parseInt(value))
|
|
986
|
+
continue;
|
|
987
|
+
else
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
let sighash = sha2562(
|
|
991
|
+
utf8Encoder.encode(`nostr:delegation:${event.pubkey}:${cond}`)
|
|
992
|
+
);
|
|
993
|
+
if (!secp256k16.schnorr.verifySync(sig, sighash, pubkey))
|
|
994
|
+
return null;
|
|
995
|
+
return pubkey;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// nip57.ts
|
|
999
|
+
var nip57_exports = {};
|
|
1000
|
+
__export(nip57_exports, {
|
|
1001
|
+
getZapEndpoint: () => getZapEndpoint,
|
|
1002
|
+
makeZapReceipt: () => makeZapReceipt,
|
|
1003
|
+
makeZapRequest: () => makeZapRequest,
|
|
1004
|
+
useFetchImplementation: () => useFetchImplementation2,
|
|
1005
|
+
validateZapRequest: () => validateZapRequest
|
|
1006
|
+
});
|
|
1007
|
+
import { bech32 as bech322 } from "@scure/base";
|
|
1008
|
+
var _fetch2;
|
|
1009
|
+
try {
|
|
1010
|
+
_fetch2 = fetch;
|
|
1011
|
+
} catch {
|
|
1012
|
+
}
|
|
1013
|
+
function useFetchImplementation2(fetchImplementation) {
|
|
1014
|
+
_fetch2 = fetchImplementation;
|
|
1015
|
+
}
|
|
1016
|
+
async function getZapEndpoint(metadata) {
|
|
1017
|
+
try {
|
|
1018
|
+
let lnurl = "";
|
|
1019
|
+
let { lud06, lud16 } = JSON.parse(metadata.content);
|
|
1020
|
+
if (lud06) {
|
|
1021
|
+
let { words } = bech322.decode(lud06, 1e3);
|
|
1022
|
+
let data = bech322.fromWords(words);
|
|
1023
|
+
lnurl = utf8Decoder.decode(data);
|
|
1024
|
+
} else if (lud16) {
|
|
1025
|
+
let [name, domain] = lud16.split("@");
|
|
1026
|
+
lnurl = `https://${domain}/.well-known/lnurlp/${name}`;
|
|
1027
|
+
} else {
|
|
1028
|
+
return null;
|
|
1029
|
+
}
|
|
1030
|
+
let res = await _fetch2(lnurl);
|
|
1031
|
+
let body = await res.json();
|
|
1032
|
+
if (body.allowsNostr && body.nostrPubkey) {
|
|
1033
|
+
return body.callback;
|
|
1034
|
+
}
|
|
1035
|
+
} catch (err) {
|
|
1036
|
+
}
|
|
1037
|
+
return null;
|
|
1038
|
+
}
|
|
1039
|
+
function makeZapRequest({
|
|
1040
|
+
profile,
|
|
1041
|
+
event,
|
|
1042
|
+
amount,
|
|
1043
|
+
relays,
|
|
1044
|
+
comment = ""
|
|
1045
|
+
}) {
|
|
1046
|
+
if (!amount)
|
|
1047
|
+
throw new Error("amount not given");
|
|
1048
|
+
if (!profile)
|
|
1049
|
+
throw new Error("profile not given");
|
|
1050
|
+
let zr = {
|
|
1051
|
+
kind: 9734,
|
|
1052
|
+
created_at: Math.round(Date.now() / 1e3),
|
|
1053
|
+
content: comment,
|
|
1054
|
+
tags: [
|
|
1055
|
+
["p", profile],
|
|
1056
|
+
["amount", amount.toString()],
|
|
1057
|
+
["relays", ...relays]
|
|
1058
|
+
]
|
|
1059
|
+
};
|
|
1060
|
+
if (event) {
|
|
1061
|
+
zr.tags.push(["e", event]);
|
|
1062
|
+
}
|
|
1063
|
+
return zr;
|
|
1064
|
+
}
|
|
1065
|
+
function validateZapRequest(zapRequestString) {
|
|
1066
|
+
let zapRequest;
|
|
1067
|
+
try {
|
|
1068
|
+
zapRequest = JSON.parse(zapRequestString);
|
|
1069
|
+
} catch (err) {
|
|
1070
|
+
return "Invalid zap request JSON.";
|
|
1071
|
+
}
|
|
1072
|
+
if (!validateEvent(zapRequest))
|
|
1073
|
+
return "Zap request is not a valid Nostr event.";
|
|
1074
|
+
if (!verifySignature(zapRequest))
|
|
1075
|
+
return "Invalid signature on zap request.";
|
|
1076
|
+
let p = zapRequest.tags.find(([t, v]) => t === "p" && v);
|
|
1077
|
+
if (!p)
|
|
1078
|
+
return "Zap request doesn't have a 'p' tag.";
|
|
1079
|
+
if (!p[1].match(/^[a-f0-9]{64}$/))
|
|
1080
|
+
return "Zap request 'p' tag is not valid hex.";
|
|
1081
|
+
let e = zapRequest.tags.find(([t, v]) => t === "e" && v);
|
|
1082
|
+
if (e && !e[1].match(/^[a-f0-9]{64}$/))
|
|
1083
|
+
return "Zap request 'e' tag is not valid hex.";
|
|
1084
|
+
let relays = zapRequest.tags.find(([t, v]) => t === "relays" && v);
|
|
1085
|
+
if (!relays)
|
|
1086
|
+
return "Zap request doesn't have a 'relays' tag.";
|
|
1087
|
+
return null;
|
|
1088
|
+
}
|
|
1089
|
+
function makeZapReceipt({
|
|
1090
|
+
zapRequest,
|
|
1091
|
+
preimage,
|
|
1092
|
+
bolt11,
|
|
1093
|
+
paidAt
|
|
1094
|
+
}) {
|
|
1095
|
+
let zr = JSON.parse(zapRequest);
|
|
1096
|
+
let tagsFromZapRequest = zr.tags.filter(
|
|
1097
|
+
([t]) => t === "e" || t === "p" || t === "a"
|
|
1098
|
+
);
|
|
1099
|
+
let zap = {
|
|
1100
|
+
kind: 9735,
|
|
1101
|
+
created_at: Math.round(paidAt.getTime() / 1e3),
|
|
1102
|
+
content: "",
|
|
1103
|
+
tags: [
|
|
1104
|
+
...tagsFromZapRequest,
|
|
1105
|
+
["bolt11", bolt11],
|
|
1106
|
+
["description", zapRequest]
|
|
1107
|
+
]
|
|
1108
|
+
};
|
|
1109
|
+
if (preimage) {
|
|
1110
|
+
zap.tags.push(["preimage", preimage]);
|
|
1111
|
+
}
|
|
1112
|
+
return zap;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// index.ts
|
|
1116
|
+
import * as secp256k17 from "@noble/secp256k1";
|
|
1117
|
+
import { hmac } from "@noble/hashes/hmac";
|
|
1118
|
+
import { sha256 as sha2563 } from "@noble/hashes/sha256";
|
|
1119
|
+
secp256k17.utils.hmacSha256Sync = (key, ...msgs) => hmac(sha2563, key, secp256k17.utils.concatBytes(...msgs));
|
|
1120
|
+
secp256k17.utils.sha256Sync = (...msgs) => sha2563(secp256k17.utils.concatBytes(...msgs));
|
|
1121
|
+
export {
|
|
1122
|
+
Kind,
|
|
1123
|
+
SimplePool,
|
|
1124
|
+
finishEvent,
|
|
1125
|
+
fakejson_exports as fj,
|
|
1126
|
+
generatePrivateKey,
|
|
1127
|
+
getBlankEvent,
|
|
1128
|
+
getEventHash,
|
|
1129
|
+
getPublicKey,
|
|
1130
|
+
matchFilter,
|
|
1131
|
+
matchFilters,
|
|
1132
|
+
nip04_exports as nip04,
|
|
1133
|
+
nip05_exports as nip05,
|
|
1134
|
+
nip06_exports as nip06,
|
|
1135
|
+
nip19_exports as nip19,
|
|
1136
|
+
nip26_exports as nip26,
|
|
1137
|
+
nip57_exports as nip57,
|
|
1138
|
+
relayInit,
|
|
1139
|
+
serializeEvent,
|
|
1140
|
+
signEvent,
|
|
1141
|
+
utils_exports as utils,
|
|
1142
|
+
validateEvent,
|
|
1143
|
+
verifySignature
|
|
1144
|
+
};
|