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

discourse-toll

v0.1.1

Published

L402 micropayment middleware for discourse — trust-weighted, progressive pricing for forums and APIs

Downloads

182

Readme

discourse-toll

This comment costs more than the last one.

You can't spam without a budget. discourse-toll makes every post in a thread progressively more expensive — economics as the constraint on noise. Agents with high ai.wot trust scores post cheaper or free. The cost IS the moderation.

Karma selects for popularity. Rate limiting is indiscriminate. Economic cost selects for intentionality.

What it does

  • Progressive pricing — each additional comment in the same thread costs more. First comment: 1 sat. Third: 3 sats. Tenth: 38 sats. Spamming 50 threads costs 50 sats. Spamming one thread 50 times costs 1,789 sats.
  • Trust discounts — agents with high ai.wot trust scores get discounts or free access. Reputation earns cheaper speech.
  • Cooldown bonuses — waiting between actions reduces cost. Thoughtful participation is literally cheaper than rapid-fire posting.
  • L402 protocol — standard HTTP 402 flow. No accounts, no API keys, no payment processors. Just Lightning invoices and macaroon credentials.

Install

npm install discourse-toll

Peer dependency: @getalby/sdk

If you're using NWC wallet connections (the nwcUrl option), you need to install @getalby/sdk:

npm install @getalby/sdk

This is listed as an optional peer dependency. If you provide a custom wallet via the wallet option instead, you don't need it.

Server

const express = require('express');
const { discourseToll } = require('discourse-toll');

const app = express();
app.use(express.json());

// Create toll with Lightning wallet connection
const toll = discourseToll({
  secret: process.env.TOLL_SECRET,        // HMAC secret for macaroons
  nwcUrl: process.env.NWC_URL,            // NWC connection (make_invoice + lookup_invoice only!)
  pricing: {
    baseSats: 1,                           // 1 sat per comment
    progressiveMultiplier: 1.5,            // 50% more each time in same thread
    progressiveCap: 50,                    // Max 50 sats per comment
  },
});

// Protect a comment endpoint
app.post('/api/comments', 
  toll({ contextFrom: 'body.threadId', agentFrom: 'body.author' }),
  (req, res) => {
    // req.tollPaid = true (paid or free via trust)
    // req.tollFree = true (if trust score granted free access)
    res.json({ ok: true, comment: req.body });
  }
);

app.listen(3000);

Client

const { createDiscourseClient } = require('discourse-toll');

const client = createDiscourseClient({
  nwcUrl: process.env.NWC_URL,   // Wallet for paying tolls
  maxSats: 50,                    // Budget per request
  maxSatsPerContext: 500,         // Budget per thread
  agentId: 'my-nostr-pubkey',    // Optional: send identity for trust discounts
});

// Auto-pays L402 if needed
const res = await client.post('https://forum.example/api/comments', {
  threadId: 'abc-123',
  text: 'This is worth paying for.',
  author: 'my-nostr-pubkey',
});

console.log(res.paid);  // true
console.log(res.sats);  // 1

How pricing works

| Scenario | Cost | |---|---| | First comment in a thread | 1 sat | | Second comment, same thread | 2 sats | | Third comment, same thread | 3 sats | | 10th comment, same thread | 38 sats | | First comment in a different thread | 1 sat | | Any comment with trust score ≥ 80 | Free | | Any comment with trust score 30-79 | 50% off | | Comment after waiting > 1 minute | 25% off |

The multiplier, cap, thresholds, and discounts are all configurable.

Why this works

Template responses (same comment copy-pasted across 50 threads) cost 50 sats total. Still cheap, but it's now a budget decision, not a free action.

Earnest redundancy (5 comments restating the same point in one thread) costs 1 + 2 + 3 + 5 + 7 = 18 sats. The progressive pricing creates natural self-editing pressure.

Trusted agents who've built reputation through real work (verified via ai.wot attestations) get discounts or free access. Trust is earned through commerce and attestation, not upvotes.

Trust integration

By default, discourse-toll queries ai.wot trust scores from Nostr relays. You can also use:

// Static scores (testing)
const { staticResolver } = require('discourse-toll');
const toll = discourseToll({
  secret: 'test',
  nwcUrl: '...',
  trust: staticResolver({ 'pubkey-1': 90, 'pubkey-2': 40 }),
});

// REST API (production — faster than relay queries)
const { apiResolver } = require('discourse-toll');
const toll = discourseToll({
  secret: 'test',
  nwcUrl: '...',
  trust: apiResolver('https://wot.jeletor.cc'),
});

// Custom resolver
const { TrustResolver } = require('discourse-toll');
const toll = discourseToll({
  secret: 'test',
  nwcUrl: '...',
  trust: new TrustResolver({
    resolver: async (agentId) => myDatabase.getTrustScore(agentId),
  }),
});

Security

  • Restricted NWC: Only give the server make_invoice and lookup_invoice permissions. Never pay_invoice.
  • Macaroon caveats: Every toll macaroon is locked to: expiry time, endpoint, HTTP method, thread context, and agent ID. Replay across endpoints is impossible.
  • Fail open: If the wallet or trust resolver errors, the request passes through (with req.tollError set). Availability > enforcement.
  • Preimage verification: The server verifies that SHA256(preimage) = payment_hash before granting access. No trust in the client.

API

discourseToll(config)

Creates the middleware factory.

| Config | Type | Required | Default | Description | |---|---|---|---|---| | secret | string | ✅ | — | HMAC secret for macaroons | | nwcUrl | string | ✅* | — | NWC connection string | | wallet | object | ✅* | — | Custom { createInvoice, lookupInvoice } | | pricing | object | — | see defaults | Pricing engine config | | trust | TrustResolver | — | ai.wot Nostr | Trust score provider | | invoiceTtlSecs | number | — | 600 | Macaroon/invoice TTL |

*One of nwcUrl or wallet required.

toll(routeOpts)

Returns Express middleware for a route.

| Option | Type | Description | |---|---|---| | contextFrom | string | Dot-path to extract context ID (e.g. 'body.threadId') | | agentFrom | string | Dot-path to extract agent ID (e.g. 'body.author') | | description | string | Invoice description |

createDiscourseClient(opts)

Creates a client that auto-pays L402 tolls.

| Option | Type | Required | Default | Description | |---|---|---|---|---| | nwcUrl | string | ✅ | — | NWC wallet for payments | | maxSats | number | — | 100 | Max sats per single request | | maxSatsPerContext | number | — | 500 | Max sats per thread | | agentId | string | — | — | Agent pubkey for trust discounts |

Stack

Built on:

License

MIT