nostr-mcp-server 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,12 +1,15 @@
1
1
  # Nostr MCP Server
2
2
 
3
+ [![npm version](https://badge.fury.io/js/nostr-mcp-server.svg)](https://www.npmjs.com/package/nostr-mcp-server)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
3
6
  A Model Context Protocol (MCP) server that provides Nostr capabilities to LLMs like Claude.
4
7
 
5
8
  https://github.com/user-attachments/assets/1d2d47d0-c61b-44e2-85be-5985d2a81c64
6
9
 
7
10
  ## Features
8
11
 
9
- This server implements 18 tools for interacting with the Nostr network:
12
+ This server implements 17 tools for interacting with the Nostr network:
10
13
 
11
14
  ### Reading & Querying Tools
12
15
  1. `getProfile`: Fetches a user's profile information by public key
@@ -15,26 +18,25 @@ This server implements 18 tools for interacting with the Nostr network:
15
18
  4. `getReceivedZaps`: Fetches zaps received by a user, including detailed payment information
16
19
  5. `getSentZaps`: Fetches zaps sent by a user, including detailed payment information
17
20
  6. `getAllZaps`: Fetches both sent and received zaps for a user, clearly labeled with direction and totals
18
- 7. `searchNips`: Search through Nostr Implementation Possibilities (NIPs) with relevance scoring
19
21
 
20
22
  ### Identity & Profile Management Tools
21
- 8. `createKeypair`: Generate new Nostr keypairs in hex and/or npub/nsec format
22
- 9. `createProfile`: Create a new Nostr profile (kind 0 event) with metadata
23
- 10. `updateProfile`: Update an existing Nostr profile with new metadata
23
+ 7. `createKeypair`: Generate new Nostr keypairs in hex and/or npub/nsec format
24
+ 8. `createProfile`: Create a new Nostr profile (kind 0 event) with metadata
25
+ 9. `updateProfile`: Update an existing Nostr profile with new metadata
24
26
 
25
- ### Note Creation & Publishing Tools
26
- 11. `createNote`: Create unsigned kind 1 note events with specified content and tags
27
- 12. `signNote`: Sign note events with a private key, generating cryptographically valid signatures
28
- 13. `publishNote`: Publish signed notes to specified Nostr relays
29
- 14. `postNote`: All-in-one authenticated note posting using an existing private key (nsec/hex)
27
+ ### Note Creation & Publishing Tools
28
+ 10. `createNote`: Create unsigned kind 1 note events with specified content and tags
29
+ 11. `signNote`: Sign note events with a private key, generating cryptographically valid signatures
30
+ 12. `publishNote`: Publish signed notes to specified Nostr relays
31
+ 13. `postNote`: All-in-one authenticated note posting using an existing private key (nsec/hex)
30
32
 
31
33
  ### Anonymous Tools
32
- 15. `sendAnonymousZap`: Prepare an anonymous zap to a profile or event, generating a lightning invoice for payment
33
- 16. `postAnonymousNote`: Post an anonymous note using a randomly generated one-time keypair
34
+ 14. `sendAnonymousZap`: Prepare an anonymous zap to a profile or event, generating a lightning invoice for payment
35
+ 15. `postAnonymousNote`: Post an anonymous note using a randomly generated one-time keypair
34
36
 
35
37
  ### NIP-19 Entity Tools
36
- 17. `convertNip19`: Convert between different NIP-19 entity formats (hex, npub, nsec, note, nprofile, nevent, naddr)
37
- 18. `analyzeNip19`: Analyze and decode any NIP-19 entity to understand its type and contents
38
+ 16. `convertNip19`: Convert between different NIP-19 entity formats (hex, npub, nsec, note, nprofile, nevent, naddr)
39
+ 17. `analyzeNip19`: Analyze and decode any NIP-19 entity to understand its type and contents
38
40
 
39
41
  All tools fully support both hex public keys and npub format, with user-friendly display of Nostr identifiers.
40
42
 
@@ -46,7 +48,21 @@ All tools fully support both hex public keys and npub format, with user-friendly
46
48
  npm install -g nostr-mcp-server
47
49
  ```
48
50
 
49
- ### Option 2: Install from source
51
+ ### Option 2: Install from source (using Bun - Recommended)
52
+
53
+ ```bash
54
+ # Clone the repository
55
+ git clone https://github.com/austinkelsay/nostr-mcp-server.git
56
+ cd nostr-mcp-server
57
+
58
+ # Install dependencies
59
+ bun install
60
+
61
+ # Build the project
62
+ bun run build
63
+ ```
64
+
65
+ ### Option 3: Install from source (using npm)
50
66
 
51
67
  ```bash
52
68
  # Clone the repository
@@ -214,9 +230,6 @@ Once configured, you can ask Claude to use the Nostr tools by making requests li
214
230
  - "How many zaps has npub1qny3tkh0acurzla8x3zy4nhrjz5zd8ne6dvrjehx9n9hr3lnj08qwuzwc8 received?"
215
231
  - "Show me the zaps sent by npub1qny3tkh0acurzla8x3zy4nhrjz5zd8ne6dvrjehx9n9hr3lnj08qwuzwc8"
216
232
  - "Show me all zaps (both sent and received) for npub1qny3tkh0acurzla8x3zy4nhrjz5zd8ne6dvrjehx9n9hr3lnj08qwuzwc8"
217
- - "Search for NIPs about zaps"
218
- - "What NIPs are related to long-form content?"
219
- - "Show me NIP-23 with full content"
220
233
 
221
234
  ### Identity & Profile Management
222
235
  - "Generate a new Nostr keypair for me"
@@ -349,12 +362,6 @@ For zap queries, you can enable extra validation and debugging:
349
362
 
350
363
  - "Show me all zaps for npub1qny3tkh0acurzla8x3zy4nhrjz5zd8ne6dvrjehx9n9hr3lnj08qwuzwc8 with validation and debug enabled"
351
364
 
352
- For NIP searches, you can control the number of results and include full content:
353
-
354
- - "Search for NIPs about zaps with full content"
355
- - "Show me the top 5 NIPs about relays"
356
- - "What NIPs are related to encryption? Show me 15 results"
357
-
358
365
  ## Limitations
359
366
 
360
367
  - The server has a default 8-second timeout for queries to prevent hanging
@@ -418,7 +425,6 @@ To modify or extend this server:
418
425
  - `profile/profile-tools.ts`: Identity management, keypair generation, profile creation ([Documentation](./profile/README.md))
419
426
  - `note/note-tools.ts`: Note creation, signing, publishing, and reading functionality ([Documentation](./note/README.md))
420
427
  - `zap/zap-tools.ts`: Zap-related functionality ([Documentation](./zap/README.md))
421
- - `nips/nips-tools.ts`: Functions for searching NIPs ([Documentation](./nips/README.md))
422
428
  - `utils/`: Shared utility functions
423
429
  - `constants.ts`: Global constants and relay configurations
424
430
  - `conversion.ts`: NIP-19 entity conversion utilities (hex/npub/nprofile/nevent/naddr)
@@ -427,23 +433,23 @@ To modify or extend this server:
427
433
  - `pool.ts`: Nostr connection pool management
428
434
  - `ephemeral-relay.ts`: In-memory Nostr relay for testing
429
435
 
430
- 2. Run `npm run build` to compile
436
+ 2. Run `bun run build` (or `npm run build`) to compile
431
437
 
432
438
  3. Restart Claude for Desktop or Cursor to pick up your changes
433
439
 
434
440
  ## Testing
435
441
 
436
- We've implemented a comprehensive test suite using Jest to test both basic functionality and integration with the Nostr protocol:
442
+ We've implemented a comprehensive test suite using Bun's native test runner to test both basic functionality and integration with the Nostr protocol:
437
443
 
438
444
  ```bash
439
- # Run all tests
440
- npm test
445
+ # Run all tests (using Bun - Recommended)
446
+ bun test
441
447
 
442
448
  # Run a specific test file
443
- npm test -- __tests__/basic.test.ts
449
+ bun test __tests__/basic.test.ts
444
450
 
445
451
  # Run integration tests
446
- npm test -- __tests__/integration.test.ts
452
+ bun test __tests__/integration.test.ts
447
453
  ```
448
454
 
449
455
  The test suite includes:
@@ -458,7 +464,6 @@ The test suite includes:
458
464
  - `profile-postnote.test.ts` - Tests authenticated note posting with existing private keys
459
465
  - `zap-tools-simple.test.ts` - Tests zap processing and anonymous zap preparation
460
466
  - `zap-tools-tests.test.ts` - Tests zap validation, parsing, and direction determination
461
- - `search-nips-simple.test.ts` - Tests NIPs search functionality with relevance scoring
462
467
  - `nip19-conversion.test.ts` - Tests NIP-19 entity conversion and analysis (28 test cases)
463
468
 
464
469
  ### Integration Tests
@@ -492,7 +497,6 @@ The codebase is organized into modules:
492
497
  - [`profile/`](./profile/README.md): Identity management, keypair generation, and profile creation
493
498
  - [`note/`](./note/README.md): Note creation, signing, publishing, and reading functionality
494
499
  - [`zap/`](./zap/README.md): Zap handling and anonymous zapping
495
- - [`nips/`](./nips/README.md): NIPs search and caching functionality
496
500
  - Common utilities in the `utils/` directory
497
501
 
498
502
  This modular structure makes the codebase more maintainable, reduces duplication, and enables easier feature extensions. For detailed information about each module's features and implementation, see their respective documentation.
@@ -1,6 +1,6 @@
1
- import { jest } from '@jest/globals';
1
+ import { describe, test, expect, mock } from 'bun:test';
2
2
  // Mock the formatProfile function
3
- const mockFormatProfile = jest.fn((profile) => {
3
+ const mockFormatProfile = mock((profile) => {
4
4
  const content = typeof profile.content === 'string'
5
5
  ? JSON.parse(profile.content)
6
6
  : profile.content;
@@ -1,6 +1,7 @@
1
+ import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
1
2
  import { NostrRelay } from '../utils/ephemeral-relay.js';
2
3
  import { schnorr } from '@noble/curves/secp256k1';
3
- import { randomBytes } from 'crypto';
4
+ import { randomBytes } from 'node:crypto';
4
5
  import { sha256 } from '@noble/hashes/sha256';
5
6
  // Generate a keypair for testing
6
7
  function generatePrivateKey() {
@@ -1,5 +1,3 @@
1
- // Mock Nostr events and utility functions for testing
2
- import { jest } from '@jest/globals';
3
1
  export const MOCK_HEX_PUBKEY = '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e';
4
2
  export const MOCK_NPUB = 'npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyjh6w6';
5
3
  export const mockProfile = {
@@ -64,14 +62,6 @@ export const mockZapReceipt = {
64
62
  content: '',
65
63
  sig: 'mock_signature'
66
64
  };
67
- // Mock pool functions
68
- export const mockPool = {
69
- get: jest.fn(),
70
- querySync: jest.fn(),
71
- close: jest.fn()
72
- };
73
- // Mock for getFreshPool function
74
- export const getFreshPoolMock = jest.fn().mockReturnValue(mockPool);
75
65
  // Mock response for lightning service for anonymous zaps
76
66
  export const mockLightningServiceResponse = {
77
67
  callback: 'https://example.com/callback',
@@ -1,3 +1,4 @@
1
+ import { describe, it, expect, beforeAll } from 'bun:test';
1
2
  import { convertNip19, analyzeNip19 } from '../utils/nip19-tools.js';
2
3
  import { generateKeypair, encodePublicKey, encodePrivateKey, encodeNoteId, encodeProfile, encodeEvent, encodeAddress } from 'snstr';
3
4
  describe('NIP-19 Conversion Tools', () => {
@@ -1,3 +1,4 @@
1
+ import { describe, it, expect } from 'bun:test';
1
2
  import { createNote, signNote, publishNote } from '../note/note-tools.js';
2
3
  import { createKeypair } from '../profile/profile-tools.js';
3
4
  describe('Note Creation Tools', () => {
@@ -1,16 +1,16 @@
1
- import { jest } from '@jest/globals';
1
+ import { mock, describe, it, expect, beforeAll, beforeEach } from 'bun:test';
2
2
  import { generateKeypair } from 'snstr';
3
3
  // Mock the pool to prevent real WebSocket connections
4
4
  const mockPool = {
5
- close: jest.fn(),
6
- publish: jest.fn().mockReturnValue([
5
+ close: mock(() => { }),
6
+ publish: mock(() => [
7
7
  Promise.resolve({ success: true }),
8
8
  Promise.resolve({ success: true })
9
9
  ])
10
10
  };
11
11
  // Mock the pool module directly
12
- jest.mock('../utils/pool.js', () => ({
13
- getFreshPool: jest.fn(() => mockPool)
12
+ mock.module('../utils/pool.js', () => ({
13
+ getFreshPool: mock(() => mockPool)
14
14
  }));
15
15
  // Now import the functions that use the mocked module
16
16
  import { formatProfile, formatNote, createNote, signNote, publishNote } from '../note/note-tools.js';
@@ -19,6 +19,11 @@ describe('Note Tools Functions', () => {
19
19
  beforeAll(async () => {
20
20
  testKeys = await generateKeypair();
21
21
  });
22
+ beforeEach(() => {
23
+ // Reset mock state between tests
24
+ mockPool.close.mockClear();
25
+ mockPool.publish.mockClear();
26
+ });
22
27
  describe('formatProfile', () => {
23
28
  it('should format a complete profile', () => {
24
29
  const profileEvent = {
@@ -1,3 +1,4 @@
1
+ import { describe, it, expect, beforeAll } from 'bun:test';
1
2
  import { generateKeypair } from 'snstr';
2
3
  import { formatProfile, formatNote, } from '../note/note-tools.js';
3
4
  describe('Note Tools Unit Tests', () => {
@@ -1,3 +1,4 @@
1
+ import { describe, test, expect } from 'bun:test';
1
2
  // Simple getProfile function for testing
2
3
  const getProfile = (pubkey) => {
3
4
  return Promise.resolve({
@@ -75,4 +76,3 @@ describe('Profile and Notes Functions', () => {
75
76
  }
76
77
  });
77
78
  });
78
- export {};
@@ -1,3 +1,4 @@
1
+ import { describe, it, expect } from 'bun:test';
1
2
  import { postNote } from '../profile/profile-tools.js';
2
3
  import { createKeypair } from '../profile/profile-tools.js';
3
4
  describe('Profile postNote Tool', () => {
@@ -1,3 +1,4 @@
1
+ import { describe, it, expect } from 'bun:test';
1
2
  import { createKeypair, createProfile, updateProfile } from '../profile/profile-tools.js';
2
3
  import { schnorr } from '@noble/curves/secp256k1';
3
4
  describe('Profile Tools', () => {
@@ -1,6 +1,7 @@
1
+ import { describe, test, expect, beforeAll, afterAll, beforeEach, afterEach } from 'bun:test';
1
2
  import { NostrRelay } from '../utils/ephemeral-relay.js';
2
3
  import { schnorr } from '@noble/curves/secp256k1';
3
- import { randomBytes } from 'crypto';
4
+ import { randomBytes } from 'node:crypto';
4
5
  import { sha256 } from '@noble/hashes/sha256';
5
6
  import WebSocket from 'ws';
6
7
  // Generate a keypair for testing
@@ -1,3 +1,4 @@
1
+ import { describe, test, expect } from 'bun:test';
1
2
  // Mock the processZapReceipt function
2
3
  const processZapReceipt = (receipt, targetPubkey) => {
3
4
  const targetTag = receipt.tags?.find(tag => tag[0] === 'p' && tag[1] === targetPubkey);
@@ -69,4 +70,3 @@ describe('Zap Tools Functions', () => {
69
70
  expect(result.comment).toBe('');
70
71
  });
71
72
  });
72
- export {};
@@ -1,3 +1,4 @@
1
+ import { describe, it, expect, beforeAll } from 'bun:test';
1
2
  import { generateKeypair } from 'snstr';
2
3
  import { validateZapReceipt, formatZapReceipt, parseZapRequestData, determineZapDirection } from '../zap/zap-tools.js';
3
4
  describe('Zap Processing Functions', () => {
@@ -0,0 +1,3 @@
1
+ // Test setup for Bun test runner
2
+ process.env.NODE_ENV = 'test';
3
+ export {};
package/build/index.js CHANGED
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { z } from "zod";
5
4
  import WebSocket from "ws";
6
- import { searchNips, formatNipResult } from "./nips/nips-tools.js";
7
5
  import { KINDS, DEFAULT_RELAYS, QUERY_TIMEOUT, getFreshPool, npubToHex, formatPubkey } from "./utils/index.js";
8
6
  import { formatZapReceipt, processZapReceipt, validateZapReceipt, prepareAnonymousZap, sendAnonymousZapToolConfig, getReceivedZapsToolConfig, getSentZapsToolConfig, getAllZapsToolConfig } from "./zap/zap-tools.js";
9
7
  import { formatProfile, formatNote, getProfileToolConfig, getKind1NotesToolConfig, getLongFormNotesToolConfig, postAnonymousNoteToolConfig, postAnonymousNote, createNote, signNote, publishNote, createNoteToolConfig, signNoteToolConfig, publishNoteToolConfig } from "./note/note-tools.js";
10
8
  import { createKeypair, createProfile, updateProfile, postNote, createKeypairToolConfig, createProfileToolConfig, updateProfileToolConfig, postNoteToolConfig } from "./profile/profile-tools.js";
11
9
  import { convertNip19, analyzeNip19, convertNip19ToolConfig, analyzeNip19ToolConfig, formatAnalysisResult } from "./utils/nip19-tools.js";
12
- // Set WebSocket implementation for Node.js
13
- globalThis.WebSocket = WebSocket;
10
+ // Set WebSocket implementation for Node.js (Bun has native WebSocket)
11
+ if (typeof globalThis.WebSocket === 'undefined') {
12
+ globalThis.WebSocket = WebSocket;
13
+ }
14
14
  // Create server instance
15
15
  const server = new McpServer({
16
16
  name: "nostr",
@@ -682,47 +682,6 @@ server.tool("getLongFormNotes", "Get long-form notes (kind 30023) by public key"
682
682
  await pool.close();
683
683
  }
684
684
  });
685
- server.tool("searchNips", "Search through Nostr Implementation Possibilities (NIPs)", {
686
- query: z.string().describe("Search query to find relevant NIPs"),
687
- limit: z.number().min(1).max(50).default(10).describe("Maximum number of results to return"),
688
- includeContent: z.boolean().default(false).describe("Whether to include the full content of each NIP in the results"),
689
- }, async ({ query, limit, includeContent }) => {
690
- try {
691
- console.error(`Searching NIPs for: "${query}"`);
692
- const results = await searchNips(query, limit);
693
- if (results.length === 0) {
694
- return {
695
- content: [
696
- {
697
- type: "text",
698
- text: `No NIPs found matching "${query}". Try different search terms or check the NIPs repository for the latest updates.`,
699
- },
700
- ],
701
- };
702
- }
703
- // Format results using the new formatter
704
- const formattedResults = results.map(result => formatNipResult(result, includeContent)).join("\n\n");
705
- return {
706
- content: [
707
- {
708
- type: "text",
709
- text: `Found ${results.length} matching NIPs:\n\n${formattedResults}`,
710
- },
711
- ],
712
- };
713
- }
714
- catch (error) {
715
- console.error("Error searching NIPs:", error);
716
- return {
717
- content: [
718
- {
719
- type: "text",
720
- text: `Error searching NIPs: ${error instanceof Error ? error.message : "Unknown error"}`,
721
- },
722
- ],
723
- };
724
- }
725
- });
726
685
  server.tool("sendAnonymousZap", "Prepare an anonymous zap to a profile or event", sendAnonymousZapToolConfig, async ({ target, amountSats, comment, relays }) => {
727
686
  // Use supplied relays or defaults
728
687
  const relaysToUse = relays || DEFAULT_RELAYS;
@@ -1,7 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { decode } from "light-bolt11-decoder";
3
3
  import { decode as nip19decode, generateKeypair, createEvent, getEventHash, signEvent } from "snstr";
4
- import fetch from "node-fetch";
5
4
  import { KINDS, DEFAULT_RELAYS, FALLBACK_RELAYS, QUERY_TIMEOUT, getFreshPool, npubToHex, hexToNpub } from "../utils/index.js";
6
5
  // Simple cache implementation for zap receipts
7
6
  export class ZapCache {
package/package.json CHANGED
@@ -1,20 +1,21 @@
1
1
  {
2
2
  "name": "nostr-mcp-server",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "A Model Context Protocol (MCP) server that provides Nostr capabilities to LLMs like Claude",
5
5
  "main": "build/index.js",
6
6
  "type": "module",
7
7
  "bin": {
8
- "nostr-mcp-server": "./build/index.js"
8
+ "nostr-mcp-server": "build/index.js"
9
9
  },
10
10
  "scripts": {
11
- "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --config=jest.config.cjs --forceExit",
11
+ "test": "bun test",
12
12
  "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
13
- "start": "node build/index.js",
14
- "prerelease": "npm test && npm run build",
15
- "release:patch": "npm run prerelease && npm version patch",
16
- "release:minor": "npm run prerelease && npm version minor",
17
- "release:major": "npm run prerelease && npm version major"
13
+ "start": "bun build/index.js",
14
+ "start:node": "node build/index.js",
15
+ "prerelease": "bun test && bun run build",
16
+ "release:patch": "bun run prerelease && npm version patch",
17
+ "release:minor": "bun run prerelease && npm version minor",
18
+ "release:major": "bun run prerelease && npm version major"
18
19
  },
19
20
  "files": [
20
21
  "build"
@@ -26,7 +27,7 @@
26
27
  "claude",
27
28
  "llm"
28
29
  ],
29
- "author": "Austin Kelsay <austinkelsay@gmail.com>",
30
+ "author": "Austin Kelsay <austinkelsay@protonmail.com>",
30
31
  "repository": {
31
32
  "type": "git",
32
33
  "url": "git+https://github.com/AustinKelsay/nostr-mcp-server.git"
@@ -35,25 +36,21 @@
35
36
  "url": "https://github.com/AustinKelsay/nostr-mcp-server/issues"
36
37
  },
37
38
  "homepage": "https://github.com/AustinKelsay/nostr-mcp-server#readme",
38
- "license": "ISC",
39
+ "license": "MIT",
39
40
  "dependencies": {
40
41
  "@modelcontextprotocol/sdk": "^1.11.0",
41
42
  "@noble/curves": "^1.8.2",
42
43
  "@noble/hashes": "^1.7.2",
43
44
  "@scure/base": "^1.2.4",
44
- "@types/node-fetch": "^2.6.12",
45
45
  "light-bolt11-decoder": "^3.2.0",
46
- "node-fetch": "^3.3.2",
47
46
  "snstr": "^0.1.0",
48
47
  "ws": "^8.16.1",
49
48
  "zod": "^3.24.2"
50
49
  },
51
50
  "devDependencies": {
52
- "@types/jest": "^29.5.14",
51
+ "@types/bun": "^1.1.14",
53
52
  "@types/node": "^22.13.11",
54
53
  "@types/ws": "^8.5.10",
55
- "jest": "^29.7.0",
56
- "ts-jest": "^29.3.2",
57
54
  "typescript": "^5.8.2"
58
55
  }
59
56
  }
@@ -1,145 +0,0 @@
1
- import { describe, expect, test, jest, beforeEach } from '@jest/globals';
2
- // Mock the actual Nostr pool functionality
3
- jest.mock('../utils/pool.js', () => {
4
- return {
5
- getRelayPool: jest.fn(() => ({
6
- connect: jest.fn(),
7
- close: jest.fn(),
8
- subscribeMany: jest.fn(),
9
- })),
10
- };
11
- });
12
- // Mock setTimeout for testing timeouts
13
- jest.useFakeTimers();
14
- // Helper function for creating timed out promises
15
- const createTimedOutPromise = () => new Promise((resolve, reject) => {
16
- setTimeout(() => reject(new Error('Request timed out')), 10000);
17
- });
18
- // Mock getProfile that simulates various error scenarios
19
- const mockGetProfile = jest.fn();
20
- // Helpers for common error scenarios
21
- const simulateTimeoutError = () => {
22
- mockGetProfile.mockImplementationOnce(() => createTimedOutPromise());
23
- };
24
- const simulateNetworkError = () => {
25
- mockGetProfile.mockImplementationOnce(() => Promise.reject(new Error('Failed to connect to relay')));
26
- };
27
- const simulateInvalidPubkey = () => {
28
- mockGetProfile.mockImplementationOnce(() => Promise.reject(new Error('Invalid pubkey format')));
29
- };
30
- const simulateMalformedEvent = () => {
31
- mockGetProfile.mockImplementationOnce(() => Promise.resolve({
32
- error: 'Malformed event',
33
- details: 'Event missing required signature'
34
- }));
35
- };
36
- describe('Error Handling and Edge Cases', () => {
37
- beforeEach(() => {
38
- mockGetProfile.mockReset();
39
- });
40
- test('timeout handling', async () => {
41
- simulateTimeoutError();
42
- try {
43
- jest.useFakeTimers();
44
- const profilePromise = mockGetProfile('valid-pubkey');
45
- // Fast-forward time to trigger timeout
46
- jest.advanceTimersByTime(10000);
47
- await profilePromise;
48
- fail('Expected promise to reject with timeout error');
49
- }
50
- catch (error) {
51
- expect(error.message).toContain('timed out');
52
- }
53
- });
54
- test('invalid pubkey format handling', async () => {
55
- simulateInvalidPubkey();
56
- try {
57
- await mockGetProfile('invalid-pubkey-format');
58
- fail('Expected promise to reject with invalid pubkey error');
59
- }
60
- catch (error) {
61
- expect(error.message).toContain('Invalid pubkey');
62
- }
63
- });
64
- test('network error handling', async () => {
65
- simulateNetworkError();
66
- try {
67
- await mockGetProfile('valid-pubkey');
68
- fail('Expected promise to reject with network error');
69
- }
70
- catch (error) {
71
- expect(error.message).toContain('Failed to connect');
72
- }
73
- });
74
- test('malformed event handling', async () => {
75
- simulateMalformedEvent();
76
- const result = await mockGetProfile('valid-pubkey');
77
- expect(result.error).toBeDefined();
78
- expect(result.error).toContain('Malformed event');
79
- });
80
- test('empty pubkey handling', async () => {
81
- // Empty string pubkey
82
- simulateInvalidPubkey();
83
- try {
84
- await mockGetProfile('');
85
- fail('Expected promise to reject with invalid pubkey error');
86
- }
87
- catch (error) {
88
- expect(error.message).toContain('Invalid pubkey');
89
- }
90
- });
91
- test('extremely long pubkey handling', async () => {
92
- // Extremely long input should be rejected
93
- simulateInvalidPubkey();
94
- const veryLongPubkey = 'a'.repeat(1000);
95
- try {
96
- await mockGetProfile(veryLongPubkey);
97
- fail('Expected promise to reject with invalid pubkey error');
98
- }
99
- catch (error) {
100
- expect(error.message).toContain('Invalid pubkey');
101
- }
102
- });
103
- test('special character handling in pubkey', async () => {
104
- // Pubkey with special characters
105
- simulateInvalidPubkey();
106
- try {
107
- await mockGetProfile('npub1<script>alert("xss")</script>');
108
- fail('Expected promise to reject with invalid pubkey error');
109
- }
110
- catch (error) {
111
- expect(error.message).toContain('Invalid pubkey');
112
- }
113
- });
114
- test('null or undefined pubkey handling', async () => {
115
- // Null pubkey
116
- simulateInvalidPubkey();
117
- try {
118
- await mockGetProfile(null);
119
- fail('Expected promise to reject with invalid pubkey error');
120
- }
121
- catch (error) {
122
- expect(error.message).toContain('Invalid pubkey');
123
- }
124
- // Undefined pubkey
125
- simulateInvalidPubkey();
126
- try {
127
- await mockGetProfile(undefined);
128
- fail('Expected promise to reject with invalid pubkey error');
129
- }
130
- catch (error) {
131
- expect(error.message).toContain('Invalid pubkey');
132
- }
133
- });
134
- test('all relays failing scenario', async () => {
135
- // Simulate all relays failing
136
- simulateNetworkError();
137
- try {
138
- await mockGetProfile('valid-pubkey', { relays: ['wss://relay1.example.com', 'wss://relay2.example.com'] });
139
- fail('Expected promise to reject when all relays fail');
140
- }
141
- catch (error) {
142
- expect(error.message).toContain('Failed to connect');
143
- }
144
- });
145
- });