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

402-announce

v2.0.1

Published

Announce HTTP 402 services (L402 and x402) on Nostr for decentralised discovery. Kind 31402 parameterised replaceable events.

Downloads

2,486

Readme

402-announce

Announce HTTP 402 services on Nostr for decentralised discovery. Supports both L402 and x402 payment protocols.

CI Coverage MIT Licence

Publishes kind 31402 parameterised replaceable events so that AI agents (and any Nostr client) can discover paid APIs without a central registry.

Live Dashboard — see every service announcing on the network right now.

How it works

flowchart LR
    A[Your API] -->|"npm install<br/>402-announce"| B[402-announce]
    B -->|"kind 31402<br/>signed event"| C[(Nostr Relays)]
    C -->|subscribe & filter| D[402-mcp]
    D -->|discover + pay| A

    style B fill:#f59e0b,color:#000
    style C fill:#8b5cf6,color:#fff
    style D fill:#3b82f6,color:#fff

Your API publishes a service announcement to Nostr relays. AI agents (via 402-mcp) discover it, pay the invoice, and consume the API. No central registry required.

Quick start

npm install 402-announce
import { announceService } from '402-announce'

const handle = await announceService({
  secretKey: '64-char-hex-nostr-secret-key',
  relays: ['wss://relay.damus.io', 'wss://relay.primal.net'],
  identifier: 'jokes-api',
  name: 'Jokes API',
  urls: ['https://jokes.example.com'],
  about: 'A joke-telling service behind an L402 paywall',
  pricing: [
    { capability: 'get_joke', price: 1, currency: 'sats' },
  ],
  paymentMethods: ['bitcoin-lightning-bolt11', 'bitcoin-cashu', 'bitcoin-cashu-xcashu'],
  topics: ['comedy', 'ai'],
  capabilities: [
    { name: 'get_joke', description: 'Returns a random joke' },
  ],
  version: '1.0.0',
})

console.log('Published event:', handle.eventId)
console.log('From pubkey:', handle.pubkey)

// Later, when shutting down:
handle.close()

Multi-transport example (clearnet + Tor + Handshake — same service, different access paths):

const handle = await announceService({
  secretKey: '64-char-hex-nostr-secret-key',
  relays: ['wss://relay.damus.io', 'wss://relay.primal.net'],
  identifier: 'jokes-api',
  name: 'Jokes API',
  urls: [
    'https://jokes.example.com',                  // clearnet
    'http://jokesxyz...onion',                    // Tor hidden service
    'https://jokes.example.hns',                  // Handshake domain
  ],
  about: 'A joke-telling service behind an L402 paywall',
  pricing: [{ capability: 'get_joke', price: 1, currency: 'sats' }],
  paymentMethods: ['bitcoin-lightning-bolt11'],
  topics: ['comedy', 'ai'],
})

Each URL becomes a separate url tag in the kind 31402 event. Clients try them in order and use whichever they can reach. urls accepts 1–10 entries; any parseable URL is valid.

See examples/ for runnable scripts.

How it fits together

flowchart LR
    subgraph "Your Server"
        API[Your API]
        TB[toll-booth<br/>L402 paywall]
        AN[402-announce]
    end

    subgraph "Nostr Network"
        R1[(Relay 1)]
        R2[(Relay 2)]
    end

    subgraph "AI Agent"
        MCP[402-mcp<br/>discovery + payment]
    end

    API --> TB
    TB -->|"protects"| API
    AN -->|"kind 31402"| R1
    AN -->|"kind 31402"| R2
    R1 -->|"subscribe"| MCP
    R2 -->|"subscribe"| MCP
    MCP -->|"HTTP + L402 token"| TB

    style TB fill:#ef4444,color:#fff
    style AN fill:#f59e0b,color:#000
    style MCP fill:#3b82f6,color:#fff
  1. toll-booth wraps your API with an L402 paywall
  2. 402-announce publishes a kind 31402 event describing the service, pricing, and payment methods
  3. 402-mcp discovers the announcement, pays the invoice, and calls your API

What it does

  • Builds and signs kind 31402 Nostr events
  • Publishes to one or more Nostr relays in parallel
  • Zeroises secret key bytes after use
  • Degrades gracefully when individual relays fail
  • Provides a close() handle for clean disconnection

What it does not do

  • Does not run an L402 paywall (use toll-booth for that)
  • Does not subscribe to or search for announcements (use 402-mcp for that)
  • Does not handle payments or token verification

API

announceService(config): Promise<Announcement>

High-level function that builds, signs, and publishes the event to multiple relays.

Returns an Announcement handle:

  • eventId — the published Nostr event ID
  • pubkey — the Nostr pubkey derived from your secret key
  • close() — disconnect from all relays

buildAnnounceEvent(secretKey, config): VerifiedEvent

Lower-level function that builds and signs the event without publishing. Useful if you manage relay connections yourself.

