Skip to content

feat: discover all protocols + l402.space bridge note#49

Draft
reneaaron wants to merge 9 commits into
masterfrom
feat/discover-all-protocols
Draft

feat: discover all protocols + l402.space bridge note#49
reneaaron wants to merge 9 commits into
masterfrom
feat/discover-all-protocols

Conversation

@reneaaron

@reneaaron reneaaron commented Jun 23, 2026

Copy link
Copy Markdown
Member

What

Two changes so an agent (or human) with only a lightning wallet can discover and pay for services on any 402 protocol:

  1. discover returns all protocols. Drop the server-side payment_asset=BTC filter so discover returns L402, x402, and MPP services, not just lightning ones.
  2. fetch transparently bridges non-lightning services. lightning-tools already pays L402, MPP, and lightning-payable x402 directly; for endpoints it can't settle over lightning (e.g. USDC-only x402) it hands back an unpaid 402. When that happens, fetch retries 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 in fetch-bridge.test.ts cover: bridge retry on a 402, direct pay on success with no bridge call, and no double-bridging an l402.space URL).
  • Live discover returns x402 services (previously empty).
  • Verified the bridge live: https://l402.space/<encoded-x402-url> returns 402 + WWW-Authenticate: L402 + a lightning invoice.

Companion skill update: getAlby/payments-skill#28.

Refs #26

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
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3fa9af2e-b74c-455b-8705-c4162441b95b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/discover-all-protocols

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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.
Comment thread src/tools/lightning/fetch.ts Outdated
// 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(), {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be more in-line also if we decide to switch to l402.space for the discovery recommendations

@rolznz

rolznz commented Jul 2, 2026

Copy link
Copy Markdown
Member

@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
@reneaaron

Copy link
Copy Markdown
Member Author

Reworked per the feedback — moved the bridging out of fetch into discover.

  • fetch.ts reverted to master — no more hidden per-request retry. That was the piece that could break follow-up/macaroon reuse (js-lightning-tools#328): a token minted by the bridge would've been replayed against the original URL.
  • discover now wraps non-lightning results in an explicit l402.space/<encoded> URL (x402 on Base/Stellar/EVM). L402/MPP and x402-on-Lightning keep their native URL. The URL a caller fetches is now visible and stable across follow-up calls, and fetch just pays it as a plain L402 endpoint. Also surfaces payment_network so callers see the rail. This also lines up with a future switch to l402.space for discovery recommendations.
  • Swapped fetch-bridge.test.tsdiscover-bridge.test.ts.

On your first-step point: I kept the discover filter-drop since that's what surfaces x402/MPP at all — without it the skill note about l402.space has nothing to act on. Happy to split the skill-only change out first if you'd rather land these in sequence.

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.
Comment thread src/tools/lightning/discover.ts Outdated
const L402_SPACE_BRIDGE = "https://l402.space/";

function bridgeUrl(url: string): string {
return `${L402_SPACE_BRIDGE}${encodeURIComponent(url)}`;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for mpp we have dedicated endpoints (/mpp-tempo and /mpp-lightning) because they are incompatible with L402

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the hint 🙏 I still have to fully test it e2e 🙌

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@rolznz

rolznz commented Jul 6, 2026

Copy link
Copy Markdown
Member

On your first-step point: I kept the discover filter-drop since that's what surfaces x402/MPP at all — without it the skill note about l402.space has nothing to act on. Happy to split the skill-only change out first if you'd rather land these in sequence.

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.
reneaaron added a commit to getAlby/payments-skill that referenced this pull request Jul 6, 2026
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.
reneaaron added 3 commits July 6, 2026 17:01
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.
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.

2 participants