NDK Core Expert Agent - Comprehensive Codebase Analysis

Complete architectural analysis of @nostr-dev-kit/ndk core package including public API surface, module responsibilities, architecture patterns, key types, testing infrastructure, and known guardrails for expert agent training.

NDK Core - Expert Agent Knowledge Base

Overview

This is a comprehensive analysis of the Nostr Development Kit (NDK) Core package, designed to train an expert agent on the architecture, design patterns, and implementation details of the library.

Project: NDK v3.0.0
Root: /Users/customer/Work/NDK-nhlteu/core
Analysis Depth: Very Thorough


Executive Summary

NDK is a comprehensive TypeScript/JavaScript library for building Nostr applications. It provides:

  • Event Model: Flexible NDKEvent class for constructing, signing, encrypting, and publishing Nostr events
  • Relay Management: Intelligent relay pool handling with connection pooling, auth, and flapping detection
  • Subscriptions: Stream-based event fetching with caching strategies and filter optimization
  • User System: Profile management, follow lists, and user discovery
  • Multiple Signers: NIP-07 (browser ext), NIP-46 (remote), private-key strategies
  • Extensible Cache: Pluggable cache adapters with module system
  • AI Guardrails: 33 runtime checks catching 90% of common LLM/dev mistakes
  • 37+ Event Kinds: Specialized handlers for articles, tasks, zaps, nutzaps, etc.

Public API Surface

Main Classes (15+)

Class Purpose Key Methods
NDK Main orchestration subscribe(), fetchEvents(), publish(), getUser(), count()
NDKEvent Event model sign(), publish(), encrypt(), decrypt(), tag()
NDKSubscription Event streams .on("event"), .on("eose"), .stop()
NDKRelay Single relay connect(), disconnect(), subscribe(), publish()
NDKPool Multi-relay mgmt addRelay(), useTemporaryRelay(), connection lifecycle
NDKRelaySet Filtered relays Targeted publish(), subscribe(), count()
NDKUser User/profile pubkey, npub, profile, getZapInfo(), follows()
NDKSigner Signing interface sign(), encrypt(), decrypt(), serialization
AIGuardrails Runtime validation 33 checks, granular control

Constructor Configuration

interface NDKConstructorParams {
    explicitRelayUrls?: string[];        // Direct relay connections
    outboxRelayUrls?: string[];          // Gossip model relays
    enableOutboxModel?: boolean = true;
    autoConnectUserRelays?: boolean = true;
    signer?: NDKSigner;
    cacheAdapter?: NDKCacheAdapter;
    aiGuardrails?: boolean | { skip: Set<string> };
    clientName?: string;
    filterValidationMode?: "validate" | "fix" | "ignore";
    signatureVerificationWorker?: Worker;
    futureTimestampGrace?: number;
}

Core Modules Breakdown

1. NDK Orchestration (src/ndk/ - 46KB)

Responsibilities:

  • Lifecycle management (connect/disconnect)
  • Signer & cache integration
  • Subscription coordination
  • Event publishing orchestration
  • AI Guardrails enforcement
  • Request queueing

Key Files:

  • index.ts - NDK class (main)
  • queue/ - Request throttling system
  • active-user.ts - Active user tracking
  • fetch-event-from-tag.ts - Event resolution

2. Event Model (src/events/ - 38KB + 37 kinds)

Responsibilities:

  • Event construction and mutation
  • Signature signing/verification
  • Content encryption (NIP-04, NIP-44)
  • Automatic content tagging (mentions, hashtags)
  • Event serialization
  • 37+ specialized event kind handlers

Key Files:

  • index.ts - NDKEvent class
  • validation.ts - Signature verification
  • encryption.ts - NIP-04/44 encryption
  • content-tagger.ts - Auto-tagging
  • kinds/ - Article, Task, Zap, Highlight, Nutzap, etc.

Example Event Kinds:

