@actagent/nostr 2026.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +142 -0
  2. package/actagent.plugin.json +17 -0
  3. package/api.ts +11 -0
  4. package/channel-plugin-api.ts +2 -0
  5. package/doctor-contract-api.test.ts +105 -0
  6. package/doctor-contract-api.ts +297 -0
  7. package/index.ts +96 -0
  8. package/npm-shrinkwrap.json +137 -0
  9. package/package.json +67 -0
  10. package/runtime-api.ts +6 -0
  11. package/setup-api.ts +2 -0
  12. package/setup-entry.ts +10 -0
  13. package/setup-plugin-api.ts +3 -0
  14. package/src/channel-api.ts +12 -0
  15. package/src/channel.inbound.test.ts +203 -0
  16. package/src/channel.lifecycle.test.ts +97 -0
  17. package/src/channel.outbound.test.ts +175 -0
  18. package/src/channel.setup.ts +161 -0
  19. package/src/channel.test.ts +527 -0
  20. package/src/channel.ts +215 -0
  21. package/src/config-schema.ts +99 -0
  22. package/src/default-relays.ts +2 -0
  23. package/src/gateway.ts +338 -0
  24. package/src/inbound-direct-dm-runtime.ts +2 -0
  25. package/src/metrics.ts +454 -0
  26. package/src/nostr-bus.fuzz.test.ts +383 -0
  27. package/src/nostr-bus.inbound.test.ts +598 -0
  28. package/src/nostr-bus.integration.test.ts +491 -0
  29. package/src/nostr-bus.test.ts +256 -0
  30. package/src/nostr-bus.ts +799 -0
  31. package/src/nostr-key-utils.ts +93 -0
  32. package/src/nostr-profile-core.ts +135 -0
  33. package/src/nostr-profile-http-runtime.ts +7 -0
  34. package/src/nostr-profile-http.test.ts +632 -0
  35. package/src/nostr-profile-http.ts +583 -0
  36. package/src/nostr-profile-import.test.ts +196 -0
  37. package/src/nostr-profile-import.ts +273 -0
  38. package/src/nostr-profile-url-safety.ts +22 -0
  39. package/src/nostr-profile.fuzz.test.ts +431 -0
  40. package/src/nostr-profile.test.ts +416 -0
  41. package/src/nostr-profile.ts +144 -0
  42. package/src/nostr-state-store.test.ts +172 -0
  43. package/src/nostr-state-store.ts +132 -0
  44. package/src/runtime.ts +10 -0
  45. package/src/seen-tracker.ts +291 -0
  46. package/src/session-route.ts +26 -0
  47. package/src/setup-adapter.ts +86 -0
  48. package/src/setup-surface.ts +204 -0
  49. package/src/test-fixtures.ts +46 -0
  50. package/src/types.ts +118 -0
  51. package/test/setup.ts +5 -0
  52. package/test-api.ts +2 -0
  53. package/tsconfig.json +16 -0
