@elizaos/plugin-nostr 2.0.0-alpha.8 → 2.0.3-beta.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shaw Walters and elizaOS Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # @elizaos/plugin-nostr
2
+
3
+ Nostr decentralized messaging plugin for elizaOS agents. Gives an Eliza agent a Nostr identity and connects it to one or more relays, enabling encrypted direct messages (NIP-04), public note publishing (kind:1), and profile management (kind:0).
4
+
5
+ ## What it adds
6
+
7
+ - **Encrypted DMs (NIP-04)** — receive and send encrypted direct messages; routes through the `MESSAGE` connector action so the agent's planner needs no plugin-specific actions.
8
+ - **Public notes (kind:1)** — publish notes to connected relays; routes through the `POST` connector action. Supports relay feed reading and NIP-50 relay search where the relay implements it.
9
+ - **Profile publishing (kind:0)** — publish agent profile metadata (name, picture, nip05, etc.) to relays.
10
+ - **Multi-relay redundancy** — publishes to all configured relays; succeeds when at least one accepts the event.
11
+ - **Multi-account support** — run the agent under multiple Nostr identities simultaneously.
12
+
13
+ ## Enabling the plugin
14
+
15
+ Add `@elizaos/plugin-nostr` to the `plugins` array in the agent character file, or let the auto-enable engine activate it automatically when a `connectors.nostr` block is present in the agent config.
16
+
17
+ ```json
18
+ {
19
+ "plugins": ["@elizaos/plugin-nostr"],
20
+ "settings": {
21
+ "NOSTR_PRIVATE_KEY": "your-private-key-hex-or-nsec",
22
+ "NOSTR_RELAYS": "wss://relay.damus.io,wss://nos.lol,wss://relay.nostr.band",
23
+ "NOSTR_DM_POLICY": "pairing"
24
+ }
25
+ }
26
+ ```
27
+
28
+ ## Configuration
29
+
30
+ ### Environment variables
31
+
32
+ | Variable | Required | Default | Description |
33
+ |---|---|---|---|
34
+ | `NOSTR_PRIVATE_KEY` | Yes | — | Private key in hex (64 chars) or `nsec1` bech32 format |
35
+ | `NOSTR_RELAYS` | No | damus.io, nos.lol, relay.nostr.band | Comma-separated relay WebSocket URLs |
36
+ | `NOSTR_DM_POLICY` | No | `pairing` | DM acceptance policy (see below) |
37
+ | `NOSTR_ALLOW_FROM` | No | — | Comma-separated pubkeys allowed to DM (required for `allowlist` policy) |
38
+ | `NOSTR_ENABLED` | No | `true` | Set to `false` to disable without removing config |
39
+ | `NOSTR_ACCOUNTS` | No | — | JSON array or object for multi-account configuration |
40
+ | `NOSTR_DEFAULT_ACCOUNT_ID` | No | `"default"` | Default account when multiple are configured |
41
+
42
+ ### Character file override
43
+
44
+ Settings can be embedded directly in the character file under `settings.nostr`:
45
+
46
+ ```json
47
+ {
48
+ "settings": {
49
+ "nostr": {
50
+ "privateKey": "...",
51
+ "relays": ["wss://relay.damus.io", "wss://nos.lol"],
52
+ "dmPolicy": "allowlist",
53
+ "allowFrom": ["npub1...", "deadbeef..."],
54
+ "profile": {
55
+ "name": "my-agent",
56
+ "nip05": "agent@example.com"
57
+ }
58
+ }
59
+ }
60
+ }
61
+ ```
62
+
63
+ For multiple accounts, use `settings.nostr.accounts`:
64
+
65
+ ```json
66
+ {
67
+ "settings": {
68
+ "nostr": {
69
+ "accounts": {
70
+ "main": { "privateKey": "...", "relays": ["wss://relay.damus.io"] },
71
+ "alt": { "privateKey": "...", "relays": ["wss://nos.lol"] }
72
+ }
73
+ }
74
+ }
75
+ }
76
+ ```
77
+
78
+ ## DM policies
79
+
80
+ | Policy | Description |
81
+ |---|---|
82
+ | `open` | Accept DMs from any pubkey |
83
+ | `pairing` | Accept DMs and remember senders |
84
+ | `allowlist` | Only accept DMs from pubkeys in `NOSTR_ALLOW_FROM` |
85
+ | `disabled` | Ignore all incoming DMs |
86
+
87
+ ## Nostr concepts
88
+
89
+ - **Private key** — signs events and decrypts messages. Never commit to version control; use env vars or a secrets manager.
90
+ - **Public key** — derived automatically from the private key. This is the agent's Nostr identity.
91
+ - **npub / nsec** — bech32-encoded formats for public/private keys. Both formats are accepted as input.
92
+ - **kind:0** — profile metadata event.
93
+ - **kind:1** — public text note.
94
+ - **kind:4** — NIP-04 encrypted DM.
95
+
96
+ ## Security
97
+
98
+ - Generate keys with a trusted tool such as `nostr-tools` (`generateSecretKey` + `getPublicKey`).
99
+ - Start with a restrictive DM policy (`allowlist`) and relax as needed.
100
+ - Consider running a private relay for sensitive agent deployments.
101
+
102
+ ## Development
103
+
104
+ ```bash
105
+ bun run --cwd plugins/plugin-nostr build
106
+ bun run --cwd plugins/plugin-nostr test
107
+ bun run --cwd plugins/plugin-nostr typecheck
108
+ ```
package/auto-enable.ts ADDED
@@ -0,0 +1,20 @@
1
+ // Auto-enable check for @elizaos/plugin-nostr.
2
+ //
3
+ // Plugin manifest entry-point — referenced by package.json's
4
+ // `elizaos.plugin.autoEnableModule`. Keep this module light: env reads only,
5
+ // no service init, no transitive imports of the full plugin runtime. The
6
+ // auto-enable engine loads dozens of these per boot.
7
+ import type { PluginAutoEnableContext } from "@elizaos/core";
8
+
9
+ /** Enable when a `nostr` connector block is present and not explicitly disabled. */
10
+ export function shouldEnable(ctx: PluginAutoEnableContext): boolean {
11
+ const c = (ctx.config.connectors as Record<string, unknown> | undefined)?.nostr;
12
+ if (!c || typeof c !== "object") return false;
13
+ const config = c as Record<string, unknown>;
14
+ if (config.enabled === false) return false;
15
+ // The full per-connector field check (private key / relays) lives in the
16
+ // central engine's isConnectorConfigured. We delegate to a simple "block
17
+ // present + not explicitly disabled" check here; the central engine's
18
+ // stricter check remains as a fallback during migration.
19
+ return true;
20
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elizaos/plugin-nostr",
3
- "version": "2.0.0-alpha.8",
3
+ "version": "2.0.3-beta.2",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -9,37 +9,67 @@
9
9
  "./package.json": "./package.json",
10
10
  ".": {
11
11
  "types": "./dist/index.d.ts",
12
+ "eliza-source": {
13
+ "types": "./src/index.ts",
14
+ "import": "./src/index.ts",
15
+ "default": "./src/index.ts"
16
+ },
12
17
  "import": "./dist/index.js",
13
18
  "default": "./dist/index.js"
19
+ },
20
+ "./*.css": "./dist/*.css",
21
+ "./*": {
22
+ "types": "./dist/*.d.ts",
23
+ "eliza-source": {
24
+ "types": "./src/*.ts",
25
+ "import": "./src/*.ts",
26
+ "default": "./src/*.ts"
27
+ },
28
+ "import": "./dist/*.js",
29
+ "default": "./dist/*.js"
14
30
  }
15
31
  },
16
32
  "files": [
17
- "dist"
33
+ "dist",
34
+ "auto-enable.ts"
18
35
  ],
36
+ "elizaos": {
37
+ "plugin": {
38
+ "autoEnableModule": "./auto-enable.ts",
39
+ "capabilities": [
40
+ "social-posting"
41
+ ]
42
+ }
43
+ },
19
44
  "publishConfig": {
20
45
  "access": "public"
21
46
  },
22
47
  "scripts": {
23
48
  "build": "bun run build.ts",
24
- "test": "vitest run --config vitest.config.ts --passWithNoTests --cache=false",
49
+ "test": "vitest run --config vitest.config.ts --cache=false",
25
50
  "lint": "bunx @biomejs/biome check --write --unsafe .",
26
51
  "lint:check": "bunx @biomejs/biome check .",
27
52
  "format": "bunx @biomejs/biome format --write .",
28
53
  "format:check": "bunx @biomejs/biome format .",
29
- "typecheck": "tsc --noEmit"
54
+ "typecheck": "tsgo --noEmit"
30
55
  },
31
56
  "dependencies": {
32
- "@elizaos/core": "2.0.0-alpha.3",
33
- "nostr-tools": "^2.0.0",
34
- "zod": "^4.3.6"
57
+ "@elizaos/core": "2.0.3-beta.2",
58
+ "nostr-tools": "^2.0.0"
35
59
  },
36
60
  "devDependencies": {
37
- "@types/node": "^20.0.0",
38
- "typescript": "^5.3.0",
39
- "@biomejs/biome": "^2.3.11",
40
- "vitest": "^3.2.4"
61
+ "@biomejs/biome": "^2.4.14",
62
+ "@types/node": "^22.19.17",
63
+ "typescript": "^6.0.3",
64
+ "vitest": "^4.0.0"
41
65
  },
42
- "milady": {
66
+ "resolutions": {
67
+ "@noble/hashes": "2.2.0"
68
+ },
69
+ "overrides": {
70
+ "@noble/hashes": "2.2.0"
71
+ },
72
+ "eliza": {
43
73
  "platforms": [
44
74
  "node"
45
75
  ],
@@ -78,5 +108,6 @@
78
108
  "sensitive": false
79
109
  }
80
110
  }
81
- }
111
+ },
112
+ "gitHead": "82fe0f44215954c2417328203f5bd6510985c1fc"
82
113
  }
