@qntx/nostr 0.1.0 → 0.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.
- package/dist/abort-Ba8tDrOI.mjs +38 -0
- package/dist/abort-Ba8tDrOI.mjs.map +1 -0
- package/dist/{builder-BphJa-Y-.d.mts → builder-BWn55Paf.d.mts} +20 -14
- package/dist/{builder-q0VsvnYE.mjs → builder-CyrGKRVP.mjs} +32 -16
- package/dist/builder-CyrGKRVP.mjs.map +1 -0
- package/dist/{client-Bd8B1mYm.mjs → client-BWsX9Cv1.mjs} +354 -195
- package/dist/client-BWsX9Cv1.mjs.map +1 -0
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +1 -1
- package/dist/core.d.mts +8 -8
- package/dist/core.mjs +8 -9
- package/dist/error-BW7re1UN.d.mts +9 -0
- package/dist/error-B__OMBRY.mjs +14 -0
- package/dist/error-B__OMBRY.mjs.map +1 -0
- package/dist/{error-WEFS-R-4.d.mts → error-CR8Urc84.d.mts} +20 -8
- package/dist/{error-CwBxle6N.mjs → error-DrEIM7FO.mjs} +5 -3
- package/dist/error-DrEIM7FO.mjs.map +1 -0
- package/dist/{event-CzoQEFMF.mjs → event-D8Nfoh4K.mjs} +40 -32
- package/dist/event-D8Nfoh4K.mjs.map +1 -0
- package/dist/{event-B5D2jxt_.d.mts → event-fU3Y01PR.d.mts} +23 -20
- package/dist/{filter-BxwjEkZp.mjs → filter-BQl3GlHt.mjs} +42 -35
- package/dist/filter-BQl3GlHt.mjs.map +1 -0
- package/dist/filter-C0IMByE3.d.mts +37 -0
- package/dist/{gossip-C5RVB1UB.d.mts → gossip-BRQwIM_q.d.mts} +25 -23
- package/dist/{gossip-3xkRRCm_.mjs → gossip-oloDMWEj.mjs} +52 -34
- package/dist/gossip-oloDMWEj.mjs.map +1 -0
- package/dist/gossip.d.mts +1 -1
- package/dist/gossip.mjs +1 -1
- package/dist/{http-6vevbBlu.d.mts → http-C4HwIu4B.d.mts} +7 -7
- package/dist/{http-D2rhokZo.mjs → http-CuazNRIP.mjs} +10 -6
- package/dist/http-CuazNRIP.mjs.map +1 -0
- package/dist/index-B425CAcQ.d.mts +328 -0
- package/dist/index-BVyfUBuG.d.mts +181 -0
- package/dist/{index-BYMnhRIC.d.mts → index-Ba7bJ7DD.d.mts} +68 -50
- package/dist/{index-DaRyjo7O.d.mts → index-Boe8FshM.d.mts} +13 -7
- package/dist/index-CUIvFQdE.d.mts +73 -0
- package/dist/index-JdaS_uce.d.mts +207 -0
- package/dist/index.d.mts +22 -20
- package/dist/index.mjs +19 -18
- package/dist/{key-Dn-aX4x4.mjs → key-Bbtys6o7.mjs} +22 -14
- package/dist/key-Bbtys6o7.mjs.map +1 -0
- package/dist/{key-BiGxGLhg.d.mts → key-BlsYhN1V.d.mts} +9 -6
- package/dist/{kind-BLl4cLOc.mjs → kind-Cxwhhkx8.mjs} +2 -1
- package/dist/kind-Cxwhhkx8.mjs.map +1 -0
- package/dist/{loaders-BNQszDAn.mjs → loaders-o6YXB_Ri.mjs} +329 -305
- package/dist/loaders-o6YXB_Ri.mjs.map +1 -0
- package/dist/loaders.d.mts +3 -3
- package/dist/loaders.mjs +2 -2
- package/dist/memory-C2EVx0_O.mjs +46 -0
- package/dist/memory-C2EVx0_O.mjs.map +1 -0
- package/dist/{memory-BCmhZPek.mjs → memory-index-Do1mz0WF.mjs} +212 -82
- package/dist/memory-index-Do1mz0WF.mjs.map +1 -0
- package/dist/{message-C_Uj-Y-c.mjs → message-DcY6Fi9o.mjs} +38 -24
- package/dist/message-DcY6Fi9o.mjs.map +1 -0
- package/dist/{message-DJC-Jz6T.d.mts → message-iGPIqnn0.d.mts} +15 -8
- package/dist/{nip17-BWq-Kfq5.d.mts → nip17-DNEkz8rM.d.mts} +18 -17
- package/dist/{nip19-D7905oIv.d.mts → nip19-BevCJZsp.d.mts} +23 -3
- package/dist/{nip46-C_DoxiS_.d.mts → nip46-DDV4dC56.d.mts} +13 -12
- package/dist/{nip59-DljW_sYh.d.mts → nip59-zQgdLcEr.d.mts} +28 -25
- package/dist/{nip65-Di5hZRuV.d.mts → nip65-DBfh2smt.d.mts} +8 -3
- package/dist/{nip77-ByxlgoQO.d.mts → nip77-DH4RhS5r.d.mts} +9 -8
- package/dist/nips/blossom.d.mts +37 -37
- package/dist/nips/blossom.mjs +67 -52
- package/dist/nips/blossom.mjs.map +1 -1
- package/dist/nips/nip04.d.mts +2 -3
- package/dist/nips/nip04.mjs +6 -10
- package/dist/nips/nip04.mjs.map +1 -1
- package/dist/nips/nip05.d.mts +37 -43
- package/dist/nips/nip05.mjs +57 -57
- package/dist/nips/nip05.mjs.map +1 -1
- package/dist/nips/nip10.d.mts +18 -19
- package/dist/nips/nip10.mjs +76 -61
- package/dist/nips/nip10.mjs.map +1 -1
- package/dist/nips/nip11.d.mts +9 -8
- package/dist/nips/nip11.mjs +14 -11
- package/dist/nips/nip11.mjs.map +1 -1
- package/dist/nips/nip13.d.mts +9 -9
- package/dist/nips/nip13.mjs +15 -17
- package/dist/nips/nip13.mjs.map +1 -1
- package/dist/nips/nip17.d.mts +1 -1
- package/dist/nips/nip17.mjs +17 -12
- package/dist/nips/nip17.mjs.map +1 -1
- package/dist/nips/nip19.d.mts +1 -1
- package/dist/nips/nip19.mjs +52 -29
- package/dist/nips/nip19.mjs.map +1 -1
- package/dist/nips/nip21.d.mts +5 -6
- package/dist/nips/nip21.mjs +8 -7
- package/dist/nips/nip21.mjs.map +1 -1
- package/dist/nips/nip27.d.mts +7 -8
- package/dist/nips/nip27.mjs +26 -25
- package/dist/nips/nip27.mjs.map +1 -1
- package/dist/nips/nip42.d.mts +5 -6
- package/dist/nips/nip42.mjs +3 -3
- package/dist/nips/nip42.mjs.map +1 -1
- package/dist/nips/nip44.d.mts +24 -7
- package/dist/nips/nip44.mjs +25 -15
- package/dist/nips/nip44.mjs.map +1 -1
- package/dist/nips/nip46.d.mts +1 -1
- package/dist/nips/nip46.mjs +52 -49
- package/dist/nips/nip46.mjs.map +1 -1
- package/dist/nips/nip49.d.mts +8 -7
- package/dist/nips/nip49.mjs +16 -12
- package/dist/nips/nip49.mjs.map +1 -1
- package/dist/nips/nip51.d.mts +25 -27
- package/dist/nips/nip51.mjs +47 -36
- package/dist/nips/nip51.mjs.map +1 -1
- package/dist/nips/nip57.d.mts +12 -13
- package/dist/nips/nip57.mjs +35 -38
- package/dist/nips/nip57.mjs.map +1 -1
- package/dist/nips/nip59.d.mts +1 -1
- package/dist/nips/nip59.mjs +14 -18
- package/dist/nips/nip59.mjs.map +1 -1
- package/dist/nips/nip65.d.mts +1 -1
- package/dist/nips/nip65.mjs +11 -8
- package/dist/nips/nip65.mjs.map +1 -1
- package/dist/nips/nip77.d.mts +1 -1
- package/dist/nips/nip77.mjs +56 -34
- package/dist/nips/nip77.mjs.map +1 -1
- package/dist/nips/nip96.d.mts +21 -12
- package/dist/nips/nip96.mjs +59 -36
- package/dist/nips/nip96.mjs.map +1 -1
- package/dist/nips/nip98.d.mts +12 -12
- package/dist/nips/nip98.mjs +18 -19
- package/dist/nips/nip98.mjs.map +1 -1
- package/dist/{pool-C_j1OmBB.mjs → pool-XmAGaufS.mjs} +611 -487
- package/dist/pool-XmAGaufS.mjs.map +1 -0
- package/dist/pool-tEr7c5bG.d.mts +302 -0
- package/dist/reactive-DDlThDSZ.mjs +324 -0
- package/dist/reactive-DDlThDSZ.mjs.map +1 -0
- package/dist/reactive-DFpqtIf_.d.mts +66 -0
- package/dist/relay.d.mts +5 -4
- package/dist/relay.mjs +2 -2
- package/dist/report-CHld4uWj.mjs +32 -0
- package/dist/report-CHld4uWj.mjs.map +1 -0
- package/dist/{signer-CZ6K6FUc.mjs → signer-CKR9EHce.mjs} +107 -72
- package/dist/signer-CKR9EHce.mjs.map +1 -0
- package/dist/signer.d.mts +3 -3
- package/dist/signer.mjs +3 -2
- package/dist/storage-xXxLGCVb.mjs +1376 -0
- package/dist/storage-xXxLGCVb.mjs.map +1 -0
- package/dist/storage.d.mts +4 -4
- package/dist/storage.mjs +5 -4
- package/dist/store.d.mts +2 -0
- package/dist/store.mjs +2 -0
- package/dist/testing.d.mts +125 -0
- package/dist/testing.mjs +957 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/types-2pCUprRU.d.mts +47 -0
- package/dist/types-CRQEk0Oi.d.mts +19 -0
- package/dist/util-LfQirgDk.mjs +113 -0
- package/dist/util-LfQirgDk.mjs.map +1 -0
- package/dist/wasm.d.mts +4 -4
- package/dist/wasm.mjs +54 -55
- package/dist/wasm.mjs.map +1 -1
- package/dist/websocket-CmeqvAkp.d.mts +27 -0
- package/package.json +31 -9
- package/dist/builder-q0VsvnYE.mjs.map +0 -1
- package/dist/client-Bd8B1mYm.mjs.map +0 -1
- package/dist/error-B49rbDzT.d.mts +0 -7
- package/dist/error-CwBxle6N.mjs.map +0 -1
- package/dist/error-DZEs5OuZ.mjs +0 -24
- package/dist/error-DZEs5OuZ.mjs.map +0 -1
- package/dist/event-CzoQEFMF.mjs.map +0 -1
- package/dist/filter-BxwjEkZp.mjs.map +0 -1
- package/dist/filter-DUEKP3q1.d.mts +0 -40
- package/dist/gossip-3xkRRCm_.mjs.map +0 -1
- package/dist/http-D2rhokZo.mjs.map +0 -1
- package/dist/index-C0C4e-Gn.d.mts +0 -55
- package/dist/index-DgOugrpF.d.mts +0 -275
- package/dist/index-EwL6fHLo.d.mts +0 -267
- package/dist/index-k2YhCgsh.d.mts +0 -57
- package/dist/key-Dn-aX4x4.mjs.map +0 -1
- package/dist/kind-BLl4cLOc.mjs.map +0 -1
- package/dist/loaders-BNQszDAn.mjs.map +0 -1
- package/dist/memory-BCmhZPek.mjs.map +0 -1
- package/dist/message-C_Uj-Y-c.mjs.map +0 -1
- package/dist/pool-C_j1OmBB.mjs.map +0 -1
- package/dist/pool-DhRi7l-b.d.mts +0 -297
- package/dist/signer-CZ6K6FUc.mjs.map +0 -1
- package/dist/storage-DOy0iLRE.mjs +0 -870
- package/dist/storage-DOy0iLRE.mjs.map +0 -1
- package/dist/types-BzyADqnw.d.mts +0 -19
- package/dist/types-Dn24TPFs.d.mts +0 -43
- package/dist/util-6tfLQ9JF.mjs +0 -73
- package/dist/util-6tfLQ9JF.mjs.map +0 -1
package/dist/nips/nip51.d.mts
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import { t as Event } from "../event-
|
|
2
|
-
import { t as EventBuilder } from "../builder-
|
|
1
|
+
import { t as Event } from "../event-fU3Y01PR.mjs";
|
|
2
|
+
import { t as EventBuilder } from "../builder-BWn55Paf.mjs";
|
|
3
3
|
//#region src/nips/nip51.d.ts
|
|
4
4
|
/**
|
|
5
|
-
* Structural crypto used by NIP-51 private tags.
|
|
6
|
-
*
|
|
7
|
-
* This module must not import src/signer/.
|
|
5
|
+
* Structural crypto used by NIP-51 private tags. Satisfied by NostrSigner when
|
|
6
|
+
* nip44Encrypt/nip44Decrypt are present. This module must not import src/signer/.
|
|
8
7
|
*/
|
|
9
|
-
type Nip51Crypto = {
|
|
10
|
-
getPublicKey()
|
|
11
|
-
nip44Encrypt(peer: string, plaintext: string)
|
|
12
|
-
nip44Decrypt(peer: string, payload: string)
|
|
8
|
+
export type Nip51Crypto = {
|
|
9
|
+
getPublicKey: () => Promise<string>;
|
|
10
|
+
nip44Encrypt: (peer: string, plaintext: string) => Promise<string>;
|
|
11
|
+
nip44Decrypt: (peer: string, payload: string) => Promise<string>;
|
|
13
12
|
};
|
|
14
|
-
type MuteItem = {
|
|
13
|
+
export type MuteItem = {
|
|
15
14
|
type: "pubkey";
|
|
16
15
|
value: string;
|
|
17
16
|
} | {
|
|
@@ -25,34 +24,34 @@ type MuteItem = {
|
|
|
25
24
|
value: string;
|
|
26
25
|
};
|
|
27
26
|
/** Parse kind:10000 mute list public tags (`p` / `e` / `t` / `word`). */
|
|
28
|
-
declare function parseMuteList(event: Pick<Event, "kind" | "tags">): MuteItem[];
|
|
27
|
+
export declare function parseMuteList(event: Pick<Event, "kind" | "tags">): MuteItem[];
|
|
29
28
|
/** Build an unsigned kind:10000 EventBuilder from public mute items. */
|
|
30
|
-
declare function muteListEventBuilder(items:
|
|
29
|
+
export declare function muteListEventBuilder(items: ReadonlyArray<MuteItem>): EventBuilder;
|
|
31
30
|
/** NIP-44 ciphertext of tags, conversation peer = author's own pubkey. */
|
|
32
|
-
declare function encryptPrivateTags(crypto: Nip51Crypto, tags: Event["tags"]): Promise<string>;
|
|
31
|
+
export declare function encryptPrivateTags(crypto: Nip51Crypto, tags: Event["tags"]): Promise<string>;
|
|
33
32
|
/** Decrypt NIP-51 `.content`. Empty content is no private tags, not ciphertext. */
|
|
34
|
-
declare function decryptPrivateTags(crypto: Nip51Crypto, event: Pick<Event, "pubkey" | "content">): Promise<Event["tags"]>;
|
|
33
|
+
export declare function decryptPrivateTags(crypto: Nip51Crypto, event: Pick<Event, "pubkey" | "content">): Promise<Event["tags"]>;
|
|
35
34
|
/** Public mute items from tags; private mute items from NIP-44 `.content`. */
|
|
36
|
-
declare function parseMuteListPrivate(crypto: Nip51Crypto, event: Pick<Event, "kind" | "tags" | "pubkey" | "content">): Promise<{
|
|
35
|
+
export declare function parseMuteListPrivate(crypto: Nip51Crypto, event: Pick<Event, "kind" | "tags" | "pubkey" | "content">): Promise<{
|
|
37
36
|
public: MuteItem[];
|
|
38
37
|
private: MuteItem[];
|
|
39
38
|
}>;
|
|
40
39
|
/** Parse kind:10001 pin list public `e` tags. */
|
|
41
|
-
declare function parsePinList(event: Pick<Event, "kind" | "tags">): string[];
|
|
40
|
+
export declare function parsePinList(event: Pick<Event, "kind" | "tags">): string[];
|
|
42
41
|
/** Build an unsigned kind:10001 EventBuilder from event ids. */
|
|
43
|
-
declare function pinListEventBuilder(ids:
|
|
42
|
+
export declare function pinListEventBuilder(ids: ReadonlyArray<string>): EventBuilder;
|
|
44
43
|
/** Parse kind:10003 bookmark list public `e` and `a` tags. */
|
|
45
|
-
declare function parseBookmarkList(event: Pick<Event, "kind" | "tags">): {
|
|
44
|
+
export declare function parseBookmarkList(event: Pick<Event, "kind" | "tags">): {
|
|
46
45
|
e: string[];
|
|
47
46
|
a: string[];
|
|
48
47
|
};
|
|
49
48
|
/** Build an unsigned kind:10003 EventBuilder from public bookmark pointers. */
|
|
50
|
-
declare function bookmarkListEventBuilder(items: {
|
|
51
|
-
e?:
|
|
52
|
-
a?:
|
|
49
|
+
export declare function bookmarkListEventBuilder(items: {
|
|
50
|
+
e?: ReadonlyArray<string>;
|
|
51
|
+
a?: ReadonlyArray<string>;
|
|
53
52
|
}): EventBuilder;
|
|
54
53
|
/** Parse kind:10030 user emoji list public `emoji` and `a` tags. */
|
|
55
|
-
declare function parseUserEmojiList(event: Pick<Event, "kind" | "tags">): {
|
|
54
|
+
export declare function parseUserEmojiList(event: Pick<Event, "kind" | "tags">): {
|
|
56
55
|
emoji: Array<{
|
|
57
56
|
shortcode: string;
|
|
58
57
|
url: string;
|
|
@@ -60,17 +59,17 @@ declare function parseUserEmojiList(event: Pick<Event, "kind" | "tags">): {
|
|
|
60
59
|
sets: string[];
|
|
61
60
|
};
|
|
62
61
|
/** Parse kind:30002 relay set public `d` and `relay` tags. */
|
|
63
|
-
declare function parseRelaySet(event: Pick<Event, "kind" | "tags">): {
|
|
62
|
+
export declare function parseRelaySet(event: Pick<Event, "kind" | "tags">): {
|
|
64
63
|
d: string;
|
|
65
64
|
relays: string[];
|
|
66
65
|
};
|
|
67
66
|
/** Parse kind:10012 favorite relays public `relay` tags and kind:30002 `a` tags. */
|
|
68
|
-
declare function parseFavoriteRelays(event: Pick<Event, "kind" | "tags">): {
|
|
67
|
+
export declare function parseFavoriteRelays(event: Pick<Event, "kind" | "tags">): {
|
|
69
68
|
relays: string[];
|
|
70
69
|
sets: string[];
|
|
71
70
|
};
|
|
72
71
|
/** Parse kind:30030 emoji set public `d` / `title` / `emoji` tags. */
|
|
73
|
-
declare function parseEmojiSet(event: Pick<Event, "kind" | "tags">): {
|
|
72
|
+
export declare function parseEmojiSet(event: Pick<Event, "kind" | "tags">): {
|
|
74
73
|
d: string;
|
|
75
74
|
title?: string;
|
|
76
75
|
emoji: Array<{
|
|
@@ -79,10 +78,9 @@ declare function parseEmojiSet(event: Pick<Event, "kind" | "tags">): {
|
|
|
79
78
|
}>;
|
|
80
79
|
};
|
|
81
80
|
/** Parse kind:39089 starter pack (follow pack) public `d` and `p` tags. */
|
|
82
|
-
declare function parseFollowPack(event: Pick<Event, "kind" | "tags">): {
|
|
81
|
+
export declare function parseFollowPack(event: Pick<Event, "kind" | "tags">): {
|
|
83
82
|
d: string;
|
|
84
83
|
pubkeys: string[];
|
|
85
84
|
};
|
|
86
85
|
//#endregion
|
|
87
|
-
export { MuteItem, Nip51Crypto, bookmarkListEventBuilder, decryptPrivateTags, encryptPrivateTags, muteListEventBuilder, parseBookmarkList, parseEmojiSet, parseFavoriteRelays, parseFollowPack, parseMuteList, parseMuteListPrivate, parsePinList, parseRelaySet, parseUserEmojiList, pinListEventBuilder };
|
|
88
86
|
//# sourceMappingURL=nip51.d.mts.map
|
package/dist/nips/nip51.mjs
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { t as EventBuilder } from "../builder-q0VsvnYE.mjs";
|
|
1
|
+
import { l as normalizeURL, n as assertHex32, o as isHex32, p as EventValidationError } from "../util-LfQirgDk.mjs";
|
|
2
|
+
import { t as Kind } from "../kind-Cxwhhkx8.mjs";
|
|
3
|
+
import { h as Tag, v as getDTag, y as isTag } from "../event-D8Nfoh4K.mjs";
|
|
4
|
+
import { t as EventBuilder } from "../builder-CyrGKRVP.mjs";
|
|
6
5
|
//#region src/nips/nip51.ts
|
|
7
6
|
/**
|
|
8
|
-
* NIP-51: Lists
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* Kind 10063 Blossom servers live in blossom.ts.
|
|
7
|
+
* NIP-51: Lists Public tags plus NIP-44 private `.content` (author encrypts to self). Crypto is
|
|
8
|
+
* injected; this module does not import signer. No NIP-04 sniff. Kind 10063 Blossom servers live in
|
|
9
|
+
* blossom.ts.
|
|
12
10
|
*
|
|
13
11
|
* @see https://github.com/nostr-protocol/nips/blob/master/51.md
|
|
14
12
|
*/
|
|
@@ -19,10 +17,11 @@ function collectRelays(tags) {
|
|
|
19
17
|
const out = [];
|
|
20
18
|
const seen = /* @__PURE__ */ new Set();
|
|
21
19
|
for (const tag of tags) {
|
|
22
|
-
|
|
20
|
+
const value = tag.at(1);
|
|
21
|
+
if (tag[0] !== "relay" || value === void 0 || value === "") continue;
|
|
23
22
|
let url;
|
|
24
23
|
try {
|
|
25
|
-
url = normalizeURL(
|
|
24
|
+
url = normalizeURL(value);
|
|
26
25
|
} catch {
|
|
27
26
|
continue;
|
|
28
27
|
}
|
|
@@ -36,9 +35,9 @@ function collectEmoji(tags) {
|
|
|
36
35
|
const out = [];
|
|
37
36
|
for (const tag of tags) {
|
|
38
37
|
if (tag[0] !== "emoji") continue;
|
|
39
|
-
const shortcode = tag
|
|
40
|
-
const url = tag
|
|
41
|
-
if (
|
|
38
|
+
const shortcode = tag.at(1);
|
|
39
|
+
const url = tag.at(2);
|
|
40
|
+
if (shortcode === void 0 || shortcode === "" || url === void 0 || url === "") continue;
|
|
42
41
|
out.push({
|
|
43
42
|
shortcode,
|
|
44
43
|
url
|
|
@@ -47,7 +46,10 @@ function collectEmoji(tags) {
|
|
|
47
46
|
return out;
|
|
48
47
|
}
|
|
49
48
|
function firstTagValue(tags, name) {
|
|
50
|
-
for (const tag of tags)
|
|
49
|
+
for (const tag of tags) {
|
|
50
|
+
const value = tag.at(1);
|
|
51
|
+
if (tag[0] === name && value !== void 0 && value !== "") return value;
|
|
52
|
+
}
|
|
51
53
|
}
|
|
52
54
|
function identifier(tags) {
|
|
53
55
|
return getDTag(tags) ?? "";
|
|
@@ -55,30 +57,30 @@ function identifier(tags) {
|
|
|
55
57
|
/** NIP-51 a-tag: `30002:<64-hex-pubkey>:<d>` with `d` possibly containing `:`. */
|
|
56
58
|
function isRelaySetAddress(value) {
|
|
57
59
|
const kindSep = value.indexOf(":");
|
|
58
|
-
if (kindSep
|
|
60
|
+
if (kindSep === -1) return false;
|
|
59
61
|
if (value.slice(0, kindSep) !== String(Kind.RelaySets)) return false;
|
|
60
62
|
const pkSep = value.indexOf(":", kindSep + 1);
|
|
61
|
-
if (pkSep
|
|
63
|
+
if (pkSep === -1) return false;
|
|
62
64
|
const pubkey = value.slice(kindSep + 1, pkSep);
|
|
63
65
|
const d = value.slice(pkSep + 1);
|
|
64
|
-
return isHex32(pubkey) && d.length > 0;
|
|
66
|
+
return isHex32(pubkey.toLowerCase()) && d.length > 0;
|
|
65
67
|
}
|
|
66
68
|
/** Parse kind:10000 mute list public tags (`p` / `e` / `t` / `word`). */
|
|
67
69
|
function parseMuteList(event) {
|
|
68
70
|
requireKind(event, Kind.MuteList);
|
|
69
71
|
const items = [];
|
|
70
72
|
for (const tag of event.tags) {
|
|
71
|
-
const value = tag
|
|
72
|
-
if (
|
|
73
|
+
const value = tag.at(1);
|
|
74
|
+
if (value === void 0 || value === "") continue;
|
|
73
75
|
switch (tag[0]) {
|
|
74
76
|
case "p":
|
|
75
|
-
if (isHex32(value)) items.push({
|
|
77
|
+
if (isHex32(value.toLowerCase())) items.push({
|
|
76
78
|
type: "pubkey",
|
|
77
79
|
value: value.toLowerCase()
|
|
78
80
|
});
|
|
79
81
|
break;
|
|
80
82
|
case "e":
|
|
81
|
-
if (isHex32(value)) items.push({
|
|
83
|
+
if (isHex32(value.toLowerCase())) items.push({
|
|
82
84
|
type: "event",
|
|
83
85
|
value: value.toLowerCase()
|
|
84
86
|
});
|
|
@@ -128,8 +130,8 @@ async function decryptPrivateTags(crypto, event) {
|
|
|
128
130
|
let parsed;
|
|
129
131
|
try {
|
|
130
132
|
parsed = JSON.parse(plaintext);
|
|
131
|
-
} catch (
|
|
132
|
-
throw new EventValidationError("invalid NIP-51 private tags", { cause });
|
|
133
|
+
} catch (error) {
|
|
134
|
+
throw new EventValidationError("invalid NIP-51 private tags", { cause: error });
|
|
133
135
|
}
|
|
134
136
|
if (!Array.isArray(parsed) || !parsed.every(isTag)) throw new EventValidationError("invalid NIP-51 private tags");
|
|
135
137
|
return parsed;
|
|
@@ -151,8 +153,9 @@ function parsePinList(event) {
|
|
|
151
153
|
requireKind(event, Kind.PinList);
|
|
152
154
|
const ids = [];
|
|
153
155
|
for (const tag of event.tags) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
+
const value = tag.at(1);
|
|
157
|
+
if (tag[0] !== "e" || value === void 0 || !isHex32(value.toLowerCase())) continue;
|
|
158
|
+
ids.push(value.toLowerCase());
|
|
156
159
|
}
|
|
157
160
|
return ids;
|
|
158
161
|
}
|
|
@@ -168,10 +171,11 @@ function parseBookmarkList(event) {
|
|
|
168
171
|
const e = [];
|
|
169
172
|
const a = [];
|
|
170
173
|
for (const tag of event.tags) {
|
|
171
|
-
|
|
174
|
+
const value = tag.at(1);
|
|
175
|
+
if (value === void 0 || value === "") continue;
|
|
172
176
|
if (tag[0] === "e") {
|
|
173
|
-
if (isHex32(
|
|
174
|
-
} else if (tag[0] === "a") a.push(
|
|
177
|
+
if (isHex32(value.toLowerCase())) e.push(value.toLowerCase());
|
|
178
|
+
} else if (tag[0] === "a") a.push(value);
|
|
175
179
|
}
|
|
176
180
|
return {
|
|
177
181
|
e,
|
|
@@ -191,7 +195,10 @@ function bookmarkListEventBuilder(items) {
|
|
|
191
195
|
function parseUserEmojiList(event) {
|
|
192
196
|
requireKind(event, Kind.UserEmojiList);
|
|
193
197
|
const sets = [];
|
|
194
|
-
for (const tag of event.tags)
|
|
198
|
+
for (const tag of event.tags) {
|
|
199
|
+
const value = tag.at(1);
|
|
200
|
+
if (tag[0] === "a" && value !== void 0 && value !== "") sets.push(value);
|
|
201
|
+
}
|
|
195
202
|
return {
|
|
196
203
|
emoji: collectEmoji(event.tags),
|
|
197
204
|
sets
|
|
@@ -210,8 +217,9 @@ function parseFavoriteRelays(event) {
|
|
|
210
217
|
requireKind(event, Kind.FavoriteRelays);
|
|
211
218
|
const sets = [];
|
|
212
219
|
for (const tag of event.tags) {
|
|
213
|
-
|
|
214
|
-
|
|
220
|
+
const value = tag.at(1);
|
|
221
|
+
if (tag[0] !== "a" || value === void 0 || !isRelaySetAddress(value)) continue;
|
|
222
|
+
sets.push(value);
|
|
215
223
|
}
|
|
216
224
|
return {
|
|
217
225
|
relays: collectRelays(event.tags),
|
|
@@ -221,19 +229,22 @@ function parseFavoriteRelays(event) {
|
|
|
221
229
|
/** Parse kind:30030 emoji set public `d` / `title` / `emoji` tags. */
|
|
222
230
|
function parseEmojiSet(event) {
|
|
223
231
|
requireKind(event, Kind.EmojiSet);
|
|
224
|
-
|
|
232
|
+
const result = {
|
|
225
233
|
d: identifier(event.tags),
|
|
226
|
-
title: firstTagValue(event.tags, "title"),
|
|
227
234
|
emoji: collectEmoji(event.tags)
|
|
228
235
|
};
|
|
236
|
+
const title = firstTagValue(event.tags, "title");
|
|
237
|
+
if (title !== void 0) result.title = title;
|
|
238
|
+
return result;
|
|
229
239
|
}
|
|
230
240
|
/** Parse kind:39089 starter pack (follow pack) public `d` and `p` tags. */
|
|
231
241
|
function parseFollowPack(event) {
|
|
232
242
|
requireKind(event, Kind.StarterPack);
|
|
233
243
|
const pubkeys = [];
|
|
234
244
|
for (const tag of event.tags) {
|
|
235
|
-
|
|
236
|
-
|
|
245
|
+
const value = tag.at(1);
|
|
246
|
+
if (tag[0] !== "p" || value === void 0 || !isHex32(value.toLowerCase())) continue;
|
|
247
|
+
pubkeys.push(value.toLowerCase());
|
|
237
248
|
}
|
|
238
249
|
return {
|
|
239
250
|
d: identifier(event.tags),
|
package/dist/nips/nip51.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nip51.mjs","names":[],"sources":["../../src/nips/nip51.ts"],"sourcesContent":["/**\n * NIP-51: Lists\n * Public tags plus NIP-44 private `.content` (author encrypts to self).\n * Crypto is injected; this module does not import signer. No NIP-04 sniff.\n * Kind 10063 Blossom servers live in blossom.ts.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/51.md\n */\nimport { EventBuilder } from \"../core/builder.ts\";\nimport { EventValidationError } from \"../core/error.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { getDTag, isTag, Tag } from \"../core/tag.ts\";\nimport { assertHex32, isHex32, normalizeURL } from \"../core/util.ts\";\n\n/**\n * Structural crypto used by NIP-51 private tags.\n * Satisfied by NostrSigner when nip44Encrypt/nip44Decrypt are present.\n * This module must not import src/signer/.\n */\nexport type Nip51Crypto = {\n getPublicKey(): Promise<string>;\n nip44Encrypt(peer: string, plaintext: string): Promise<string>;\n nip44Decrypt(peer: string, payload: string): Promise<string>;\n};\n\nexport type MuteItem =\n | { type: \"pubkey\"; value: string }\n | { type: \"event\"; value: string }\n | { type: \"hashtag\"; value: string }\n | { type: \"word\"; value: string };\n\nfunction requireKind(event: Pick<Event, \"kind\">, kind: number): void {\n if (event.kind !== kind) {\n throw new EventValidationError(`expected kind ${kind}, got ${event.kind}`);\n }\n}\n\nfunction collectRelays(tags: Event[\"tags\"]): string[] {\n const out: string[] = [];\n const seen = new Set<string>();\n for (const tag of tags) {\n if (tag[0] !== \"relay\" || !tag[1]) continue;\n let url: string;\n try {\n url = normalizeURL(tag[1]);\n } catch {\n continue;\n }\n if (seen.has(url)) continue;\n seen.add(url);\n out.push(url);\n }\n return out;\n}\n\nfunction collectEmoji(tags: Event[\"tags\"]): Array<{ shortcode: string; url: string }> {\n const out: Array<{ shortcode: string; url: string }> = [];\n for (const tag of tags) {\n if (tag[0] !== \"emoji\") continue;\n const shortcode = tag[1];\n const url = tag[2];\n if (!shortcode || !url) continue;\n out.push({ shortcode, url });\n }\n return out;\n}\n\nfunction firstTagValue(tags: Event[\"tags\"], name: string): string | undefined {\n for (const tag of tags) {\n if (tag[0] === name && tag[1]) return tag[1];\n }\n return undefined;\n}\n\nfunction identifier(tags: Event[\"tags\"]): string {\n return getDTag(tags) ?? \"\";\n}\n\n/** NIP-51 a-tag: `30002:<64-hex-pubkey>:<d>` with `d` possibly containing `:`. */\nfunction isRelaySetAddress(value: string): boolean {\n const kindSep = value.indexOf(\":\");\n if (kindSep < 0) return false;\n if (value.slice(0, kindSep) !== String(Kind.RelaySets)) return false;\n const pkSep = value.indexOf(\":\", kindSep + 1);\n if (pkSep < 0) return false;\n const pubkey = value.slice(kindSep + 1, pkSep);\n const d = value.slice(pkSep + 1);\n return isHex32(pubkey) && d.length > 0;\n}\n\n/** Parse kind:10000 mute list public tags (`p` / `e` / `t` / `word`). */\nexport function parseMuteList(event: Pick<Event, \"kind\" | \"tags\">): MuteItem[] {\n requireKind(event, Kind.MuteList);\n const items: MuteItem[] = [];\n for (const tag of event.tags) {\n const value = tag[1];\n if (!value) continue;\n switch (tag[0]) {\n case \"p\":\n if (isHex32(value)) items.push({ type: \"pubkey\", value: value.toLowerCase() });\n break;\n case \"e\":\n if (isHex32(value)) items.push({ type: \"event\", value: value.toLowerCase() });\n break;\n case \"t\":\n // NIP-51 does not require hashtag case-folding.\n items.push({ type: \"hashtag\", value });\n break;\n case \"word\":\n items.push({ type: \"word\", value: value.toLowerCase() });\n break;\n }\n }\n return items;\n}\n\n/** Build an unsigned kind:10000 EventBuilder from public mute items. */\nexport function muteListEventBuilder(items: readonly MuteItem[]): EventBuilder {\n const b = new EventBuilder(Kind.MuteList, \"\");\n for (const item of items) {\n switch (item.type) {\n case \"pubkey\":\n b.tag(Tag.p(assertHex32(item.value, \"public key\")));\n break;\n case \"event\":\n b.tag(Tag.e(assertHex32(item.value, \"event id\")));\n break;\n case \"hashtag\":\n if (item.value) b.tag(Tag.t(item.value));\n break;\n case \"word\":\n if (item.value) b.tag([\"word\", item.value.toLowerCase()]);\n break;\n }\n }\n return b;\n}\n\n/** NIP-44 ciphertext of tags, conversation peer = author's own pubkey. */\nexport async function encryptPrivateTags(\n crypto: Nip51Crypto,\n tags: Event[\"tags\"],\n): Promise<string> {\n const self = await crypto.getPublicKey();\n return crypto.nip44Encrypt(self, JSON.stringify(tags));\n}\n\n/** Decrypt NIP-51 `.content`. Empty content is no private tags, not ciphertext. */\nexport async function decryptPrivateTags(\n crypto: Nip51Crypto,\n event: Pick<Event, \"pubkey\" | \"content\">,\n): Promise<Event[\"tags\"]> {\n // NIP-44 rejects empty plaintext; empty `.content` means no private items.\n if (event.content === \"\") return [];\n const self = await crypto.getPublicKey();\n if (self.toLowerCase() !== event.pubkey.toLowerCase()) {\n throw new EventValidationError(\"NIP-51 private content is only for the author\");\n }\n const plaintext = await crypto.nip44Decrypt(self, event.content);\n let parsed: unknown;\n try {\n parsed = JSON.parse(plaintext);\n } catch (cause) {\n throw new EventValidationError(\"invalid NIP-51 private tags\", { cause });\n }\n if (!Array.isArray(parsed) || !parsed.every(isTag)) {\n throw new EventValidationError(\"invalid NIP-51 private tags\");\n }\n return parsed;\n}\n\n/** Public mute items from tags; private mute items from NIP-44 `.content`. */\nexport async function parseMuteListPrivate(\n crypto: Nip51Crypto,\n event: Pick<Event, \"kind\" | \"tags\" | \"pubkey\" | \"content\">,\n): Promise<{ public: MuteItem[]; private: MuteItem[] }> {\n const pub = parseMuteList(event);\n const decrypted = await decryptPrivateTags(crypto, event);\n return {\n public: pub,\n private: parseMuteList({ kind: event.kind, tags: decrypted }),\n };\n}\n\n/** Parse kind:10001 pin list public `e` tags. */\nexport function parsePinList(event: Pick<Event, \"kind\" | \"tags\">): string[] {\n requireKind(event, Kind.PinList);\n const ids: string[] = [];\n for (const tag of event.tags) {\n if (tag[0] !== \"e\" || !tag[1] || !isHex32(tag[1])) continue;\n ids.push(tag[1].toLowerCase());\n }\n return ids;\n}\n\n/** Build an unsigned kind:10001 EventBuilder from event ids. */\nexport function pinListEventBuilder(ids: readonly string[]): EventBuilder {\n const b = new EventBuilder(Kind.PinList, \"\");\n for (const id of ids) b.tag(Tag.e(assertHex32(id, \"event id\")));\n return b;\n}\n\n/** Parse kind:10003 bookmark list public `e` and `a` tags. */\nexport function parseBookmarkList(event: Pick<Event, \"kind\" | \"tags\">): {\n e: string[];\n a: string[];\n} {\n requireKind(event, Kind.BookmarkList);\n const e: string[] = [];\n const a: string[] = [];\n for (const tag of event.tags) {\n if (!tag[1]) continue;\n if (tag[0] === \"e\") {\n if (isHex32(tag[1])) e.push(tag[1].toLowerCase());\n } else if (tag[0] === \"a\") {\n a.push(tag[1]);\n }\n }\n return { e, a };\n}\n\n/** Build an unsigned kind:10003 EventBuilder from public bookmark pointers. */\nexport function bookmarkListEventBuilder(items: {\n e?: readonly string[];\n a?: readonly string[];\n}): EventBuilder {\n const b = new EventBuilder(Kind.BookmarkList, \"\");\n if (items.e) {\n for (const id of items.e) b.tag(Tag.e(assertHex32(id, \"event id\")));\n }\n if (items.a) {\n for (const coord of items.a) {\n if (coord) b.tag(Tag.a(coord));\n }\n }\n return b;\n}\n\n/** Parse kind:10030 user emoji list public `emoji` and `a` tags. */\nexport function parseUserEmojiList(event: Pick<Event, \"kind\" | \"tags\">): {\n emoji: Array<{ shortcode: string; url: string }>;\n sets: string[];\n} {\n requireKind(event, Kind.UserEmojiList);\n const sets: string[] = [];\n for (const tag of event.tags) {\n if (tag[0] === \"a\" && tag[1]) sets.push(tag[1]);\n }\n return { emoji: collectEmoji(event.tags), sets };\n}\n\n/** Parse kind:30002 relay set public `d` and `relay` tags. */\nexport function parseRelaySet(event: Pick<Event, \"kind\" | \"tags\">): {\n d: string;\n relays: string[];\n} {\n requireKind(event, Kind.RelaySets);\n return { d: identifier(event.tags), relays: collectRelays(event.tags) };\n}\n\n/** Parse kind:10012 favorite relays public `relay` tags and kind:30002 `a` tags. */\nexport function parseFavoriteRelays(event: Pick<Event, \"kind\" | \"tags\">): {\n relays: string[];\n sets: string[];\n} {\n requireKind(event, Kind.FavoriteRelays);\n const sets: string[] = [];\n for (const tag of event.tags) {\n if (tag[0] !== \"a\" || !tag[1] || !isRelaySetAddress(tag[1])) continue;\n sets.push(tag[1]);\n }\n return { relays: collectRelays(event.tags), sets };\n}\n\n/** Parse kind:30030 emoji set public `d` / `title` / `emoji` tags. */\nexport function parseEmojiSet(event: Pick<Event, \"kind\" | \"tags\">): {\n d: string;\n title?: string;\n emoji: Array<{ shortcode: string; url: string }>;\n} {\n requireKind(event, Kind.EmojiSet);\n return {\n d: identifier(event.tags),\n title: firstTagValue(event.tags, \"title\"),\n emoji: collectEmoji(event.tags),\n };\n}\n\n/** Parse kind:39089 starter pack (follow pack) public `d` and `p` tags. */\nexport function parseFollowPack(event: Pick<Event, \"kind\" | \"tags\">): {\n d: string;\n pubkeys: string[];\n} {\n requireKind(event, Kind.StarterPack);\n const pubkeys: string[] = [];\n for (const tag of event.tags) {\n if (tag[0] !== \"p\" || !tag[1] || !isHex32(tag[1])) continue;\n pubkeys.push(tag[1].toLowerCase());\n }\n return { d: identifier(event.tags), pubkeys };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"nip51.mjs","names":[],"sources":["../../src/nips/nip51.ts"],"sourcesContent":["/**\n * NIP-51: Lists Public tags plus NIP-44 private `.content` (author encrypts to self). Crypto is\n * injected; this module does not import signer. No NIP-04 sniff. Kind 10063 Blossom servers live in\n * blossom.ts.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/51.md\n */\nimport { EventBuilder } from \"../core/builder.ts\";\nimport { EventValidationError } from \"../core/error.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { getDTag, isTag, Tag } from \"../core/tag.ts\";\nimport { assertHex32, isHex32, normalizeURL } from \"../core/util.ts\";\n\n/**\n * Structural crypto used by NIP-51 private tags. Satisfied by NostrSigner when\n * nip44Encrypt/nip44Decrypt are present. This module must not import src/signer/.\n */\nexport type Nip51Crypto = {\n getPublicKey: () => Promise<string>;\n nip44Encrypt: (peer: string, plaintext: string) => Promise<string>;\n nip44Decrypt: (peer: string, payload: string) => Promise<string>;\n};\n\nexport type MuteItem =\n | { type: \"pubkey\"; value: string }\n | { type: \"event\"; value: string }\n | { type: \"hashtag\"; value: string }\n | { type: \"word\"; value: string };\n\nfunction requireKind(event: Pick<Event, \"kind\">, kind: number): void {\n if (event.kind !== kind) {\n throw new EventValidationError(`expected kind ${kind}, got ${event.kind}`);\n }\n}\n\nfunction collectRelays(tags: Event[\"tags\"]): string[] {\n const out: string[] = [];\n const seen = new Set<string>();\n for (const tag of tags) {\n const value = tag.at(1);\n if (tag[0] !== \"relay\" || value === undefined || value === \"\") {\n continue;\n }\n let url: string;\n try {\n url = normalizeURL(value);\n } catch {\n continue;\n }\n if (seen.has(url)) {\n continue;\n }\n seen.add(url);\n out.push(url);\n }\n return out;\n}\n\nfunction collectEmoji(tags: Event[\"tags\"]): Array<{ shortcode: string; url: string }> {\n const out: Array<{ shortcode: string; url: string }> = [];\n for (const tag of tags) {\n if (tag[0] !== \"emoji\") {\n continue;\n }\n const shortcode = tag.at(1);\n const url = tag.at(2);\n if (shortcode === undefined || shortcode === \"\" || url === undefined || url === \"\") {\n continue;\n }\n out.push({ shortcode, url });\n }\n return out;\n}\n\nfunction firstTagValue(tags: Event[\"tags\"], name: string): string | undefined {\n for (const tag of tags) {\n const value = tag.at(1);\n if (tag[0] === name && value !== undefined && value !== \"\") {\n return value;\n }\n }\n return undefined;\n}\n\nfunction identifier(tags: Event[\"tags\"]): string {\n return getDTag(tags) ?? \"\";\n}\n\n/** NIP-51 a-tag: `30002:<64-hex-pubkey>:<d>` with `d` possibly containing `:`. */\nfunction isRelaySetAddress(value: string): boolean {\n const kindSep = value.indexOf(\":\");\n if (kindSep === -1) {\n return false;\n }\n if (value.slice(0, kindSep) !== String(Kind.RelaySets)) {\n return false;\n }\n const pkSep = value.indexOf(\":\", kindSep + 1);\n if (pkSep === -1) {\n return false;\n }\n const pubkey = value.slice(kindSep + 1, pkSep);\n const d = value.slice(pkSep + 1);\n return isHex32(pubkey.toLowerCase()) && d.length > 0;\n}\n\n/** Parse kind:10000 mute list public tags (`p` / `e` / `t` / `word`). */\nexport function parseMuteList(event: Pick<Event, \"kind\" | \"tags\">): MuteItem[] {\n requireKind(event, Kind.MuteList);\n const items: MuteItem[] = [];\n for (const tag of event.tags) {\n const value = tag.at(1);\n if (value === undefined || value === \"\") {\n continue;\n }\n switch (tag[0]) {\n case \"p\":\n if (isHex32(value.toLowerCase())) {\n items.push({ type: \"pubkey\", value: value.toLowerCase() });\n }\n break;\n case \"e\":\n if (isHex32(value.toLowerCase())) {\n items.push({ type: \"event\", value: value.toLowerCase() });\n }\n break;\n case \"t\":\n // NIP-51 does not require hashtag case-folding.\n items.push({ type: \"hashtag\", value });\n break;\n case \"word\":\n items.push({ type: \"word\", value: value.toLowerCase() });\n break;\n default:\n break;\n }\n }\n return items;\n}\n\n/** Build an unsigned kind:10000 EventBuilder from public mute items. */\nexport function muteListEventBuilder(items: ReadonlyArray<MuteItem>): EventBuilder {\n const b = new EventBuilder(Kind.MuteList, \"\");\n for (const item of items) {\n switch (item.type) {\n case \"pubkey\":\n b.tag(Tag.p(assertHex32(item.value, \"public key\")));\n break;\n case \"event\":\n b.tag(Tag.e(assertHex32(item.value, \"event id\")));\n break;\n case \"hashtag\":\n if (item.value) {\n b.tag(Tag.t(item.value));\n }\n break;\n case \"word\":\n if (item.value) {\n b.tag([\"word\", item.value.toLowerCase()]);\n }\n break;\n }\n }\n return b;\n}\n\n/** NIP-44 ciphertext of tags, conversation peer = author's own pubkey. */\nexport async function encryptPrivateTags(\n crypto: Nip51Crypto,\n tags: Event[\"tags\"],\n): Promise<string> {\n const self = await crypto.getPublicKey();\n return crypto.nip44Encrypt(self, JSON.stringify(tags));\n}\n\n/** Decrypt NIP-51 `.content`. Empty content is no private tags, not ciphertext. */\nexport async function decryptPrivateTags(\n crypto: Nip51Crypto,\n event: Pick<Event, \"pubkey\" | \"content\">,\n): Promise<Event[\"tags\"]> {\n // NIP-44 rejects empty plaintext; empty `.content` means no private items.\n if (event.content === \"\") {\n return [];\n }\n const self = await crypto.getPublicKey();\n if (self.toLowerCase() !== event.pubkey.toLowerCase()) {\n throw new EventValidationError(\"NIP-51 private content is only for the author\");\n }\n const plaintext = await crypto.nip44Decrypt(self, event.content);\n let parsed: unknown;\n try {\n parsed = JSON.parse(plaintext);\n } catch (error) {\n throw new EventValidationError(\"invalid NIP-51 private tags\", { cause: error });\n }\n if (!Array.isArray(parsed) || !parsed.every(isTag)) {\n throw new EventValidationError(\"invalid NIP-51 private tags\");\n }\n return parsed;\n}\n\n/** Public mute items from tags; private mute items from NIP-44 `.content`. */\nexport async function parseMuteListPrivate(\n crypto: Nip51Crypto,\n event: Pick<Event, \"kind\" | \"tags\" | \"pubkey\" | \"content\">,\n): Promise<{ public: MuteItem[]; private: MuteItem[] }> {\n const pub = parseMuteList(event);\n const decrypted = await decryptPrivateTags(crypto, event);\n return {\n public: pub,\n private: parseMuteList({ kind: event.kind, tags: decrypted }),\n };\n}\n\n/** Parse kind:10001 pin list public `e` tags. */\nexport function parsePinList(event: Pick<Event, \"kind\" | \"tags\">): string[] {\n requireKind(event, Kind.PinList);\n const ids: string[] = [];\n for (const tag of event.tags) {\n const value = tag.at(1);\n if (tag[0] !== \"e\" || value === undefined || !isHex32(value.toLowerCase())) {\n continue;\n }\n ids.push(value.toLowerCase());\n }\n return ids;\n}\n\n/** Build an unsigned kind:10001 EventBuilder from event ids. */\nexport function pinListEventBuilder(ids: ReadonlyArray<string>): EventBuilder {\n const b = new EventBuilder(Kind.PinList, \"\");\n for (const id of ids) {\n b.tag(Tag.e(assertHex32(id, \"event id\")));\n }\n return b;\n}\n\n/** Parse kind:10003 bookmark list public `e` and `a` tags. */\nexport function parseBookmarkList(event: Pick<Event, \"kind\" | \"tags\">): {\n e: string[];\n a: string[];\n} {\n requireKind(event, Kind.BookmarkList);\n const e: string[] = [];\n const a: string[] = [];\n for (const tag of event.tags) {\n const value = tag.at(1);\n if (value === undefined || value === \"\") {\n continue;\n }\n if (tag[0] === \"e\") {\n if (isHex32(value.toLowerCase())) {\n e.push(value.toLowerCase());\n }\n } else if (tag[0] === \"a\") {\n a.push(value);\n }\n }\n return { e, a };\n}\n\n/** Build an unsigned kind:10003 EventBuilder from public bookmark pointers. */\nexport function bookmarkListEventBuilder(items: {\n e?: ReadonlyArray<string>;\n a?: ReadonlyArray<string>;\n}): EventBuilder {\n const b = new EventBuilder(Kind.BookmarkList, \"\");\n if (items.e) {\n for (const id of items.e) {\n b.tag(Tag.e(assertHex32(id, \"event id\")));\n }\n }\n if (items.a) {\n for (const coord of items.a) {\n if (coord) {\n b.tag(Tag.a(coord));\n }\n }\n }\n return b;\n}\n\n/** Parse kind:10030 user emoji list public `emoji` and `a` tags. */\nexport function parseUserEmojiList(event: Pick<Event, \"kind\" | \"tags\">): {\n emoji: Array<{ shortcode: string; url: string }>;\n sets: string[];\n} {\n requireKind(event, Kind.UserEmojiList);\n const sets: string[] = [];\n for (const tag of event.tags) {\n const value = tag.at(1);\n if (tag[0] === \"a\" && value !== undefined && value !== \"\") {\n sets.push(value);\n }\n }\n return { emoji: collectEmoji(event.tags), sets };\n}\n\n/** Parse kind:30002 relay set public `d` and `relay` tags. */\nexport function parseRelaySet(event: Pick<Event, \"kind\" | \"tags\">): {\n d: string;\n relays: string[];\n} {\n requireKind(event, Kind.RelaySets);\n return { d: identifier(event.tags), relays: collectRelays(event.tags) };\n}\n\n/** Parse kind:10012 favorite relays public `relay` tags and kind:30002 `a` tags. */\nexport function parseFavoriteRelays(event: Pick<Event, \"kind\" | \"tags\">): {\n relays: string[];\n sets: string[];\n} {\n requireKind(event, Kind.FavoriteRelays);\n const sets: string[] = [];\n for (const tag of event.tags) {\n const value = tag.at(1);\n if (tag[0] !== \"a\" || value === undefined || !isRelaySetAddress(value)) {\n continue;\n }\n sets.push(value);\n }\n return { relays: collectRelays(event.tags), sets };\n}\n\n/** Parse kind:30030 emoji set public `d` / `title` / `emoji` tags. */\nexport function parseEmojiSet(event: Pick<Event, \"kind\" | \"tags\">): {\n d: string;\n title?: string;\n emoji: Array<{ shortcode: string; url: string }>;\n} {\n requireKind(event, Kind.EmojiSet);\n const result: {\n d: string;\n title?: string;\n emoji: Array<{ shortcode: string; url: string }>;\n } = { d: identifier(event.tags), emoji: collectEmoji(event.tags) };\n const title = firstTagValue(event.tags, \"title\");\n if (title !== undefined) {\n result.title = title;\n }\n return result;\n}\n\n/** Parse kind:39089 starter pack (follow pack) public `d` and `p` tags. */\nexport function parseFollowPack(event: Pick<Event, \"kind\" | \"tags\">): {\n d: string;\n pubkeys: string[];\n} {\n requireKind(event, Kind.StarterPack);\n const pubkeys: string[] = [];\n for (const tag of event.tags) {\n const value = tag.at(1);\n if (tag[0] !== \"p\" || value === undefined || !isHex32(value.toLowerCase())) {\n continue;\n }\n pubkeys.push(value.toLowerCase());\n }\n return { d: identifier(event.tags), pubkeys };\n}\n"],"mappings":"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"}
|
package/dist/nips/nip57.d.mts
CHANGED
|
@@ -1,43 +1,42 @@
|
|
|
1
|
-
import { n as EventTemplate, t as Event } from "../event-
|
|
1
|
+
import { n as EventTemplate, t as Event } from "../event-fU3Y01PR.mjs";
|
|
2
2
|
//#region src/nips/nip57.d.ts
|
|
3
|
-
type ProfileZapRequest = {
|
|
3
|
+
export type ProfileZapRequest = {
|
|
4
4
|
pubkey: string;
|
|
5
5
|
/** Amount in millisats. */
|
|
6
6
|
amount: number;
|
|
7
|
-
relays:
|
|
7
|
+
relays: ReadonlyArray<string>;
|
|
8
8
|
comment?: string;
|
|
9
9
|
lnurl?: string;
|
|
10
10
|
};
|
|
11
|
-
type EventZapRequest = {
|
|
11
|
+
export type EventZapRequest = {
|
|
12
12
|
event: Event;
|
|
13
13
|
/** Amount in millisats. */
|
|
14
14
|
amount: number;
|
|
15
|
-
relays:
|
|
15
|
+
relays: ReadonlyArray<string>;
|
|
16
16
|
comment?: string;
|
|
17
17
|
lnurl?: string;
|
|
18
18
|
};
|
|
19
|
-
declare function makeZapRequest(params: ProfileZapRequest | EventZapRequest): EventTemplate;
|
|
20
|
-
type ZapReceiptContext = {
|
|
19
|
+
export declare function makeZapRequest(params: ProfileZapRequest | EventZapRequest): EventTemplate;
|
|
20
|
+
export type ZapReceiptContext = {
|
|
21
21
|
nostrPubkey: string;
|
|
22
22
|
lnurl?: string;
|
|
23
23
|
};
|
|
24
|
-
type ZapReceiptValidation = {
|
|
24
|
+
export type ZapReceiptValidation = {
|
|
25
25
|
valid: boolean;
|
|
26
26
|
reason?: string;
|
|
27
27
|
request?: Event;
|
|
28
28
|
amountMsats?: number;
|
|
29
29
|
};
|
|
30
|
-
type Bolt11Fields = {
|
|
30
|
+
export type Bolt11Fields = {
|
|
31
31
|
amountMsats?: number;
|
|
32
32
|
descriptionHash?: Uint8Array;
|
|
33
33
|
paymentHash?: Uint8Array;
|
|
34
34
|
};
|
|
35
35
|
/** Decode a BOLT11 invoice. Never throws. Requires a 32-byte payment hash (type 1). */
|
|
36
|
-
declare function parseBolt11(pr: string): Bolt11Fields | undefined;
|
|
36
|
+
export declare function parseBolt11(pr: string): Bolt11Fields | undefined;
|
|
37
37
|
/** Parse the `description` tag of a kind 9735 receipt as a signed kind 9734. Never throws. */
|
|
38
|
-
declare function parseZapRequestFromReceipt(receipt: Event): Event | undefined;
|
|
38
|
+
export declare function parseZapRequestFromReceipt(receipt: Event): Event | undefined;
|
|
39
39
|
/** Validate a kind 9735 zap receipt. Never throws. */
|
|
40
|
-
declare function validateZapReceipt(receipt: Event, ctx: ZapReceiptContext): ZapReceiptValidation;
|
|
40
|
+
export declare function validateZapReceipt(receipt: Event, ctx: ZapReceiptContext): ZapReceiptValidation;
|
|
41
41
|
//#endregion
|
|
42
|
-
export { Bolt11Fields, EventZapRequest, ProfileZapRequest, ZapReceiptContext, ZapReceiptValidation, makeZapRequest, parseBolt11, parseZapRequestFromReceipt, validateZapReceipt };
|
|
43
42
|
//# sourceMappingURL=nip57.d.mts.map
|
package/dist/nips/nip57.mjs
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { s as verifyEvent } from "../key-Dn-aX4x4.mjs";
|
|
1
|
+
import { a as hexToBytes, d as utf8Encoder, p as EventValidationError } from "../util-LfQirgDk.mjs";
|
|
2
|
+
import { r as isAddressableKind, t as Kind } from "../kind-Cxwhhkx8.mjs";
|
|
3
|
+
import { b as parseEventAddress, g as eventAddress, h as Tag, m as validateSignedEvent, v as getDTag } from "../event-D8Nfoh4K.mjs";
|
|
4
|
+
import { s as verifyEvent } from "../key-Bbtys6o7.mjs";
|
|
6
5
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
7
6
|
import { bech32 } from "@scure/base";
|
|
7
|
+
import { equalBytes } from "@noble/ciphers/utils.js";
|
|
8
8
|
//#region src/nips/nip57.ts
|
|
9
9
|
/**
|
|
10
|
-
* NIP-57 Lightning Zaps — zap request template (kind 9734) and receipt validation (kind 9735).
|
|
11
|
-
*
|
|
10
|
+
* NIP-57 Lightning Zaps — zap request template (kind 9734) and receipt validation (kind 9735). Does
|
|
11
|
+
* not fetch LNURL. Receipt checks never throw.
|
|
12
12
|
*
|
|
13
13
|
* @see https://github.com/nostr-protocol/nips/blob/master/57.md
|
|
14
14
|
*/
|
|
@@ -26,11 +26,11 @@ function makeZapRequest(params) {
|
|
|
26
26
|
if (isAddressableKind(event.kind)) {
|
|
27
27
|
if (getDTag(event.tags) === void 0) throw new EventValidationError("d tag not found");
|
|
28
28
|
const addr = eventAddress(event);
|
|
29
|
-
if (addr) tags.push(["a", addr]);
|
|
29
|
+
if (addr !== void 0) tags.push(["a", addr]);
|
|
30
30
|
}
|
|
31
31
|
tags.push(["k", event.kind.toString()]);
|
|
32
32
|
}
|
|
33
|
-
if (params.lnurl) tags.push(["lnurl", params.lnurl]);
|
|
33
|
+
if (params.lnurl !== void 0 && params.lnurl !== "") tags.push(["lnurl", params.lnurl]);
|
|
34
34
|
return {
|
|
35
35
|
kind: Kind.ZapRequest,
|
|
36
36
|
created_at: Math.floor(Date.now() / 1e3),
|
|
@@ -41,12 +41,6 @@ function makeZapRequest(params) {
|
|
|
41
41
|
function firstTagValue(tags, name) {
|
|
42
42
|
for (const tag of tags) if (tag[0] === name && tag[1] !== void 0) return tag[1];
|
|
43
43
|
}
|
|
44
|
-
function bytesEq(a, b) {
|
|
45
|
-
if (a.length !== b.length) return false;
|
|
46
|
-
let x = 0;
|
|
47
|
-
for (let i = 0; i < a.length; i++) x |= a[i] ^ b[i];
|
|
48
|
-
return x === 0;
|
|
49
|
-
}
|
|
50
44
|
function fail(reason) {
|
|
51
45
|
return {
|
|
52
46
|
valid: false,
|
|
@@ -59,29 +53,29 @@ const MSATS_PER_MICRO = 1e5;
|
|
|
59
53
|
const MSATS_PER_NANO = 100;
|
|
60
54
|
/** Amount is digits after `ln` + currency letters, optional m/u/n/p. Non-integer pico is omitted. */
|
|
61
55
|
function amountMsatsFromHrp(hrp) {
|
|
62
|
-
if (!hrp.startsWith("ln")) return
|
|
56
|
+
if (!hrp.startsWith("ln")) return;
|
|
63
57
|
let i = 2;
|
|
64
58
|
while (i < hrp.length) {
|
|
65
|
-
const c = hrp.
|
|
66
|
-
if (c < 97 || c > 122) break;
|
|
59
|
+
const c = hrp.codePointAt(i);
|
|
60
|
+
if (c === void 0 || c < 97 || c > 122) break;
|
|
67
61
|
i++;
|
|
68
62
|
}
|
|
69
|
-
if (i === hrp.length) return
|
|
63
|
+
if (i === hrp.length) return;
|
|
70
64
|
const rest = hrp.slice(i);
|
|
71
65
|
const m = /^([0-9]+)([munp])?$/.exec(rest);
|
|
72
|
-
if (!m) return
|
|
66
|
+
if (!m) return;
|
|
73
67
|
const n = Number(m[1]);
|
|
74
|
-
if (!Number.isSafeInteger(n)) return
|
|
75
|
-
const mul = m
|
|
68
|
+
if (!Number.isSafeInteger(n)) return;
|
|
69
|
+
const mul = m.at(2);
|
|
76
70
|
if (mul === void 0) return n * MSATS_PER_BTC;
|
|
77
71
|
if (mul === "m") return n * MSATS_PER_MILLI;
|
|
78
72
|
if (mul === "u") return n * MSATS_PER_MICRO;
|
|
79
73
|
if (mul === "n") return n * MSATS_PER_NANO;
|
|
80
|
-
if (n % 10 !== 0) return
|
|
74
|
+
if (n % 10 !== 0) return;
|
|
81
75
|
return n / 10;
|
|
82
76
|
}
|
|
83
77
|
function parseMsatsTag(value) {
|
|
84
|
-
if (!/^[0-9]+$/.test(value)) return
|
|
78
|
+
if (!/^[0-9]+$/.test(value)) return;
|
|
85
79
|
const n = Number(value);
|
|
86
80
|
return Number.isSafeInteger(n) ? n : void 0;
|
|
87
81
|
}
|
|
@@ -113,8 +107,8 @@ const HASH_BYTES = 32;
|
|
|
113
107
|
function parseBolt11(pr) {
|
|
114
108
|
try {
|
|
115
109
|
const { prefix, words } = bech32.decode(pr.toLowerCase(), false);
|
|
116
|
-
if (!prefix.startsWith("ln")) return
|
|
117
|
-
if (words.length < 111) return
|
|
110
|
+
if (!prefix.startsWith("ln")) return;
|
|
111
|
+
if (words.length < 111) return;
|
|
118
112
|
const fields = {};
|
|
119
113
|
const amountMsats = amountMsatsFromHrp(prefix);
|
|
120
114
|
if (amountMsats !== void 0) fields.amountMsats = amountMsats;
|
|
@@ -122,7 +116,10 @@ function parseBolt11(pr) {
|
|
|
122
116
|
let i = BOLT11_TIMESTAMP_WORDS;
|
|
123
117
|
while (i + 3 <= tlvEnd) {
|
|
124
118
|
const type = words[i];
|
|
125
|
-
const
|
|
119
|
+
const lenHigh = words[i + 1];
|
|
120
|
+
const lenLow = words[i + 2];
|
|
121
|
+
if (type === void 0 || lenHigh === void 0 || lenLow === void 0) break;
|
|
122
|
+
const dataLen = lenHigh * 32 + lenLow;
|
|
126
123
|
i += 3;
|
|
127
124
|
if (i + dataLen > tlvEnd) break;
|
|
128
125
|
const data = words.slice(i, i + dataLen);
|
|
@@ -133,7 +130,7 @@ function parseBolt11(pr) {
|
|
|
133
130
|
if (type === 1) fields.paymentHash ??= bytes;
|
|
134
131
|
else fields.descriptionHash ??= bytes;
|
|
135
132
|
}
|
|
136
|
-
if (!fields.paymentHash) return
|
|
133
|
+
if (!fields.paymentHash) return;
|
|
137
134
|
return fields;
|
|
138
135
|
} catch {
|
|
139
136
|
return;
|
|
@@ -143,10 +140,10 @@ function parseBolt11(pr) {
|
|
|
143
140
|
function parseZapRequestFromReceipt(receipt) {
|
|
144
141
|
try {
|
|
145
142
|
const raw = firstTagValue(receipt.tags, "description");
|
|
146
|
-
if (raw === void 0) return
|
|
143
|
+
if (raw === void 0) return;
|
|
147
144
|
const parsed = JSON.parse(raw);
|
|
148
|
-
if (!validateSignedEvent(parsed) || parsed.kind !== Kind.ZapRequest) return
|
|
149
|
-
if (!verifyEvent(parsed)) return
|
|
145
|
+
if (!validateSignedEvent(parsed) || parsed.kind !== Kind.ZapRequest) return;
|
|
146
|
+
if (!verifyEvent(parsed)) return;
|
|
150
147
|
return parsed;
|
|
151
148
|
} catch {
|
|
152
149
|
return;
|
|
@@ -162,7 +159,8 @@ function validateZapReceipt(receipt, ctx) {
|
|
|
162
159
|
if (descriptionRaw === void 0 || !request) return fail("invalid zap request");
|
|
163
160
|
if (countTags(request.tags, "p") !== 1) return fail("invalid p count");
|
|
164
161
|
if (countTags(request.tags, "e") > 1) return fail("too many e tags");
|
|
165
|
-
|
|
162
|
+
const relaysValue = firstTagValue(request.tags, "relays");
|
|
163
|
+
if (relaysValue === void 0 || relaysValue === "") return fail("missing relays");
|
|
166
164
|
for (const tag of request.tags) {
|
|
167
165
|
if (tag[0] !== "a") continue;
|
|
168
166
|
if (tag[1] === void 0) return fail("invalid a");
|
|
@@ -184,11 +182,10 @@ function validateZapReceipt(receipt, ctx) {
|
|
|
184
182
|
const msats = parseMsatsTag(requestAmount);
|
|
185
183
|
if (msats === void 0 || bolt11.amountMsats !== msats) return fail("amount mismatch");
|
|
186
184
|
}
|
|
187
|
-
|
|
185
|
+
const digest = sha256(utf8Encoder.encode(descriptionRaw));
|
|
186
|
+
if (!equalBytes(digest, bolt11.descriptionHash)) return fail("description hash mismatch");
|
|
188
187
|
const requestLnurl = firstTagValue(request.tags, "lnurl");
|
|
189
|
-
if (requestLnurl !== void 0 && ctx.lnurl !== void 0)
|
|
190
|
-
if (requestLnurl.toLowerCase() !== ctx.lnurl.toLowerCase()) return fail("lnurl mismatch");
|
|
191
|
-
}
|
|
188
|
+
if (requestLnurl !== void 0 && ctx.lnurl !== void 0 && requestLnurl.toLowerCase() !== ctx.lnurl.toLowerCase()) return fail("lnurl mismatch");
|
|
192
189
|
const preimageHex = firstTagValue(receipt.tags, "preimage");
|
|
193
190
|
if (preimageHex !== void 0 && bolt11.paymentHash) {
|
|
194
191
|
let preimage;
|
|
@@ -197,7 +194,7 @@ function validateZapReceipt(receipt, ctx) {
|
|
|
197
194
|
} catch {
|
|
198
195
|
return fail("preimage mismatch");
|
|
199
196
|
}
|
|
200
|
-
if (!
|
|
197
|
+
if (!equalBytes(sha256(preimage), bolt11.paymentHash)) return fail("preimage mismatch");
|
|
201
198
|
}
|
|
202
199
|
const recipient = firstTagValue(request.tags, "p");
|
|
203
200
|
if (recipient === void 0 || !hasHexTagValue(receipt.tags, "p", recipient)) return fail("missing p");
|
|
@@ -211,7 +208,7 @@ function validateZapReceipt(receipt, ctx) {
|
|
|
211
208
|
if (tag[0] !== "P") continue;
|
|
212
209
|
if (tag[1] === void 0 || tag[1].toLowerCase() !== request.pubkey.toLowerCase()) return fail("receipt P mismatch");
|
|
213
210
|
}
|
|
214
|
-
const amountMsats = bolt11.amountMsats ?? (requestAmount
|
|
211
|
+
const amountMsats = bolt11.amountMsats ?? (requestAmount === void 0 ? void 0 : parseMsatsTag(requestAmount));
|
|
215
212
|
return amountMsats === void 0 ? {
|
|
216
213
|
valid: true,
|
|
217
214
|
request
|