Skip to content

Add L402 discovery: manifest + quote endpoints#241

Open
Roasbeef wants to merge 4 commits into
masterfrom
l402-discovery
Open

Add L402 discovery: manifest + quote endpoints#241
Roasbeef wants to merge 4 commits into
masterfrom
l402-discovery

Conversation

@Roasbeef

Copy link
Copy Markdown
Member

In this PR, we add the server side of the L402 discovery layer to aperture, as
specified in lightninglabs/L402#27.
Today aperture is reactive: a client only learns what a service costs by hitting
it and getting a 402 back. Discovery lets a client see the catalog, the prices,
and the caveat vocabulary up front, and gives us a place for dynamic pricing and
the start of in-protocol negotiation.

Two new endpoints are mounted as priority local services, the same mechanism the
admin and dashboard endpoints use, so they bypass the 402 gate and are always
free. /.well-known/l402.json serves a static manifest built from the
configured services: their capabilities, tiers, prices, constraint bounds, and
the caveat conditions the proxy enforces. /l402/quote takes a client-chosen
bundle (service, capabilities, constraint values, optional budget), prices it,
maps it to the macaroon's first-party caveats, and mints a ready-to-pay
challenge.

No new verification machinery

The quote endpoint reuses the existing mint and challenger. We factor the core
of MintL402 into a shared helper and add MintL402WithCaveats, which takes an
explicit price and caveat set instead of deriving them from the static
ServiceLimiter. A quote response is just an ordinary L402 challenge minted for
a bundle the client chose, so the credential verifies through the unmodified
base path: nothing downstream of the mint changes.

Formula pricing

We add a formula pricing model (basemsat + sum(pricemsatperunit * ceil(value / unit))) as a value struct on proxy.Service, mirroring DynamicPrice. It is
surfaced only through discovery: the manifest advertises it, and the quote
endpoint computes the price from the client's chosen constraint values. The
reactive 402 price is untouched and still uses Price or DynamicPrice. The
sample config documents the new option.

The quote endpoint returns a structured error envelope on failure, sends
permissive CORS headers (the manifest carries no secrets), and the discovery
package ships with unit tests covering the manifest build, the pricing models,
the bundle-to-caveat mapping, and the error cases.

BOLT 12 is intentionally not wired here: a macaroon must commit to the payment
hash, which a BOLT 12 offer does not expose until an invoice is fetched, so that
binding needs its own treatment, as noted in the spec.

Roasbeef added 4 commits June 9, 2026 18:41
In this commit, we factor the core of MintL402 (identifier creation, secret
generation, macaroon assembly, and transaction logging) into a shared
mintWithChallenge helper, and expose a new MintL402WithCaveats method on top of
it.

Where MintL402 derives its caveats from the configured ServiceLimiter,
MintL402WithCaveats takes an explicit price and caveat set from the caller. This
is what the L402 discovery quote endpoint needs: it lets us mint a credential
for a client-chosen bundle while reusing the same identifier, secret, and
challenge machinery, so the result is indistinguishable from a reactive
challenge at verification time.
In this commit, we add a formula pricing model: a base price plus per-unit
components that scale with a client's chosen constraint values, computed as
basemsat + sum(pricemsatperunit * ceil(value / unit)). The full price is
computable by a client from the discovery manifest alone, while the invoice the
server issues stays authoritative.

The config lives on proxy.Service as a value struct mirroring DynamicPrice, and
is surfaced only through the L402 discovery layer. It does not change the
reactive 402 price, which still uses Price or DynamicPrice. We also expose
Service.ResourcePrice so callers outside the proxy package (the discovery quote
endpoint) can query the service's configured pricer.
In this commit, we add the server side of the L402 discovery layer. The new
package serves a free static manifest at /.well-known/l402.json that advertises
the configured services, their tiers, capabilities, prices (fixed, formula, or
dynamic), constraint bounds, and the caveat vocabulary the proxy enforces.

It also serves an optional quote endpoint at /l402/quote. A client proposes a
bundle (service, capabilities, constraint values, optional budget), and we
price it, map it to the macaroon's first-party caveats, and mint a ready-to-pay
challenge via MintL402WithCaveats. Because the quote is an ordinary L402
challenge, the credential verifies through the unmodified base path. Both
handlers are free, send permissive CORS headers, and return a structured error
envelope on failure.
In this commit, we wire the discovery manifest and quote endpoint into the
proxy as priority local services, the same mechanism the admin and dashboard
endpoints use, so they bypass the 402 gate and are always available. They reuse
the same minter as the reactive flow. The sample config documents the new
formula pricing option.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant