Nostr Client Development Tools
nostr-tools is a core JavaScript/TypeScript library providing low-level utilities for developing Nostr clients. It enables essential functionalities such as generating Nostr secret and public keys, creating and signing events, verifying event integrity, and interacting with Nostr relays through a `SimplePool` abstraction. The current stable version is 2.23.3, and the project appears to have an active release cadence, evidenced by significant breaking changes in version 2.0.0 and subsequent updates. Key differentiators include its minimalist dependency footprint, relying primarily on `@scure` and `@noble` cryptography packages, and its modular structure which allows importing only necessary components. It specifically focuses on lower-level primitives, suggesting `@nostr/gadgets` for higher-level client features. It also provides robust relay management features like configurable pinging and automatic reconnection.
Common errors
-
TypeError: global.WebSocket is not a constructor
cause Attempting to use `SimplePool` or `Relay` in a Node.js environment without a global WebSocket implementation being set.fixInstall `ws` (`npm install ws`) and call `useWebSocketImplementation(WebSocket)` from `@nostr/tools/pool` (or `/relay`) with the `ws` import. -
ReferenceError: WebSocket is not defined
cause Similar to the `TypeError`, this occurs in Node.js when the `SimplePool` or `Relay` tries to instantiate a WebSocket without an available global `WebSocket` constructor.fixProvide a WebSocket implementation by installing `ws` and calling `useWebSocketImplementation(WebSocket)` from `@nostr/tools/pool` (or `/relay`). -
SyntaxError: Cannot use import statement outside a module
cause The `nostr-tools` library is primarily distributed as an ES Module (ESM). This error occurs when trying to use `import` statements in a CommonJS (CJS) context in Node.js.fixConvert your Node.js project to use ES Modules by adding `"type": "module"` to your `package.json` and using `.js` files (or `.mjs`). Alternatively, use a bundler (e.g., Webpack, Rollup) for client-side applications.
Warnings
- breaking Version 2.0.0 introduced significant breaking changes, including API renames, removal of deprecated methods, changes to Event type parameters (e.g., `Event<number>` was removed), and kind constants replacing enums.
- breaking With v2.0.0, the package transitioned to a modular structure, requiring imports from specific subpaths (e.g., `@nostr/tools/pure`, `@nostr/tools/pool`) instead of the root `@nostr/tools` package for most functionality.
- gotcha When using `SimplePool` or `Relay` in a Node.js environment, you must explicitly provide a WebSocket implementation, typically by installing the `ws` package and calling `useWebSocketImplementation(WebSocket)`.
- gotcha The package lists TypeScript >= 5.0.0 as a peer dependency. Using an older version of TypeScript may lead to type incompatibility issues or compilation errors.
Install
-
npm install nostr-tools -
yarn add nostr-tools -
pnpm add nostr-tools
Imports
- generateSecretKey, getPublicKey
const { generateSecretKey, getPublicKey } = require('nostr-tools/pure')import { generateSecretKey, getPublicKey } from '@nostr/tools/pure' - finalizeEvent, verifyEvent
import { finalizeEvent } from 'nostr-tools'import { finalizeEvent, verifyEvent } from '@nostr/tools/pure' - SimplePool
import { SimplePool } from 'nostr-tools'import { SimplePool } from '@nostr/tools/pool' - useWebSocketImplementation
import { useWebSocketImplementation } from '@nostr/tools/relay'import { useWebSocketImplementation } from '@nostr/tools/pool'
Quickstart
import { finalizeEvent, generateSecretKey, getPublicKey } from '@nostr/tools/pure';
import { SimplePool, useWebSocketImplementation } from '@nostr/tools/pool';
import WebSocket from 'ws'; // For Node.js environments
import { bytesToHex } from '@noble/hashes/utils'; // For convenience
// For Node.js, set up the WebSocket implementation
useWebSocketImplementation(WebSocket);
async function runNostrClient() {
const pool = new SimplePool({ enablePing: true, enableReconnect: true });
// Use a real, publicly available relay for testing
const relays = ['wss://relay.damus.io', 'wss://nostr.wine', 'wss://eden.nostr.land'];
// 1. Generate keys
let sk = generateSecretKey(); // Uint8Array
let pk = getPublicKey(sk); // hex string
console.log(`Generated secret key (hex): ${bytesToHex(sk)}`);
console.log(`Generated public key (hex): ${pk}`);
// 2. Create and sign an event
let eventTemplate = {
kind: 1, // Text Note
created_at: Math.floor(Date.now() / 1000),
tags: [],
content: `Hello Nostr from nostr-tools! This is a test event from a registry quickstart. ${Math.random()}`,
};
const signedEvent = finalizeEvent(eventTemplate, sk);
console.log('Signed event:', signedEvent);
// 3. Publish the event to a couple of relays
console.log(`Publishing event to ${relays.slice(0, 2).join(', ')}...`);
try {
// Promise.any will resolve as soon as one relay successfully publishes
await Promise.any(pool.publish(relays.slice(0, 2), signedEvent));
console.log('Event published successfully to at least one relay.');
} catch (error) {
console.error('Failed to publish event to any relay:', error);
}
// 4. Subscribe to events from our public key
console.log(`Subscribing to events from public key ${pk}...`);
const sub = pool.subscribe(
relays,
{
kinds: [1],
authors: [pk],
since: Math.floor(Date.now() / 1000) - 60, // Look for events in the last 60 seconds
},
{
onevent(event) {
console.log('Received own event:', event.content);
// Once we receive our own event, we can unsubscribe if desired
sub.close();
},
oneose() {
console.log('Subscription End of Stored Events (EOSE) received.');
},
onclose(wasClean: boolean) {
console.log(`Subscription closed (wasClean: ${wasClean}).`);
}
}
);
// Optional: Query for some existing events
console.log('Querying for recent text notes (kind 1)...');
const recentEvents = await pool.querySync(
relays,
{
kinds: [1],
limit: 5,
},
{
onprogress(percent: number) {
// console.log(`Query progress: ${Math.round(percent)}%`);
}
}
);
if (recentEvents && recentEvents.length > 0) {
console.log(`Found ${recentEvents.length} recent events. Example:`, recentEvents[0].content);
} else {
console.log('No recent events found.');
}
// Allow some time for events to propagate and be received
await new Promise(resolve => setTimeout(resolve, 5000));
pool.close(); // Close all relay connections
console.log('Pool closed.');
}
runNostrClient().catch(console.error);