npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nostr-claw-bootstrap

v2026.5.50

Published

Bootstrap installer for the OpenClaw Nostr channel plugin — overrides the hostile bundled copy

Readme

nostr-claw-bootstrap

Nostr channel plugin for OpenClaw — encrypted DMs, public notes, identity resolution, relay discovery, public channels, and 20+ NIP implementations.

Overview

This extension adds Nostr as a full-featured messaging and social channel to OpenClaw. It enables your agent to:

  • Receive and send encrypted DMs via NIP-04 and NIP-17 (gift-wrapped)
  • Publish and react to public notes (kind:1)
  • Resolve NIP-05 identities and discover relay capabilities
  • Create and moderate public channels (NIP-28)
  • Repost content, publish file metadata, and generate zap requests
  • Encrypt messages with NIP-44 versioned encryption
  • Authenticate to relays (NIP-42) and HTTP services (NIP-98)
  • Access 20+ additional NIP implementations from nostr-tools

Installation

Quick install

npx nostr-claw-bootstrap

This single command:

  1. detects the host OpenClaw version
  2. resolves bundled/global install roots
  3. installs this plugin through openclaw plugins install <path> --force
  4. refreshes the persisted plugin registry
  5. validates that the managed global install wins over the bundled copy
  6. runs a runtime inspect smoke test
  7. prints the effective plugin graph

OpenClaw ranks a tracked global install ahead of the bundled copy, so the hostile upstream nostr plugin is permanently overridden.

With an explicit OpenClaw checkout / CLI path:

npx nostr-claw-bootstrap --openclaw /path/to/openclaw.mjs

For machine-readable output:

npx nostr-claw-bootstrap --json

From this repo (Cascadia fork)

git clone https://git.sharegap.net/cascadia/openclaw-nostr.git

See Docker Deployment for containerized setups.

Cascadia Fleet Role

This repo is the durable maintenance layer for Cascadia's Nostr fixes when upstream OpenClaw upgrades clobber local agent installs.

We do not currently maintain a separately named OpenClaw package. Instead:

  • Upstream OpenClaw remains the base runtime
  • This repo is the source of truth for Nostr-specific durability patches and extended features
  • Agent upgrades should re-apply this repo's patch layer after each OpenClaw upgrade

See:

  • docs/UPGRADE-WORKFLOW.md
  • docs/COMPATIBILITY.md
  • scripts/apply-to-agent.sh
  • scripts/check-agent.sh