package/dist/index.d.ts DELETED
@@ -1 +0,0 @@
1
- // stub
package/dist/index.js DELETED
@@ -1,876 +0,0 @@
1
- // src/index.ts
2
- import { logger as logger3 } from "@elizaos/core";
3
-
4
- // src/actions/publishProfile.ts
5
- import {
6
- composePromptFromState,
7
- ModelType,
8
- parseJSONObjectFromText
9
- } from "@elizaos/core";
10
-
11
- // src/types.ts
12
- import { nip19 } from "nostr-tools";
13
- var MAX_NOSTR_MESSAGE_LENGTH = 4000;
14
- var NOSTR_SERVICE_NAME = "nostr";
15
- var DEFAULT_NOSTR_RELAYS = [
16
- "wss://relay.damus.io",
17
- "wss://nos.lol",
18
- "wss://relay.nostr.band"
19
- ];
20
- var NostrEventTypes;
21
- ((NostrEventTypes2) => {
22
- NostrEventTypes2["MESSAGE_RECEIVED"] = "NOSTR_MESSAGE_RECEIVED";
23
- NostrEventTypes2["MESSAGE_SENT"] = "NOSTR_MESSAGE_SENT";
24
- NostrEventTypes2["RELAY_CONNECTED"] = "NOSTR_RELAY_CONNECTED";
25
- NostrEventTypes2["RELAY_DISCONNECTED"] = "NOSTR_RELAY_DISCONNECTED";
26
- NostrEventTypes2["PROFILE_PUBLISHED"] = "NOSTR_PROFILE_PUBLISHED";
27
- NostrEventTypes2["CONNECTION_READY"] = "NOSTR_CONNECTION_READY";
28
- })(NostrEventTypes ||= {});
29
-
30
- class NostrPluginError extends Error {
31
- code;
32
- cause;
33
- constructor(message, code, cause) {
34
- super(message);
35
- this.code = code;
36
- this.cause = cause;
37
- this.name = "NostrPluginError";
38
- }
39
- }
40
-
41
- class NostrConfigurationError extends NostrPluginError {
42
- setting;
43
- constructor(message, setting, cause) {
44
- super(message, "CONFIGURATION_ERROR", cause);
45
- this.name = "NostrConfigurationError";
46
- this.setting = setting;
47
- }
48
- }
49
-
50
- class NostrRelayError extends NostrPluginError {
51
- relay;
52
- constructor(message, relay, cause) {
53
- super(message, "RELAY_ERROR", cause);
54
- this.name = "NostrRelayError";
55
- this.relay = relay;
56
- }
57
- }
58
-
59
- class NostrCryptoError extends NostrPluginError {
60
- constructor(message, cause) {
61
- super(message, "CRYPTO_ERROR", cause);
62
- this.name = "NostrCryptoError";
63
- }
64
- }
65
- function isValidPubkey(input) {
66
- if (typeof input !== "string") {
67
- return false;
68
- }
69
- const trimmed = input.trim();
70
- if (trimmed.startsWith("npub1")) {
71
- try {
72
- const decoded = nip19.decode(trimmed);
73
- return decoded.type === "npub";
74
- } catch {
75
- return false;
76
- }
77
- }
78
- return /^[0-9a-fA-F]{64}$/.test(trimmed);
79
- }
80
- function normalizePubkey(input) {
81
- const trimmed = input.trim();
82
- if (trimmed.startsWith("npub1")) {
83
- const decoded = nip19.decode(trimmed);
84
- if (decoded.type !== "npub") {
85
- throw new NostrCryptoError("Invalid npub key");
86
- }
87
- const data = decoded.data;
88
- return Array.from(data).map((b) => b.toString(16).padStart(2, "0")).join("");
89
- }
90
- if (!/^[0-9a-fA-F]{64}$/.test(trimmed)) {
91
- throw new NostrCryptoError("Pubkey must be 64 hex characters or npub format");
92
- }
93
- return trimmed.toLowerCase();
94
- }
95
- function pubkeyToNpub(hexPubkey) {
96
- const normalized = normalizePubkey(hexPubkey);
97
- return nip19.npubEncode(normalized);
98
- }
99
- function validatePrivateKey(key) {
100
- const trimmed = key.trim();
101
- if (trimmed.startsWith("nsec1")) {
102
- const decoded = nip19.decode(trimmed);
103
- if (decoded.type !== "nsec") {
104
- throw new NostrCryptoError("Invalid nsec key: wrong type");
105
- }
106
- return decoded.data;
107
- }
108
- if (!/^[0-9a-fA-F]{64}$/.test(trimmed)) {
109
- throw new NostrCryptoError("Private key must be 64 hex characters or nsec bech32 format");
110
- }
111
- const bytes = new Uint8Array(32);
112
- for (let i = 0;i < 32; i++) {
113
- bytes[i] = parseInt(trimmed.slice(i * 2, i * 2 + 2), 16);
114
- }
115
- return bytes;
116
- }
117
- function getPubkeyDisplayName(pubkey) {
118
- const normalized = normalizePubkey(pubkey);
119
- return `${normalized.slice(0, 8)}...${normalized.slice(-8)}`;
120
- }
121
- function splitMessageForNostr(text, maxLength = MAX_NOSTR_MESSAGE_LENGTH) {
122
- if (text.length <= maxLength) {
123
- return [text];
124
- }
125
- const chunks = [];
126
- let remaining = text;
127
- while (remaining.length > 0) {
128
- if (remaining.length <= maxLength) {
129
- chunks.push(remaining);
130
- break;
131
- }
132
- let breakPoint = maxLength;
133
- const newlineIndex = remaining.lastIndexOf(`
134
- `, maxLength);
135
- if (newlineIndex > maxLength * 0.5) {
136
- breakPoint = newlineIndex + 1;
137
- } else {
138
- const spaceIndex = remaining.lastIndexOf(" ", maxLength);
139
- if (spaceIndex > maxLength * 0.5) {
140
- breakPoint = spaceIndex + 1;
141
- }
142
- }
143
- chunks.push(remaining.slice(0, breakPoint).trimEnd());
144
- remaining = remaining.slice(breakPoint).trimStart();
145
- }
146
- return chunks;
147
- }
148
-
149
- // src/actions/publishProfile.ts
150
- var PUBLISH_PROFILE_TEMPLATE = `# Task: Extract Nostr profile data
151
- Based on the conversation, determine what profile information to update.
152
-
153
- Recent conversation:
154
- {{recentMessages}}
155
-
156
- Extract any of the following profile fields that should be updated:
157
- - name: Display name
158
- - about: Bio/description
159
- - picture: Profile picture URL
160
- - banner: Banner image URL
161
- - nip05: Nostr verification (user@domain.com)
162
- - lud16: Lightning address (user@domain.com)
163
- - website: Website URL
164
-
165
- Respond with a JSON object containing only the fields to update:
166
- \`\`\`json
167
- {
168
- "name": "optional name",
169
- "about": "optional bio"
170
- }
171
- \`\`\``;
172
- var publishProfile = {
173
- name: "NOSTR_PUBLISH_PROFILE",
174
- similes: ["UPDATE_NOSTR_PROFILE", "SET_NOSTR_PROFILE", "NOSTR_PROFILE"],
175
- description: "Publish or update the bot's Nostr profile (kind:0 metadata)",
176
- validate: async (_runtime, message, _state) => {
177
- return message.content.source === "nostr";
178
- },
179
- handler: async (runtime, message, state, _options, callback) => {
180
- const nostrService = runtime.getService(NOSTR_SERVICE_NAME);
181
- if (!nostrService || !nostrService.isConnected()) {
182
- if (callback) {
183
- callback({ text: "Nostr service is not available.", source: "nostr" });
184
- }
185
- return { success: false, error: "Nostr service not available" };
186
- }
187
- const currentState = state ?? await runtime.composeState(message);
188
- const prompt = await composePromptFromState({
189
- template: PUBLISH_PROFILE_TEMPLATE,
190
- state: currentState
191
- });
192
- let profileInfo = null;
193
- for (let attempt = 0;attempt < 3; attempt++) {
194
- const response = await runtime.useModel(ModelType.TEXT_SMALL, {
195
- prompt
196
- });
197
- const parsed = parseJSONObjectFromText(String(response));
198
- if (parsed) {
199
- profileInfo = {
200
- name: parsed.name ? String(parsed.name) : undefined,
201
- displayName: parsed.displayName ? String(parsed.displayName) : undefined,
202
- about: parsed.about ? String(parsed.about) : undefined,
203
- picture: parsed.picture ? String(parsed.picture) : undefined,
204
- banner: parsed.banner ? String(parsed.banner) : undefined,
205
- nip05: parsed.nip05 ? String(parsed.nip05) : undefined,
206
- lud16: parsed.lud16 ? String(parsed.lud16) : undefined,
207
- website: parsed.website ? String(parsed.website) : undefined
208
- };
209
- break;
210
- }
211
- }
212
- if (!profileInfo) {
213
- if (callback) {
214
- callback({
215
- text: "I couldn't understand the profile information. Please try again.",
216
- source: "nostr"
217
- });
218
- }
219
- return { success: false, error: "Could not extract profile parameters" };
220
- }
221
- const result = await nostrService.publishProfile(profileInfo);
222
- if (!result.success) {
223
- if (callback) {
224
- callback({
225
- text: `Failed to publish profile: ${result.error}`,
226
- source: "nostr"
227
- });
228
- }
229
- return { success: false, error: result.error };
230
- }
231
- if (callback) {
232
- callback({
233
- text: "Profile published successfully.",
234
- source: message.content.source
235
- });
236
- }
237
- return {
238
- success: true,
239
- data: {
240
- eventId: result.eventId,
241
- relays: result.relays,
242
- profile: profileInfo
243
- }
244
- };
245
- },
246
- examples: [
247
- [
248
- {
249
- name: "{{user1}}",
250
- content: { text: "Update your profile name to 'Bot Assistant'" }
251
- },
252
- {
253
- name: "{{agent}}",
254
- content: {
255
- text: "I'll update my Nostr profile.",
256
- actions: ["NOSTR_PUBLISH_PROFILE"]
257
- }
258
- }
259
- ]
260
- ]
261
- };
262
- // src/actions/sendDm.ts
263
- import {
264
- composePromptFromState as composePromptFromState2,
265
- logger,
266
- ModelType as ModelType2,
267
- parseJSONObjectFromText as parseJSONObjectFromText2
268
- } from "@elizaos/core";
269
- var SEND_DM_TEMPLATE = `# Task: Extract Nostr DM parameters
270
- Based on the conversation, determine what message to send and to whom.
271
-
272
- Recent conversation:
273
- {{recentMessages}}
274
-
275
- Extract the following:
276
- - text: The message content to send
277
- - toPubkey: The target pubkey (npub or hex format, or "current" for the current conversation)
278
-
279
- Respond with a JSON object:
280
- \`\`\`json
281
- {
282
- "text": "message content here",
283
- "toPubkey": "npub1... or hex pubkey or current"
284
- }
285
- \`\`\``;
286
- var sendDm = {
287
- name: "NOSTR_SEND_DM",
288
- similes: ["SEND_NOSTR_DM", "NOSTR_MESSAGE", "NOSTR_TEXT", "DM_NOSTR"],
289
- description: "Send an encrypted direct message via Nostr (NIP-04)",
290
- validate: async (_runtime, message, _state) => {
291
- return message.content.source === "nostr";
292
- },
293
- handler: async (runtime, message, state, _options, callback) => {
294
- const nostrService = runtime.getService(NOSTR_SERVICE_NAME);
295
- if (!nostrService || !nostrService.isConnected()) {
296
- if (callback) {
297
- callback({ text: "Nostr service is not available.", source: "nostr" });
298
- }
299
- return { success: false, error: "Nostr service not available" };
300
- }
301
- const currentState = state ?? await runtime.composeState(message);
302
- const prompt = await composePromptFromState2({
303
- template: SEND_DM_TEMPLATE,
304
- state: currentState
305
- });
306
- let dmInfo = null;
307
- for (let attempt = 0;attempt < 3; attempt++) {
308
- const response = await runtime.useModel(ModelType2.TEXT_SMALL, {
309
- prompt
310
- });
311
- const parsed = parseJSONObjectFromText2(String(response));
312
- if (parsed?.text) {
313
- dmInfo = {
314
- text: String(parsed.text),
315
- toPubkey: String(parsed.toPubkey || "current")
316
- };
317
- break;
318
- }
319
- }
320
- if (!dmInfo || !dmInfo.text) {
321
- if (callback) {
322
- callback({
323
- text: "I couldn't understand what message you want me to send. Please try again.",
324
- source: "nostr"
325
- });
326
- }
327
- return { success: false, error: "Could not extract message parameters" };
328
- }
329
- let targetPubkey;
330
- if (dmInfo.toPubkey && dmInfo.toPubkey !== "current") {
331
- if (isValidPubkey(dmInfo.toPubkey)) {
332
- try {
333
- targetPubkey = normalizePubkey(dmInfo.toPubkey);
334
- } catch {}
335
- }
336
- }
337
- if (!targetPubkey && currentState?.data?.senderPubkey) {
338
- targetPubkey = currentState.data.senderPubkey;
339
- }
340
- if (!targetPubkey) {
341
- if (callback) {
342
- callback({
343
- text: "I couldn't determine who to send the message to. Please specify a pubkey.",
344
- source: "nostr"
345
- });
346
- }
347
- return { success: false, error: "Could not determine target pubkey" };
348
- }
349
- const chunks = splitMessageForNostr(dmInfo.text);
350
- let lastResult;
351
- for (const chunk of chunks) {
352
- const result = await nostrService.sendDm({
353
- toPubkey: targetPubkey,
354
- text: chunk
355
- });
356
- if (!result.success) {
357
- if (callback) {
358
- callback({
359
- text: `Failed to send message: ${result.error}`,
360
- source: "nostr"
361
- });
362
- }
363
- return { success: false, error: result.error };
364
- }
365
- lastResult = { eventId: result.eventId, relays: result.relays };
366
- logger.debug(`Sent Nostr DM: ${result.eventId}`);
367
- }
368
- if (callback) {
369
- callback({
370
- text: "Message sent successfully.",
371
- source: message.content.source
372
- });
373
- }
374
- return {
375
- success: true,
376
- data: {
377
- toPubkey: targetPubkey,
378
- eventId: lastResult?.eventId,
379
- relays: lastResult?.relays,
380
- chunksCount: chunks.length
381
- }
382
- };
383
- },
384
- examples: [
385
- [
386
- {
387
- name: "{{user1}}",
388
- content: { text: "Send them a message saying 'Hello!'" }
389
- },
390
- {
391
- name: "{{agent}}",
392
- content: {
393
- text: "I'll send that DM via Nostr.",
394
- actions: ["NOSTR_SEND_DM"]
395
- }
396
- }
397
- ],
398
- [
399
- {
400
- name: "{{user1}}",
401
- content: { text: "Message npub1abc... saying 'Thanks for the zap!'" }
402
- },
403
- {
404
- name: "{{agent}}",
405
- content: {
406
- text: "I'll send that message to the specified pubkey.",
407
- actions: ["NOSTR_SEND_DM"]
408
- }
409
- }
410
- ]
411
- ]
412
- };
413
- // src/providers/identityContext.ts
414
- var identityContextProvider = {
415
- name: "nostrIdentityContext",
416
- description: "Provides information about the bot's Nostr identity",
417
- get: async (runtime, message, state) => {
418
- if (message.content.source !== "nostr") {
419
- return {
420
- data: {},
421
- values: {},
422
- text: ""
423
- };
424
- }
425
- const nostrService = runtime.getService(NOSTR_SERVICE_NAME);
426
- if (!nostrService || !nostrService.isConnected()) {
427
- return {
428
- data: { connected: false },
429
- values: { connected: false },
430
- text: ""
431
- };
432
- }
433
- const agentName = state?.agentName || "The agent";
434
- const publicKey = nostrService.getPublicKey();
435
- const npub = nostrService.getNpub();
436
- const relays = nostrService.getRelays();
437
- const responseText = `${agentName} is connected to Nostr with pubkey ${npub}. ` + `Connected to ${relays.length} relay(s): ${relays.join(", ")}. ` + `Nostr is a decentralized social protocol using cryptographic keys for identity.`;
438
- return {
439
- data: {
440
- publicKey,
441
- npub,
442
- relays,
443
- relayCount: relays.length,
444
- connected: true
445
- },
446
- values: {
447
- publicKey,
448
- npub,
449
- relayCount: relays.length
450
- },
451
- text: responseText
452
- };
453
- }
454
- };
455
- // src/providers/senderContext.ts
456
- var senderContextProvider = {
457
- name: "nostrSenderContext",
458
- description: "Provides information about the Nostr user in the current conversation",
459
- get: async (runtime, message, state) => {
460
- if (message.content.source !== "nostr") {
461
- return {
462
- data: {},
463
- values: {},
464
- text: ""
465
- };
466
- }
467
- const nostrService = runtime.getService(NOSTR_SERVICE_NAME);
468
- if (!nostrService || !nostrService.isConnected()) {
469
- return {
470
- data: { connected: false },
471
- values: { connected: false },
472
- text: ""
473
- };
474
- }
475
- const agentName = state?.agentName || "The agent";
476
- const senderPubkey = state?.data?.senderPubkey;
477
- if (!senderPubkey) {
478
- return {
479
- data: { connected: true },
480
- values: { connected: true },
481
- text: ""
482
- };
483
- }
484
- let senderNpub = "";
485
- try {
486
- senderNpub = pubkeyToNpub(senderPubkey);
487
- } catch {}
488
- const displayName = getPubkeyDisplayName(senderPubkey);
489
- const responseText = `${agentName} is talking to ${displayName} on Nostr. ` + `Their pubkey is ${senderNpub || senderPubkey}. ` + `This is an encrypted direct message conversation using NIP-04.`;
490
- return {
491
- data: {
492
- senderPubkey,
493
- senderNpub,
494
- displayName,
495
- isEncrypted: true
496
- },
497
- values: {
498
- senderPubkey,
499
- senderNpub,
500
- displayName
501
- },
502
- text: responseText
503
- };
504
- }
505
- };
506
- // src/service.ts
507
- import {
508
- logger as logger2,
509
- Service
510
- } from "@elizaos/core";
511
- import {
512
- finalizeEvent,
513
- getPublicKey,
514
- SimplePool,
515
- verifyEvent
516
- } from "nostr-tools";
517
- import { decrypt, encrypt } from "nostr-tools/nip04";
518
- class NostrService extends Service {
519
- static serviceType = NOSTR_SERVICE_NAME;
520
- capabilityDescription = "Provides Nostr protocol integration for encrypted direct messages";
521
- settings = null;
522
- pool = null;
523
- privateKey = null;
524
- connected = false;
525
- seenEventIds = new Set;
526
- static async start(runtime) {
527
- logger2.info("Starting Nostr service...");
528
- const service = new NostrService(runtime);
529
- await service.initialize();
530
- return service;
531
- }
532
- async initialize() {
533
- this.settings = this.loadSettings();
534
- this.validateSettings();
535
- this.privateKey = validatePrivateKey(this.settings.privateKey);
536
- this.pool = new SimplePool;
537
- await this.startSubscription();
538
- this.connected = true;
539
- logger2.info(`Nostr service started (pubkey: ${this.settings.publicKey.slice(0, 16)}...)`);
540
- this.runtime.emitEvent("NOSTR_CONNECTION_READY" /* CONNECTION_READY */, {
541
- runtime: this.runtime,
542
- service: this
543
- });
544
- }
545
- async stop() {
546
- logger2.info("Stopping Nostr service...");
547
- this.connected = false;
548
- if (this.pool) {
549
- this.pool.close(this.settings?.relays || []);
550
- this.pool = null;
551
- }
552
- this.privateKey = null;
553
- this.seenEventIds.clear();
554
- logger2.info("Nostr service stopped");
555
- }
556
- loadSettings() {
557
- const runtime = this.runtime;
558
- if (!runtime) {
559
- throw new NostrConfigurationError("Runtime not initialized");
560
- }
561
- const privateKeySetting = runtime.getSetting("NOSTR_PRIVATE_KEY");
562
- const privateKey = typeof privateKeySetting === "string" ? privateKeySetting : process.env.NOSTR_PRIVATE_KEY || "";
563
- const relaysRawSetting = runtime.getSetting("NOSTR_RELAYS");
564
- const relaysRaw = typeof relaysRawSetting === "string" ? relaysRawSetting : process.env.NOSTR_RELAYS || "";
565
- const dmPolicySetting = runtime.getSetting("NOSTR_DM_POLICY");
566
- const dmPolicy = typeof dmPolicySetting === "string" ? dmPolicySetting : process.env.NOSTR_DM_POLICY || "pairing";
567
- const allowFromRawSetting = runtime.getSetting("NOSTR_ALLOW_FROM");
568
- const allowFromRaw = typeof allowFromRawSetting === "string" ? allowFromRawSetting : process.env.NOSTR_ALLOW_FROM || "";
569
- const enabledSetting = runtime.getSetting("NOSTR_ENABLED");
570
- const enabled = typeof enabledSetting === "string" ? enabledSetting : process.env.NOSTR_ENABLED || "true";
571
- const relays = relaysRaw ? relaysRaw.split(",").map((r) => r.trim()).filter(Boolean) : DEFAULT_NOSTR_RELAYS;
572
- const allowFrom = allowFromRaw ? allowFromRaw.split(",").map((p) => {
573
- try {
574
- return normalizePubkey(p.trim());
575
- } catch {
576
- return p.trim();
577
- }
578
- }).filter(Boolean) : [];
579
- let publicKey = "";
580
- if (privateKey) {
581
- try {
582
- const sk = validatePrivateKey(privateKey);
583
- publicKey = getPublicKey(sk);
584
- } catch {}
585
- }
586
- return {
587
- privateKey,
588
- publicKey,
589
- relays,
590
- dmPolicy,
591
- allowFrom,
592
- enabled: enabled.toLowerCase() !== "false"
593
- };
594
- }
595
- validateSettings() {
596
- const settings = this.settings;
597
- if (!settings) {
598
- throw new NostrConfigurationError("Settings not loaded");
599
- }
600
- if (!settings.privateKey) {
601
- throw new NostrConfigurationError("NOSTR_PRIVATE_KEY is required", "NOSTR_PRIVATE_KEY");
602
- }
603
- if (!settings.publicKey) {
604
- throw new NostrConfigurationError("Invalid private key - could not derive public key", "NOSTR_PRIVATE_KEY");
605
- }
606
- if (settings.relays.length === 0) {
607
- throw new NostrConfigurationError("At least one relay is required", "NOSTR_RELAYS");
608
- }
609
- for (const relay of settings.relays) {
610
- if (!relay.startsWith("wss://") && !relay.startsWith("ws://")) {
611
- throw new NostrConfigurationError(`Invalid relay URL: ${relay}`, "NOSTR_RELAYS");
612
- }
613
- }
614
- }
615
- async startSubscription() {
616
- const settings = this.settings;
617
- const pool = this.pool;
618
- const privateKey = this.privateKey;
619
- if (!settings || !pool || !privateKey) {
620
- throw new NostrConfigurationError("Service not properly initialized");
621
- }
622
- const pk = settings.publicKey;
623
- const since = Math.floor(Date.now() / 1000) - 120;
624
- const filter = { kinds: [4], "#p": [pk], since };
625
- pool.subscribeMany(settings.relays, [filter], {
626
- onevent: async (event) => {
627
- await this.handleEvent(event);
628
- },
629
- oneose: () => {
630
- logger2.debug("Nostr EOSE received - initial sync complete");
631
- }
632
- });
633
- logger2.info(`Subscribed to ${settings.relays.length} relay(s)`);
634
- }
635
- async handleEvent(event) {
636
- const settings = this.settings;
637
- const privateKey = this.privateKey;
638
- if (!settings || !privateKey) {
639
- return;
640
- }
641
- if (this.seenEventIds.has(event.id)) {
642
- return;
643
- }
644
- this.seenEventIds.add(event.id);
645
- if (this.seenEventIds.size > 1e4) {
646
- const toDelete = Array.from(this.seenEventIds).slice(0, 5000);
647
- for (const id of toDelete) {
648
- this.seenEventIds.delete(id);
649
- }
650
- }
651
- if (event.pubkey === settings.publicKey) {
652
- return;
653
- }
654
- if (!verifyEvent(event)) {
655
- logger2.warn(`Invalid signature on event ${event.id}`);
656
- return;
657
- }
658
- const isToUs = event.tags.some((t) => t[0] === "p" && t[1] === settings.publicKey);
659
- if (!isToUs) {
660
- return;
661
- }
662
- if (settings.dmPolicy === "disabled") {
663
- logger2.debug(`DM from ${event.pubkey} blocked - DMs disabled`);
664
- return;
665
- }
666
- if (settings.dmPolicy === "allowlist") {
667
- const allowed = settings.allowFrom.includes(event.pubkey);
668
- if (!allowed) {
669
- logger2.debug(`DM from ${event.pubkey} blocked - not in allowlist`);
670
- return;
671
- }
672
- }
673
- let plaintext;
674
- try {
675
- plaintext = decrypt(privateKey, event.pubkey, event.content);
676
- } catch (err) {
677
- logger2.warn(`Failed to decrypt DM from ${event.pubkey}: ${err}`);
678
- return;
679
- }
680
- logger2.debug(`Received DM from ${event.pubkey.slice(0, 8)}...: ${plaintext.slice(0, 50)}...`);
681
- if (this.runtime) {
682
- this.runtime.emitEvent("NOSTR_MESSAGE_RECEIVED" /* MESSAGE_RECEIVED */, {
683
- runtime: this.runtime,
684
- from: event.pubkey,
685
- text: plaintext,
686
- eventId: event.id,
687
- createdAt: event.created_at
688
- });
689
- }
690
- }
691
- isConnected() {
692
- return this.connected;
693
- }
694
- getPublicKey() {
695
- return this.settings?.publicKey || "";
696
- }
697
- getNpub() {
698
- const pk = this.getPublicKey();
699
- return pk ? pubkeyToNpub(pk) : "";
700
- }
701
- getRelays() {
702
- return this.settings?.relays || [];
703
- }
704
- async sendDm(options) {
705
- const settings = this.settings;
706
- const pool = this.pool;
707
- const privateKey = this.privateKey;
708
- if (!settings || !pool || !privateKey) {
709
- return {
710
- success: false,
711
- error: "Service not initialized"
712
- };
713
- }
714
- let toPubkey;
715
- try {
716
- toPubkey = normalizePubkey(options.toPubkey);
717
- } catch (err) {
718
- return {
719
- success: false,
720
- error: `Invalid target pubkey: ${err}`
721
- };
722
- }
723
- let ciphertext;
724
- try {
725
- ciphertext = encrypt(privateKey, toPubkey, options.text);
726
- } catch (err) {
727
- return {
728
- success: false,
729
- error: `Encryption failed: ${err}`
730
- };
731
- }
732
- const event = finalizeEvent({
733
- kind: 4,
734
- content: ciphertext,
735
- tags: [["p", toPubkey]],
736
- created_at: Math.floor(Date.now() / 1000)
737
- }, privateKey);
738
- const successRelays = [];
739
- const errors = [];
740
- for (const relay of settings.relays) {
741
- try {
742
- await pool.publish([relay], event);
743
- successRelays.push(relay);
744
- } catch (err) {
745
- errors.push(`${relay}: ${err}`);
746
- }
747
- }
748
- if (successRelays.length === 0) {
749
- return {
750
- success: false,
751
- error: `Failed to publish to any relay: ${errors.join("; ")}`
752
- };
753
- }
754
- logger2.debug(`DM sent to ${toPubkey.slice(0, 8)}... via ${successRelays.length} relay(s)`);
755
- if (this.runtime) {
756
- this.runtime.emitEvent("NOSTR_MESSAGE_SENT" /* MESSAGE_SENT */, {
757
- runtime: this.runtime,
758
- to: toPubkey,
759
- eventId: event.id,
760
- relays: successRelays
761
- });
762
- }
763
- return {
764
- success: true,
765
- eventId: event.id,
766
- relays: successRelays
767
- };
768
- }
769
- async publishProfile(profile) {
770
- const settings = this.settings;
771
- const pool = this.pool;
772
- const privateKey = this.privateKey;
773
- if (!settings || !pool || !privateKey) {
774
- return {
775
- success: false,
776
- error: "Service not initialized"
777
- };
778
- }
779
- const content = JSON.stringify({
780
- name: profile.name,
781
- display_name: profile.displayName,
782
- about: profile.about,
783
- picture: profile.picture,
784
- banner: profile.banner,
785
- nip05: profile.nip05,
786
- lud16: profile.lud16,
787
- website: profile.website
788
- });
789
- const event = finalizeEvent({
790
- kind: 0,
791
- content,
792
- tags: [],
793
- created_at: Math.floor(Date.now() / 1000)
794
- }, privateKey);
795
- const successRelays = [];
796
- const errors = [];
797
- for (const relay of settings.relays) {
798
- try {
799
- await pool.publish([relay], event);
800
- successRelays.push(relay);
801
- } catch (err) {
802
- errors.push(`${relay}: ${err}`);
803
- }
804
- }
805
- if (successRelays.length === 0) {
806
- return {
807
- success: false,
808
- error: `Failed to publish profile to any relay: ${errors.join("; ")}`
809
- };
810
- }
811
- logger2.info(`Profile published via ${successRelays.length} relay(s)`);
812
- if (this.runtime) {
813
- this.runtime.emitEvent("NOSTR_PROFILE_PUBLISHED" /* PROFILE_PUBLISHED */, {
814
- runtime: this.runtime,
815
- eventId: event.id,
816
- relays: successRelays
817
- });
818
- }
819
- return {
820
- success: true,
821
- eventId: event.id,
822
- relays: successRelays
823
- };
824
- }
825
- getSettings() {
826
- return this.settings;
827
- }
828
- }
829
- // src/index.ts
830
- var nostrPlugin = {
831
- name: "nostr",
832
- description: "Nostr decentralized messaging plugin for ElizaOS agents",
833
- services: [NostrService],
834
- actions: [sendDm, publishProfile],
835
- providers: [identityContextProvider, senderContextProvider],
836
- tests: [],
837
- init: async (config, _runtime) => {
838
- logger3.info("Initializing Nostr plugin...");
839
- const hasPrivateKey = Boolean(config.NOSTR_PRIVATE_KEY || process.env.NOSTR_PRIVATE_KEY);
840
- const relaysRaw = config.NOSTR_RELAYS || process.env.NOSTR_RELAYS || "";
841
- const relays = relaysRaw ? relaysRaw.split(",").length : DEFAULT_NOSTR_RELAYS.length;
842
- logger3.info(`Nostr plugin configuration:`);
843
- logger3.info(` - Private key configured: ${hasPrivateKey ? "Yes" : "No"}`);
844
- logger3.info(` - Relays: ${relays} relay(s)`);
845
- logger3.info(` - DM policy: ${config.NOSTR_DM_POLICY || process.env.NOSTR_DM_POLICY || "pairing"}`);
846
- if (!hasPrivateKey) {
847
- logger3.warn("Nostr private key not configured. Set NOSTR_PRIVATE_KEY (hex or nsec format).");
848
- }
849
- logger3.info("Nostr plugin initialized");
850
- }
851
- };
852
- var src_default = nostrPlugin;
853
- export {
854
- validatePrivateKey,
855
- splitMessageForNostr,
856
- senderContextProvider,
857
- sendDm,
858
- publishProfile,
859
- pubkeyToNpub,
860
- normalizePubkey,
861
- isValidPubkey,
862
- identityContextProvider,
863
- getPubkeyDisplayName,
864
- src_default as default,
865
- NostrService,
866
- NostrRelayError,
867
- NostrPluginError,
868
- NostrEventTypes,
869
- NostrCryptoError,
870
- NostrConfigurationError,
871
- NOSTR_SERVICE_NAME,
872
- MAX_NOSTR_MESSAGE_LENGTH,
873
- DEFAULT_NOSTR_RELAYS
874
- };
875
-
876
- //# debugId=210FEFBBF268CA8064756E2164756E21
package/dist/index.js.map DELETED
@@ -1,16 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/index.ts", "../src/actions/publishProfile.ts", "../src/types.ts", "../src/actions/sendDm.ts", "../src/providers/identityContext.ts", "../src/providers/senderContext.ts", "../src/service.ts"],
4
- "sourcesContent": [
5
- "/**\n * Nostr Plugin for ElizaOS\n *\n * Provides Nostr decentralized messaging integration for ElizaOS agents,\n * supporting encrypted DMs via NIP-04 and profile management.\n */\n\nimport type { IAgentRuntime, Plugin } from \"@elizaos/core\";\nimport { logger } from \"@elizaos/core\";\nimport { publishProfile, sendDm } from \"./actions/index.js\";\nimport {\n identityContextProvider,\n senderContextProvider,\n} from \"./providers/index.js\";\nimport { NostrService } from \"./service.js\";\nimport { DEFAULT_NOSTR_RELAYS } from \"./types.js\";\n\n// Export types\nexport * from \"./types.js\";\n\n// Export service\nexport { NostrService };\n\n// Export actions\nexport { sendDm, publishProfile };\n\n// Export providers\nexport { identityContextProvider, senderContextProvider };\n\n/**\n * Nostr plugin definition\n */\nconst nostrPlugin: Plugin = {\n name: \"nostr\",\n description: \"Nostr decentralized messaging plugin for ElizaOS agents\",\n\n services: [NostrService],\n\n actions: [sendDm, publishProfile],\n\n providers: [identityContextProvider, senderContextProvider],\n\n tests: [],\n\n /**\n * Plugin initialization hook\n */\n init: async (\n config: Record<string, string>,\n _runtime: IAgentRuntime,\n ): Promise<void> => {\n logger.info(\"Initializing Nostr plugin...\");\n\n // Log configuration status\n const hasPrivateKey = Boolean(\n config.NOSTR_PRIVATE_KEY || process.env.NOSTR_PRIVATE_KEY,\n );\n const relaysRaw = config.NOSTR_RELAYS || process.env.NOSTR_RELAYS || \"\";\n const relays = relaysRaw\n ? relaysRaw.split(\",\").length\n : DEFAULT_NOSTR_RELAYS.length;\n\n logger.info(`Nostr plugin configuration:`);\n logger.info(` - Private key configured: ${hasPrivateKey ? \"Yes\" : \"No\"}`);\n logger.info(` - Relays: ${relays} relay(s)`);\n logger.info(\n ` - DM policy: ${config.NOSTR_DM_POLICY || process.env.NOSTR_DM_POLICY || \"pairing\"}`,\n );\n\n if (!hasPrivateKey) {\n logger.warn(\n \"Nostr private key not configured. Set NOSTR_PRIVATE_KEY (hex or nsec format).\",\n );\n }\n\n logger.info(\"Nostr plugin initialized\");\n },\n};\n\nexport default nostrPlugin;\n",
6
- "/**\n * Publish profile action for Nostr plugin.\n */\n\nimport {\n type Action,\n type ActionResult,\n composePromptFromState,\n type IAgentRuntime,\n type Memory,\n ModelType,\n parseJSONObjectFromText,\n type State,\n} from \"@elizaos/core\";\nimport type { NostrService } from \"../service.js\";\nimport { NOSTR_SERVICE_NAME, type NostrProfile } from \"../types.js\";\n\nconst PUBLISH_PROFILE_TEMPLATE = `# Task: Extract Nostr profile data\nBased on the conversation, determine what profile information to update.\n\nRecent conversation:\n{{recentMessages}}\n\nExtract any of the following profile fields that should be updated:\n- name: Display name\n- about: Bio/description\n- picture: Profile picture URL\n- banner: Banner image URL\n- nip05: Nostr verification (user@domain.com)\n- lud16: Lightning address (user@domain.com)\n- website: Website URL\n\nRespond with a JSON object containing only the fields to update:\n\\`\\`\\`json\n{\n \"name\": \"optional name\",\n \"about\": \"optional bio\"\n}\n\\`\\`\\``;\n\nexport const publishProfile: Action = {\n name: \"NOSTR_PUBLISH_PROFILE\",\n similes: [\"UPDATE_NOSTR_PROFILE\", \"SET_NOSTR_PROFILE\", \"NOSTR_PROFILE\"],\n description: \"Publish or update the bot's Nostr profile (kind:0 metadata)\",\n\n validate: async (\n _runtime: IAgentRuntime,\n message: Memory,\n _state?: State,\n ): Promise<boolean> => {\n return message.content.source === \"nostr\";\n },\n\n handler: async (\n runtime: IAgentRuntime,\n message: Memory,\n state?: State,\n _options?: Record<string, unknown>,\n callback?: (response: { text: string; source?: string }) => void,\n ): Promise<ActionResult> => {\n const nostrService = runtime.getService<NostrService>(NOSTR_SERVICE_NAME);\n\n if (!nostrService || !nostrService.isConnected()) {\n if (callback) {\n callback({ text: \"Nostr service is not available.\", source: \"nostr\" });\n }\n return { success: false, error: \"Nostr service not available\" };\n }\n\n // Get or compose state\n const currentState = state ?? (await runtime.composeState(message));\n\n // Compose prompt\n const prompt = await composePromptFromState({\n template: PUBLISH_PROFILE_TEMPLATE,\n state: currentState,\n });\n\n // Extract parameters using LLM\n let profileInfo: NostrProfile | null = null;\n for (let attempt = 0; attempt < 3; attempt++) {\n const response = await runtime.useModel(ModelType.TEXT_SMALL, {\n prompt,\n });\n\n const parsed = parseJSONObjectFromText(String(response));\n if (parsed) {\n profileInfo = {\n name: parsed.name ? String(parsed.name) : undefined,\n displayName: parsed.displayName\n ? String(parsed.displayName)\n : undefined,\n about: parsed.about ? String(parsed.about) : undefined,\n picture: parsed.picture ? String(parsed.picture) : undefined,\n banner: parsed.banner ? String(parsed.banner) : undefined,\n nip05: parsed.nip05 ? String(parsed.nip05) : undefined,\n lud16: parsed.lud16 ? String(parsed.lud16) : undefined,\n website: parsed.website ? String(parsed.website) : undefined,\n };\n break;\n }\n }\n\n if (!profileInfo) {\n if (callback) {\n callback({\n text: \"I couldn't understand the profile information. Please try again.\",\n source: \"nostr\",\n });\n }\n return { success: false, error: \"Could not extract profile parameters\" };\n }\n\n // Publish profile\n const result = await nostrService.publishProfile(profileInfo);\n\n if (!result.success) {\n if (callback) {\n callback({\n text: `Failed to publish profile: ${result.error}`,\n source: \"nostr\",\n });\n }\n return { success: false, error: result.error };\n }\n\n if (callback) {\n callback({\n text: \"Profile published successfully.\",\n source: message.content.source as string,\n });\n }\n\n return {\n success: true,\n data: {\n eventId: result.eventId,\n relays: result.relays,\n profile: profileInfo,\n },\n };\n },\n\n examples: [\n [\n {\n name: \"{{user1}}\",\n content: { text: \"Update your profile name to 'Bot Assistant'\" },\n },\n {\n name: \"{{agent}}\",\n content: {\n text: \"I'll update my Nostr profile.\",\n actions: [\"NOSTR_PUBLISH_PROFILE\"],\n },\n },\n ],\n ],\n};\n",
7
- "/**\n * Type definitions for the Nostr plugin.\n */\n\nimport type { Service } from \"@elizaos/core\";\nimport { nip19 } from \"nostr-tools\";\n\n/** Maximum message length for Nostr DMs */\nexport const MAX_NOSTR_MESSAGE_LENGTH = 4000;\n\n/** Nostr service name */\nexport const NOSTR_SERVICE_NAME = \"nostr\";\n\n/** Default Nostr relays */\nexport const DEFAULT_NOSTR_RELAYS = [\n \"wss://relay.damus.io\",\n \"wss://nos.lol\",\n \"wss://relay.nostr.band\",\n];\n\n/** Event types emitted by the Nostr plugin */\nexport enum NostrEventTypes {\n MESSAGE_RECEIVED = \"NOSTR_MESSAGE_RECEIVED\",\n MESSAGE_SENT = \"NOSTR_MESSAGE_SENT\",\n RELAY_CONNECTED = \"NOSTR_RELAY_CONNECTED\",\n RELAY_DISCONNECTED = \"NOSTR_RELAY_DISCONNECTED\",\n PROFILE_PUBLISHED = \"NOSTR_PROFILE_PUBLISHED\",\n CONNECTION_READY = \"NOSTR_CONNECTION_READY\",\n}\n\n/** DM policy types */\nexport type NostrDmPolicy = \"open\" | \"pairing\" | \"allowlist\" | \"disabled\";\n\n/** Nostr profile data (kind:0) */\nexport interface NostrProfile {\n name?: string;\n displayName?: string;\n about?: string;\n picture?: string;\n banner?: string;\n nip05?: string;\n lud16?: string;\n website?: string;\n}\n\n/** Configuration settings for the Nostr plugin */\nexport interface NostrSettings {\n /** Private key in hex or nsec format */\n privateKey: string;\n /** Public key (derived from private key) */\n publicKey: string;\n /** List of relay WebSocket URLs */\n relays: string[];\n /** DM policy */\n dmPolicy: NostrDmPolicy;\n /** Allowed pubkeys for DMs */\n allowFrom: string[];\n /** Profile data */\n profile?: NostrProfile;\n /** Whether the plugin is enabled */\n enabled: boolean;\n}\n\n/** Nostr event (kind:4 for DMs) */\nexport interface NostrMessage {\n id: string;\n pubkey: string;\n content: string;\n created_at: number;\n kind: number;\n tags: string[][];\n sig: string;\n}\n\n/** Options for sending a DM */\nexport interface NostrDmSendOptions {\n /** Target pubkey (hex or npub) */\n toPubkey: string;\n /** Message text */\n text: string;\n}\n\n/** Result from sending a DM */\nexport interface NostrSendResult {\n success: boolean;\n eventId?: string;\n relays?: string[];\n error?: string;\n}\n\n/** Nostr service interface */\nexport interface INostrService extends Service {\n /** Check if the service is connected */\n isConnected(): boolean;\n\n /** Get the bot's public key in hex format */\n getPublicKey(): string;\n\n /** Get the bot's public key in npub format */\n getNpub(): string;\n\n /** Get connected relays */\n getRelays(): string[];\n\n /** Send a DM to a pubkey */\n sendDm(options: NostrDmSendOptions): Promise<NostrSendResult>;\n\n /** Publish profile (kind:0) */\n publishProfile(profile: NostrProfile): Promise<NostrSendResult>;\n}\n\n// Custom error classes\n\n/** Base error class for Nostr plugin errors */\nexport class NostrPluginError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly cause?: Error,\n ) {\n super(message);\n this.name = \"NostrPluginError\";\n }\n}\n\n/** Configuration error */\nexport class NostrConfigurationError extends NostrPluginError {\n public readonly setting?: string;\n\n constructor(message: string, setting?: string, cause?: Error) {\n super(message, \"CONFIGURATION_ERROR\", cause);\n this.name = \"NostrConfigurationError\";\n this.setting = setting;\n }\n}\n\n/** Relay error */\nexport class NostrRelayError extends NostrPluginError {\n public readonly relay?: string;\n\n constructor(message: string, relay?: string, cause?: Error) {\n super(message, \"RELAY_ERROR\", cause);\n this.name = \"NostrRelayError\";\n this.relay = relay;\n }\n}\n\n/** Cryptography error */\nexport class NostrCryptoError extends NostrPluginError {\n constructor(message: string, cause?: Error) {\n super(message, \"CRYPTO_ERROR\", cause);\n this.name = \"NostrCryptoError\";\n }\n}\n\n// Utility functions\n\n/** Check if a string is a valid Nostr pubkey (hex or npub) */\nexport function isValidPubkey(input: string): boolean {\n if (typeof input !== \"string\") {\n return false;\n }\n const trimmed = input.trim();\n\n // npub format\n if (trimmed.startsWith(\"npub1\")) {\n try {\n const decoded = nip19.decode(trimmed);\n return decoded.type === \"npub\";\n } catch {\n return false;\n }\n }\n\n // Hex format\n return /^[0-9a-fA-F]{64}$/.test(trimmed);\n}\n\n/** Normalize a pubkey to hex format (accepts npub or hex) */\nexport function normalizePubkey(input: string): string {\n const trimmed = input.trim();\n\n // npub format - decode to hex\n if (trimmed.startsWith(\"npub1\")) {\n const decoded = nip19.decode(trimmed);\n if (decoded.type !== \"npub\") {\n throw new NostrCryptoError(\"Invalid npub key\");\n }\n // Convert Uint8Array to hex string\n const data = decoded.data as unknown as Uint8Array;\n return Array.from(data)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n }\n\n // Already hex - validate and return lowercase\n if (!/^[0-9a-fA-F]{64}$/.test(trimmed)) {\n throw new NostrCryptoError(\n \"Pubkey must be 64 hex characters or npub format\",\n );\n }\n return trimmed.toLowerCase();\n}\n\n/** Convert a hex pubkey to npub format */\nexport function pubkeyToNpub(hexPubkey: string): string {\n const normalized = normalizePubkey(hexPubkey);\n return nip19.npubEncode(normalized);\n}\n\n/** Validate and normalize a private key (accepts hex or nsec format) */\nexport function validatePrivateKey(key: string): Uint8Array {\n const trimmed = key.trim();\n\n // Handle nsec (bech32) format\n if (trimmed.startsWith(\"nsec1\")) {\n const decoded = nip19.decode(trimmed);\n if (decoded.type !== \"nsec\") {\n throw new NostrCryptoError(\"Invalid nsec key: wrong type\");\n }\n return decoded.data as unknown as Uint8Array;\n }\n\n // Handle hex format\n if (!/^[0-9a-fA-F]{64}$/.test(trimmed)) {\n throw new NostrCryptoError(\n \"Private key must be 64 hex characters or nsec bech32 format\",\n );\n }\n\n // Convert hex string to Uint8Array\n const bytes = new Uint8Array(32);\n for (let i = 0; i < 32; i++) {\n bytes[i] = parseInt(trimmed.slice(i * 2, i * 2 + 2), 16);\n }\n return bytes;\n}\n\n/** Get display name for a pubkey */\nexport function getPubkeyDisplayName(pubkey: string): string {\n const normalized = normalizePubkey(pubkey);\n return `${normalized.slice(0, 8)}...${normalized.slice(-8)}`;\n}\n\n/** Split long text into chunks for Nostr */\nexport function splitMessageForNostr(\n text: string,\n maxLength: number = MAX_NOSTR_MESSAGE_LENGTH,\n): string[] {\n if (text.length <= maxLength) {\n return [text];\n }\n\n const chunks: string[] = [];\n let remaining = text;\n\n while (remaining.length > 0) {\n if (remaining.length <= maxLength) {\n chunks.push(remaining);\n break;\n }\n\n // Find a good break point\n let breakPoint = maxLength;\n const newlineIndex = remaining.lastIndexOf(\"\\n\", maxLength);\n if (newlineIndex > maxLength * 0.5) {\n breakPoint = newlineIndex + 1;\n } else {\n const spaceIndex = remaining.lastIndexOf(\" \", maxLength);\n if (spaceIndex > maxLength * 0.5) {\n breakPoint = spaceIndex + 1;\n }\n }\n\n chunks.push(remaining.slice(0, breakPoint).trimEnd());\n remaining = remaining.slice(breakPoint).trimStart();\n }\n\n return chunks;\n}\n",
8
- "/**\n * Send DM action for Nostr plugin.\n */\n\nimport {\n type Action,\n type ActionResult,\n composePromptFromState,\n type IAgentRuntime,\n logger,\n type Memory,\n ModelType,\n parseJSONObjectFromText,\n type State,\n} from \"@elizaos/core\";\nimport type { NostrService } from \"../service.js\";\nimport {\n isValidPubkey,\n NOSTR_SERVICE_NAME,\n normalizePubkey,\n splitMessageForNostr,\n} from \"../types.js\";\n\ninterface SendDmParams {\n text: string;\n toPubkey: string;\n}\n\nconst SEND_DM_TEMPLATE = `# Task: Extract Nostr DM parameters\nBased on the conversation, determine what message to send and to whom.\n\nRecent conversation:\n{{recentMessages}}\n\nExtract the following:\n- text: The message content to send\n- toPubkey: The target pubkey (npub or hex format, or \"current\" for the current conversation)\n\nRespond with a JSON object:\n\\`\\`\\`json\n{\n \"text\": \"message content here\",\n \"toPubkey\": \"npub1... or hex pubkey or current\"\n}\n\\`\\`\\``;\n\nexport const sendDm: Action = {\n name: \"NOSTR_SEND_DM\",\n similes: [\"SEND_NOSTR_DM\", \"NOSTR_MESSAGE\", \"NOSTR_TEXT\", \"DM_NOSTR\"],\n description: \"Send an encrypted direct message via Nostr (NIP-04)\",\n\n validate: async (\n _runtime: IAgentRuntime,\n message: Memory,\n _state?: State,\n ): Promise<boolean> => {\n return message.content.source === \"nostr\";\n },\n\n handler: async (\n runtime: IAgentRuntime,\n message: Memory,\n state?: State,\n _options?: Record<string, unknown>,\n callback?: (response: { text: string; source?: string }) => void,\n ): Promise<ActionResult> => {\n const nostrService = runtime.getService<NostrService>(NOSTR_SERVICE_NAME);\n\n if (!nostrService || !nostrService.isConnected()) {\n if (callback) {\n callback({ text: \"Nostr service is not available.\", source: \"nostr\" });\n }\n return { success: false, error: \"Nostr service not available\" };\n }\n\n // Get or compose state\n const currentState = state ?? (await runtime.composeState(message));\n\n // Compose prompt\n const prompt = await composePromptFromState({\n template: SEND_DM_TEMPLATE,\n state: currentState,\n });\n\n // Extract parameters using LLM\n let dmInfo: SendDmParams | null = null;\n for (let attempt = 0; attempt < 3; attempt++) {\n const response = await runtime.useModel(ModelType.TEXT_SMALL, {\n prompt,\n });\n\n const parsed = parseJSONObjectFromText(String(response));\n if (parsed?.text) {\n dmInfo = {\n text: String(parsed.text),\n toPubkey: String(parsed.toPubkey || \"current\"),\n };\n break;\n }\n }\n\n if (!dmInfo || !dmInfo.text) {\n if (callback) {\n callback({\n text: \"I couldn't understand what message you want me to send. Please try again.\",\n source: \"nostr\",\n });\n }\n return { success: false, error: \"Could not extract message parameters\" };\n }\n\n // Determine target pubkey\n let targetPubkey: string | undefined;\n if (dmInfo.toPubkey && dmInfo.toPubkey !== \"current\") {\n if (isValidPubkey(dmInfo.toPubkey)) {\n try {\n targetPubkey = normalizePubkey(dmInfo.toPubkey);\n } catch {\n // Invalid pubkey format\n }\n }\n }\n\n // Get pubkey from state context if available\n if (!targetPubkey && currentState?.data?.senderPubkey) {\n targetPubkey = currentState.data.senderPubkey as string;\n }\n\n if (!targetPubkey) {\n if (callback) {\n callback({\n text: \"I couldn't determine who to send the message to. Please specify a pubkey.\",\n source: \"nostr\",\n });\n }\n return { success: false, error: \"Could not determine target pubkey\" };\n }\n\n // Split message if too long\n const chunks = splitMessageForNostr(dmInfo.text);\n\n // Send message(s)\n let lastResult: { eventId?: string; relays?: string[] } | undefined;\n for (const chunk of chunks) {\n const result = await nostrService.sendDm({\n toPubkey: targetPubkey,\n text: chunk,\n });\n\n if (!result.success) {\n if (callback) {\n callback({\n text: `Failed to send message: ${result.error}`,\n source: \"nostr\",\n });\n }\n return { success: false, error: result.error };\n }\n\n lastResult = { eventId: result.eventId, relays: result.relays };\n logger.debug(`Sent Nostr DM: ${result.eventId}`);\n }\n\n if (callback) {\n callback({\n text: \"Message sent successfully.\",\n source: message.content.source as string,\n });\n }\n\n return {\n success: true,\n data: {\n toPubkey: targetPubkey,\n eventId: lastResult?.eventId,\n relays: lastResult?.relays,\n chunksCount: chunks.length,\n },\n };\n },\n\n examples: [\n [\n {\n name: \"{{user1}}\",\n content: { text: \"Send them a message saying 'Hello!'\" },\n },\n {\n name: \"{{agent}}\",\n content: {\n text: \"I'll send that DM via Nostr.\",\n actions: [\"NOSTR_SEND_DM\"],\n },\n },\n ],\n [\n {\n name: \"{{user1}}\",\n content: { text: \"Message npub1abc... saying 'Thanks for the zap!'\" },\n },\n {\n name: \"{{agent}}\",\n content: {\n text: \"I'll send that message to the specified pubkey.\",\n actions: [\"NOSTR_SEND_DM\"],\n },\n },\n ],\n ],\n};\n",
9
- "/**\n * Identity context provider for Nostr plugin.\n */\n\nimport type {\n IAgentRuntime,\n Memory,\n Provider,\n ProviderResult,\n State,\n} from \"@elizaos/core\";\nimport type { NostrService } from \"../service.js\";\nimport { NOSTR_SERVICE_NAME } from \"../types.js\";\n\nexport const identityContextProvider: Provider = {\n name: \"nostrIdentityContext\",\n description: \"Provides information about the bot's Nostr identity\",\n\n get: async (\n runtime: IAgentRuntime,\n message: Memory,\n state: State,\n ): Promise<ProviderResult> => {\n // Only provide context for Nostr messages\n if (message.content.source !== \"nostr\") {\n return {\n data: {},\n values: {},\n text: \"\",\n };\n }\n\n const nostrService = runtime.getService<NostrService>(NOSTR_SERVICE_NAME);\n\n if (!nostrService || !nostrService.isConnected()) {\n return {\n data: { connected: false },\n values: { connected: false },\n text: \"\",\n };\n }\n\n const agentName = state?.agentName || \"The agent\";\n const publicKey = nostrService.getPublicKey();\n const npub = nostrService.getNpub();\n const relays = nostrService.getRelays();\n\n const responseText =\n `${agentName} is connected to Nostr with pubkey ${npub}. ` +\n `Connected to ${relays.length} relay(s): ${relays.join(\", \")}. ` +\n `Nostr is a decentralized social protocol using cryptographic keys for identity.`;\n\n return {\n data: {\n publicKey,\n npub,\n relays,\n relayCount: relays.length,\n connected: true,\n },\n values: {\n publicKey,\n npub,\n relayCount: relays.length,\n },\n text: responseText,\n };\n },\n};\n",
10
- "/**\n * Sender context provider for Nostr plugin.\n */\n\nimport type {\n IAgentRuntime,\n Memory,\n Provider,\n ProviderResult,\n State,\n} from \"@elizaos/core\";\nimport type { NostrService } from \"../service.js\";\nimport {\n getPubkeyDisplayName,\n NOSTR_SERVICE_NAME,\n pubkeyToNpub,\n} from \"../types.js\";\n\nexport const senderContextProvider: Provider = {\n name: \"nostrSenderContext\",\n description:\n \"Provides information about the Nostr user in the current conversation\",\n\n get: async (\n runtime: IAgentRuntime,\n message: Memory,\n state: State,\n ): Promise<ProviderResult> => {\n // Only provide context for Nostr messages\n if (message.content.source !== \"nostr\") {\n return {\n data: {},\n values: {},\n text: \"\",\n };\n }\n\n const nostrService = runtime.getService<NostrService>(NOSTR_SERVICE_NAME);\n\n if (!nostrService || !nostrService.isConnected()) {\n return {\n data: { connected: false },\n values: { connected: false },\n text: \"\",\n };\n }\n\n const agentName = state?.agentName || \"The agent\";\n\n // Get sender pubkey from state if available\n const senderPubkey = state?.data?.senderPubkey as string | undefined;\n\n if (!senderPubkey) {\n return {\n data: { connected: true },\n values: { connected: true },\n text: \"\",\n };\n }\n\n let senderNpub = \"\";\n try {\n senderNpub = pubkeyToNpub(senderPubkey);\n } catch {\n // Use hex if npub conversion fails\n }\n\n const displayName = getPubkeyDisplayName(senderPubkey);\n\n const responseText =\n `${agentName} is talking to ${displayName} on Nostr. ` +\n `Their pubkey is ${senderNpub || senderPubkey}. ` +\n `This is an encrypted direct message conversation using NIP-04.`;\n\n return {\n data: {\n senderPubkey,\n senderNpub,\n displayName,\n isEncrypted: true,\n },\n values: {\n senderPubkey,\n senderNpub,\n displayName,\n },\n text: responseText,\n };\n },\n};\n",
11
- "/**\n * Nostr service implementation for ElizaOS.\n */\n\nimport {\n type EventPayload,\n type IAgentRuntime,\n logger,\n Service,\n} from \"@elizaos/core\";\nimport {\n type Event,\n finalizeEvent,\n getPublicKey,\n SimplePool,\n verifyEvent,\n} from \"nostr-tools\";\nimport { decrypt, encrypt } from \"nostr-tools/nip04\";\nimport {\n DEFAULT_NOSTR_RELAYS,\n type INostrService,\n NOSTR_SERVICE_NAME,\n NostrConfigurationError,\n type NostrDmPolicy,\n type NostrDmSendOptions,\n NostrEventTypes,\n type NostrProfile,\n type NostrSendResult,\n type NostrSettings,\n normalizePubkey,\n pubkeyToNpub,\n validatePrivateKey,\n} from \"./types.js\";\n\nexport class NostrService extends Service implements INostrService {\n static serviceType = NOSTR_SERVICE_NAME;\n capabilityDescription =\n \"Provides Nostr protocol integration for encrypted direct messages\";\n\n private settings: NostrSettings | null = null;\n private pool: SimplePool | null = null;\n private privateKey: Uint8Array | null = null;\n private connected = false;\n private seenEventIds = new Set<string>();\n\n /**\n * Start the Nostr service.\n */\n static async start(runtime: IAgentRuntime): Promise<NostrService> {\n logger.info(\"Starting Nostr service...\");\n const service = new NostrService(runtime);\n await service.initialize();\n return service;\n }\n\n /**\n * Initialize the service.\n */\n private async initialize(): Promise<void> {\n this.settings = this.loadSettings();\n this.validateSettings();\n\n // Initialize private key\n this.privateKey = validatePrivateKey(this.settings.privateKey);\n\n // Initialize SimplePool\n this.pool = new SimplePool();\n\n // Start subscription\n await this.startSubscription();\n\n this.connected = true;\n logger.info(\n `Nostr service started (pubkey: ${this.settings.publicKey.slice(0, 16)}...)`,\n );\n this.runtime.emitEvent(NostrEventTypes.CONNECTION_READY, {\n runtime: this.runtime,\n service: this,\n } as EventPayload);\n }\n\n /**\n * Stop the Nostr service.\n */\n async stop(): Promise<void> {\n logger.info(\"Stopping Nostr service...\");\n this.connected = false;\n\n if (this.pool) {\n this.pool.close(this.settings?.relays || []);\n this.pool = null;\n }\n\n this.privateKey = null;\n this.seenEventIds.clear();\n logger.info(\"Nostr service stopped\");\n }\n\n /**\n * Load settings from runtime configuration.\n */\n private loadSettings(): NostrSettings {\n const runtime = this.runtime;\n if (!runtime) {\n throw new NostrConfigurationError(\"Runtime not initialized\");\n }\n\n const privateKeySetting = runtime.getSetting(\"NOSTR_PRIVATE_KEY\");\n const privateKey =\n typeof privateKeySetting === \"string\"\n ? privateKeySetting\n : process.env.NOSTR_PRIVATE_KEY || \"\";\n\n const relaysRawSetting = runtime.getSetting(\"NOSTR_RELAYS\");\n const relaysRaw =\n typeof relaysRawSetting === \"string\"\n ? relaysRawSetting\n : process.env.NOSTR_RELAYS || \"\";\n\n const dmPolicySetting = runtime.getSetting(\"NOSTR_DM_POLICY\");\n const dmPolicy = (\n typeof dmPolicySetting === \"string\"\n ? dmPolicySetting\n : process.env.NOSTR_DM_POLICY || \"pairing\"\n ) as NostrDmPolicy;\n\n const allowFromRawSetting = runtime.getSetting(\"NOSTR_ALLOW_FROM\");\n const allowFromRaw =\n typeof allowFromRawSetting === \"string\"\n ? allowFromRawSetting\n : process.env.NOSTR_ALLOW_FROM || \"\";\n\n const enabledSetting = runtime.getSetting(\"NOSTR_ENABLED\");\n const enabled =\n typeof enabledSetting === \"string\"\n ? enabledSetting\n : process.env.NOSTR_ENABLED || \"true\";\n\n // Parse relays\n const relays = relaysRaw\n ? relaysRaw\n .split(\",\")\n .map((r: string) => r.trim())\n .filter(Boolean)\n : DEFAULT_NOSTR_RELAYS;\n\n // Parse allow list\n const allowFrom = allowFromRaw\n ? allowFromRaw\n .split(\",\")\n .map((p: string) => {\n try {\n return normalizePubkey(p.trim());\n } catch {\n return p.trim();\n }\n })\n .filter(Boolean)\n : [];\n\n // Derive public key\n let publicKey = \"\";\n if (privateKey) {\n try {\n const sk = validatePrivateKey(privateKey);\n publicKey = getPublicKey(sk);\n } catch {\n // Will be caught in validation\n }\n }\n\n return {\n privateKey,\n publicKey,\n relays,\n dmPolicy,\n allowFrom,\n enabled: enabled.toLowerCase() !== \"false\",\n };\n }\n\n /**\n * Validate the settings.\n */\n private validateSettings(): void {\n const settings = this.settings;\n if (!settings) {\n throw new NostrConfigurationError(\"Settings not loaded\");\n }\n\n if (!settings.privateKey) {\n throw new NostrConfigurationError(\n \"NOSTR_PRIVATE_KEY is required\",\n \"NOSTR_PRIVATE_KEY\",\n );\n }\n\n if (!settings.publicKey) {\n throw new NostrConfigurationError(\n \"Invalid private key - could not derive public key\",\n \"NOSTR_PRIVATE_KEY\",\n );\n }\n\n if (settings.relays.length === 0) {\n throw new NostrConfigurationError(\n \"At least one relay is required\",\n \"NOSTR_RELAYS\",\n );\n }\n\n // Validate relay URLs\n for (const relay of settings.relays) {\n if (!relay.startsWith(\"wss://\") && !relay.startsWith(\"ws://\")) {\n throw new NostrConfigurationError(\n `Invalid relay URL: ${relay}`,\n \"NOSTR_RELAYS\",\n );\n }\n }\n }\n\n /**\n * Start the DM subscription.\n */\n private async startSubscription(): Promise<void> {\n const settings = this.settings;\n const pool = this.pool;\n const privateKey = this.privateKey;\n\n if (!settings || !pool || !privateKey) {\n throw new NostrConfigurationError(\"Service not properly initialized\");\n }\n\n const pk = settings.publicKey;\n const since = Math.floor(Date.now() / 1000) - 120; // Last 2 minutes\n\n // Subscribe to DMs (kind:4)\n const filter = { kinds: [4], \"#p\": [pk], since };\n pool.subscribeMany(\n settings.relays,\n [filter] as unknown as Parameters<typeof pool.subscribeMany>[1],\n {\n onevent: async (event: Event) => {\n await this.handleEvent(event);\n },\n oneose: () => {\n logger.debug(\"Nostr EOSE received - initial sync complete\");\n },\n },\n );\n\n logger.info(`Subscribed to ${settings.relays.length} relay(s)`);\n }\n\n /**\n * Handle an incoming event.\n */\n private async handleEvent(event: Event): Promise<void> {\n const settings = this.settings;\n const privateKey = this.privateKey;\n\n if (!settings || !privateKey) {\n return;\n }\n\n // Dedupe\n if (this.seenEventIds.has(event.id)) {\n return;\n }\n this.seenEventIds.add(event.id);\n\n // Limit seen set size\n if (this.seenEventIds.size > 10000) {\n const toDelete = Array.from(this.seenEventIds).slice(0, 5000);\n for (const id of toDelete) {\n this.seenEventIds.delete(id);\n }\n }\n\n // Skip self-messages\n if (event.pubkey === settings.publicKey) {\n return;\n }\n\n // Verify signature\n if (!verifyEvent(event)) {\n logger.warn(`Invalid signature on event ${event.id}`);\n return;\n }\n\n // Check if this is addressed to us\n const isToUs = event.tags.some(\n (t) => t[0] === \"p\" && t[1] === settings.publicKey,\n );\n if (!isToUs) {\n return;\n }\n\n // Check DM policy\n if (settings.dmPolicy === \"disabled\") {\n logger.debug(`DM from ${event.pubkey} blocked - DMs disabled`);\n return;\n }\n\n if (settings.dmPolicy === \"allowlist\") {\n const allowed = settings.allowFrom.includes(event.pubkey);\n if (!allowed) {\n logger.debug(`DM from ${event.pubkey} blocked - not in allowlist`);\n return;\n }\n }\n\n // Decrypt the message\n let plaintext: string;\n try {\n plaintext = decrypt(privateKey, event.pubkey, event.content);\n } catch (err) {\n logger.warn(`Failed to decrypt DM from ${event.pubkey}: ${err}`);\n return;\n }\n\n logger.debug(\n `Received DM from ${event.pubkey.slice(0, 8)}...: ${plaintext.slice(0, 50)}...`,\n );\n\n // Emit event\n if (this.runtime) {\n this.runtime.emitEvent(NostrEventTypes.MESSAGE_RECEIVED, {\n runtime: this.runtime,\n from: event.pubkey,\n text: plaintext,\n eventId: event.id,\n createdAt: event.created_at,\n } as EventPayload);\n }\n }\n\n /**\n * Check if the service is connected.\n */\n isConnected(): boolean {\n return this.connected;\n }\n\n /**\n * Get the bot's public key in hex format.\n */\n getPublicKey(): string {\n return this.settings?.publicKey || \"\";\n }\n\n /**\n * Get the bot's public key in npub format.\n */\n getNpub(): string {\n const pk = this.getPublicKey();\n return pk ? pubkeyToNpub(pk) : \"\";\n }\n\n /**\n * Get connected relays.\n */\n getRelays(): string[] {\n return this.settings?.relays || [];\n }\n\n /**\n * Send a DM to a pubkey.\n */\n async sendDm(options: NostrDmSendOptions): Promise<NostrSendResult> {\n const settings = this.settings;\n const pool = this.pool;\n const privateKey = this.privateKey;\n\n if (!settings || !pool || !privateKey) {\n return {\n success: false,\n error: \"Service not initialized\",\n };\n }\n\n // Normalize the target pubkey\n let toPubkey: string;\n try {\n toPubkey = normalizePubkey(options.toPubkey);\n } catch (err) {\n return {\n success: false,\n error: `Invalid target pubkey: ${err}`,\n };\n }\n\n // Encrypt the message\n let ciphertext: string;\n try {\n ciphertext = encrypt(privateKey, toPubkey, options.text);\n } catch (err) {\n return {\n success: false,\n error: `Encryption failed: ${err}`,\n };\n }\n\n // Create the event\n const event = finalizeEvent(\n {\n kind: 4,\n content: ciphertext,\n tags: [[\"p\", toPubkey]],\n created_at: Math.floor(Date.now() / 1000),\n },\n privateKey,\n );\n\n // Publish to relays\n const successRelays: string[] = [];\n const errors: string[] = [];\n\n for (const relay of settings.relays) {\n try {\n await pool.publish([relay], event);\n successRelays.push(relay);\n } catch (err) {\n errors.push(`${relay}: ${err}`);\n }\n }\n\n if (successRelays.length === 0) {\n return {\n success: false,\n error: `Failed to publish to any relay: ${errors.join(\"; \")}`,\n };\n }\n\n logger.debug(\n `DM sent to ${toPubkey.slice(0, 8)}... via ${successRelays.length} relay(s)`,\n );\n\n if (this.runtime) {\n this.runtime.emitEvent(NostrEventTypes.MESSAGE_SENT, {\n runtime: this.runtime,\n to: toPubkey,\n eventId: event.id,\n relays: successRelays,\n } as EventPayload);\n }\n\n return {\n success: true,\n eventId: event.id,\n relays: successRelays,\n };\n }\n\n /**\n * Publish profile (kind:0).\n */\n async publishProfile(profile: NostrProfile): Promise<NostrSendResult> {\n const settings = this.settings;\n const pool = this.pool;\n const privateKey = this.privateKey;\n\n if (!settings || !pool || !privateKey) {\n return {\n success: false,\n error: \"Service not initialized\",\n };\n }\n\n // Build profile content\n const content = JSON.stringify({\n name: profile.name,\n display_name: profile.displayName,\n about: profile.about,\n picture: profile.picture,\n banner: profile.banner,\n nip05: profile.nip05,\n lud16: profile.lud16,\n website: profile.website,\n });\n\n // Create the event\n const event = finalizeEvent(\n {\n kind: 0,\n content,\n tags: [],\n created_at: Math.floor(Date.now() / 1000),\n },\n privateKey,\n );\n\n // Publish to relays\n const successRelays: string[] = [];\n const errors: string[] = [];\n\n for (const relay of settings.relays) {\n try {\n await pool.publish([relay], event);\n successRelays.push(relay);\n } catch (err) {\n errors.push(`${relay}: ${err}`);\n }\n }\n\n if (successRelays.length === 0) {\n return {\n success: false,\n error: `Failed to publish profile to any relay: ${errors.join(\"; \")}`,\n };\n }\n\n logger.info(`Profile published via ${successRelays.length} relay(s)`);\n\n if (this.runtime) {\n this.runtime.emitEvent(NostrEventTypes.PROFILE_PUBLISHED, {\n runtime: this.runtime,\n eventId: event.id,\n relays: successRelays,\n } as EventPayload);\n }\n\n return {\n success: true,\n eventId: event.id,\n relays: successRelays,\n };\n }\n\n /**\n * Get the settings.\n */\n getSettings(): NostrSettings | null {\n return this.settings;\n }\n}\n"
12
- ],
13
- "mappings": "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",
14
- "debugId": "210FEFBBF268CA8064756E2164756E21",
15
- "names": []
16
- }