Kind 0: Metadata (Profiles)
Kind 1: Text Notes
Kind 3: Contacts (Follow lists)
Kind 30023: Articles (NIP-23)
Kind 30000-30063: NIP-51 Lists (Mutes, Pins, etc.)
Kind 9735: Zaps (NIP-57)
Kind 30061: Nutzap (NIP-61)

3. Relay Management (src/relay/)

Responsibilities:

  • WebSocket lifecycle management
  • Multi-relay connection pooling
  • NIP-42 relay authentication
  • NIP-11 relay info fetching
  • Keep-alive & reconnection backoff
  • Signature verification stats
  • Flapping detection & scoring

Key Files:

  • index.ts - NDKRelay (single connection)
  • pool/index.ts - NDKPool (multi-relay)
  • sets/index.ts - NDKRelaySet (filtered groups)
  • connectivity.ts - WebSocket lifecycle
  • subscription.ts - Per-relay subscriptions
  • auth-policies.ts - NIP-42 handling

Relay Status Machine:

DISCONNECTED → CONNECTING → CONNECTED → AUTH_REQUESTED → AUTHENTICATED
                 ↓
             RECONNECTING/FLAPPING

4. Subscriptions (src/subscription/)

Responsibilities:

  • Stream-based event fetching
  • Filter grouping & optimization
  • Cache integration (4 modes)
  • EOSE handling
  • Relay set calculation
  • Event deduplication

Cache Modes:

ONLY_CACHE    // Skip relays, cache only
CACHE_FIRST   // Cache first, relay fallback
PARALLEL      // Query cache AND relays
ONLY_RELAY    // Skip cache, relays only

Filter Specification (NIP-01):

{
    ids?: string[];
    kinds?: number[];
    authors?: string[];
    since?: number;
    until?: number;
    limit?: number;
    search?: string;
    ['#p']?: string[];  // Mentions
    ['#e']?: string[];  // Replies
    ['#t']?: string[];  // Tags
    ['#a']?: string[];  // NIP-33 addresses
}

5. Signing Strategies (src/signers/)

Responsibilities:

  • Abstract signing interface
  • Multiple signing implementations
  • Encryption/decryption support
  • Relay preference discovery
  • Signer serialization/persistence

Strategies:

  1. NIP-07 - Browser extensions (MetaMask, Alby, Nos2x)
  2. NIP-46 - Remote signing via relay (bunker/nsecBunker)
  3. Private Key - Direct signing (fastest, least safe)

Interface:

interface NDKSigner {
    get pubkey(): string;
    blockUntilReady(): Promise<NDKUser>;
    sign(event: NostrEvent): Promise<string>;
    relays?(ndk?: NDK): Promise<NDKRelay[]>;
    encrypt(recipient: NDKUser, value: string): Promise<string>;
    decrypt(sender: NDKUser, value: string): Promise<string>;
    toPayload(): string;  // Serialization
}

6. User Management (src/user/)

Responsibilities:

  • User identity (pubkey/npub/nprofile)
  • Profile metadata (NIP-01 kind 0)
  • Follow list retrieval
  • Zap endpoint discovery
  • NIP-05 identity verification

Key Data:

class NDKUser {
    pubkey: string;           // Hex public key
    npub: string;             // Bech32
    nprofile: string;         // With relay hints
    profile?: NDKUserProfile; // Metadata cache
    relayUrls: string[];      // User preferences
    nip46Urls: string[];      // Remote signing
}

7. Caching Layer (src/cache/)

Responsibilities:

  • Define cache adapter interface
  • Support cache extensions (modules)
  • Filter ephemeral kinds
  • Schema migration framework

Adapter Interface:

interface NDKCacheAdapter {
    query(filters: NDKFilter[]): Promise<NDKEvent[]>;
    saveEvent(event: NDKEvent): Promise<void>;
    warmupCache(filters?: NDKFilter[]): Promise<void>;
}

