nostr-mcp-server 2.0.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.
Files changed (36) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +498 -0
  3. package/build/__tests__/basic.test.js +87 -0
  4. package/build/__tests__/error-handling.test.js +145 -0
  5. package/build/__tests__/format-conversion.test.js +137 -0
  6. package/build/__tests__/integration.test.js +163 -0
  7. package/build/__tests__/mocks.js +109 -0
  8. package/build/__tests__/nip19-conversion.test.js +268 -0
  9. package/build/__tests__/nips-search.test.js +109 -0
  10. package/build/__tests__/note-creation.test.js +148 -0
  11. package/build/__tests__/note-tools-functions.test.js +173 -0
  12. package/build/__tests__/note-tools-unit.test.js +97 -0
  13. package/build/__tests__/profile-notes-simple.test.js +78 -0
  14. package/build/__tests__/profile-postnote.test.js +120 -0
  15. package/build/__tests__/profile-tools.test.js +90 -0
  16. package/build/__tests__/relay-specification.test.js +136 -0
  17. package/build/__tests__/search-nips-simple.test.js +96 -0
  18. package/build/__tests__/websocket-integration.test.js +257 -0
  19. package/build/__tests__/zap-tools-simple.test.js +72 -0
  20. package/build/__tests__/zap-tools-tests.test.js +197 -0
  21. package/build/index.js +1285 -0
  22. package/build/nips/nips-tools.js +567 -0
  23. package/build/nips-tools.js +421 -0
  24. package/build/note/note-tools.js +296 -0
  25. package/build/note-tools.js +53 -0
  26. package/build/profile/profile-tools.js +260 -0
  27. package/build/utils/constants.js +27 -0
  28. package/build/utils/conversion.js +332 -0
  29. package/build/utils/ephemeral-relay.js +438 -0
  30. package/build/utils/formatting.js +34 -0
  31. package/build/utils/index.js +6 -0
  32. package/build/utils/nip19-tools.js +117 -0
  33. package/build/utils/pool.js +55 -0
  34. package/build/zap/zap-tools.js +980 -0
  35. package/build/zap-tools.js +989 -0
  36. package/package.json +59 -0