Quick Setup

  1. Generate a Nostr keypair (if you don't have one):

    # Using nak CLI
    nak key generate
    
    # Or use any Nostr key generator
  2. Add to your config (~/.openclaw/openclaw.json):

    {
      "channels": {
        "nostr": {
          "privateKey": "${NOSTR_PRIVATE_KEY}",
          "relays": ["wss://relay.sharegap.net", "wss://nos.lol"]
        }
      }
    }
  3. Set the environment variable:

    export NOSTR_PRIVATE_KEY="nsec1..."  # or 64-char hex format
  4. Restart the gateway

Configuration

| Key | Type | Default | Description | | ------------ | -------- | ------------------------------------------- | ---------------------------------------------------------- | | privateKey | string | required | Bot's private key (nsec or hex format) | | relays | string[] | ["wss://relay.sharegap.net", "wss://nos.lol"] | WebSocket relay URLs | | dmPolicy | string | "pairing" | Access control: pairing, allowlist, open, disabled | | allowFrom | string[] | [] | Allowed sender pubkeys (npub or hex) | | enabled | boolean | true | Enable/disable the channel | | name | string | - | Display name for the account |

Access Control

DM Policies

  • pairing (default): Unknown senders receive a pairing code to request access
  • allowlist: Only pubkeys in allowFrom can message the bot
  • open: Anyone can message the bot (use with caution)
  • disabled: DMs are disabled

Example: Allowlist Mode

{
  "channels": {
    "nostr": {
      "privateKey": "${NOSTR_PRIVATE_KEY}",
      "dmPolicy": "allowlist",
      "allowFrom": ["npub1abc...", "0123456789abcdef..."]
    }
  }
}

Protocol Support

Core Messaging (Tier 0)

| NIP | Kind(s) | Status | Description | | ------ | ------------ | ----------- | ------------------------------------- | | NIP-01 | 1 | ✅ Full | Basic event structure & public notes | | NIP-04 | 4 | ✅ Full | Encrypted DMs (legacy) | | NIP-09 | 5 | ✅ Full | Event deletion | | NIP-10 | — | ✅ Full | Thread references (root/reply/mention)| | NIP-17 | 1059 | ✅ Full | Gift-wrapped DMs (modern) | | NIP-25 | 7 | ✅ Full | Reactions | | NIP-40 | — | ✅ Full | Event expiration | | NIP-65 | 10002 | ✅ Full | Relay list metadata |

Tier 1 — Agent-Essential Features

| NIP | Kind(s) | Status | Description | | ------ | ------------ | ----------- | ------------------------------------- | | NIP-05 | — | ✅ Full | Identity resolution + domain search | | NIP-11 | — | ✅ Full | Relay information + capability checks | | NIP-42 | 22242 | ✅ Full | Relay authentication | | NIP-46 | — | ✅ Re-export| Remote signing (Nostr Connect/Bunker) | | NIP-57 | 9734, 9735 | ✅ Re-export| Zaps (Lightning payments) | | NIP-94 | 1063 | ✅ Full | File metadata | | NIP-98 | 27235 | ✅ Full | HTTP authentication | | NIP-B7 | — | ✅ Re-export| Blossom media server |

Tier 2 — Social & Channel Features

| NIP | Kind(s) | Status | Description | | ------ | ------------------ | ----------- | ------------------------------- | | NIP-13 | — | ✅ Re-export| Proof of Work | | NIP-18 | 6, 16 | ✅ Full | Reposts (short text + generic) | | NIP-27 | — | ✅ Full | Content parsing (text/URLs/refs)| | NIP-28 | 40, 42, 43, 44 | ✅ Full | Public channels (CRUD + mod) | | NIP-44 | — | ✅ Full | Versioned encryption |

Tier 3 — Niche / Advanced (Namespace Re-exports)

| NIP | Module | Description | | ------ | ------------------ | ------------------------------------- | | NIP-29 | nostr-extras | Relay-based groups | | NIP-30 | nostr-extras | Custom emoji | | NIP-39 | nostr-extras | External identity verification | | NIP-47 | nostr-extras | Nostr Wallet Connect (NWC) | | NIP-49 | nostr-extras | Private key encryption (ncryptsec) | | NIP-58 | nostr-extras | Badges | | NIP-75 | nostr-extras | Zap goals (fundraising) | | NIP-77 | nostr-extras | Negentropy sync |

Architecture

The plugin follows a three-layer architecture:

nostr-capabilities.ts   ← Event builders (pure functions, no I/O)
nostr-discovery.ts      ← NIP-05/NIP-11/NIP-27 (network I/O with caching)
nostr-extras.ts         ← Tier 3 namespace re-exports
        │
nostr-bus.ts            ← Runtime wiring (signing, publishing, subscriptions)
        │
channel.ts              ← Public API (OpenClaw plugin interface)
        │
nostr-profile-http.ts   ← HTTP endpoints (/api/channels/nostr/...)

Key Design Decisions

  • Unsigned templates: All event builders return EventTemplate objects. The bus layer handles signing via finalizeEvent and publishing via the pool. This keeps builders pure and testable.
  • NIP-28 custom builders: The upstream nostr-tools/nip28 functions call finalizeEvent internally. We provide our own builders that return unsigned templates to fit the fork's signAndPublish pattern.
  • Caching: NIP-05 uses a 5-minute TTL with 500-entry LRU. NIP-11 uses a 10-minute TTL with 100-entry LRU. Network errors are not cached (retry on next call).
  • Per-sender serialization: Inbound messages from the same pubkey are processed serially to prevent race conditions during relay EOSE bursts (see PATCHES.md).
  • Bounded startup catch-up: DM subscriptions first query a capped historical window through startup time, then promote to live subscriptions after EOSE/timeout so relay backlog and live traffic have distinct lifecycles.
  • Real outbound IDs: Outbound channel messageId values are real Nostr identifiers. NIP-04 returns the signed kind:4 event ID; NIP-17 returns the recipient rumor ID after at least one recipient wrap is accepted while the bus also tracks accepted gift-wrap IDs.

HTTP API

All endpoints are under /api/channels/nostr/:accountId/. Authentication is handled by the OpenClaw gateway.

Profile Management

| Method | Endpoint | Description | | ------ | ------------------------------- | --------------------------- | | GET | /profile | Get current profile state | | PUT | /profile | Update and publish profile | | POST | /profile/import | Import profile from relays |

Identity & Discovery

| Method | Endpoint | Description | | ------ | ------------------------------- | -------------------------------------- | | GET | /identity/:nip05 | Resolve NIP-05 address to pubkey | | GET | /identity/search/:domain?q= | Search NIP-05 domain for users | | GET | /relay-info?url=wss://... | Get relay NIP-11 capability summary |

Events

| Method | Endpoint | Description | | ------ | ------------------------------- | --------------------------- | | POST | /note | Publish a public note | | POST | /reaction | React to an event | | DELETE | /events | Delete events |

Example: Resolve a NIP-05 Identity

curl http://localhost:18789/api/channels/nostr/default/identity/alice%40example.com
{
  "ok": true,
  "nip05": "[email protected]",
  "pubkey": "aabbccdd...",
  "relays": ["wss://relay1.example", "wss://relay2.example"]
}

Example: Check Relay Capabilities

curl "http://localhost:18789/api/channels/nostr/default/relay-info?url=wss://relay.sharegap.net"
{
  "ok": true,
  "url": "wss://relay.sharegap.net",
  "name": "Damus Relay",
  "supportedNips": [1, 4, 9, 11, 12, 16, 20, 22, 28, 33, 40],
  "authRequired": false,
  "paymentRequired": false,
  "restrictedWrites": false
}

Docker Deployment

There are four ways to deploy this fork to an existing OpenClaw Docker setup, listed from simplest to most involved.

Option 1: Volume Mount (Recommended)

Mount the fork's source directory into the container and point OpenClaw's plugin loader at it. No image rebuild required.

1. Clone the fork on the Docker host:

git clone https://git.sharegap.net/cascadia/openclaw-nostr.git /opt/openclaw-nostr

2. Add to your docker-compose.yml:

services:
  openclaw-gateway:
    volumes:
      - ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
      - ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace
      # Mount the fork's source
      - /opt/openclaw-nostr:/opt/openclaw-nostr:ro

3. Tell OpenClaw to load the plugin via openclaw.json:

{
  "plugins": {
    "load": {
      "paths": ["/opt/openclaw-nostr"]
    }
  },
  "channels": {
    "nostr": {
      "privateKey": "${NOSTR_PRIVATE_KEY}",
      "relays": ["wss://relay.sharegap.net", "wss://nos.lol"]
    }
  }
}

4. Restart:

docker compose restart openclaw-gateway

Option 2: Config Extensions Directory

Copy the fork into OpenClaw's user-level extensions directory, which is auto-scanned on startup.

# Copy into the config dir that's already mounted
cp -r /opt/openclaw-nostr "${OPENCLAW_CONFIG_DIR}/extensions/nostr"

# Restart
docker compose restart openclaw-gateway

OpenClaw discovers plugins from ~/.openclaw/extensions/ automatically — no plugins.load.paths config needed.

Option 3: Custom Dockerfile Layer

Build a derived image with the fork baked in. Best for CI/CD pipelines and reproducible deployments.

FROM openclaw:latest

# Copy in the fork
COPY openclaw-nostr /app/extensions/nostr

# The fork overrides the bundled nostr extension at the same path

Build and run:

docker build -t openclaw-nostr:custom .
OPENCLAW_IMAGE=openclaw-nostr:custom docker compose up -d

Option 4: Build-Arg with Full Source

If you're building OpenClaw from source, include the nostr extension via the OPENCLAW_EXTENSIONS build arg:

# From the openclaw source root
docker build \
  --build-arg OPENCLAW_EXTENSIONS="nostr" \
  -t openclaw:with-nostr .

This uses the extensions/nostr directory within the OpenClaw source tree. To use the fork instead, replace extensions/nostr with the fork's source before building.

Plugin Discovery Precedence

OpenClaw discovers plugins in this order (first match wins):

  1. plugins.load.paths — Explicit paths from config (Option 1)
  2. Workspace extensions<workspace>/.openclaw/extensions/
  3. User extensions~/.openclaw/extensions/ (Option 2)
  4. Bundled extensions/app/extensions/ inside the image (Options 3 & 4)

The fork at a higher-precedence path will shadow the bundled upstream version.

Docker + Durability Patches

If you also need the runtime durability patches (reconnect fix, subscription handling, etc.), apply them after image build or container start:

# For volume-mount setups, run against the container
docker exec -it openclaw-gateway bash -c '...'

# Or use the apply script against a Docker host
scripts/apply-to-agent.sh user@docker-host

See PATCHES.md for the full list of runtime patches.

Programmatic Usage

Channel-Level Functions

These are available from channel.ts and operate on named accounts:

import {
  // Messaging
  publishNostrNote,
  publishNostrReaction,
  deleteNostrEvents,
  // Identity
  resolveNostrIdentity,
  searchNostrDomain,
  validateNostrIdentity,
  // Discovery
  getNostrRelayInfo,
  getNostrRelayCapabilities,
  // Auth
  getNostrHttpAuthToken,
  // Media
  publishNostrFileMetadata,
  // Social
  repostNostrEvent,
  // Parsing
  parseNostrContent,
} from "./src/channel.js";

// Resolve a NIP-05 identity
const alice = await resolveNostrIdentity("[email protected]");
// { nip05: "[email protected]", pubkey: "aabb...", relays: ["wss://..."] }

// Check relay capabilities
const caps = await getNostrRelayCapabilities("wss://relay.sharegap.net");
// { supportedNips: [1, 4, ...], authRequired: false, ... }

// Parse content into structured blocks
const blocks = parseNostrContent("Hello https://example.com #nostr");
// [{ type: "text", ... }, { type: "url", ... }, { type: "hashtag", ... }]

Bus Handle (Direct Access)

For advanced use, get the bus handle from getActiveNostrBuses():

import { getActiveNostrBuses } from "./src/channel.js";

const bus = getActiveNostrBuses().get("default");

// Send a DM and keep the real Nostr ID for logging/threading
const sent = await bus.sendDm(recipientPubkey, "hello from OpenClaw");
// sent.eventId is the kind:4 ID for NIP-04, or the recipient rumor ID for NIP-17
// sent.publishedEventIds contains the signed event IDs accepted by relays

// NIP-44 encrypt a message
const key = bus.getNip44ConversationKey(recipientPubkey);
const encrypted = bus.nip44Encrypt("secret message", key);

// Create a public channel
const channelId = await bus.createChannel({
  name: "My Channel",
  about: "A public channel for discussion",
});

// Send a channel message
await bus.sendChannelMessage({
  channelId,
  content: "Hello channel!",
  relayUrl: "wss://relay.example",
});

// Generate NIP-98 auth token for a Blossom upload
const token = await bus.getNip98Token("https://media.example.com/upload", "POST");

Tier 3 Extras (Namespace Imports)

import { nip49, nip58, nip29, nip47, nip30, BlossomClient } from "./src/nostr-extras.js";

// NIP-49: Encrypt a private key for storage
const ncryptsec = nip49.encrypt(secretKey, "password");
const recovered = nip49.decrypt(ncryptsec, "password");

// NIP-47: Parse a Nostr Wallet Connect string
const connection = nip47.parseConnectionString("nostr+walletconnect://...");

// NIP-30: Find custom emoji in content
for (const match of nip30.matchAll(":custom_emoji: hello")) {
  console.log(match.shortcode, match.url);
}

Testing

Local Relay (Recommended)

# Using strfry
docker run -p 7777:7777 ghcr.io/hoytech/strfry

# Configure openclaw to use local relay
"relays": ["ws://localhost:7777"]

Running Tests

Tests run via vitest from the parent OpenClaw workspace:

# From the openclaw workspace root
pnpm vitest run --config vitest.extensions.config.ts extensions/nostr/

# Run a specific test file
pnpm vitest run --config vitest.extensions.config.ts extensions/nostr/src/nostr-discovery.test.ts

Test Coverage

| Test File | Covers | | -------------------------------------- | --------------------------------------------------------- | | nostr-bus.protocol.test.ts | NIP-04/NIP-17 DM pipeline, reply routing, serialization | | nostr-capabilities-extended.test.ts | NIP-18 reposts, NIP-28 channels, NIP-13/44/57/94/42/98 | | nostr-discovery.test.ts | NIP-05 identity, NIP-11 relay info, NIP-27 content parsing| | nostr-extras.test.ts | Tier 3 re-export surface verification | | nostr-profile-http.test.ts | HTTP API endpoints including identity/relay-info routes |

Manual Test

  1. Start the gateway with Nostr configured
  2. Open Damus, Amethyst, or another Nostr client
  3. Send a DM to your bot's npub
  4. Verify the bot responds

Security Notes

  • Private keys are never logged
  • Event signatures are verified before processing
  • Use environment variables for keys, never commit to config files
  • Consider using allowlist mode in production
  • NIP-98 tokens are signed with the bus's key — scope them to specific URLs
  • NIP-44 conversation keys are derived from the bus's secret key
  • HTTP mutation endpoints (PUT, POST, DELETE) are restricted to loopback addresses

Troubleshooting

Bot not receiving messages

  1. Verify private key is correctly configured
  2. Check relay connectivity
  3. Ensure enabled is not set to false
  4. Check the bot's public key matches what you're sending to

Messages not being delivered

  1. Check relay URLs are correct (must use wss://)
  2. Verify relays are online and accepting connections
  3. Check for rate limiting (reduce message frequency)

Docker: Plugin not loading

  1. Verify the volume mount path is correct and readable
  2. Check plugins.load.paths points to the right directory
  3. Run openclaw plugins list to see discovered plugins
  4. Check container logs: docker compose logs openclaw-gateway

NIP-05 resolution failing

  1. The target domain must serve /.well-known/nostr.json
  2. Check for CORS issues if resolving from a browser context
  3. Results are cached for 5 minutes — wait or restart to retry

License

MIT