Skip to content

Commit 0d96d8b

Browse files
picsoulabsclaude
andauthored
Add Lightning as a payment method (#302)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ed35444 commit 0d96d8b

6 files changed

Lines changed: 461 additions & 1 deletion

File tree

src/components/cards.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,39 @@ export function TransportsCard() {
220220
);
221221
}
222222

223+
export function LightningMethodCard() {
224+
return (
225+
<Card
226+
description="Bitcoin payments over the Lightning Network"
227+
icon="lucide:zap"
228+
title="Lightning"
229+
to="/payment-methods/lightning"
230+
/>
231+
);
232+
}
233+
234+
export function LightningChargeCard() {
235+
return (
236+
<Card
237+
description="One-time payments using BOLT11 invoices"
238+
icon="lucide:zap"
239+
title="Lightning charge"
240+
to="/payment-methods/lightning/charge"
241+
/>
242+
);
243+
}
244+
245+
export function LightningSessionCard() {
246+
return (
247+
<Card
248+
description="Prepaid metered access with per-request billing"
249+
icon="lucide:waves"
250+
title="Lightning session"
251+
to="/payment-methods/lightning/session"
252+
/>
253+
);
254+
}
255+
223256
export function StripeMethodCard() {
224257
return (
225258
<Card

src/pages/payment-methods/index.mdx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Cards, Tab, Tabs } from 'vocs'
2-
import { TempoMethodCard, StripeMethodCard, CustomMethodCard } from '../../components/cards'
2+
import { TempoMethodCard, LightningMethodCard, StripeMethodCard, CustomMethodCard } from '../../components/cards'
33

44
# Payment methods [Available methods and how to choose one]
55

@@ -20,6 +20,12 @@ WWW-Authenticate: Payment method="tempo" intent="charge", ...
2020
```http
2121
HTTP/1.1 402 Payment Required
2222
WWW-Authenticate: Payment method="stripe", intent="charge", ...
23+
```
24+
</Tab>
25+
<Tab title="Lightning">
26+
```http
27+
HTTP/1.1 402 Payment Required
28+
WWW-Authenticate: Payment method="lightning", intent="charge", ...
2329
```
2430
</Tab>
2531
</Tabs>
@@ -29,5 +35,6 @@ WWW-Authenticate: Payment method="stripe", intent="charge", ...
2935
<Cards>
3036
<TempoMethodCard />
3137
<StripeMethodCard />
38+
<LightningMethodCard />
3239
<CustomMethodCard />
3340
</Cards>
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { Cards } from 'vocs'
2+
import { SpecCard } from '../../../components/SpecCard'
3+
4+
# Lightning charge [One-time payments using BOLT11 invoices]
5+
6+
The Lightning implementation of the [charge](/intents/charge) intent.
7+
8+
The server generates a fresh [BOLT11](https://github.com/lightning/bolts/blob/master/11-payment-encoding.md) invoice for each request. The client pays it over the [Lightning Network](https://lightning.network) and presents the payment preimage as a credential. The server verifies `sha256(preimage) == paymentHash` locally and returns the resource with a receipt.
9+
10+
This method is best for single API calls, content access, or one-off purchases.
11+
12+
## Server
13+
14+
The `spark` namespace is the reference implementation using [Spark](https://spark.money) wallets. The protocol works with any Lightning node—you can build your own method handler using [LND](https://github.com/lightningnetwork/lnd), [LDK](https://lightningdevkit.org), or any stack that can create BOLT11 invoices and verify preimages.
15+
16+
Use `spark.charge` to gate any endpoint behind a one-time Lightning payment. The method handles invoice generation, Challenge creation, preimage verification, and Receipt generation.
17+
18+
```ts
19+
import { Mppx, spark } from '@buildonspark/lightning-mpp-sdk/server'
20+
21+
const mppx = Mppx.create({
22+
methods: [spark.charge({ mnemonic: process.env.MNEMONIC! })],
23+
secretKey: process.env.MPP_SECRET_KEY!,
24+
})
25+
26+
export async function handler(request: Request) {
27+
const result = await mppx.charge({
28+
amount: '100',
29+
currency: 'BTC',
30+
description: 'Premium API access',
31+
})(request)
32+
33+
if (result.status === 402) return result.challenge
34+
35+
return result.withReceipt(Response.json({ data: '...' }))
36+
}
37+
```
38+
39+
### With expiry
40+
41+
```ts
42+
import { Mppx, Expires, spark } from '@buildonspark/lightning-mpp-sdk/server'
43+
44+
const mppx = Mppx.create({
45+
methods: [spark.charge({ mnemonic: process.env.MNEMONIC! })],
46+
secretKey: process.env.MPP_SECRET_KEY!,
47+
})
48+
49+
export async function handler(request: Request) {
50+
const result = await mppx.charge({
51+
amount: '100',
52+
currency: 'BTC',
53+
expires: Expires.minutes(10), // [!code hl]
54+
})(request)
55+
56+
if (result.status === 402) return result.challenge
57+
return result.withReceipt(Response.json({ data: '...' }))
58+
}
59+
```
60+
61+
### With regtest
62+
63+
For local development and testing, set `network` to `'regtest'` and use the [Spark faucet](https://docs.spark.money/tools/faucet) to fund wallets.
64+
65+
```ts
66+
import { Mppx, spark } from '@buildonspark/lightning-mpp-sdk/server'
67+
68+
const mppx = Mppx.create({
69+
methods: [spark.charge({
70+
mnemonic: process.env.MNEMONIC!,
71+
network: 'regtest', // [!code hl]
72+
})],
73+
secretKey: process.env.MPP_SECRET_KEY!,
74+
})
75+
```
76+
77+
### Server parameters
78+
79+
| Parameter | Type | Required | Default |
80+
| --- | --- | --- | --- |
81+
| `mnemonic` | `string` | Required | |
82+
| `network` | `'mainnet'` \| `'regtest'` \| `'signet'` | Optional | `'mainnet'` |
83+
84+
## Client
85+
86+
Use `spark.charge` with `Mppx.create` to automatically handle `402` responses. The client parses the Challenge, pays the BOLT11 invoice, and retries with the preimage as a Credential.
87+
88+
```ts
89+
import { Mppx, spark } from '@buildonspark/lightning-mpp-sdk/client'
90+
91+
Mppx.create({
92+
methods: [spark.charge({ mnemonic: process.env.MNEMONIC! })],
93+
})
94+
95+
const response = await fetch('https://api.example.com/resource')
96+
```
97+
98+
### Without polyfill
99+
100+
If you don't want to patch `globalThis.fetch`, use `mppx.fetch` directly:
101+
102+
```ts
103+
import { Mppx, spark } from '@buildonspark/lightning-mpp-sdk/client'
104+
105+
const method = spark.charge({ mnemonic: process.env.MNEMONIC! })
106+
107+
const mppx = Mppx.create({
108+
polyfill: false,
109+
methods: [method],
110+
})
111+
112+
try {
113+
const response = await mppx.fetch('https://api.example.com/resource')
114+
console.log(await response.json())
115+
} finally {
116+
await method.cleanup()
117+
}
118+
```
119+
120+
:::info
121+
The Spark SDK maintains WebSocket connections for Lightning payments. Call `method.cleanup()` when done to close connections and allow the process to exit.
122+
:::
123+
124+
### Client parameters
125+
126+
| Parameter | Type | Required | Default |
127+
| --- | --- | --- | --- |
128+
| `mnemonic` | `string` | Required | |
129+
| `network` | `'mainnet'` \| `'regtest'` \| `'signet'` | Optional | `'mainnet'` |
130+
| `maxFeeSats` | `number` | Optional | `100` |
131+
132+
## Request fields
133+
134+
The Challenge request includes the base charge fields plus Lightning method details.
135+
136+
| Field | Type | Required | Description |
137+
| --- | --- | --- | --- |
138+
| `amount` | `string` | Required | Invoice amount in satoshis |
139+
| `currency` | `string` | Optional | Must be `'BTC'` if present. Defaults to `'BTC'` |
140+
| `description` | `string` | Optional | Human-readable memo. Maps to the BOLT11 description field |
141+
| `methodDetails.invoice` | `string` | Required | Full BOLT11-encoded payment request (`lnbc...`). Authoritative source for all payment parameters |
142+
| `methodDetails.paymentHash` | `string` | Optional | SHA-256 hash of the preimage, lowercase hex. Convenience field—must match the hash decoded from `invoice` |
143+
| `methodDetails.network` | `string` | Optional | `'mainnet'`, `'regtest'`, or `'signet'`. Convenience field—must match `invoice`'s human-readable prefix. Defaults to `'mainnet'` |
144+
145+
## Credential payload
146+
147+
The Credential payload contains the payment preimage revealed by Lightning HTLC settlement.
148+
149+
| Field | Type | Required | Description |
150+
| --- | --- | --- | --- |
151+
| `preimage` | `string` | Required | 32-byte payment preimage, lowercase hex (64 characters) |
152+
153+
## Verification
154+
155+
The server verifies payment with a single hash operation:
156+
157+
1. Decode the Credential and extract `preimage`.
158+
2. Compute `sha256(hex_to_bytes(preimage))`.
159+
3. Compare against the `paymentHash` from the original Challenge.
160+
4. If equal, payment is verified. Return the resource with a Receipt.
161+
162+
The entire verification path is local and self-contained.
163+
164+
## Specification
165+
166+
<Cards>
167+
<SpecCard to="https://tempoxyz.github.io/mpp-specs/draft-lightning-charge-00" />
168+
</Cards>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Lightning [Bitcoin payments over the Lightning Network]
2+
3+
The Lightning payment method enables payments using Bitcoin over the [Lightning Network](https://lightning.network) within the MPP framework. Lightning supports two intents—**charge** for one-time payments and **session** for prepaid metered access—covering everything from single API calls to high-frequency streaming billing.
4+
5+
The implementation is provided by [`@buildonspark/lightning-mpp-sdk`](https://github.com/buildonspark/lightning-mpp-sdk), which extends the [`mppx`](https://github.com/tempoxyz/mpp) SDK with Lightning Network support alongside built-in methods like [Stripe](/payment-methods/stripe/) and [Tempo](/payment-methods/tempo/). The reference implementation uses [Spark](https://spark.money) for wallet and node operations, but the protocol works with any Lightning node or wallet that can create BOLT11 invoices and verify preimages.
6+
7+
## Installation
8+
9+
:::code-group
10+
```bash [npm]
11+
npm install @buildonspark/lightning-mpp-sdk
12+
```
13+
```bash [pnpm]
14+
pnpm add @buildonspark/lightning-mpp-sdk
15+
```
16+
:::
17+
18+
## Payments on Lightning
19+
20+
Lightning brings a distinct set of properties to MPP:
21+
22+
- **Cryptographic verification**—The server checks `sha256(preimage) == paymentHash` with a single hash operation. Verification is entirely local and self-contained.
23+
- **Synchronous settlement**—Lightning HTLC settlement reveals the preimage atomically. The preimage *is* the proof of payment, available the instant the payment settles.
24+
- **Global and permissionless**—Bitcoin works identically in every jurisdiction. Anyone can participate without accounts, approvals, or special routing.
25+
- **Self-custodial**—Both client and server hold their own keys via Spark wallets. Funds stay under each party's control throughout the entire flow.
26+
27+
## Choosing an intent
28+
29+
| | **Charge** | **Session** |
30+
|---|---|---|
31+
| **Pattern** | One-time payment per request | Prepaid deposit, per-request billing |
32+
| **Latency overhead** | One Lightning payment per request | Near-zero (bearer token after deposit) |
33+
| **Throughput** | One invoice + payment per request | Hundreds of requests per session |
34+
| **Best for** | Single API calls, content access, one-off purchases | LLM APIs, metered services, streaming |
35+
| **Settlement** | Immediate per-request via HTLC | Deposit upfront, per-request deduction, refund on close |
36+
37+
## Intents
38+
39+
<div className="vocs:grid vocs:grid-cols-1 vocs:md:grid-cols-2 vocs:gap-4">
40+
<a href="/payment-methods/lightning/charge" className="vocs:relative vocs:flex vocs:flex-col vocs:space-y-2 vocs:rounded-md vocs:bg-surfaceTint/70 vocs:border vocs:border-primary vocs:p-4 vocs:no-underline vocs:transition-colors vocs:hover:bg-surfaceTint">
41+
<div className="vocs:size-8 vocs:flex vocs:items-center vocs:justify-center vocs:rounded-lg vocs:border vocs:border-primary vocs:bg-surface vocs:text-accent">
42+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"/></svg>
43+
</div>
44+
<div className="vocs:text-[15px] vocs:font-medium vocs:text-heading">Lightning charge</div>
45+
<div className="vocs:text-sm vocs:leading-relaxed vocs:text-secondary">One-time payments using BOLT11 invoices</div>
46+
</a>
47+
<a href="/payment-methods/lightning/session" className="vocs:relative vocs:flex vocs:flex-col vocs:space-y-2 vocs:rounded-md vocs:bg-surfaceTint/70 vocs:border vocs:border-primary vocs:p-4 vocs:no-underline vocs:transition-colors vocs:hover:bg-surfaceTint">
48+
<div className="vocs:size-8 vocs:flex vocs:items-center vocs:justify-center vocs:rounded-lg vocs:border vocs:border-primary vocs:bg-surface vocs:text-accent">
49+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M2 6c.6.5 1.2 1 2.5 1C7 7 7 5 9.5 5c2.6 0 2.4 2 5 2 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1"/><path d="M2 12c.6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 2.6 0 2.4 2 5 2 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1"/><path d="M2 18c.6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 2.6 0 2.4 2 5 2 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1"/></svg>
50+
</div>
51+
<div className="vocs:text-[15px] vocs:font-medium vocs:text-heading">Lightning session</div>
52+
<div className="vocs:text-sm vocs:leading-relaxed vocs:text-secondary">Prepaid metered access with per-request billing</div>
53+
</a>
54+
</div>

0 commit comments

Comments
 (0)