@@ -0,0 +1,438 @@
1
+ import { z } from 'zod';
2
+ import { schnorr } from '@noble/curves/secp256k1';
3
+ import { sha256 } from '@noble/hashes/sha256';
4
+ import EventEmitter from 'events';
5
+ import { WebSocket, WebSocketServer } from 'ws';
6
+ /* ================ [ Configuration ] ================ */
7
+ const HOST = 'ws://localhost';
8
+ const DEBUG = process.env['DEBUG'] === 'true';
9
+ const VERBOSE = process.env['VERBOSE'] === 'true' || DEBUG;
10
+ // Only log output mode in non-test environments
11
+ if (process.env.NODE_ENV !== 'test') {
12
+ console.error('output mode:', DEBUG ? 'debug' : VERBOSE ? 'verbose' : 'silent');
13
+ }
14
+ /* ================ [ Schema ] ================ */
15
+ const num = z.number().max(Number.MAX_SAFE_INTEGER), str = z.string(), stamp = num.min(500_000_000), hex = str.regex(/^[0-9a-fA-F]*$/).refine(e => e.length % 2 === 0), hash = hex.refine((e) => e.length === 64), sig = hex.refine((e) => e.length === 128), tags = str.array();
16
+ const event_schema = z.object({
17
+ content: str,
18
+ created_at: stamp,
19
+ id: hash,
20
+ kind: num,
21
+ pubkey: hash,
22
+ sig: sig,
23
+ tags: tags.array()
24
+ });
25
+ const filter_schema = z.object({
26
+ ids: hash.array().optional(),
27
+ authors: hash.array().optional(),
28
+ kinds: num.array().optional(),
29
+ since: stamp.optional(),
30
+ until: stamp.optional(),
31
+ limit: num.optional(),
32
+ }).catchall(tags);
33
+ const sub_schema = z.tuple([str]).rest(filter_schema);
34
+ /* ================ [ Server Class ] ================ */
35
+ export class NostrRelay {
36
+ _emitter;
37
+ _port;
38
+ _purge;
39
+ _subs;
40
+ _wss;
41
+ _cache;
42
+ _isClosing = false;
43
+ conn;
44
+ constructor(port, purge_ival) {
45
+ this._cache = [];
46
+ this._emitter = new EventEmitter();
47
+ this._port = port;
48
+ this._purge = purge_ival ?? null;
49
+ this._subs = new Map();
50
+ this._wss = null;
51
+ this.conn = 0;
52
+ }
53
+ get cache() {
54
+ return this._cache;
55
+ }
56
+ get subs() {
57
+ return this._subs;
58
+ }
59
+ get url() {
60
+ return `${HOST}:${this._port}`;
61
+ }
62
+ get wss() {
63
+ if (this._wss === null) {
64
+ throw new Error('websocket server not initialized');
65
+ }
66
+ return this._wss;
67
+ }
68
+ async start() {
69
+ this._wss = new WebSocketServer({ port: this._port });
70
+ this._isClosing = false;
71
+ DEBUG && console.log('[ relay ] running on port:', this._port);
72
+ this.wss.on('connection', socket => {
73
+ const instance = new ClientSession(this, socket);
74
+ socket.on('message', msg => instance._handler(msg.toString()));
75
+ socket.on('error', err => instance._onerr(err));
76
+ socket.on('close', code => instance._cleanup(code));
77
+ this.conn += 1;
78
+ });
79
+ return new Promise(res => {
80
+ this.wss.on('listening', () => {
81
+ if (this._purge !== null) {
82
+ DEBUG && console.log(`[ relay ] purging events every ${this._purge} seconds`);
83
+ setInterval(() => {
84
+ this._cache = [];
85
+ }, this._purge * 1000);
86
+ }
87
+ this._emitter.emit('connected');
88
+ res(this);
89
+ });
90
+ });
91
+ }
92
+ onconnect(cb) {
93
+ this._emitter.on('connected', cb);
94
+ }
95
+ close() {
96
+ return new Promise(resolve => {
97
+ if (this._isClosing) {
98
+ DEBUG && console.log('[ relay ] already closing, skipping duplicate close call');
99
+ resolve();
100
+ return;
101
+ }
102
+ this._isClosing = true;
103
+ if (this._wss) {
104
+ // Clean up clients first
105
+ if (this._wss.clients && this._wss.clients.size > 0) {
106
+ this._wss.clients.forEach(client => {
107
+ try {
108
+ client.close(1000, 'Server shutting down');
109
+ }
110
+ catch (e) {
111
+ // Ignore errors
112
+ }
113
+ });
114
+ }
115
+ // Clear state
116
+ this._subs.clear();
117
+ this._cache = [];
118
+ // Close server with timeout
119
+ const timeout = setTimeout(() => {
120
+ DEBUG && console.log('[ relay ] server close timed out, forcing cleanup');
121
+ this._wss = null;
122
+ resolve();
123
+ }, 500);
124
+ const wss = this._wss;
125
+ this._wss = null;
126
+ wss.close(() => {
127
+ clearTimeout(timeout);
128
+ resolve();
129
+ });
130
+ }
131
+ else {
132
+ resolve();
133
+ }
134
+ });
135
+ }
136
+ store(event) {
137
+ this._cache = this._cache.concat(event).sort((a, b) => a > b ? -1 : 1);
138
+ }
139
+ }
140
+ /* ================ [ Instance Class ] ================ */
141
+ class ClientSession {
142
+ _sid;
143
+ _relay;
144
+ _socket;
145
+ _subs;
146
+ constructor(relay, socket) {
147
+ this._relay = relay;
148
+ this._sid = Math.random().toString().slice(2, 8);
149
+ this._socket = socket;
150
+ this._subs = new Set();
151
+ this.log.client('client connected');
152
+ }
153
+ get sid() {
154
+ return this._sid;
155
+ }
156
+ get relay() {
157
+ return this._relay;
158
+ }
159
+ get socket() {
160
+ return this._socket;
161
+ }
162
+ _cleanup(code) {
163
+ try {
164
+ // First remove all subscriptions associated with this client
165
+ for (const subId of this._subs) {
166
+ this.remSub(subId);
167
+ }
168
+ this._subs.clear();
169
+ // Close the socket if it's still open
170
+ if (this.socket.readyState === WebSocket.OPEN) {
171
+ this.socket.close();
172
+ }
173
+ this.relay.conn -= 1;
174
+ this.log.client(`[ ${this._sid} ]`, 'client disconnected with code:', code);
175
+ }
176
+ catch (e) {
177
+ DEBUG && console.error(`[ client ][ ${this._sid} ]`, 'error during cleanup:', e);
178
+ }
179
+ }
180
+ _handler(message) {
181
+ let verb, payload;
182
+ try {
183
+ // Try to parse as JSON
184
+ const parsed = JSON.parse(message);
185
+ // Handle NIP-46 messages (which might not follow standard Nostr format)
186
+ if (parsed && Array.isArray(parsed) && parsed.length > 0) {
187
+ // Check if it's a standard Nostr message
188
+ if (['EVENT', 'REQ', 'CLOSE'].includes(parsed[0])) {
189
+ // Handle standard Nostr messages
190
+ [verb, ...payload] = parsed;
191
+ switch (verb) {
192
+ case 'EVENT':
193
+ if (parsed.length !== 2) {
194
+ DEBUG && console.log(`[ ${this._sid} ]`, 'EVENT message missing params:', parsed);
195
+ return this.send(['NOTICE', 'invalid: EVENT message missing params']);
196
+ }
197
+ return this._onevent(parsed[1]);
198
+ case 'REQ':
199
+ if (parsed.length < 2) {
200
+ DEBUG && console.log(`[ ${this._sid} ]`, 'REQ message missing params:', parsed);
201
+ return this.send(['NOTICE', 'invalid: REQ message missing params']);
202
+ }
203
+ const sub_id = parsed[1];
204
+ const filters = parsed.slice(2);
205
+ return this._onreq(sub_id, filters);
206
+ case 'CLOSE':
207
+ if (parsed.length !== 2) {
208
+ DEBUG && console.log(`[ ${this._sid} ]`, 'CLOSE message missing params:', parsed);
209
+ return this.send(['NOTICE', 'invalid: CLOSE message missing params']);
210
+ }
211
+ return this._onclose(parsed[1]);
212
+ }
213
+ }
214
+ else {
215
+ // This could be a direct NIP-46 message, broadcast it to other clients
216
+ try {
217
+ this.relay.wss.clients.forEach(client => {
218
+ if (client !== this.socket && client.readyState === WebSocket.OPEN) {
219
+ client.send(message);
220
+ }
221
+ });
222
+ return;
223
+ }
224
+ catch (e) {
225
+ DEBUG && console.error('Error broadcasting message:', e);
226
+ return;
227
+ }
228
+ }
229
+ }
230
+ this.log.debug('unhandled message format:', message);
231
+ return this.send(['NOTICE', '', 'Unable to handle message']);
232
+ }
233
+ catch (e) {
234
+ this.log.debug('failed to parse message:\n\n', message);
235
+ return this.send(['NOTICE', '', 'Unable to parse message']);
236
+ }
237
+ }
238
+ _onclose(sub_id) {
239
+ this.log.info('closed subscription:', sub_id);
240
+ this.remSub(sub_id);
241
+ }
242
+ _onerr(err) {
243
+ this.log.info('socket encountered an error:\n\n', err);
244
+ }
245
+ _onevent(event) {
246
+ try {
247
+ // Special handling for NIP-46 events (kind 24133)
248
+ if (event.kind === 24133) {
249
+ this.relay.store(event);
250
+ // Find subscriptions that match this event
251
+ for (const [uid, sub] of this.relay.subs.entries()) {
252
+ for (const filter of sub.filters) {
253
+ if (filter.kinds?.includes(24133)) {
254
+ // Check for #p tag filter
255
+ const pTags = event.tags.filter(tag => tag[0] === 'p').map(tag => tag[1]);
256
+ const pFilters = Object.entries(filter)
257
+ .filter(([key]) => key === '#p')
258
+ .map(([_, value]) => value)
259
+ .flat();
260
+ // If there's a #p filter, make sure the event matches it
261
+ if (pFilters.length > 0 && !pTags.some(tag => pFilters.includes(tag))) {
262
+ continue;
263
+ }
264
+ // Send to matching subscription
265
+ const [clientId, subId] = uid.split('/');
266
+ sub.instance.send(['EVENT', subId, event]);
267
+ break;
268
+ }
269
+ }
270
+ }
271
+ // Send OK message
272
+ this.send(['OK', event.id, true, '']);
273
+ return;
274
+ }
275
+ // Standard event processing
276
+ this.log.client('received event id:', event.id);
277
+ this.log.debug('event:', event);
278
+ if (!verify_event(event)) {
279
+ this.log.debug('event failed validation:', event);
280
+ this.send(['OK', event.id, false, 'event failed validation']);
281
+ return;
282
+ }
283
+ this.send(['OK', event.id, true, '']);
284
+ this.relay.store(event);
285
+ for (const { filters, instance, sub_id } of this.relay.subs.values()) {
286
+ for (const filter of filters) {
287
+ if (match_filter(event, filter)) {
288
+ instance.log.client(`event matched subscription: ${sub_id}`);
289
+ instance.send(['EVENT', sub_id, event]);
290
+ }
291
+ }
292
+ }
293
+ }
294
+ catch (e) {
295
+ DEBUG && console.error('Error processing event:', e);
296
+ }
297
+ }
298
+ _onreq(sub_id, filters) {
299
+ if (filters.length === 0) {
300
+ this.log.client('request has no filters');
301
+ return;
302
+ }
303
+ this.log.client('received subscription request:', sub_id);
304
+ this.log.debug('filters:', filters);
305
+ // Add subscription
306
+ this.addSub(sub_id, ...filters);
307
+ // Check for NIP-46 subscription
308
+ const hasNip46Filter = filters.some(f => f.kinds?.includes(24133));
309
+ // For each filter
310
+ let count = 0;
311
+ for (const filter of filters) {
312
+ // Set the limit count, if any
313
+ let limitCount = filter.limit;
314
+ for (const event of this.relay.cache) {
315
+ // If limit is reached, stop sending events
316
+ if (limitCount !== undefined && limitCount <= 0)
317
+ break;
318
+ // Check if event matches filter
319
+ if (match_filter(event, filter)) {
320
+ this.send(['EVENT', sub_id, event]);
321
+ count++;
322
+ this.log.client(`event matched in cache: ${event.id}`);
323
+ this.log.client(`event matched subscription: ${sub_id}`);
324
+ // Update limit counter
325
+ if (limitCount !== undefined)
326
+ limitCount--;
327
+ }
328
+ }
329
+ }
330
+ DEBUG && this.log.debug(`sent ${count} matching events from cache`);
331
+ // Send EOSE
332
+ this.send(['EOSE', sub_id]);
333
+ }
334
+ get log() {
335
+ return {
336
+ client: (...msg) => VERBOSE && console.log(`[ client ][ ${this._sid} ]`, ...msg),
337
+ debug: (...msg) => DEBUG && console.log(`[ debug ][ ${this._sid} ]`, ...msg),
338
+ info: (...msg) => VERBOSE && console.log(`[ info ][ ${this._sid} ]`, ...msg),
339
+ };
340
+ }
341
+ addSub(sub_id, ...filters) {
342
+ const uid = `${this.sid}/${sub_id}`;
343
+ this.relay.subs.set(uid, { filters, instance: this, sub_id });
344
+ this._subs.add(sub_id);
345
+ }
346
+ remSub(subId) {
347
+ try {
348
+ const uid = `${this.sid}/${subId}`;
349
+ this.relay.subs.delete(uid);
350
+ this._subs.delete(subId);
351
+ }
352
+ catch (e) {
353
+ // Ignore errors
354
+ }
355
+ }
356
+ send(message) {
357
+ try {
358
+ if (this.socket.readyState === WebSocket.OPEN) {
359
+ this.socket.send(JSON.stringify(message));
360
+ }
361
+ }
362
+ catch (e) {
363
+ DEBUG && console.error(`Failed to send message to client ${this._sid}:`, e);
364
+ }
365
+ }
366
+ }
367
+ /* ================ [ Methods ] ================ */
368
+ function assert(value) {
369
+ if (value === false)
370
+ throw new Error('assertion failed!');
371
+ }
372
+ function match_filter(event, filter = {}) {
373
+ const { authors, ids, kinds, since, until, limit, ...rest } = filter;
374
+ const tag_filters = Object.entries(rest)
375
+ .filter(e => e[0].startsWith('#'))
376
+ .map(e => [e[0].slice(1, 2), ...e[1]]);
377
+ if (ids !== undefined && !ids.includes(event.id)) {
378
+ return false;
379
+ }
380
+ else if (since !== undefined && event.created_at < since) {
381
+ return false;
382
+ }
383
+ else if (until !== undefined && event.created_at > until) {
384
+ return false;
385
+ }
386
+ else if (authors !== undefined && !authors.includes(event.pubkey)) {
387
+ return false;
388
+ }
389
+ else if (kinds !== undefined && !kinds.includes(event.kind)) {
390
+ return false;
391
+ }
392
+ else if (tag_filters.length > 0) {
393
+ return match_tags(tag_filters, event.tags);
394
+ }
395
+ else {
396
+ return true;
397
+ }
398
+ }
399
+ function match_tags(filters, tags) {
400
+ // For each filter, we need to find at least one match in event tags
401
+ for (const [key, ...terms] of filters) {
402
+ let filterMatched = false;
403
+ // Skip empty filter terms
404
+ if (terms.length === 0) {
405
+ filterMatched = true;
406
+ continue;
407
+ }
408
+ // For each tag that matches the filter key
409
+ for (const [tag, ...params] of tags) {
410
+ if (tag !== key)
411
+ continue;
412
+ // For each term in the filter
413
+ for (const term of terms) {
414
+ // If any term matches any parameter, this filter condition is satisfied
415
+ if (params.includes(term)) {
416
+ filterMatched = true;
417
+ break;
418
+ }
419
+ }
420
+ // If we found a match for this filter, we can stop checking tags
421
+ if (filterMatched)
422
+ break;
423
+ }
424
+ // If no match was found for this filter condition, event doesn't match
425
+ if (!filterMatched)
426
+ return false;
427
+ }
428
+ // All filter conditions were satisfied
429
+ return true;
430
+ }
431
+ function verify_event(event) {
432
+ const { content, created_at, id, kind, pubkey, sig, tags } = event;
433
+ const pimg = JSON.stringify([0, pubkey, created_at, kind, tags, content]);
434
+ const dig = Buffer.from(sha256(pimg)).toString('hex');
435
+ if (dig !== id)
436
+ return false;
437
+ return schnorr.verify(sig, id, pubkey);
438
+ }
@@ -0,0 +1,34 @@
1
+ import { hexToNpub } from './conversion.js';
2
+ /**
3
+ * Format a pubkey for display, converting to npub format
4
+ * @param pubkey The pubkey in hex format
5
+ * @param useShortFormat Whether to use a shortened format
6
+ * @returns The formatted pubkey
7
+ */
8
+ export function formatPubkey(pubkey, useShortFormat = false) {
9
+ try {
10
+ if (!pubkey)
11
+ return 'unknown';
12
+ // Convert to npub
13
+ const npub = hexToNpub(pubkey);
14
+ // If converting to npub failed, return a shortened hex
15
+ if (!npub) {
16
+ return useShortFormat
17
+ ? `${pubkey.substring(0, 4)}...${pubkey.substring(60)}`
18
+ : pubkey;
19
+ }
20
+ // Return appropriately formatted npub
21
+ if (useShortFormat) {
22
+ // For short format, show the first 8 and last 4 characters of the npub
23
+ return `${npub.substring(0, 8)}...${npub.substring(npub.length - 4)}`;
24
+ }
25
+ else {
26
+ // For regular format, use the full npub
27
+ return npub;
28
+ }
29
+ }
30
+ catch (error) {
31
+ console.error('Error formatting pubkey:', error);
32
+ return 'error';
33
+ }
34
+ }
@@ -0,0 +1,6 @@
1
+ // Re-export all utilities from their modules
2
+ export * from './constants.js';
3
+ export * from './conversion.js';
4
+ export * from './formatting.js';
5
+ export * from './pool.js';
6
+ export * from './ephemeral-relay.js';
@@ -0,0 +1,117 @@
1
+ import { z } from "zod";
2
+ import { convertNip19Entity, analyzeNip19Entity } from "./conversion.js";
3
+ // Schema for convertNip19 tool
4
+ export const convertNip19ToolConfig = {
5
+ input: z.string().describe("The NIP-19 entity or hex string to convert"),
6
+ targetType: z.enum(['npub', 'nsec', 'note', 'hex', 'nprofile', 'nevent', 'naddr']).describe("The target format to convert to"),
7
+ relays: z.array(z.string()).optional().describe("Optional relay URLs for complex entities (nprofile, nevent, naddr)"),
8
+ author: z.string().optional().describe("Optional author pubkey (hex format) for nevent/naddr"),
9
+ kind: z.number().optional().describe("Optional event kind for nevent/naddr"),
10
+ identifier: z.string().optional().describe("Required identifier for naddr conversion"),
11
+ };
12
+ // Schema for analyzeNip19 tool
13
+ export const analyzeNip19ToolConfig = {
14
+ input: z.string().describe("The NIP-19 entity or hex string to analyze"),
15
+ };
16
+ /**
17
+ * Convert any NIP-19 entity to another format
18
+ */
19
+ export async function convertNip19(input, targetType, relays, author, kind, identifier) {
20
+ try {
21
+ const options = {
22
+ input,
23
+ targetType,
24
+ entityData: {
25
+ ...(relays && { relays }),
26
+ ...(author && { author }),
27
+ ...(kind && { kind }),
28
+ ...(identifier && { identifier })
29
+ }
30
+ };
31
+ const result = convertNip19Entity(options);
32
+ if (!result.success) {
33
+ return {
34
+ success: false,
35
+ message: result.message || 'Conversion failed'
36
+ };
37
+ }
38
+ return {
39
+ success: true,
40
+ message: result.message || 'Conversion successful',
41
+ result: result.result,
42
+ originalType: result.originalType,
43
+ data: result.data
44
+ };
45
+ }
46
+ catch (error) {
47
+ return {
48
+ success: false,
49
+ message: `Error during conversion: ${error instanceof Error ? error.message : 'Unknown error'}`
50
+ };
51
+ }
52
+ }
53
+ /**
54
+ * Analyze any NIP-19 entity to get its type and decoded data
55
+ */
56
+ export async function analyzeNip19(input) {
57
+ try {
58
+ const result = analyzeNip19Entity(input);
59
+ if (!result.success) {
60
+ return {
61
+ success: false,
62
+ message: result.message || 'Analysis failed'
63
+ };
64
+ }
65
+ return {
66
+ success: true,
67
+ message: result.message || 'Analysis successful',
68
+ type: result.originalType,
69
+ data: result.data
70
+ };
71
+ }
72
+ catch (error) {
73
+ return {
74
+ success: false,
75
+ message: `Error during analysis: ${error instanceof Error ? error.message : 'Unknown error'}`
76
+ };
77
+ }
78
+ }
79
+ /**
80
+ * Format analysis result for display
81
+ */
82
+ export function formatAnalysisResult(type, data) {
83
+ switch (type) {
84
+ case 'hex':
85
+ return `Hex String: ${data}`;
86
+ case 'npub':
87
+ return `Public Key (npub): ${data}`;
88
+ case 'nsec':
89
+ return `Private Key (nsec): ${data}`;
90
+ case 'note':
91
+ return `Note ID: ${data}`;
92
+ case 'nprofile':
93
+ return [
94
+ `Profile Entity:`,
95
+ ` Public Key: ${data.pubkey}`,
96
+ ` Relays: ${data.relays?.length ? data.relays.join(', ') : 'None'}`
97
+ ].join('\n');
98
+ case 'nevent':
99
+ return [
100
+ `Event Entity:`,
101
+ ` Event ID: ${data.id}`,
102
+ ` Author: ${data.author || 'Not specified'}`,
103
+ ` Kind: ${data.kind || 'Not specified'}`,
104
+ ` Relays: ${data.relays?.length ? data.relays.join(', ') : 'None'}`
105
+ ].join('\n');
106
+ case 'naddr':
107
+ return [
108
+ `Address Entity:`,
109
+ ` Identifier: ${data.identifier}`,
110
+ ` Public Key: ${data.pubkey}`,
111
+ ` Kind: ${data.kind}`,
112
+ ` Relays: ${data.relays?.length ? data.relays.join(', ') : 'None'}`
113
+ ].join('\n');
114
+ default:
115
+ return `Unknown type: ${type}`;
116
+ }
117
+ }
@@ -0,0 +1,55 @@
1
+ import { RelayPool } from "snstr";
2
+ /**
3
+ * Extended RelayPool with compatibility methods for existing codebase
4
+ */
5
+ export class CompatibleRelayPool extends RelayPool {
6
+ constructor(relays = []) {
7
+ super(relays);
8
+ }
9
+ /**
10
+ * Compatibility method to match existing codebase API
11
+ * Maps to snstr's querySync method
12
+ */
13
+ async get(relays, filter) {
14
+ try {
15
+ const events = await this.querySync(relays, filter, { timeout: 8000 });
16
+ return events.length > 0 ? events[0] : null;
17
+ }
18
+ catch (error) {
19
+ console.error('Error in pool.get:', error);
20
+ return null;
21
+ }
22
+ }
23
+ /**
24
+ * Compatibility method to match existing codebase API
25
+ * Maps to snstr's querySync method for multiple events
26
+ */
27
+ async getMany(relays, filter) {
28
+ try {
29
+ return await this.querySync(relays, filter, { timeout: 8000 });
30
+ }
31
+ catch (error) {
32
+ console.error('Error in pool.getMany:', error);
33
+ return [];
34
+ }
35
+ }
36
+ /**
37
+ * Compatibility method to match existing codebase API
38
+ * Maps to snstr's close method but ignores relay parameter
39
+ */
40
+ async close(_relays) {
41
+ try {
42
+ await super.close();
43
+ }
44
+ catch (error) {
45
+ console.error('Error in pool.close:', error);
46
+ }
47
+ }
48
+ }
49
+ /**
50
+ * Create a fresh RelayPool instance for making Nostr requests
51
+ * @returns A new CompatibleRelayPool instance
52
+ */
53
+ export function getFreshPool(relays = []) {
54
+ return new CompatibleRelayPool(relays);
55
+ }