@@ -0,0 +1,256 @@
1
+ // Nostr tests cover nostr bus plugin behavior.
2
+ import { describe, expect, it } from "vitest";
3
+ import {
4
+ validatePrivateKey,
5
+ getPublicKeyFromPrivate,
6
+ isValidPubkey,
7
+ normalizePubkey,
8
+ pubkeyToNpub,
9
+ } from "./nostr-key-utils.js";
10
+ import { TEST_HEX_PRIVATE_KEY, TEST_NSEC } from "./test-fixtures.js";
11
+
12
+ const UPPERCASE_HEX = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
13
+ const INVALID_HEX = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdeg";
14
+
15
+ function expectThrowsError(run: () => unknown): void {
16
+ let error: unknown;
17
+ try {
18
+ run();
19
+ } catch (caught) {
20
+ error = caught;
21
+ }
22
+ expect(error).toBeInstanceOf(Error);
23
+ }
24
+
25
+ const uppercaseHexAcceptanceCases = [
26
+ {
27
+ name: "validatePrivateKey",
28
+ assert: () => {
29
+ const result = validatePrivateKey(TEST_HEX_PRIVATE_KEY.toUpperCase());
30
+ expect(result).toBeInstanceOf(Uint8Array);
31
+ },
32
+ },
33
+ {
34
+ name: "isValidPubkey",
35
+ assert: () => {
36
+ expect(isValidPubkey(UPPERCASE_HEX)).toBe(true);
37
+ },
38
+ },
39
+ ];
40
+
41
+ const invalidHexRejectionCases = [
42
+ {
43
+ name: "validatePrivateKey",
44
+ assert: (input: string) => {
45
+ expect(() => validatePrivateKey(input)).toThrow("Private key must be 64 hex characters");
46
+ },
47
+ },
48
+ {
49
+ name: "isValidPubkey",
50
+ assert: (input: string) => {
51
+ expect(isValidPubkey(input)).toBe(false);
52
+ },
53
+ },
54
+ ];
55
+
56
+ const whitespaceNormalizationCases = [
57
+ {
58
+ name: "validatePrivateKey",
59
+ assert: () => {
60
+ const result = validatePrivateKey(` ${TEST_HEX_PRIVATE_KEY} `);
61
+ expect(result).toBeInstanceOf(Uint8Array);
62
+ },
63
+ },
64
+ {
65
+ name: "normalizePubkey",
66
+ assert: () => {
67
+ expect(normalizePubkey(` ${TEST_HEX_PRIVATE_KEY} `)).toBe(TEST_HEX_PRIVATE_KEY);
68
+ },
69
+ },
70
+ ];
71
+
72
+ describe("hex key helper contracts", () => {
73
+ it.each(uppercaseHexAcceptanceCases)("$name accepts uppercase hex", ({ assert }) => {
74
+ assert();
75
+ });
76
+
77
+ it.each(invalidHexRejectionCases)("$name rejects non-hex characters", ({ assert }) => {
78
+ assert(INVALID_HEX);
79
+ });
80
+
81
+ it.each(invalidHexRejectionCases)("$name rejects empty string", ({ assert }) => {
82
+ assert("");
83
+ });
84
+
85
+ it.each(whitespaceNormalizationCases)("$name trims whitespace", ({ assert }) => {
86
+ assert();
87
+ });
88
+ });
89
+
90
+ describe("validatePrivateKey", () => {
91
+ describe("validatePrivateKey hex format", () => {
92
+ it("accepts valid 64-char hex key", () => {
93
+ const result = validatePrivateKey(TEST_HEX_PRIVATE_KEY);
94
+ expect(result).toBeInstanceOf(Uint8Array);
95
+ expect(result.length).toBe(32);
96
+ });
97
+
98
+ it("accepts lowercase hex", () => {
99
+ const result = validatePrivateKey(TEST_HEX_PRIVATE_KEY.toLowerCase());
100
+ expect(result).toBeInstanceOf(Uint8Array);
101
+ });
102
+
103
+ it("accepts mixed case hex", () => {
104
+ const mixed = "0123456789ABCdef0123456789abcDEF0123456789abcdef0123456789ABCDEF";
105
+ const result = validatePrivateKey(mixed);
106
+ expect(result).toBeInstanceOf(Uint8Array);
107
+ });
108
+
109
+ it("trims newlines", () => {
110
+ const result = validatePrivateKey(`${TEST_HEX_PRIVATE_KEY}\n`);
111
+ expect(result).toBeInstanceOf(Uint8Array);
112
+ });
113
+
114
+ it("rejects 63-char hex (too short)", () => {
115
+ expect(() => validatePrivateKey(TEST_HEX_PRIVATE_KEY.slice(0, 63))).toThrow(
116
+ "Private key must be 64 hex characters",
117
+ );
118
+ });
119
+
120
+ it("rejects 65-char hex (too long)", () => {
121
+ expect(() => validatePrivateKey(TEST_HEX_PRIVATE_KEY + "0")).toThrow(
122
+ "Private key must be 64 hex characters",
123
+ );
124
+ });
125
+
126
+ it("rejects whitespace-only string", () => {
127
+ expect(() => validatePrivateKey(" ")).toThrow("Private key must be 64 hex characters");
128
+ });
129
+
130
+ it("rejects key with 0x prefix", () => {
131
+ expect(() => validatePrivateKey("0x" + TEST_HEX_PRIVATE_KEY)).toThrow(
132
+ "Private key must be 64 hex characters",
133
+ );
134
+ });
135
+ });
136
+
137
+ describe("nsec format", () => {
138
+ it("rejects invalid nsec (wrong checksum)", () => {
139
+ const badNsec = "nsec1invalidinvalidinvalidinvalidinvalidinvalidinvalidinvalid";
140
+ expectThrowsError(() => validatePrivateKey(badNsec));
141
+ });
142
+
143
+ it("rejects npub (wrong type)", () => {
144
+ const npub = "npub1qypqxpq9qtpqscx7peytzfwtdjmcv0mrz5rjpej8vjppfkqfqy8s5epk55";
145
+ expectThrowsError(() => validatePrivateKey(npub));
146
+ });
147
+ });
148
+ });
149
+
150
+ describe("isValidPubkey", () => {
151
+ describe("isValidPubkey hex format", () => {
152
+ it("accepts valid 64-char hex pubkey", () => {
153
+ expect(isValidPubkey(TEST_HEX_PRIVATE_KEY)).toBe(true);
154
+ });
155
+
156
+ it("rejects 63-char hex", () => {
157
+ const shortHex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde";
158
+ expect(isValidPubkey(shortHex)).toBe(false);
159
+ });
160
+
161
+ it("rejects 65-char hex", () => {
162
+ const longHex = `${TEST_HEX_PRIVATE_KEY}0`;
163
+ expect(isValidPubkey(longHex)).toBe(false);
164
+ });
165
+ });
166
+
167
+ describe("npub format", () => {
168
+ it("rejects invalid npub", () => {
169
+ expect(isValidPubkey("npub1invalid")).toBe(false);
170
+ });
171
+
172
+ it("rejects nsec (wrong type)", () => {
173
+ expect(isValidPubkey(TEST_NSEC)).toBe(false);
174
+ });
175
+ });
176
+
177
+ describe("edge cases", () => {
178
+ it("handles whitespace-padded input", () => {
179
+ expect(isValidPubkey(` ${TEST_HEX_PRIVATE_KEY} `)).toBe(true);
180
+ });
181
+ });
182
+ });
183
+
184
+ describe("normalizePubkey", () => {
185
+ describe("normalizePubkey hex format", () => {
186
+ it("lowercases hex pubkey", () => {
187
+ const upper = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
188
+ const result = normalizePubkey(upper);
189
+ expect(result).toBe(upper.toLowerCase());
190
+ });
191
+
192
+ it("rejects invalid hex", () => {
193
+ expect(() => normalizePubkey("invalid")).toThrow("Pubkey must be 64 hex characters");
194
+ });
195
+ });
196
+
197
+ describe("normalizePubkey npub format", () => {
198
+ // Regression: pre-fix this returned a 128-char garbage string because the
199
+ // implementation treated nip19.decode(npub).data as a Uint8Array, but
200
+ // nostr-tools >=2.0 returns it as the hex string directly. allowFrom
201
+ // entries written as npubs therefore never matched any hex sender pubkey.
202
+ const HEX = "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789";
203
+ const NPUB = pubkeyToNpub(HEX);
204
+
205
+ it("decodes npub to the original 64-char hex pubkey", () => {
206
+ const result = normalizePubkey(NPUB);
207
+ expect(result).toBe(HEX);
208
+ expect(result).toMatch(/^[0-9a-f]{64}$/);
209
+ expect(result.length).toBe(64);
210
+ });
211
+
212
+ it("survives a hex→npub→normalizePubkey roundtrip", () => {
213
+ expect(normalizePubkey(pubkeyToNpub(HEX))).toBe(HEX);
214
+ });
215
+
216
+ it("trims surrounding whitespace before decoding", () => {
217
+ expect(normalizePubkey(` ${NPUB} `)).toBe(HEX);
218
+ });
219
+ });
220
+ });
221
+
222
+ describe("getPublicKeyFromPrivate", () => {
223
+ it("derives public key from hex private key", () => {
224
+ const pubkey = getPublicKeyFromPrivate(TEST_HEX_PRIVATE_KEY);
225
+ expect(pubkey).toMatch(/^[0-9a-f]{64}$/);
226
+ expect(pubkey.length).toBe(64);
227
+ });
228
+
229
+ it("derives consistent public key", () => {
230
+ const pubkey1 = getPublicKeyFromPrivate(TEST_HEX_PRIVATE_KEY);
231
+ const pubkey2 = getPublicKeyFromPrivate(TEST_HEX_PRIVATE_KEY);
232
+ expect(pubkey1).toBe(pubkey2);
233
+ });
234
+
235
+ it("throws for invalid private key", () => {
236
+ expectThrowsError(() => getPublicKeyFromPrivate("invalid"));
237
+ });
238
+ });
239
+
240
+ describe("pubkeyToNpub", () => {
241
+ it("converts hex pubkey to npub format", () => {
242
+ const npub = pubkeyToNpub(TEST_HEX_PRIVATE_KEY);
243
+ expect(npub).toMatch(/^npub1[a-z0-9]+$/);
244
+ });
245
+
246
+ it("produces consistent output", () => {
247
+ const npub1 = pubkeyToNpub(TEST_HEX_PRIVATE_KEY);
248
+ const npub2 = pubkeyToNpub(TEST_HEX_PRIVATE_KEY);
249
+ expect(npub1).toBe(npub2);
250
+ });
251
+
252
+ it("normalizes uppercase hex first", () => {
253
+ const upper = TEST_HEX_PRIVATE_KEY.toUpperCase();
254
+ expect(pubkeyToNpub(TEST_HEX_PRIVATE_KEY)).toBe(pubkeyToNpub(upper));
255
+ });
256
+ });