nostr-tools

repository·master·Indexed 21 days ago

https://github.com/nbd-wtf/nostr-tools

A low-level toolkit for Nostr client development (v2.24.1). It provides essential primitives for key management, event signing, and relay interaction, including the SimplePool class for managing multiple relay connections, NIP-42 authentication, and NIP-27 reference parsing. The library includes AbstractSimplePool and AbstractRelay for custom relay implementations and supports NIP-45 distributed counting with HyperLogLog (HLL).

Tokens
42.9K
Snippets
164
Records
191
Agent score
75%

What's inside nostr-tools

  1. Overview of @nostr/tools

    master

    @nostr/tools is a library providing low-level tools for developing Nostr clients.

    If you require higher-level features, it is recommended to use @nostr/gadgets, which is built on top of this library and provides expanded functionality. @nostr/gadgets is exclusively available on JSR.

  2. Authenticate with relays (NIP-42)

    master

    When a relay returns a CLOSED message with an auth-required: prefix, you must authenticate.

    Using SimplePool (Recommended): Pass an onauth handler to subscribeMany, subscribeManyEose, or subscribeEose. The pool will automatically sign the challenge and resubscribe.

    Using Relay directly: If using the Relay class, you must manually handle the onclose event, call relay.auth(callback), and restart the subscription.

    // SimplePool approach
    import { SimplePool } from '@nostr/tools/pool'
    const pool = new SimplePool()
    
    pool.subscribeMany(
      ['wss://myrelay.com'],
      [{ '#t': ['restricted'] }],
      {
        onevent(event) { console.log('got event:', event) },
        onauth: (eventTemplate) => window.nostr.signEvent(eventTemplate),
      }
    )
  3. Install @nostr/tools via JSR

    master

    Install the @nostr/tools package using the JSR CLI. This package provides lower-level Nostr development functionality and depends on @scure and @noble packages.

    Note: If you are using TypeScript, ensure you are using version 5.0 or higher.

    npx jsr add @nostr/tools
  4. Use the core nostr-tools API

    master

    The @nostr/tools package provides a comprehensive suite of utilities for working with the Nostr protocol. The main entry point exports core primitives for event manipulation, relay interaction, and NIP (Nostr Implementation Possibility) compliance.

    Key functional areas include:

    • Pure Utilities: Core functions for signing, verifying, and creating events (exported from ./pure.ts).
    • Relay & Pool Management: Classes like Relay and SimplePool for connecting to and interacting with Nostr relays.
    • Filtering: Tools for constructing event filters (exported from ./filter.ts).
    • Reference Parsing: Utilities for parsing mentions and references (exported from ./references.ts).
    • NIP Implementations: Namespaced modules for specific NIPs (e.g., nip01, nip19, nip42).
    • Constants & Helpers: Access to event kinds, utils, and fj (fakejson).
    import {
      Relay,
      SimplePool,
      nip19,
      kinds,
      // ... other imports
    } from '@nostr/tools';
  5. Classify Nostr event kinds

    master

    Nostr events are categorized into four main types based on how relays are expected to store and manage them:

    • Regular: Expected to be stored by relays (e.g., kind < 10000 excluding 0 and 3).
    • Replaceable: For a specific pubkey and kind, only the latest event should be stored; older versions are discarded (e.g., kind === 0, kind === 3, or 10000 <= kind < 20000).
    • Ephemeral: Not expected to be stored by relays (e.g., 20000 <= kind < 30000).
    • Addressable (Parameterized): For a specific pubkey, kind, and d tag, only the latest event should be stored (e.g., 30000 <= kind < 40000).

    You can use classifyKind(kind) to get a KindClassification string: 'regular' | 'replaceable' | 'ephemeral' | 'parameterized' | 'unknown'.

    import { classifyKind } from './kinds.ts'
    
    const classification = classifyKind(1)
    // returns 'regular'
    
    const ephemeralClassification = classifyKind(25000)
    // returns 'ephemeral'
  6. Understand Relay Event Retention policies

    master

    Relays may specify how long they store certain types of events via the retention field. This is useful for clients to know if they can rely on a relay for historical data.

    Retention specifications can target specific kinds (including ranges) and define limits based on time (in seconds) or count (number of events).

    Example Retention Schema:

    {
      "retention": [
        { "kinds": [0, 1, [5, 7]], "time": 3600 },
        { "kinds": [[40000, 49999]], "time": 100 },
        { "kinds": [[30000, 39999]], "count": 1000 },
        { "time": 3600, "count": 10000 }
      ]
    }
    • time: null indicates infinity.
    • time: 0 indicates the event will not be stored.
  7. Manage NIP-29 Groups

    master

    NIP-29 provides a specification for group management on Nostr. This module allows you to generate event templates for group metadata, admins, and members, validate these events, and load complete group objects from a pool using group codes or references.

    Core Workflow

    1. Generate Templates: Use generateGroupMetadataEventTemplate, generateGroupAdminsEventTemplate, or generateGroupMembersEventTemplate to create unsigned EventTemplate objects.
    2. Sign & Publish: Sign the templates using your private key and publish them to the appropriate relay.
    3. Load Groups: Use loadGroup or loadGroupFromCode to fetch and reconstruct a full Group object from the network.
    // Example: Loading a group from a code
    const group = await loadGroupFromCode(pool, "relay.example.com'group-id");
    console.log(group.metadata.name);
  8. Understand Nostr event types: Event, EventTemplate, and VerifiedEvent

    master

    The library uses several type definitions to represent the lifecycle of a Nostr event:

    • Event (alias for NostrEvent): A complete, signed Nostr event containing kind, tags, content, created_at, pubkey, id, and sig.
    • EventTemplate: A subset of an event used for creating new events. It includes only the fields required before signing: kind, tags, content, and created_at.
    • UnsignedEvent: An event that includes the pubkey but lacks the id and sig fields.
    • VerifiedEvent: An Event that has been cryptographically verified. It is distinguished by the presence of a [verifiedSymbol] property set to true.
  9. Understand Relay Server Limitations

    master

    Relays provide a limitation object to prevent clients from sending requests that would be rejected. Key fields to monitor include:

    • max_message_length: Maximum bytes for incoming JSON (affects subscription size and event size).
    • max_subscriptions: Maximum active subscriptions per connection.
    • max_filters: Maximum filter values per subscription.
    • max_limit: The value the relay uses to clamp filter limit parameters.
    • max_event_tags: Maximum elements allowed in an event's tags list.
    • max_content_length: Maximum Unicode characters in the content field.
    • auth_required: Whether NIP-42 authentication is required before performing actions.
    • payment_required: Whether payment is required before performing actions.
  10. Implement a custom relay with AbstractRelay

    master

    The AbstractRelay class serves as the base class for implementing custom relay connections. To use it, you must provide a verifyEvent function in the constructor options to validate incoming events. It handles WebSocket management, reconnection logic, ping/pong heartbeats, and subscription lifecycles.

    Key features include:

    • Automatic Reconnection: Configurable via enableReconnect.
    • Idle Management: Can automatically close the connection after a period of inactivity using idleTimeout.
    • Heartbeats: Uses pingFrequency and pingTimeout to maintain connection health.
    • Authentication: Supports NIP-42 via the auth method.
    import { AbstractRelay } from './abstract-relay.ts';
    
    // Example of how you might extend it (conceptual)
    class MyCustomRelay extends AbstractRelay {
      // Implement custom logic here
    }
    
    const relay = await AbstractRelay.connect('wss://relay.example.com', {
      verifyEvent: (event, url) => true, // Always verify events in production!
      enableReconnect: true,
      idleTimeout: 60000 // 1 minute
    });
  11. Implement NIP-45 HLL count estimation

    master

    NIP-45 uses HyperLogLog (HLL) to estimate the cardinality of sets (like the number of followers) without storing every individual ID.

    To implement the estimation flow:

    1. Initialize: Create a new HLL structure using newHll().
    2. Determine Offset: Calculate the hash-based offset using computeOffset(filterFirstTagValue) to ensure consistent register mapping.
    3. Feed Data: As you receive events or pubkeys, update the HLL using feedEvent(hll, event, offset) or feedPubkey(hll, pubkey, offset).
    4. Merge (Optional): If you have multiple HLL structures, combine them using mergeHll(target, source).
    5. Estimate: Get the final count using estimateCount(hll).

    Encoding/Decoding:

    • Use hllEncode(registers) to convert the Uint8Array to a hex string for storage/transmission.
    • Use hllDecode(hex) to convert a hex string back into a Uint8Array.
    import { 
      newHll, 
      computeOffset, 
      getFilterFirstTagValue, 
      feedEvent, 
      estimateCount 
    } from '@nostr/tools/nip45';
    
    const hll = newHll();
    const filter = { '#p': ['TARGET_PUBKEY'], kinds: [3] };
    
    // 1. Get the first tag value from the filter to compute offset
    const firstTagValue = getFilterFirstTagValue(filter);
    if (firstTagValue) {
      const offset = computeOffset(firstTagValue);
    
      // 2. As events arrive, feed them into the HLL
      // (Assuming 'event' is a valid Nostr Event object)
      feedEvent(hll, event, offset);
    
      // 3. Estimate the total count
      const count = estimateCount(hll);
      console.log(`Estimated count: ${count}`);
    }
  12. Implement NIP-59 Rumor and Seal wrapping

    master

    NIP-59 provides a mechanism for creating encrypted 'rumors' and 'seals' to facilitate private communication. This module provides high-level functions to create rumors, seal them for a specific recipient using NIP-44 encryption, and wrap them in a GiftWrap (kind GiftWrap) for secure delivery.

    Core Workflow

    1. Create a Rumor: Use createRumor to generate an unsigned event with a calculated ID.
    2. Seal a Rumor: Use createSeal to encrypt the rumor for a specific recipient using NIP-44.
    3. Wrap a Seal: Use createWrap to further wrap the seal using a randomly generated secret key, ensuring only the intended recipient can access the content.
    4. Unwrap: Use unwrapEvent with the recipient's private key to decrypt the layers and retrieve the original Rumor.
    import { createRumor, createSeal, createWrap, wrapEvent, unwrapEvent } from './nip59.ts';
    
    // 1. Setup
    const senderPrivateKey = generateSecretKey(); // From pure.ts
    const recipientPublicKey = '...';
    const eventData = { content: 'Hello, secret world!' };
    
    // 2. Wrap an event (High-level API)
    const wrappedEvent = wrapEvent(eventData, senderPrivateKey, recipientPublicKey);
    
    // 3. Unwrap (Recipient side)
    const recipientPrivateKey = '...';
    const rumor = unwrapEvent(wrappedEvent, recipientPrivateKey);
    console.log(rumor.content);