Join Nostr
2026-08-17 01:43:03 UTC
in reply to

npub1qn…d9r6h on Nostr: Caveats are the interesting bit, yeah. The macaroon carries four: RequestPath = ...

Caveats are the interesting bit, yeah. The macaroon carries four: RequestPath = /exact, Realm = name, RequestMethod = GET, ExpiresAt = <unix>. Method is always bound, otherwise a token bought for GET /x replays against POST /x, which is usually a different resource and often a different price.

Path is the default, metered per route. If you want one payment to cover a group of routes you set l402_realm and it binds to the realm instead of the exact path. There's a config-time guard on that one: realm without l402_indefinite_access refuses to start, because a realm token carries a single preimage across many paths, the first request claims it, the replay check kills everything after, and you've sold exactly one request without noticing. Time-bound is l402_macaroon_timeout → ExpiresAt, or indefinite access if you want subscription semantics.

On pricing, l402_amount_msat_default is per-location rather than global, so route granularity is just nginx config. Beyond that there's Redis-backed dynamic pricing: a per-path price key plus an lnurl:<path> override, both fetched in one pipelined round-trip, so you change a price with a single SET and it applies on the next request with no reload. That LNURL override is what makes multi-tenant work, different routes settling to different wallets.

Backends are direct, no intermediary. LND over gRPC (LNC if the node's remote, SOCKS5 for .onion-only), CLN over its unix socket, Eclair over HTTP, plus LNURL, NWC and BOLT12. No LNbits or facilitator in the path, which is most of the point. Anything in between is something that can freeze you or be leaned on.

There's a Cashu path too if you want payer privacy or a single round trip instead of 402 → pay → retry.

Binding logic is in l402_middleware/src/caveats.rs (https://github.com/DhananjayPurohit/l402_middleware/blob/main/src/caveats.rs) if you want to read it directly.