{"id":19178044,"url":"https://github.com/cmdruid/nostr-sdk","last_synced_at":"2026-01-24T05:18:46.571Z","repository":{"id":225581050,"uuid":"765453143","full_name":"cmdruid/nostr-sdk","owner":"cmdruid","description":"A development kit for building nostr apps.","archived":false,"fork":false,"pushed_at":"2024-03-05T23:07:37.000Z","size":307,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-09-13T12:36:41.117Z","etag":null,"topics":["nostr"],"latest_commit_sha":null,"homepage":"https://cmdruid.github.io/nostr-sdk","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cmdruid.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-01T00:14:49.000Z","updated_at":"2024-05-19T03:08:44.000Z","dependencies_parsed_at":"2024-03-03T02:23:00.661Z","dependency_job_id":"278649e7-0541-40c7-b074-5a236bd4ede8","html_url":"https://github.com/cmdruid/nostr-sdk","commit_stats":null,"previous_names":["cmdruid/nostr-sdk"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/cmdruid/nostr-sdk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmdruid%2Fnostr-sdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmdruid%2Fnostr-sdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmdruid%2Fnostr-sdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmdruid%2Fnostr-sdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cmdruid","download_url":"https://codeload.github.com/cmdruid/nostr-sdk/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmdruid%2Fnostr-sdk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28712886,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-24T05:01:10.984Z","status":"ssl_error","status_checked_at":"2026-01-24T04:59:18.328Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["nostr"],"created_at":"2024-11-09T10:36:27.685Z","updated_at":"2026-01-24T05:18:46.566Z","avatar_url":"https://github.com/cmdruid.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @vbyte/nostr-sdk\n\n[![npm version](https://img.shields.io/npm/v/@vbyte/nostr-sdk)](https://npmjs.com/package/@vbyte/nostr-sdk)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\nA TypeScript SDK for the Nostr protocol.\n\n## Features\n\n- **Single relay connections** - `NostrSocket` for connecting to individual relays\n- **Multi-relay aggregation** - `NostrClient` for managing multiple relay connections\n- **P2P communication** - `NostrNode` for encrypted RPC messaging between peers\n- **NIP-04 and NIP-44 encryption** - End-to-end encrypted direct messages\n- **Schnorr signatures** - secp256k1 cryptographic signing\n- **Rate-limited message queuing** - Configurable batch publishing\n- **Event deduplication** - O(1) cache for filtering duplicate events\n- **TypeScript-first** - Full type definitions included\n- **Zod schema validation** - Runtime validation for events and messages\n\n## Installation\n\n```bash\nnpm install @vbyte/nostr-sdk\n```\n\n```bash\npnpm add @vbyte/nostr-sdk\n```\n\n**Note:** The `ws` package is a peer dependency for Node.js environments.\n\n## Quick Start\n\n### Connect to a Single Relay\n\n```typescript\nimport { NostrSocket, CRYPTO, LIB } from '@vbyte/nostr-sdk'\n\n// Create socket and connect\nconst socket = new NostrSocket('wss://relay.example.com')\nawait socket.connect()\n\n// Generate keys\nconst seckey = CRYPTO.gen_seckey()\nconst pubkey = CRYPTO.get_pubkey(seckey)\n\n// Create and sign an event\nconst template = LIB.create_event({\n  kind: 1,\n  content: 'Hello Nostr!',\n  pubkey\n})\nconst event = LIB.sign_event(template, seckey)\n\n// Publish the event\nawait socket.publish(event)\n\n// Close connection\nsocket.close()\n```\n\n### Subscribe to Events\n\n```typescript\nconst sub = await socket.subscribe({ kinds: [1], limit: 10 })\n\nsub.on('event', (event) =\u003e console.log(event))\n```\n\n### Query Events\n\n```typescript\nconst events = await socket.query({ kinds: [1], limit: 10 })\nconsole.log(events)\n```\n\n### Multi-Relay Client\n\n```typescript\nimport { NostrClient } from '@vbyte/nostr-sdk'\n\nconst client = new NostrClient([\n  'wss://relay1.example.com',\n  'wss://relay2.example.com'\n])\nawait client.connect()\n\n// Publish to all relays (resolves on first success)\nawait client.publish(event)\n\n// Subscribe with automatic deduplication\nconst sub = client.subscribe({ kinds: [1] })\nsub.on('event', (event) =\u003e console.log(event))\n\n// Close all connections\nclient.close()\n```\n\n### P2P Node\n\n```typescript\nimport { NostrNode, CRYPTO } from '@vbyte/nostr-sdk'\n\n// Generate keys for two peers\nconst aliceSecret = CRYPTO.gen_seckey()\nconst alicePubkey = CRYPTO.get_pubkey(aliceSecret)\nconst bobPubkey = '...' // Bob's public key\n\n// Create node with peer list\nconst node = new NostrNode(\n  [bobPubkey],                    // Peers to communicate with\n  ['wss://relay.example.com'],    // Relay URLs\n  aliceSecret                     // Your secret key\n)\n\n// Connect and start listening\nawait node.connect()\n\n// Handle incoming messages\nnode.on('message', (msg) =\u003e {\n  if (msg.type === 'request') {\n    console.log(`Request from ${msg.event.pubkey}: ${msg.method}`)\n    // Respond to requests\n    node.respond(msg).accept({ result: 'ok' })\n  }\n})\n\n// Send request to a peer\nconst response = await node.request(\n  { method: 'ping' },\n  bobPubkey,\n  { timeout: 5000 }\n)\n\n// Broadcast to all peers\nnode.announce({ topic: 'status', data: { online: true } }, [bobPubkey])\n\n// Close node\nnode.close()\n```\n\n## API Reference\n\n### NostrSocket\n\nSingle WebSocket connection to a Nostr relay.\n\n**Constructor**\n\n```typescript\nnew NostrSocket(url: string, options?: Partial\u003cNostrSocketConfig\u003e)\n```\n\n**Options**\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `max_retries` | `number` | `3` | Max reconnection attempts |\n| `queue_ival` | `number` | `500` | Queue processing interval (ms) |\n| `queue_limit` | `number` | `10` | Messages per queue batch |\n| `msg_timeout` | `number` | `5000` | Message response timeout (ms) |\n| `sub_timeout` | `number` | `30000` | Subscription EOSE timeout (ms) |\n\n**Methods**\n\n| Method | Returns | Description |\n|--------|---------|-------------|\n| `connect()` | `Promise\u003cvoid\u003e` | Establish connection |\n| `publish(event)` | `Promise\u003cPublishResponse\u003e` | Publish signed event |\n| `subscribe(filters)` | `NostrSubscription` | Create persistent subscription |\n| `query(filters, duration?)` | `Promise\u003cSignedEvent[]\u003e` | One-shot event query |\n| `close(delay?)` | `void` | Close connection |\n| `send(msg)` | `void` | Send message via queue |\n\n**Events**\n\n| Event | Payload | Description |\n|-------|---------|-------------|\n| `ready` | `NostrSocket` | Connection established |\n| `closed` | `NostrSocket` | Connection closed |\n| `error` | `string` | Error occurred |\n| `message` | `RelayMessage` | Relay message received |\n| `notice` | `string` | NOTICE message from relay |\n| `receipt` | `RelayReceiptMessage` | OK receipt for published event |\n\n### NostrClient\n\nMulti-relay client with event deduplication.\n\n**Constructor**\n\n```typescript\nnew NostrClient(relays: string[], options?: Partial\u003cNostrClientConfig\u003e)\n```\n\n**Additional Options**\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `cache_size` | `number` | `500` | Event deduplication cache size |\n\n**Methods**\n\nSame as `NostrSocket`, but operations are distributed across all relays:\n- `connect()` - Resolves when first relay connects\n- `publish(event)` - Resolves on first successful publish\n- `query(filters, duration?)` - Resolves with first relay's response\n- `subscribe(filter)` - Returns `SubscriptionManager` directly with deduplication\n- `close()` - Closes all connections\n\n### NostrNode\n\nP2P communication node for encrypted RPC messaging over Nostr.\n\n**Constructor**\n\n```typescript\nnew NostrNode(\n  peers: string[],                    // Public keys of peers\n  relays: string[],                   // Relay URLs\n  seckey: string,                     // Your secret key\n  options?: Partial\u003cNostrNodeConfig\u003e\n)\n```\n\n**Options**\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `msg_timeout` | `number` | `5000` | Request timeout (ms) |\n| `sub_timeout` | `number` | `30000` | Subscription timeout (ms) |\n| `rpc_kind` | `number` | `25000` | Event kind for RPC messages |\n\n**Properties**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `is_ready` | `boolean` | Whether node is connected and active |\n| `client` | `NostrClient` | Underlying multi-relay client |\n| `peers` | `Set\u003cstring\u003e` | Registered peer public keys |\n| `pubkey` | `string` | Node's public key |\n\n**Methods**\n\n| Method | Returns | Description |\n|--------|---------|-------------|\n| `connect()` | `Promise\u003cvoid\u003e` | Connect and start listening |\n| `request(template, peer, options?)` | `Promise\u003cRpcMessageData\u003e` | Send request and wait for response |\n| `respond(request)` | `{ accept, reject }` | Create response to a request |\n| `announce(template, peers)` | `Promise\u003cvoid\u003e[]` | Broadcast event to peers |\n| `close()` | `void` | Close node and connections |\n\n**Events**\n\n| Event | Payload | Description |\n|-------|---------|-------------|\n| `ready` | `NostrNode` | Node connected and active |\n| `closed` | `NostrNode` | Node closed |\n| `error` | `[string, unknown]` | Error occurred |\n| `message` | `RpcMessageData` | RPC message received |\n| `bounced` | `SignedEvent` | Event failed decryption/filtering |\n| `notice` | `string` | NOTICE from relay |\n\n## Crypto Module\n\n```typescript\nimport { CRYPTO } from '@vbyte/nostr-sdk'\n// or\nimport * as CRYPTO from '@vbyte/nostr-sdk/crypto'\n```\n\n### Key Management\n\n| Function | Description |\n|----------|-------------|\n| `gen_seckey(seed?)` | Generate secret key (optionally from seed) |\n| `get_pubkey(seckey)` | Derive public key from secret key |\n| `get_shared_secret(seckey, pubkey)` | Compute ECDH shared secret |\n\n### Signatures\n\n| Function | Description |\n|----------|-------------|\n| `create_signature(seckey, message)` | Create Schnorr signature |\n| `verify_signature(message, pubkey, sig)` | Verify Schnorr signature |\n\n### Encryption\n\n| Function | Description |\n|----------|-------------|\n| `nip04_encrypt(secret, content, iv?)` | NIP-04 AES-CBC encryption |\n| `nip04_decrypt(secret, content)` | NIP-04 AES-CBC decryption |\n| `nip44_encrypt(secret, content, nonce?)` | NIP-44 ChaCha20 encryption |\n| `nip44_decrypt(secret, payload)` | NIP-44 ChaCha20 decryption |\n\n### Encoding\n\n| Function | Description |\n|----------|-------------|\n| `encode_b64url(data)` | Encode bytes to base64url |\n| `decode_b64url(str)` | Decode base64url to bytes |\n\n## Library Module\n\n```typescript\nimport { LIB } from '@vbyte/nostr-sdk'\n// or\nimport * as LIB from '@vbyte/nostr-sdk/lib'\n```\n\n### Event Handling\n\n| Function | Description |\n|----------|-------------|\n| `create_event(config)` | Create event template |\n| `sign_event(template, seckey)` | Sign event with secret key |\n| `verify_event(event)` | Verify event signature (returns error string or null) |\n| `get_event_id(template)` | Compute event ID hash |\n| `get_event_tag(event, tag)` | Get first tag by name |\n| `filter_event_tags(event, tag)` | Get all tags by name |\n| `is_pubkey_mentioned(event, pubkey)` | Check if pubkey is in 'p' tags |\n| `is_event_expired(event, current?)` | Check if event has expired |\n\n### Filtering\n\n| Function | Description |\n|----------|-------------|\n| `match_filter(event, filter)` | Check if event matches filter |\n| `match_any_filter(event, filters)` | Check if event matches any filter |\n| `process_filters(events, filters)` | Filter event array |\n| `is_kind_regular(kind)` | Check if kind is regular (1, 2, 4-44, 1000-9999) |\n| `is_kind_replace(kind)` | Check if kind is replaceable (0, 3, 10000-19999) |\n| `is_kind_ephemeral(kind)` | Check if kind is ephemeral (20000-29999) |\n| `is_kind_address(kind)` | Check if kind is addressable (30000-39999) |\n\n## Types\n\n```typescript\nimport type {\n  // Events\n  SignedEvent,\n  EventTemplate,\n  EventFilter,\n  EventConfig,\n\n  // Configuration\n  NostrSocketConfig,\n  NostrClientConfig,\n  NostrNodeConfig,\n\n  // Responses\n  PublishResponse,\n\n  // RPC (for NostrNode)\n  RpcMessageData,\n  RequestRpcTemplate,\n  EventRpcTemplate\n} from '@vbyte/nostr-sdk'\n```\n\n## Development\n\n### Requirements\n\n- Node.js 18+ (20+ recommended)\n\n### Setup\n\n```bash\ngit clone https://github.com/cmdcode/nostr-sdk\ncd nostr-sdk\nnpm install\n```\n\n### Scripts\n\n| Command | Description |\n|---------|-------------|\n| `npm run check` | TypeScript type checking |\n| `npm run lint` | Biome linting |\n| `npm run lint:fix` | Auto-fix linting issues |\n| `npm run format` | Format code with Biome |\n| `npm run test` | Run test suite |\n| `npm run build` | Build distribution |\n| `npm run package` | Full pipeline (lint, check, test, build) |\n\n### Build Outputs\n\n- `dist/` - ES Modules with TypeScript declarations\n- Organized by module: `class/`, `lib/`, `crypto/`, `schema/`, `types/`\n\n## Resources\n\n- [NIP-01: Basic Protocol](https://github.com/nostr-protocol/nips/blob/master/01.md)\n- [NIP-04: Encrypted Direct Messages](https://github.com/nostr-protocol/nips/blob/master/04.md)\n- [NIP-44: Versioned Encryption](https://github.com/nostr-protocol/nips/blob/master/44.md)\n- [@noble/curves](https://github.com/paulmillr/noble-curves) - Cryptography\n- [@noble/hashes](https://github.com/paulmillr/noble-hashes) - Hashing\n- [@noble/ciphers](https://github.com/paulmillr/noble-ciphers) - Ciphers\n- [Zod](https://zod.dev) - Schema validation\n\n## Contributing\n\nContributions are welcome. Please open an issue or submit a pull request.\n\n## License\n\n[MIT License](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcmdruid%2Fnostr-sdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcmdruid%2Fnostr-sdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcmdruid%2Fnostr-sdk/lists"}