feat(ts): TypeScript Lightning (bip122/exact) mechanism - ExactBip122{Client,Server,Facilitator}Scheme#2262
Conversation
Implements ExactBip122ClientScheme, ExactBip122ServerScheme, and ExactBip122FacilitatorScheme following the spec in PR x402-foundation#1311 and the Python reference in PR x402-foundation#1873. Key design decisions: - light-bolt11-decoder (optional peer dep, lazy-required) for BOLT11 decode - 10-step verification per spec: network/asset/payTo/paymentMethod/invoice presence/invoice-substitution/decode/expiry/amount/replay-cache/node-check - Invoice substitution guard runs before BOLT11 decode (prevents cross-bind of a stale paid invoice from a prior request) - expiresAt = timestamp + expiry; checked explicitly since most Lightning backends don't surface "expired" as a distinct status (LNBits, Blink) - TTL = (expiresAt - now) + 60s buffer for settlement dedup cache - Fire-and-forget safe: decodeBolt11Fn and nowFn are injectable for testing Prior art: @powforge/mcp-pay-gate (MIT) — verifier shape and LNBits adapter pattern informed the lookupInvoice interface and invoice-substitution test. Closes x402-foundation#2258
|
@ThiagoDataEngineer is attempting to deploy a commit to the Coinbase Team on Vercel. A member of the Team first needs to authorize it. |
|
@ThiagoDataEngineer nice. The step-6-before-decode order is the load-bearing bit and you got the spec right. Glad to be cited; the credit reads cleanly. One distinction worth pulling apart in the test matrix while the PR is still draft: the invoice-substitution guard (three-way string compare across The minted-invoice check is optional in our impl (gated on Also: the |
|
@ThiagoDataEngineer — pulled the standalone verifier out into its own package in case it's useful here: Zero runtime dependencies. Exports 35 unit tests. MIT. No expectation either way — just figured I'd flag it before this PR lands in case it's easier to depend on a tagged surface than vendor the logic. |
1 similar comment
|
@ThiagoDataEngineer — pulled the standalone verifier out into its own package in case it's useful here: Zero runtime dependencies. Exports 35 unit tests. MIT. No expectation either way — just figured I'd flag it before this PR lands in case it's easier to depend on a tagged surface than vendor the logic. |
|
One thing worth surfacing while the LNBits and Blink are great defaults for "works out of the box," but both are custodial. NWC fills the self-custodial slot without enlarging the facilitator interface — a wallet RPC over a Nostr relay, with const nwc = new NWCClient({ nostrWalletConnectUrl: process.env.NWC_URL! });
async function mintInvoice(sats: number, memo: string) {
const inv = await nwc.makeInvoice({ amount: sats * 1000, description: memo });
return { paymentRequest: inv.invoice, paymentHash: inv.payment_hash };
}
async function isPaid(paymentHash: string) {
const r = await nwc.lookupInvoice({ payment_hash: paymentHash });
return r.settled_at != null;
}Why it's worth a slot:
Buyer-side reference: Not a blocker on #2262 — just flagging while the shape of |
Closes #2258.
Adds TypeScript Lightning (bip122/exact) mechanism implementation following the spec in PR #1311 and the Python reference in PR #1873.
What's here
13 files, 801 LOC in
typescript/packages/mechanisms/bip122/:src/exact/types.tsLightningPayer,LightningReceiver,ExactBip122Payload,LightningInvoiceStatus,DecodedBolt11src/exact/constants.tssrc/exact/utils.tsdecodeBolt11()- lazy-requireslight-bolt11-decodersrc/exact/settlementCache.tssrc/exact/client/scheme.tsExactBip122ClientScheme- extracts invoice, callsLightningPayer.payInvoicesrc/exact/server/scheme.tsExactBip122ServerScheme- creates invoice viaLightningReceiver, injects intoextrasrc/exact/facilitator/scheme.tsExactBip122FacilitatorScheme- 10-step verify + settletest/unit/facilitator.test.tstest/unit/settlementCache.test.tsVerification (10 steps, per #1311)
`
`
Step 6 (invoice substitution) runs before BOLT11 decode per the test case in @powforge/mcp-pay-gate - prevents a stale paid invoice from a prior request cross-binding to new requirements.
Design choices
BOLT11 library:
light-bolt11-decoder(optional peer dep, lazy-required viarequire()). Zero-deps, ~12kB, decode-only - the verifier never signs. InjectingdecodeBolt11Fnas a constructor option keeps tests fast and deterministic.expiresAt:
timestamp + expiryfrom BOLT11 header, compared toDate.now() / 1000. Documented inLightningInvoiceStatusbecause LNBits (and Blink) don't return a distinct "expired" status - callers must check independently. ThenowFnoption is injectable for testing.TTL formula:
(expiresAt * 1000 - now) + 60_000 ms- mirrors theexpiry + 60sused in production by @powforge/mcp-pay-gate.No facilitator required: Lightning settles off-chain; the server verifies directly via its own node/wallet adapter.
ExactBip122FacilitatorSchemecan run co-located with the server.LightningReceiverinterface: thelookupInvoiceslot maps to LNBits'checkPaidFnpattern. For Blink,makeBlinkCheckPaidFnfollows the same shape - the facilitator core is adapter-agnostic.Prior art
@powforge/mcp-pay-gate (MIT) - verifier shape and invoice-substitution test informed the
lookupInvoiceinterface and the step-6 guard order. Credited in commit message.What's NOT here yet
@x402/bip122published to npm (pending maintainer review)Testing
�ash cd typescript/packages/mechanisms/bip122 npm install npm testAll 14 unit tests pass.
decodeBolt11FnandnowFnare injected in tests - no live Lightning node required.cc @ocknamo (spec, #1311) @Bortlesboat (Python reference, #1873) @zekebuilds-lab (prior art, invoice-substitution test)