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

@lnbot/l402

v1.0.0

Published

L402 Lightning payment middleware for Express.js — paywall any API in one line

Downloads

23

Readme

@lnbot/l402

npm version npm downloads bundle size TypeScript License: MIT

L402 payment middleware for Express.js — paywall any API in one line. Built on ln.bot.

Add Lightning-powered pay-per-request to any Express API. Protect premium routes with a paywall, or build clients that auto-pay L402-protected services — all without touching any cryptography.

import express from "express";
import { l402, LnBot } from "@lnbot/l402";

const app = express();
const ln = new LnBot({ apiKey: "key_..." });

app.use("/api/premium", l402.paywall(ln, { price: 10 }));

app.get("/api/premium/data", (req, res) => {
  res.json({ data: "premium content" });
});

This package is a thin glue layer. All L402 logic — macaroon creation, signature verification, preimage checking — lives in the ln.bot API via @lnbot/sdk. Zero crypto dependencies.


What is L402?

L402 is a protocol built on HTTP 402 Payment Required. It enables machine-to-machine micropayments over the Lightning Network:

  1. Client requests a protected resource
  2. Server returns 402 with a Lightning invoice and a macaroon token
  3. Client pays the invoice, obtains the preimage as proof of payment
  4. Client retries the request with Authorization: L402 <macaroon>:<preimage>
  5. Server verifies the token and grants access

L402 is ideal for API monetization, AI agent tool access, pay-per-request data feeds, and any scenario where you want instant, permissionless, per-request payments without subscriptions or API key provisioning.


Install

npm install @lnbot/l402
pnpm add @lnbot/l402
yarn add @lnbot/l402

@lnbot/sdk and express are peer dependencies and will be resolved automatically.


Server — Protect Routes with L402

The l402.paywall() middleware intercepts requests, verifies L402 tokens via the SDK, and issues new challenges when payment is needed. Two SDK calls, ~40 lines of glue code, zero crypto.

import express from "express";
import { l402, LnBot } from "@lnbot/l402";

const app = express();
const ln = new LnBot({ apiKey: "key_..." });

// Paywall a route group — 10 sats per request
app.use("/api/premium", l402.paywall(ln, {
  price: 10,
  description: "API access",
}));

app.get("/api/premium/data", (req, res) => {
  // req.l402 is populated after successful payment verification
  res.json({
    data: "premium content",
    paymentHash: req.l402?.paymentHash,
  });
});

// Free routes still work normally
app.get("/api/free/health", (req, res) => {
  res.json({ status: "ok" });
});

app.listen(3000);

How the middleware works

  1. Checks for an Authorization: L402 ... header
  2. If present, calls ln.l402.verify() — the SDK checks signature, preimage, and caveats server-side
  3. If valid, populates req.l402 and calls next()
  4. If missing or invalid, calls ln.l402.createChallenge() and returns a 402 response with the invoice and macaroon

Dynamic pricing

// Fixed price per route
app.use("/api/cheap", l402.paywall(ln, { price: 1 }));
app.use("/api/expensive", l402.paywall(ln, { price: 100 }));

// Custom pricing function — receives the request, returns price in sats
app.use("/api/dynamic", l402.paywall(ln, {
  price: (req) => {
    if (req.path.includes("/bulk")) return 50;
    return 5;
  },
}));

Paywall options

| Option | Type | Description | | --- | --- | --- | | price | number \| (req) => number | Price in satoshis — fixed or per-request | | description | string | Invoice memo shown in wallets | | expirySeconds | number | Challenge expiry in seconds | | caveats | string[] | Macaroon caveats to attach |


Client — Auto-Pay L402 APIs

The l402.client() wrapper makes L402 payment transparent. It detects 402 responses, pays the Lightning invoice via the SDK, caches the token, and retries — all in one fetch call.

import { l402, LnBot } from "@lnbot/l402";

const ln = new LnBot({ apiKey: "key_..." });

const client = l402.client(ln, {
  maxPrice: 100,         // refuse to pay more than 100 sats per request
  budgetSats: 50000,     // spending limit for the period
  budgetPeriod: "day",   // reset period: "hour" | "day" | "week" | "month"
  store: "memory",       // token cache: "memory" (default) | "none" | custom TokenStore
});

// Use like fetch — L402 payment is transparent
const response = await client.fetch("https://api.example.com/premium/data");
const data = await response.json();

