Nostr Client Development Tools

2.23.3 · active · verified Tue Apr 21

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

Warnings

Install

Imports

Quickstart

Demonstrates generating Nostr keys, creating and signing an event, publishing it to relays, subscribing to events from a public key, and configuring the WebSocket implementation for Node.js.

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);

view raw JSON →