Config options

| Field | Type | Required | Description | |------------------|-------------------|----------|------------------------------------------------| | secretKey | string | yes | 64-char hex Nostr secret key | | relays | string[] | yes | Relay URLs (wss:// or ws://) | | identifier | string | yes | Unique listing ID (Nostr d tag) | | name | string | yes | Human-readable service name | | urls | string[] | yes | HTTP endpoints for the service (1–10 entries, any parseable URL) | | about | string | yes | Short description | | pricing | PricingDef[] | yes | Per-capability pricing | | paymentMethods | string[] | yes | Accepted payment methods | | picture | string | no | Icon URL | | topics | string[] | no | Topic tags for filtering | | capabilities | CapabilityDef[] | no | Capability details (stored in event content) | | version | string | no | Service version (stored in event content) |

Announce flow

flowchart TD
    A[announceService config] --> B{Validate}
    B -->|"secretKey: 64-char hex"| C[Derive pubkey]
    B -->|"relays: wss:// URLs"| C
    C --> D[Build kind 31402 event]
    D --> E[Sign with secret key]
    E --> F[Zeroise key bytes]
    F --> G["Connect relays (parallel, 10s timeout)"]
    G --> H{Each relay}
    H -->|success| I[Publish event]
    H -->|failure| J[Log warning, continue]
    I --> K["Return { eventId, pubkey, close() }"]
    J --> K

Event format

Each announcement is a kind 31402 parameterised replaceable event. The combination of pubkey + d tag uniquely identifies a listing — publishing again with the same values updates the existing listing.

graph TB
    subgraph "Kind 31402 Event"
        direction TB
        subgraph Tags
            D["d: jokes-api"]
            N["name: Jokes API"]
            U1["url: https://jokes.example.com"]
            U2["url: https://jokesapi.example.onion (optional)"]
            AB["about: A joke-telling service"]
            PMI1["pmi: bitcoin-lightning-bolt11"]
            PMI2["pmi: bitcoin-cashu"]
            P["price: get_joke, 1, sats"]
            T["t: comedy"]
        end
        subgraph Content["Content (JSON)"]
            CAP["capabilities: [{ name, description }]"]
            VER["version: 1.0.0"]
        end
    end

    style Tags fill:#1e293b,color:#e2e8f0
    style Content fill:#1e293b,color:#e2e8f0

Tags

| Tag | Required | Description | Example | |-----------|----------|----------------------------------------------|-----------------------------------| | d | yes | Unique identifier for this listing | jokes-api | | name | yes | Human-readable service name | Jokes API | | url | yes | HTTP endpoint (one tag per URL; repeatable) | https://jokes.example.com | | about | yes | Short description | A joke-telling service | | pmi | yes | Payment method identifier (repeatable) | bitcoin-lightning-bolt11 | | price | yes | Capability pricing (repeatable) | get_joke, 1, sats | | t | no | Topic tag for search/filtering (repeatable) | comedy | | picture | no | Icon URL | https://example.com/icon.png |

Recognised Payment Method Identifiers

| Identifier | Description | |------------|-------------| | bitcoin-lightning-bolt11 | Lightning Network (BOLT-11 invoices) | | bitcoin-cashu | Cashu ecash (generic) | | bitcoin-cashu-xcashu | Cashu ecash via NUT-24 (X-Cashu header) | | x402-evm | x402 stablecoin payments (EVM chains) |

Content

The event content is a JSON object with optional fields:

{
  "capabilities": [
    { "name": "get_joke", "description": "Returns a random joke" }
  ],
  "version": "1.0.0"
}

Multiple URLs vs multiple events

This distinction is important for operators:

Multiple URLs in one event — use urls: ['...', '...'] when the URLs represent the same service on different transports (clearnet, Tor, Handshake). The pricing, credentials, and macaroon signing key are identical. Clients pick whichever URL they can reach. This is for censorship resistance and redundancy.

Separate kind 31402 events — publish a new event (different identifier) when you have genuinely different services: different pricing tiers per transport, different capabilities, or services that operate independently. A single event should describe one logical service.

In short: same service + different network paths → one event with multiple url tags. Different services → separate events.

Security

  • Secret key bytes are zeroised immediately after signing (in both announceService and buildAnnounceEvent)
  • Never log or persist the secret key — pass it in and let the library handle cleanup
  • Relay connections use a 10-second timeout to prevent hanging
  • Individual relay failures are logged as warnings but don't reject the promise

Ecosystem

| Package | Purpose | |---------|---------| | toll-booth | L402 middleware — any API becomes a toll booth in minutes | | toll-booth-announce | Bridge — announce toll-booth services with one function call | | satgate | Production L402 gateway with Lightning and Cashu support | | 402-mcp | MCP server for AI agents to discover, pay, and consume 402 APIs | | Live Dashboard | See every service announcing on the network |

Licence

MIT