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
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ 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.
| // lightning and handed the response back. Retry once through the l402.space | ||
| // bridge, which converts it to an L402 lightning challenge we can pay. | ||
| if (result.status === 402 && !params.url.startsWith(L402_SPACE_BRIDGE)) { | ||
| result = await fetch402Lib(bridgeUrl(params.url), buildRequestOptions(), { |
There was a problem hiding this comment.
I wonder if there are any side effects of this e.g. regarding getAlby/js-lightning-tools#328
if the client doesn't know at all they are going through this bridge, if they do the request again passing a macaroon (or x402/MPP equivalent) I wonder if this will break.
Would it be possible to wrap the discover response urls with the bridge url instead?
There was a problem hiding this comment.
This will be more in-line also if we decide to switch to l402.space for the discovery recommendations
|
@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.
| const L402_SPACE_BRIDGE = "https://l402.space/"; | ||
|
|
||
| function bridgeUrl(url: string): string { | ||
| return `${L402_SPACE_BRIDGE}${encodeURIComponent(url)}`; |
There was a problem hiding this comment.
for mpp we have dedicated endpoints (/mpp-tempo and /mpp-lightning) because they are incompatible with L402
There was a problem hiding this comment.
Thanks for the hint 🙏 I still have to fully test it e2e 🙌
There was a problem hiding this comment.
Good catch — fixed in the latest commit. discover now routes MPP services through l402.space/mpp-lightning/<encoded> and leaves x402 on the default path.
I probed a live MPP/Tempo upstream to confirm the routing: the path prefix selects the inbound rail (how we pay the gateway), and since we always pay over lightning, MPP has to use /mpp-lightning/ (returns a WWW-Authenticate: Payment method="lightning" + lnbc invoice). /mpp-tempo/ would demand a Tempo stablecoin we can't settle from a lightning wallet, and the generic path only reliably folds L402 for x402. Native L402 still never touches the bridge.
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.
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