// Convenience methods — all HTTP verbs
const json = await client.get("https://api.example.com/premium/data");
const result = await client.post("https://api.example.com/premium/submit", {
  body: JSON.stringify({ query: "test" }),
});
await client.put("https://api.example.com/premium/item/1", {
  body: JSON.stringify({ name: "updated" }),
});
await client.patch("https://api.example.com/premium/item/1", {
  body: JSON.stringify({ status: "active" }),
});
await client.delete("https://api.example.com/premium/item/1");

How the client works

  1. Checks the token cache for a valid credential
  2. If cached, sends the request with the Authorization header
  3. If no cache (or server rejects), makes a plain request
  4. On 402, parses the challenge and checks budget limits
  5. Calls ln.l402.pay() — the SDK pays the invoice and returns a ready-to-use token
  6. Caches the token and retries the request with authorization

Client options

| Option | Type | Default | Description | | --- | --- | --- | --- | | maxPrice | number | 1000 | Max sats to pay for a single request | | budgetSats | number | unlimited | Total budget in sats for the period | | budgetPeriod | string | — | Reset period: "hour", "day", "week", "month" | | store | string \| TokenStore | "memory" | Token cache: "memory", "none", or custom |

Custom token store

Implement the TokenStore interface for Redis, file system, or any persistence layer:

import { l402, LnBot } from "@lnbot/l402";
import type { TokenStore } from "@lnbot/l402";

const ln = new LnBot({ apiKey: "key_..." });

const redisStore: TokenStore = {
  async get(url) { /* read from Redis */ },
  async set(url, token) { /* write to Redis */ },
  async delete(url) { /* delete from Redis */ },
};

const client = l402.client(ln, { store: redisStore });

Header Utilities

Parse and format L402 headers for custom integrations:

import { l402 } from "@lnbot/l402";

// Parse Authorization: L402 <macaroon>:<preimage>
l402.parseAuthorization("L402 mac_base64:preimage_hex");
// → { macaroon: "mac_base64", preimage: "preimage_hex" }

// Parse WWW-Authenticate: L402 macaroon="...", invoice="..."
l402.parseChallenge('L402 macaroon="abc", invoice="lnbc1..."');
// → { macaroon: "abc", invoice: "lnbc1..." }

// Format headers
l402.formatAuthorization("mac_base64", "preimage_hex");
// → "L402 mac_base64:preimage_hex"

l402.formatChallenge("abc", "lnbc1...");
// → 'L402 macaroon="abc", invoice="lnbc1..."'

Error Handling

import { L402Error, L402BudgetExceededError, L402PaymentFailedError } from "@lnbot/l402";

try {
  const data = await client.get("https://api.example.com/expensive");
} catch (err) {
  if (err instanceof L402BudgetExceededError) {
    // Price exceeds maxPrice or total budget exhausted
  } else if (err instanceof L402PaymentFailedError) {
    // Lightning payment failed or didn't settle
  } else if (err instanceof L402Error) {
    // Other L402 protocol error (missing header, parse failure)
  }
}

API Reference

Server

| Export | Description | | --- | --- | | l402.paywall(ln, options) | Express middleware factory — protects routes behind an L402 paywall |

Client

| Export | Description | | --- | --- | | l402.client(ln, options?) | Creates an L402-aware HTTP client with automatic payment | | client.fetch(url, init?) | L402-aware fetch — pays 402 challenges automatically | | client.get(url, init?) | GET + JSON parse with automatic L402 payment | | client.post(url, init?) | POST + JSON parse with automatic L402 payment | | client.put(url, init?) | PUT + JSON parse with automatic L402 payment | | client.patch(url, init?) | PATCH + JSON parse with automatic L402 payment | | client.delete(url, init?) | DELETE + JSON parse with automatic L402 payment |

Header Utilities

| Export | Description | | --- | --- | | l402.parseAuthorization(header) | Parse Authorization: L402 ... into { macaroon, preimage } | | l402.parseChallenge(header) | Parse WWW-Authenticate: L402 ... into { macaroon, invoice } | | l402.formatAuthorization(macaroon, preimage) | Format an Authorization header value | | l402.formatChallenge(macaroon, invoice) | Format a WWW-Authenticate header value |

Types

| Type | Description | | --- | --- | | L402PaywallOptions | Options for l402.paywall() | | L402ClientOptions | Options for l402.client() | | L402Token | Cached L402 credential (macaroon + preimage + metadata) | | TokenStore | Interface for custom token caches | | L402RequestData | Data attached to req.l402 after verification |

Errors

| Class | Description | | --- | --- | | L402Error | Base error for all L402 protocol errors | | L402BudgetExceededError | Price or cumulative spend exceeds configured limits | | L402PaymentFailedError | Lightning payment failed or didn't return authorization |


Requirements


Related packages

Links

License

MIT