Known Implementations:

  • @nostr-dev-kit/ndk-cache-dexie (Browser)
  • @nostr-dev-kit/ndk-cache-sqlite (Node/Bun)
  • @nostr-dev-kit/ndk-cache-redis (Server)

8. AI Guardrails (src/ai-guardrails/ - 33 Checks)

33 Runtime Checks catch common mistakes:

Filter Validation (7):

  • Bech32 in filter arrays (npub instead of hex)
  • Invalid hex values
  • Large limits (> 1000)
  • Empty filters
  • Timestamp logic (since > until)
  • Invalid NIP-33 tags

Event Construction (8):

  • Missing kind
  • Param-replaceable without d-tag
  • created_at in milliseconds (not seconds)
  • Content as object (not string)
  • Modified after signing

Subscriptions (4):

  • Not started (forgot .start())
  • closeOnEose without handler
  • Wrong argument type

Plus: Tags, Relay, Validation checks

Configuration:

// Enable globally
const ndk = new NDK({ aiGuardrails: true });

// Enable with exceptions
const ndk = new NDK({ aiGuardrails: { skip: new Set(['check-id']) } });

// Disable for one call
ndk.guardrailOff().fetchEvents(filter);
ndk.guardrailOff('specific-check').subscribe(filter);

Architecture Patterns

Flow Diagrams

Orchestration Stack:

NDK Instance
├─ NDKPool (Relay connections)
│  ├─ NDKRelay (WebSocket per relay)
│  └─ NDKRelaySet (Filtered groups)
├─ NDKSubscriptionManager (Active subscriptions)
├─ NDKSigner (Sign/encrypt)
├─ NDKCacheAdapter (Persistence)
└─ AIGuardrails (Validation)

Subscription Lifecycle:

ndk.subscribe(filter, options)
  ├─ Calculate relay set
  ├─ Group with similar filters
  ├─ Create NDKSubscription
  └─ Start (cache query + relay REQ)
    ├─ Emit cached events (onEvents)
    ├─ Emit relay events (onEvent)
    ├─ Emit EOSE (onEose)
    └─ Emit close (onClose)

Event Publishing:

event.sign(signer)
event.publish(relaySet?)
  ├─ Calculate relay set if needed
  ├─ Connect relays
  ├─ Send EVENT message
  ├─ Handle NIP-42 auth if needed
  └─ Track per-relay results
    ├─ Emit "published"
    └─ Emit "publish:failed"

Extension Points

  1. Custom Signers - Implement NDKSigner
  2. Cache Backends - Implement NDKCacheAdapter
  3. Cache Modules - Define CacheModuleDefinition
  4. Event Kinds - Subclass NDKEvent
  5. Relay Auth - Custom NDKAuthPolicy functions
  6. Event Filters - Relay list resolution (NIP-65)

Key Types & Constants

Event Construction

type NDKRawEvent = {
    created_at: number;
    content: string;
    tags: string[][];
    kind: NDKKind | number;
    pubkey: string;
    id: string;
    sig: string;
};

Filter Validation Modes

"validate"  // Throw on undefined values
"fix"       // Auto-remove undefined
"ignore"    // Pass through (legacy)

Relay Status Enum

DISCONNECTED = 1
RECONNECTING = 2
FLAPPING = 3
CONNECTING = 4
CONNECTED = 5
AUTH_REQUESTED = 6
AUTHENTICATING = 7
AUTHENTICATED = 8

Encryption Schemes

"nip04"  // Legacy (25519 + chacha20poly1305)
"nip44"  // Modern (25519 + chacha20poly1305 with v2)

Testing & Development

Test Utilities Exported

// Mocks
export { RelayMock } from "./mocks/relay-mock";
export { RelayPoolMock } from "./mocks/relay-pool-mock";

// Generators
export { EventGenerator } from "./mocks/event-generator";
export { SignerGenerator } from "./helpers/test-fixtures";
export { UserGenerator } from "./helpers/test-fixtures";

