@agentchurch/l402
v0.1.1
Published
L402 (Lightning HTTP 402) payment protocol for TypeScript — HMAC-SHA256 macaroons, challenge/verify, and an LND REST invoice client. Edge-safe core.
Maintainers
Readme
@agentchurch/l402
Charge for HTTP requests with Bitcoin Lightning. This library implements L402, the protocol that turns HTTP's 402 Payment Required status into a real payment flow: your server quotes a price, the client pays a Lightning invoice, and the retry carries cryptographic proof of payment. No accounts, no API keys, no payment processor.
It was extracted from Agent Church, where it runs in production letting AI agents pay for services autonomously. Machine-to-machine payments are L402's sweet spot — an agent can read the challenge, pay, and retry without a human in the loop.
What's in the box:
- The full server side: mint challenges, parse
Authorizationheaders, verify payments. - An LND REST client for creating invoices (works with Umbrel and other self-signed-cert nodes).
- The core is Edge-safe — verification is pure crypto (@noble/hashes), so it runs in Next.js middleware, Cloudflare Workers, and Deno. The LND client is Node-only and lives on a separate subpath so it never enters your Edge bundle.
How the protocol works
- Client requests a paid endpoint → server responds
402with a macaroon (a signed token) and a Lightning invoice. - Client pays the invoice and receives the preimage — a secret that only exists once payment settles.
- Client retries with
Authorization: L402 <macaroon>:<preimage>. - Server checks the macaroon's signature and that the preimage hashes to the invoice's payment hash. Payment proven, no database lookup.
Install
npm install @agentchurch/l402Usage
Quote a price (Node — needs your LND node to mint the invoice):
import { buildL402Challenge, formatWWWAuthenticate, rootKeyFromEnv } from "@agentchurch/l402";
import { createLndClient } from "@agentchurch/l402/lnd";
const lnd = createLndClient({ restUrl: "https://your-node:8080", macaroonHex: process.env.LND_MACAROON_HEX! });
const { paymentHash, invoice } = await lnd.createInvoice(5000, "premium_api access");
const challenge = buildL402Challenge(rootKeyFromEnv(), {
paymentHash, invoice,
service: "premium_api",
amountSats: 5000,
location: "api.example.com",
});
// 402 response with: WWW-Authenticate: formatWWWAuthenticate(challenge)Verify the payment (anywhere, including Edge):
import { parseL402Authorization, verifyL402Token, l402CaveatsMatch } from "@agentchurch/l402";
const token = parseL402Authorization(request.headers.get("Authorization"));
const result = token && verifyL402Token(rootKey, token);
if (result?.valid && l402CaveatsMatch(result, "premium_api", 5000)) {
// Paid. Serve the request.
}l402CaveatsMatch matters: it stops a token bought for a cheap service being replayed against an expensive one.
One payment, one use? That part is yours — verification proves the payment is real, not that it's unused. If your endpoint is pay-per-call, store consumed preimages (a unique-constrained table works) and reject repeats. If one payment should grant a session, skip this; the token's expiry caveat bounds its lifetime.
Reference
- Root key: 32-byte hex (
openssl rand -hex 32), kept secret; it signs and verifies every macaroon. - Macaroon primitives (
createMacaroon,verifyMacaroonSignature, serialization) are exported if you need lower-level control. - Serialization is base64(JSON) — simple and debuggable. Clients treat the macaroon as an opaque string, which is all L402 requires, so standard L402 clients interoperate.
- Full types are shipped; the
.d.tsfiles document every export.
License
MIT © Hypno Labs
