Add L402 discovery awareness - #25
Conversation
In this commit, we add the client side of the L402 discovery layer. The new package fetches a provider's manifest from the well-known path (falling back to a Link header), and parses both profiles: the bespoke JSON and the self-describing OpenAPI document with its x-l402-* extensions, into one common shape. A formula price can be computed locally from the manifest. It also requests quotes and, crucially, verifies them before the client pays. We decode the returned BOLT 11 invoice in-process with zpay32 (no node round trip) and confirm the macaroon commits to the invoice's payment hash, that its caveats grant at least the requested bundle, that the amount fits the budget, and that it pays the expected node. A quote that fails any check is rejected, so a buggy or hostile provider cannot take payment for a credential that does not grant what was asked.
In this commit, we add Discover and Quote to the shared service layer so the CLI and MCP server expose discovery through one code path. Discover fetches and parses the manifest; Quote resolves the quote endpoint, posts the bundle, and runs the pre-pay verification using the client's configured max cost and the provider's advertised node pubkey.
In this commit, we add the discover command with manifest and quote subcommands. "lnget discover manifest <url>" shows what a provider offers and how it prices things; "lnget discover quote <url> --service ..." proposes a bundle and returns a verified, ready-to-pay challenge. Both work without paying, and support JSON output for agents.
In this commit, we expose discovery to agents as two MCP tools. discover returns a provider's manifest; quote proposes a bundle and returns a challenge that is verified before it is handed back. Both delegate to the shared service layer.
|
test |
Code ReviewFound 4 CLAUDE.md compliance issues, 0 bugs. 1. Exported struct fields missing GoDoc — discovery/manifest.goLine 40 and throughout the file. Exported struct fields in Provider, Manifest, ServiceEntry, Tier, ResourceEntry, Pricing, FormulaComponent, Constraint, and CaveatSpec are missing GoDoc comments. Per CLAUDE.md L57-L58:
Other structs in this PR (DecodedInvoice, VerifyOptions, QuoteResult) follow the expected pattern. These structs should be updated to match. 2. Exported struct fields missing GoDoc — discovery/quote.goExported struct fields in QuoteRequest, QuoteResponse, and QuoteError are missing GoDoc comments. Only QuoteError.Status has a comment. All other exported fields need GoDoc comments starting with their name. Same rule: CLAUDE.md L57-L58. 3. Missing spacing between switch cases — discovery/verify.goThe switch cases in stringifyValue have no blank lines between them. Per CLAUDE.md L70-L71:
Add blank lines between each case/default clause. 4. Missing function comment — discovery/discovery_test.gomakeMacaroon is missing a function comment. Per CLAUDE.md L47-L48:
Compare with makeInvoice (line 257) and hexEncode (line 406) which both have proper comments. |
In this PR, we teach lnget about L402 discovery, the client side of the layer
specified in lightninglabs/L402#27
and served by aperture#241.
Until now lnget only learned what a resource cost by hitting it and getting a 402
back. Now it can fetch a provider's manifest, see the catalog and prices ahead of
time, and request a quote for a specific bundle, all without paying.
The new
discoverypackage fetches the manifest from the well-known path,falling back to a
Link: rel="l402-manifest"header, and parses both profiles:the bespoke JSON and the self-describing OpenAPI document with its
x-l402-*extensions, into one common shape. A formula price can be computed locally from
the manifest.
Verify before you pay
The interesting part is the quote path. A quote response is an ordinary L402
challenge minted for a bundle the client chose, and lnget verifies it before
paying. We decode the returned BOLT 11 invoice in-process with
zpay32, no noderound trip, and confirm the macaroon commits to the invoice's payment hash, that
its caveats grant at least the bundle we asked for, that the amount is within the
configured max cost, and that it pays the provider's advertised node. A quote
that fails any of these is rejected, so a buggy or hostile provider can't take
payment for a credential that doesn't grant what we requested.
Surfaces
Discovery lives in the shared service layer (
DiscoverandQuote), so the CLIand MCP server expose it through one code path. The CLI gains a
discovercommand with
manifestandquotesubcommands, both with JSON output foragents. The MCP server gains
discoverandquotetools. Paying a quote stillgoes through the existing flow; this PR is about awareness and verification.
The
discoverypackage ships with unit tests covering both manifest profiles,the well-known and Link-header fetch paths, the quote request and error
handling, in-process invoice decoding, and the full pre-pay verification against
a generated invoice whose payment hash we control.
OpenAPI parsing here is JSON-only (the common case for a served
openapi.json);both the bespoke and OpenAPI profiles are covered.