feat: discover all protocols + l402.space bridge note#49
Conversation
Drop the server-side payment_asset=BTC filter so discover returns L402, x402 and MPP services. Non-lightning services (e.g. x402/USDC) can be paid in sats by fetching them through the l402.space bridge. Also surface payment_network in results so callers can tell which rail a service settles on and decide whether the bridge is needed. Refs #26
|
Warning Review limit reached
Next review available in: 45 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThe discover flow now searches 402index.io broadly, filters results to Lightning-native or bridge-settleable services, wraps bridgeable URLs through l402.space, preserves the index total, and exposes payment network data. ChangesPayable service discovery
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Discover as discover()
participant Index as 402index.io
participant Bridge as l402.space
Discover->>Index: Fetch over-fetched services
Index-->>Discover: Return services and total
Discover->>Bridge: Construct wrapped URLs for bridgeable services
Bridge-->>Discover: Return l402.space URL form
Discover-->>Discover: Filter payable services and preserve total
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
When lightning-tools hands back an unpaid 402 (e.g. a USDC-only x402 endpoint it can't settle over lightning), retry once through the l402.space bridge, which re-wraps the upstream as an L402 lightning challenge we can pay. Native L402 and lightning-payable x402/MPP are still paid directly and never touch the bridge - no flag, no manual URL encoding for the caller. Refs #26
It was added so callers could decide whether to use the bridge, but fetch now bridges non-lightning services transparently, so the rail is an implementation detail. protocol already covers what a service is.
|
@reneaaron as a first step I think we don't need any changes in the CLI - instead only on the skill side (don't less mention lightning, and mention l402.space can enable paying to x402/MPP endpoints) |
Address PR feedback (rolznz): the transparent bridge retry in fetch was hidden from callers, which breaks macaroon/x402 token reuse for follow-up calls (js-lightning-tools#328) since the token would belong to the bridge URL, not the original. Instead, wrap non-lightning discover results (x402 on Base/Stellar/EVM) in an explicit l402.space bridge URL up front. The URL a caller fetches is now visible and consistent across follow-up requests, and fetch pays it as a plain L402 endpoint with no per-request redirection. L402/MPP and x402 on the Lightning network keep their native URL. Also surfaces payment_network in the output so callers see the rail. - discover.ts: wrap non-lightning URLs; revert fetch.ts to master - replace fetch-bridge.test.ts with discover-bridge.test.ts
|
Reworked per the feedback — moved the bridging out of
On your first-step point: I kept the |
MPP (like x402) is payment-network agnostic: the index lists MPP services on Stripe/USD and with no declared rail, none of which settle over lightning. The previous predicate treated MPP as natively lightning and left those unwrapped, so they'd fail to pay from a lightning wallet. Decide by payment_network instead: native only when the rail is Lightning (handling multi-rail values like "Lightning, Base"), with L402 kept native by definition even when the index reports no network. Validated against 2000 live 402index.io services: all x402/MPP on Base/eip155/stellar/ Solana/Polygon/Stripe/None bridge; L402 (any network) and x402-on-Lightning stay native; zero L402-on-non-lightning anomalies.
That is fine 🤖 👍 |
Bridging a Stripe/USD or Stellar endpoint is pointless - the bridge can't
pay those upstreams, so the wrapped URL would just 402. Whitelist the
rails l402.space reports as funded (/api/info fundedNetworks: base, solana,
tempo, lightning) instead of bridging everything non-lightning.
Three-way decision per service:
- native (pay directly): L402, or any rail == lightning
- bridged: rail in {base, solana, tempo} (eip155:8453 aliased to base;
"Base, Solana" multi-rail supported)
- unwrapped: stellar, polygon, stripe, testnets (Base Sepolia != base),
unknown/None - not payable from a lightning wallet, left as-is
Validated the bucketing against 2000 live 402index.io services.
Match getAlby/cli#49 (Option B): discover now returns services on a bridge-funded rail with their l402.space URL already set, so agents just fetch the result's url - no manual encoding. Scope the bridge's reach to the rails l402.space actually funds (USDC on Base/Solana, stablecoin on Tempo, plus native Lightning/L402) instead of implying any endpoint works; call out that Stellar/Polygon/Stripe aren't payable from lightning. Also merges master (resolves the stale conflict on these sections) and bumps 1.4.0 -> 1.4.1.
Filter out services on rails the wallet can't reach (not native lightning and not a bridge-funded rail), so every discover result is payable in sats by fetching its url as-is - agents never need to reason about rails or the bridge. Native lightning keeps its own url; bridge-funded rails (Base/Solana/Tempo) come back already wrapped in l402.space. The index can't filter by rail server-side (payment_network is ignored; payment_asset can't tell USDC-on-Base from USDC-on-Stellar), so we filter client-side and over-fetch 2x (capped at the index's 200/page) to still return up to the requested limit. Verified live: a 20-result request comes back with 20 payable services, no Stellar/Polygon/Stripe leaking through.
Per review feedback (rolznz): MPP payment challenges are incompatible with L402 and can't be folded into one, so MPP upstreams need the gateway's dedicated inbound endpoint. The path prefix selects the rail we pay the gateway over, and we always pay via lightning - so MPP goes through /mpp-lightning/ (hands back a lightning invoice), while x402 stays on the default path (gateway folds a lightning L402 challenge). /mpp-tempo/ would demand a Tempo stablecoin we can't pay from a lightning wallet. Verified live against a real MPP/Tempo upstream: /mpp-lightning/ returns a 402 with a lnbc invoice; discover now emits mpp-lightning-prefixed URLs for MPP services and default-prefixed URLs for x402.
Consumer testing flagged that total == services.length made agents read 'total: 10' as 'only 10 services exist'. Report the index's match count for the query instead, so the caller knows the corpus is larger than the returned (payability-filtered, sliced) page.
Lightning-native x402 exists (e.g. x402.albylabs.com/demo/quote, which offers a lnbc invoice directly in its x402 challenge) and must never be bridged. The index currently reports that service's rail as "Lightning", which we already keep unwrapped - but it passes raw CAIP-2 ids through for other rails (eip155:8453 for Base), and a lightning-native challenge's own network id is bip122:000000000019d6689c085ae165831e93 (Bitcoin mainnet). Alias that id to lightning so such a service stays direct-paid rather than routed through the bridge with its markup. Also covers MPP-on-Lightning with a test (already handled by the rail check, previously untested).
Per review: the gateway's /mpp-* endpoints select the *inbound* rail (how you pay the gateway), not the upstream protocol - and the default route folds a lightning L402 challenge for MPP upstreams too (verified live: a wrapped MPP/Tempo upstream returns L402 + lnbc with rail "mpp-tempo"). So non-lightning MPP shares the plain bridge path with x402, and MPP on the Lightning rail is still paid directly, never bridged.
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/tools/lightning/discover.ts (2)
44-49: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider early return for empty
paymentNetworkto avoid[""]array.When
paymentNetworkis empty or null,(paymentNetwork ?? "").split(",")produces[""]. While harmless in the current logic (since""does not match"lightning"or the funded rails), returning an empty array is safer and avoids potential gotchas if the network mapping logic is expanded in the future.♻️ Proposed refactor
function paymentRails(paymentNetwork: string | null): string[] { - return (paymentNetwork ?? "").split(",").map((rail) => { - const normalized = rail.trim().toLowerCase(); - return NETWORK_ALIASES[normalized] ?? normalized; - }); + if (!paymentNetwork) return []; + return paymentNetwork.split(",").map((rail) => { + const normalized = rail.trim().toLowerCase(); + return NETWORK_ALIASES[normalized] ?? normalized; + }); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/tools/lightning/discover.ts` around lines 44 - 49, Update paymentRails to return an empty string array immediately when paymentNetwork is null or empty, before splitting and normalizing values. Preserve the existing alias mapping and normalization behavior for non-empty paymentNetwork values.
84-84: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueConsider fetching a higher baseline margin to better fulfill
requestedLimit.With
requestedLimit * 2, if you request 10 services and more than half of the first 20 index results are on unsupported rails, the function will return fewer than 10 results even if more payable services exist further down the page.Since the API limit is 200 and the JSON payload is very small, fetching a higher minimum (e.g., 50) adds negligible overhead but significantly increases the likelihood of satisfying small requests.
♻️ Proposed refactor
- const fetchLimit = Math.min(200, requestedLimit * 2); + const fetchLimit = Math.min(200, Math.max(50, requestedLimit * 2));🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/tools/lightning/discover.ts` at line 84, Increase the baseline fetch limit in the discovery flow around fetchLimit so small requestedLimit values retrieve at least 50 results, while continuing to cap the value at the API maximum of 200 and scaling larger requests appropriately.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/tools/lightning/discover.ts`:
- Around line 44-49: Update paymentRails to return an empty string array
immediately when paymentNetwork is null or empty, before splitting and
normalizing values. Preserve the existing alias mapping and normalization
behavior for non-empty paymentNetwork values.
- Line 84: Increase the baseline fetch limit in the discovery flow around
fetchLimit so small requestedLimit values retrieve at least 50 results, while
continuing to cap the value at the API maximum of 200 and scaling larger
requests appropriately.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4488bf1c-4f7b-49d6-9c9e-a34903dca74f
📒 Files selected for processing (3)
src/commands/discover.tssrc/test/discover-bridge.test.tssrc/tools/lightning/discover.ts
With limit*2 alone, discover -l 3 fetched a 6-row page that could be (and in practice was) entirely unpayable rails, returning zero services despite 1000+ matches. Floor the fetched page at 50 (still capped at the index's 200) so the payability filter has enough raw material. Also make paymentRails return [] for a null/empty network instead of [""] - no behavior change (callers only do membership checks), just an honest contract.
What
Two changes so an agent (or human) with only a lightning wallet can discover and pay for services on any 402 protocol:
payment_asset=BTCfilter sodiscoverreturns L402, x402, and MPP services, not just lightning ones.402. When that happens,fetchretries once through the l402.space bridge, which re-wraps the upstream as an L402 lightning challenge it can pay. Native L402 is still paid directly and never touches the bridge.No new flags, no manual URL-encoding, no protocol knowledge required —
fetch <service-url>just works regardless of protocol.Why
Per #26, x402 / MPP services were silently filtered out of discovery, and there was no way to pay them from a lightning wallet. The bridge fallback is an internal implementation detail, so callers never construct a special URL or reason about rails.
Test
yarn test— 109 pass, 2 skipped (3 new tests infetch-bridge.test.tscover: bridge retry on a 402, direct pay on success with no bridge call, and no double-bridging an l402.space URL).discoverreturns x402 services (previously empty).https://l402.space/<encoded-x402-url>returns402+WWW-Authenticate: L402+ a lightning invoice.Companion skill update: getAlby/payments-skill#28.
Refs #26
Summary by CodeRabbit
New Features
Bug Fixes