// Helpers
export { TestFixture } from "./helpers/test-fixtures";
export { TimeController, withTimeControl } from "./helpers/time";

// Mocking data
export { mockNutzap, mockProof } from "./mocks/nutzaps";

Example Test Pattern

const relay = new RelayMock('wss://relay.example.com', {
    connectionDelay: 100,
    autoConnect: true,
});

const event = await EventGenerator.createSignedTextNote('Hello!', alice.pubkey);
relay.simulateEvent(event);
relay.simulateEOSE();

Documentation Available

Snippets (Code Examples):

  • Event creation, signing, publishing
  • User profile fetching
  • Key generation (NIP-49)
  • Subscription patterns
  • Testing strategies

Tutorials:

  • Relay authentication (NIP-42)
  • Filter validation best practices
  • Content muting/filtering
  • NIP-19 encoding
  • Signer persistence
  • Performance optimization
  • Zap integration

References:

  • CHANGELOG.md - Historical changes
  • MIGRATION-2.16.md - Breaking changes
  • OUTBOX.md - Gossip model details

Known Antipatterns & Safeguards

Common Mistakes (Guarded)

  1. Bech32 in Filters - Use hex, not npub/note
  2. Timestamp Units - Seconds, not milliseconds
  3. Missing Kind - Always set event.kind
  4. No d-tag on Param-Replaceable - NIP-33 requires d-tag
  5. Unmanaged Subscriptions - Explicitly .stop()
  6. No Cache Adapter - Warns if not configured after 2.5s
  7. Large Limits - Limits > 1000 flagged

Performance Antipatterns (Warned)

  • Overuse of fetchEvents() - Use subscriptions instead
  • Empty filters - Add constraints (authors, kinds, etc.)
  • No relay set calculation - Specify relays explicitly

Integration Highlights

Outbox Model

  • Auto-queries purplepag.es & nos.lol
  • Enabled by default
  • Gossip-based relay discovery

Extensible Cache

  • Modular cache system
  • Custom collections & indexes
  • Migration framework for schema evolution

37+ Event Kinds

  • Text notes, articles, tasks
  • Zaps, nutzaps, highlights
  • Lists, metadata, profiles
  • DVMs, classifications, wikis

Relay Authentication

  • NIP-42 implementation
  • Customizable auth policies
  • Per-relay or global defaults

Critical Design Principles

  1. Async-First - Most operations return promises
  2. Event-Driven - EventEmitter for lifecycle hooks
  3. Streaming Subscriptions - Events as they arrive, not batches
  4. Flexible Caching - Multiple strategies per subscription
  5. Zero-Config Defaults - Works with minimal setup
  6. Developer Safety - 33 guardrails catch 90% of mistakes
  7. Performance-Optimized - Pooling, grouping, verification workers
  8. Highly Extensible - Custom signers, cache, kinds, auth

File Organization

src/
├── ndk/              # Orchestration (46KB)
├── events/           # Event model (38KB + 37 kinds)
├── relay/            # Relay management
├── subscription/     # Event streams
├── signers/          # NIP-07, NIP-46, Private-key
├── user/             # Profile & identity
├── cache/            # Cache interfaces
├── ai-guardrails/    # 33 runtime checks
├── outbox/           # Gossip model
├── zapper/           # NIP-57/61 utilities
└── utils/            # Helpers

test/
├── mocks/            # RelayMock, EventGenerator
├── helpers/          # Fixtures, utilities
└── index.ts          # Test exports

Key Statistics

  • Version: 3.0.0
  • Main Classes: 15+
  • Public Methods: 100+
  • Event Kinds: 37+
  • Guardrail Checks: 33
  • Signer Strategies: 3
  • Cache Modes: 4
  • Test Utilities: 7+
  • Documentation: Snippets, tutorials, migration guides

This analysis serves as the foundation for expert agent training on NDK Core architecture and implementation.

Write a comment
No comments yet.