@lnbot/l402 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +309 -0
- package/dist/client/index.cjs +225 -0
- package/dist/client/index.cjs.map +1 -0
- package/dist/client/index.d.cts +36 -0
- package/dist/client/index.d.ts +36 -0
- package/dist/client/index.js +194 -0
- package/dist/client/index.js.map +1 -0
- package/dist/fetch-B0tuycqO.d.cts +20 -0
- package/dist/fetch-BPQpEg8M.d.ts +20 -0
- package/dist/headers-BjcT_Am-.d.ts +29 -0
- package/dist/headers-ByStTJM9.d.cts +29 -0
- package/dist/index.cjs +291 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +27 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +260 -0
- package/dist/index.js.map +1 -0
- package/dist/server/index.cjs +111 -0
- package/dist/server/index.cjs.map +1 -0
- package/dist/server/index.d.cts +10 -0
- package/dist/server/index.d.ts +10 -0
- package/dist/server/index.js +79 -0
- package/dist/server/index.js.map +1 -0
- package/dist/types-FRMMn5ej.d.cts +52 -0
- package/dist/types-FRMMn5ej.d.ts +52 -0
- package/package.json +80 -0
package/README.md
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# @lnbot/l402
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@lnbot/l402)
|
|
4
|
+
[](https://www.npmjs.com/package/@lnbot/l402)
|
|
5
|
+
[](https://bundlephobia.com/package/@lnbot/l402)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
|
|
9
|
+
**L402 payment middleware for Express.js** — paywall any API in one line. Built on [ln.bot](https://ln.bot).
|
|
10
|
+
|
|
11
|
+
Add Lightning-powered pay-per-request to any Express API. Protect premium routes with a paywall, or build clients that auto-pay L402-protected services — all without touching any cryptography.
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import express from "express";
|
|
15
|
+
import { l402, LnBot } from "@lnbot/l402";
|
|
16
|
+
|
|
17
|
+
const app = express();
|
|
18
|
+
const ln = new LnBot({ apiKey: "key_..." });
|
|
19
|
+
|
|
20
|
+
app.use("/api/premium", l402.paywall(ln, { price: 10 }));
|
|
21
|
+
|
|
22
|
+
app.get("/api/premium/data", (req, res) => {
|
|
23
|
+
res.json({ data: "premium content" });
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
> This package is a thin glue layer. All L402 logic — macaroon creation, signature verification, preimage checking — lives in the [ln.bot API](https://ln.bot/docs) via [`@lnbot/sdk`](https://www.npmjs.com/package/@lnbot/sdk). Zero crypto dependencies.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## What is L402?
|
|
32
|
+
|
|
33
|
+
[L402](https://github.com/lightninglabs/L402) is a protocol built on HTTP `402 Payment Required`. It enables machine-to-machine micropayments over the Lightning Network:
|
|
34
|
+
|
|
35
|
+
1. **Client** requests a protected resource
|
|
36
|
+
2. **Server** returns `402` with a Lightning invoice and a macaroon token
|
|
37
|
+
3. **Client** pays the invoice, obtains the preimage as proof of payment
|
|
38
|
+
4. **Client** retries the request with `Authorization: L402 <macaroon>:<preimage>`
|
|
39
|
+
5. **Server** verifies the token and grants access
|
|
40
|
+
|
|
41
|
+
L402 is ideal for API monetization, AI agent tool access, pay-per-request data feeds, and any scenario where you want instant, permissionless, per-request payments without subscriptions or API key provisioning.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm install @lnbot/l402
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pnpm add @lnbot/l402
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
yarn add @lnbot/l402
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`@lnbot/sdk` and `express` are peer dependencies and will be resolved automatically.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Server — Protect Routes with L402
|
|
64
|
+
|
|
65
|
+
The `l402.paywall()` middleware intercepts requests, verifies L402 tokens via the SDK, and issues new challenges when payment is needed. Two SDK calls, ~40 lines of glue code, zero crypto.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import express from "express";
|
|
69
|
+
import { l402, LnBot } from "@lnbot/l402";
|
|
70
|
+
|
|
71
|
+
const app = express();
|
|
72
|
+
const ln = new LnBot({ apiKey: "key_..." });
|
|
73
|
+
|
|
74
|
+
// Paywall a route group — 10 sats per request
|
|
75
|
+
app.use("/api/premium", l402.paywall(ln, {
|
|
76
|
+
price: 10,
|
|
77
|
+
description: "API access",
|
|
78
|
+
}));
|
|
79
|
+
|
|
80
|
+
app.get("/api/premium/data", (req, res) => {
|
|
81
|
+
// req.l402 is populated after successful payment verification
|
|
82
|
+
res.json({
|
|
83
|
+
data: "premium content",
|
|
84
|
+
paymentHash: req.l402?.paymentHash,
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Free routes still work normally
|
|
89
|
+
app.get("/api/free/health", (req, res) => {
|
|
90
|
+
res.json({ status: "ok" });
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
app.listen(3000);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### How the middleware works
|
|
97
|
+
|
|
98
|
+
1. Checks for an `Authorization: L402 ...` header
|
|
99
|
+
2. If present, calls `ln.l402.verify()` — the SDK checks signature, preimage, and caveats server-side
|
|
100
|
+
3. If valid, populates `req.l402` and calls `next()`
|
|
101
|
+
4. If missing or invalid, calls `ln.l402.createChallenge()` and returns a `402` response with the invoice and macaroon
|
|
102
|
+
|
|
103
|
+
### Dynamic pricing
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// Fixed price per route
|
|
107
|
+
app.use("/api/cheap", l402.paywall(ln, { price: 1 }));
|
|
108
|
+
app.use("/api/expensive", l402.paywall(ln, { price: 100 }));
|
|
109
|
+
|
|
110
|
+
// Custom pricing function — receives the request, returns price in sats
|
|
111
|
+
app.use("/api/dynamic", l402.paywall(ln, {
|
|
112
|
+
price: (req) => {
|
|
113
|
+
if (req.path.includes("/bulk")) return 50;
|
|
114
|
+
return 5;
|
|
115
|
+
},
|
|
116
|
+
}));
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Paywall options
|
|
120
|
+
|
|
121
|
+
| Option | Type | Description |
|
|
122
|
+
| --- | --- | --- |
|
|
123
|
+
| `price` | `number \| (req) => number` | Price in satoshis — fixed or per-request |
|
|
124
|
+
| `description` | `string` | Invoice memo shown in wallets |
|
|
125
|
+
| `expirySeconds` | `number` | Challenge expiry in seconds |
|
|
126
|
+
| `caveats` | `string[]` | Macaroon caveats to attach |
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Client — Auto-Pay L402 APIs
|
|
131
|
+
|
|
132
|
+
The `l402.client()` wrapper makes L402 payment transparent. It detects `402` responses, pays the Lightning invoice via the SDK, caches the token, and retries — all in one `fetch` call.
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { l402, LnBot } from "@lnbot/l402";
|
|
136
|
+
|
|
137
|
+
const ln = new LnBot({ apiKey: "key_..." });
|
|
138
|
+
|
|
139
|
+
const client = l402.client(ln, {
|
|
140
|
+
maxPrice: 100, // refuse to pay more than 100 sats per request
|
|
141
|
+
budgetSats: 50000, // spending limit for the period
|
|
142
|
+
budgetPeriod: "day", // reset period: "hour" | "day" | "week" | "month"
|
|
143
|
+
store: "memory", // token cache: "memory" (default) | "none" | custom TokenStore
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Use like fetch — L402 payment is transparent
|
|
147
|
+
const response = await client.fetch("https://api.example.com/premium/data");
|
|
148
|
+
const data = await response.json();
|
|
149
|
+
|
|
150
|
+
// Convenience methods
|
|
151
|
+
const json = await client.get("https://api.example.com/premium/data");
|
|
152
|
+
const result = await client.post("https://api.example.com/premium/submit", {
|
|
153
|
+
body: JSON.stringify({ query: "test" }),
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### How the client works
|
|
158
|
+
|
|
159
|
+
1. Checks the token cache for a valid credential
|
|
160
|
+
2. If cached, sends the request with the `Authorization` header
|
|
161
|
+
3. If no cache (or server rejects), makes a plain request
|
|
162
|
+
4. On `402`, parses the challenge and checks budget limits
|
|
163
|
+
5. Calls `ln.l402.pay()` — the SDK pays the invoice and returns a ready-to-use token
|
|
164
|
+
6. Caches the token and retries the request with authorization
|
|
165
|
+
|
|
166
|
+
### Client options
|
|
167
|
+
|
|
168
|
+
| Option | Type | Default | Description |
|
|
169
|
+
| --- | --- | --- | --- |
|
|
170
|
+
| `maxPrice` | `number` | `1000` | Max sats to pay for a single request |
|
|
171
|
+
| `budgetSats` | `number` | unlimited | Total budget in sats for the period |
|
|
172
|
+
| `budgetPeriod` | `string` | — | Reset period: `"hour"`, `"day"`, `"week"`, `"month"` |
|
|
173
|
+
| `store` | `string \| TokenStore` | `"memory"` | Token cache: `"memory"`, `"none"`, or custom |
|
|
174
|
+
|
|
175
|
+
### Custom token store
|
|
176
|
+
|
|
177
|
+
Implement the `TokenStore` interface for Redis, file system, or any persistence layer:
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
import { l402, LnBot } from "@lnbot/l402";
|
|
181
|
+
import type { TokenStore } from "@lnbot/l402";
|
|
182
|
+
|
|
183
|
+
const ln = new LnBot({ apiKey: "key_..." });
|
|
184
|
+
|
|
185
|
+
const redisStore: TokenStore = {
|
|
186
|
+
async get(url) { /* read from Redis */ },
|
|
187
|
+
async set(url, token) { /* write to Redis */ },
|
|
188
|
+
async delete(url) { /* delete from Redis */ },
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const client = l402.client(ln, { store: redisStore });
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Header Utilities
|
|
197
|
+
|
|
198
|
+
Parse and format L402 headers for custom integrations:
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
import { l402 } from "@lnbot/l402";
|
|
202
|
+
|
|
203
|
+
// Parse Authorization: L402 <macaroon>:<preimage>
|
|
204
|
+
l402.parseAuthorization("L402 mac_base64:preimage_hex");
|
|
205
|
+
// → { macaroon: "mac_base64", preimage: "preimage_hex" }
|
|
206
|
+
|
|
207
|
+
// Parse WWW-Authenticate: L402 macaroon="...", invoice="..."
|
|
208
|
+
l402.parseChallenge('L402 macaroon="abc", invoice="lnbc1..."');
|
|
209
|
+
// → { macaroon: "abc", invoice: "lnbc1..." }
|
|
210
|
+
|
|
211
|
+
// Format headers
|
|
212
|
+
l402.formatAuthorization("mac_base64", "preimage_hex");
|
|
213
|
+
// → "L402 mac_base64:preimage_hex"
|
|
214
|
+
|
|
215
|
+
l402.formatChallenge("abc", "lnbc1...");
|
|
216
|
+
// → 'L402 macaroon="abc", invoice="lnbc1..."'
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Error Handling
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
import { L402Error, L402BudgetExceededError, L402PaymentFailedError } from "@lnbot/l402";
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
const data = await client.get("https://api.example.com/expensive");
|
|
228
|
+
} catch (err) {
|
|
229
|
+
if (err instanceof L402BudgetExceededError) {
|
|
230
|
+
// Price exceeds maxPrice or total budget exhausted
|
|
231
|
+
} else if (err instanceof L402PaymentFailedError) {
|
|
232
|
+
// Lightning payment failed or didn't settle
|
|
233
|
+
} else if (err instanceof L402Error) {
|
|
234
|
+
// Other L402 protocol error (missing header, parse failure)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## API Reference
|
|
242
|
+
|
|
243
|
+
### Server
|
|
244
|
+
|
|
245
|
+
| Export | Description |
|
|
246
|
+
| --- | --- |
|
|
247
|
+
| `l402.paywall(ln, options)` | Express middleware factory — protects routes behind an L402 paywall |
|
|
248
|
+
|
|
249
|
+
### Client
|
|
250
|
+
|
|
251
|
+
| Export | Description |
|
|
252
|
+
| --- | --- |
|
|
253
|
+
| `l402.client(ln, options?)` | Creates an L402-aware HTTP client with automatic payment |
|
|
254
|
+
|
|
255
|
+
### Header Utilities
|
|
256
|
+
|
|
257
|
+
| Export | Description |
|
|
258
|
+
| --- | --- |
|
|
259
|
+
| `l402.parseAuthorization(header)` | Parse `Authorization: L402 ...` into `{ macaroon, preimage }` |
|
|
260
|
+
| `l402.parseChallenge(header)` | Parse `WWW-Authenticate: L402 ...` into `{ macaroon, invoice }` |
|
|
261
|
+
| `l402.formatAuthorization(macaroon, preimage)` | Format an `Authorization` header value |
|
|
262
|
+
| `l402.formatChallenge(macaroon, invoice)` | Format a `WWW-Authenticate` header value |
|
|
263
|
+
|
|
264
|
+
### Types
|
|
265
|
+
|
|
266
|
+
| Type | Description |
|
|
267
|
+
| --- | --- |
|
|
268
|
+
| `L402PaywallOptions` | Options for `l402.paywall()` |
|
|
269
|
+
| `L402ClientOptions` | Options for `l402.client()` |
|
|
270
|
+
| `L402Token` | Cached L402 credential (macaroon + preimage + metadata) |
|
|
271
|
+
| `TokenStore` | Interface for custom token caches |
|
|
272
|
+
| `L402RequestData` | Data attached to `req.l402` after verification |
|
|
273
|
+
|
|
274
|
+
### Errors
|
|
275
|
+
|
|
276
|
+
| Class | Description |
|
|
277
|
+
| --- | --- |
|
|
278
|
+
| `L402Error` | Base error for all L402 protocol errors |
|
|
279
|
+
| `L402BudgetExceededError` | Price or cumulative spend exceeds configured limits |
|
|
280
|
+
| `L402PaymentFailedError` | Lightning payment failed or didn't return authorization |
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Requirements
|
|
285
|
+
|
|
286
|
+
- **Node.js 18+**, Bun, or Deno
|
|
287
|
+
- **Express 4+** (server middleware)
|
|
288
|
+
- An [ln.bot](https://ln.bot) API key — [create a wallet](https://ln.bot/docs) to get one
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Related packages
|
|
293
|
+
|
|
294
|
+
- [`@lnbot/sdk`](https://www.npmjs.com/package/@lnbot/sdk) — The TypeScript SDK this package is built on
|
|
295
|
+
- [Python SDK](https://github.com/lnbotdev/python-sdk) · [pypi](https://pypi.org/project/lnbot/)
|
|
296
|
+
- [Go SDK](https://github.com/lnbotdev/go-sdk) · [pkg.go.dev](https://pkg.go.dev/github.com/lnbotdev/go-sdk)
|
|
297
|
+
- [Rust SDK](https://github.com/lnbotdev/rust-sdk) · [crates.io](https://crates.io/crates/lnbot)
|
|
298
|
+
|
|
299
|
+
## Links
|
|
300
|
+
|
|
301
|
+
- [ln.bot](https://ln.bot) — website
|
|
302
|
+
- [Documentation](https://ln.bot/docs)
|
|
303
|
+
- [L402 specification](https://github.com/lightninglabs/L402)
|
|
304
|
+
- [GitHub](https://github.com/lnbotdev)
|
|
305
|
+
- [npm](https://www.npmjs.com/package/@lnbot/l402)
|
|
306
|
+
|
|
307
|
+
## License
|
|
308
|
+
|
|
309
|
+
MIT
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/client/index.ts
|
|
21
|
+
var client_exports = {};
|
|
22
|
+
__export(client_exports, {
|
|
23
|
+
Budget: () => Budget,
|
|
24
|
+
MemoryStore: () => MemoryStore,
|
|
25
|
+
NoStore: () => NoStore,
|
|
26
|
+
client: () => client,
|
|
27
|
+
resolveStore: () => resolveStore
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(client_exports);
|
|
30
|
+
|
|
31
|
+
// src/errors.ts
|
|
32
|
+
var L402Error = class extends Error {
|
|
33
|
+
constructor(message) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = "L402Error";
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var L402BudgetExceededError = class extends L402Error {
|
|
39
|
+
constructor(message) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = "L402BudgetExceededError";
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var L402PaymentFailedError = class extends L402Error {
|
|
45
|
+
constructor(message) {
|
|
46
|
+
super(message);
|
|
47
|
+
this.name = "L402PaymentFailedError";
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// src/server/headers.ts
|
|
52
|
+
function parseAuthorization(header) {
|
|
53
|
+
if (!header.startsWith("L402 ")) return null;
|
|
54
|
+
const token = header.slice(5);
|
|
55
|
+
const colonIndex = token.lastIndexOf(":");
|
|
56
|
+
if (colonIndex === -1) return null;
|
|
57
|
+
return {
|
|
58
|
+
macaroon: token.slice(0, colonIndex),
|
|
59
|
+
preimage: token.slice(colonIndex + 1)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function parseChallenge(header) {
|
|
63
|
+
if (!header.startsWith("L402 ")) return null;
|
|
64
|
+
const macaroonMatch = header.match(/macaroon="([^"]+)"/);
|
|
65
|
+
const invoiceMatch = header.match(/invoice="([^"]+)"/);
|
|
66
|
+
if (!macaroonMatch || !invoiceMatch) return null;
|
|
67
|
+
return { macaroon: macaroonMatch[1], invoice: invoiceMatch[1] };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/client/budget.ts
|
|
71
|
+
var PERIOD_MS = {
|
|
72
|
+
hour: 60 * 60 * 1e3,
|
|
73
|
+
day: 24 * 60 * 60 * 1e3,
|
|
74
|
+
week: 7 * 24 * 60 * 60 * 1e3,
|
|
75
|
+
month: 30 * 24 * 60 * 60 * 1e3
|
|
76
|
+
};
|
|
77
|
+
var Budget = class {
|
|
78
|
+
spent = 0;
|
|
79
|
+
periodStart = Date.now();
|
|
80
|
+
totalSats;
|
|
81
|
+
periodMs;
|
|
82
|
+
constructor(options) {
|
|
83
|
+
this.totalSats = options.budgetSats;
|
|
84
|
+
if (options.budgetPeriod) {
|
|
85
|
+
this.periodMs = PERIOD_MS[options.budgetPeriod];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
maybeReset() {
|
|
89
|
+
if (this.periodMs && Date.now() - this.periodStart >= this.periodMs) {
|
|
90
|
+
this.spent = 0;
|
|
91
|
+
this.periodStart = Date.now();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** Throws L402BudgetExceededError if spending `price` would exceed the budget. */
|
|
95
|
+
check(price) {
|
|
96
|
+
if (this.totalSats === void 0) return;
|
|
97
|
+
this.maybeReset();
|
|
98
|
+
if (this.spent + price > this.totalSats) {
|
|
99
|
+
throw new L402BudgetExceededError(
|
|
100
|
+
`Payment of ${price} sats would exceed budget (${this.spent}/${this.totalSats} sats spent)`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** Record a successful payment. */
|
|
105
|
+
record(price) {
|
|
106
|
+
this.maybeReset();
|
|
107
|
+
this.spent += price;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// src/client/store.ts
|
|
112
|
+
function normalizeUrl(url) {
|
|
113
|
+
try {
|
|
114
|
+
const u = new URL(url);
|
|
115
|
+
u.search = "";
|
|
116
|
+
u.hash = "";
|
|
117
|
+
return u.toString().replace(/\/+$/, "");
|
|
118
|
+
} catch {
|
|
119
|
+
return url;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
var MemoryStore = class {
|
|
123
|
+
tokens = /* @__PURE__ */ new Map();
|
|
124
|
+
async get(url) {
|
|
125
|
+
return this.tokens.get(normalizeUrl(url)) ?? null;
|
|
126
|
+
}
|
|
127
|
+
async set(url, token) {
|
|
128
|
+
this.tokens.set(normalizeUrl(url), token);
|
|
129
|
+
}
|
|
130
|
+
async delete(url) {
|
|
131
|
+
this.tokens.delete(normalizeUrl(url));
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
var NoStore = class {
|
|
135
|
+
async get() {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
async set() {
|
|
139
|
+
}
|
|
140
|
+
async delete() {
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
function resolveStore(store) {
|
|
144
|
+
if (!store || store === "memory") return new MemoryStore();
|
|
145
|
+
if (store === "none") return new NoStore();
|
|
146
|
+
return store;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/client/fetch.ts
|
|
150
|
+
function client(ln, options = {}) {
|
|
151
|
+
const store = resolveStore(options.store);
|
|
152
|
+
const budget = new Budget(options);
|
|
153
|
+
const maxPrice = options.maxPrice ?? 1e3;
|
|
154
|
+
async function l402Fetch(url, init) {
|
|
155
|
+
const cached = await store.get(url);
|
|
156
|
+
if (cached) {
|
|
157
|
+
const isExpired = cached.expiresAt && cached.expiresAt.getTime() <= Date.now();
|
|
158
|
+
if (!isExpired) {
|
|
159
|
+
const headers = new Headers(init?.headers);
|
|
160
|
+
headers.set("Authorization", cached.authorization);
|
|
161
|
+
const res2 = await globalThis.fetch(url, { ...init, headers });
|
|
162
|
+
if (res2.status !== 402) return res2;
|
|
163
|
+
await store.delete(url);
|
|
164
|
+
} else {
|
|
165
|
+
await store.delete(url);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const res = await globalThis.fetch(url, init);
|
|
169
|
+
if (res.status !== 402) return res;
|
|
170
|
+
const wwwAuth = res.headers.get("www-authenticate");
|
|
171
|
+
if (!wwwAuth)
|
|
172
|
+
throw new L402Error("402 response missing WWW-Authenticate header");
|
|
173
|
+
const challenge = parseChallenge(wwwAuth);
|
|
174
|
+
if (!challenge) throw new L402Error("Could not parse L402 challenge");
|
|
175
|
+
const body = await res.json().catch(() => null);
|
|
176
|
+
const price = body?.price ?? 0;
|
|
177
|
+
if (price > maxPrice) {
|
|
178
|
+
throw new L402Error(
|
|
179
|
+
`Price ${price} sats exceeds maxPrice ${maxPrice}`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
budget.check(price);
|
|
183
|
+
const payment = await ln.l402.pay({ wwwAuthenticate: wwwAuth });
|
|
184
|
+
if (payment.status === "failed") {
|
|
185
|
+
throw new L402PaymentFailedError("L402 payment failed");
|
|
186
|
+
}
|
|
187
|
+
if (!payment.authorization) {
|
|
188
|
+
throw new L402PaymentFailedError(
|
|
189
|
+
"Payment did not return authorization token"
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
const parsed = parseAuthorization(payment.authorization);
|
|
193
|
+
await store.set(url, {
|
|
194
|
+
macaroon: parsed?.macaroon ?? challenge.macaroon,
|
|
195
|
+
preimage: payment.preimage ?? parsed?.preimage ?? "",
|
|
196
|
+
authorization: payment.authorization,
|
|
197
|
+
paidAt: /* @__PURE__ */ new Date(),
|
|
198
|
+
expiresAt: body?.expiresAt ? new Date(body.expiresAt) : void 0
|
|
199
|
+
});
|
|
200
|
+
budget.record(price);
|
|
201
|
+
const retryHeaders = new Headers(init?.headers);
|
|
202
|
+
retryHeaders.set("Authorization", payment.authorization);
|
|
203
|
+
return globalThis.fetch(url, { ...init, headers: retryHeaders });
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
fetch: l402Fetch,
|
|
207
|
+
async get(url, init) {
|
|
208
|
+
const res = await l402Fetch(url, { ...init, method: "GET" });
|
|
209
|
+
return res.json();
|
|
210
|
+
},
|
|
211
|
+
async post(url, init) {
|
|
212
|
+
const res = await l402Fetch(url, { ...init, method: "POST" });
|
|
213
|
+
return res.json();
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
218
|
+
0 && (module.exports = {
|
|
219
|
+
Budget,
|
|
220
|
+
MemoryStore,
|
|
221
|
+
NoStore,
|
|
222
|
+
client,
|
|
223
|
+
resolveStore
|
|
224
|
+
});
|
|
225
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/client/index.ts","../../src/errors.ts","../../src/server/headers.ts","../../src/client/budget.ts","../../src/client/store.ts","../../src/client/fetch.ts"],"sourcesContent":["export { client, type L402Client } from \"./fetch.js\";\nexport { Budget } from \"./budget.js\";\nexport { MemoryStore, NoStore, resolveStore } from \"./store.js\";\n","export class L402Error extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"L402Error\";\n }\n}\n\nexport class L402BudgetExceededError extends L402Error {\n constructor(message: string) {\n super(message);\n this.name = \"L402BudgetExceededError\";\n }\n}\n\nexport class L402PaymentFailedError extends L402Error {\n constructor(message: string) {\n super(message);\n this.name = \"L402PaymentFailedError\";\n }\n}\n","/** Parse an L402 Authorization header into { macaroon, preimage }. */\nexport function parseAuthorization(\n header: string,\n): { macaroon: string; preimage: string } | null {\n if (!header.startsWith(\"L402 \")) return null;\n const token = header.slice(5);\n const colonIndex = token.lastIndexOf(\":\");\n if (colonIndex === -1) return null;\n return {\n macaroon: token.slice(0, colonIndex),\n preimage: token.slice(colonIndex + 1),\n };\n}\n\n/** Parse a WWW-Authenticate: L402 header into { macaroon, invoice }. */\nexport function parseChallenge(\n header: string,\n): { macaroon: string; invoice: string } | null {\n if (!header.startsWith(\"L402 \")) return null;\n const macaroonMatch = header.match(/macaroon=\"([^\"]+)\"/);\n const invoiceMatch = header.match(/invoice=\"([^\"]+)\"/);\n if (!macaroonMatch || !invoiceMatch) return null;\n return { macaroon: macaroonMatch[1], invoice: invoiceMatch[1] };\n}\n\n/** Format an Authorization header value. */\nexport function formatAuthorization(\n macaroon: string,\n preimage: string,\n): string {\n return `L402 ${macaroon}:${preimage}`;\n}\n\n/** Format a WWW-Authenticate header value. */\nexport function formatChallenge(\n macaroon: string,\n invoice: string,\n): string {\n return `L402 macaroon=\"${macaroon}\", invoice=\"${invoice}\"`;\n}\n","import type { L402ClientOptions } from \"../types.js\";\nimport { L402BudgetExceededError } from \"../errors.js\";\n\nconst PERIOD_MS: Record<string, number> = {\n hour: 60 * 60 * 1000,\n day: 24 * 60 * 60 * 1000,\n week: 7 * 24 * 60 * 60 * 1000,\n month: 30 * 24 * 60 * 60 * 1000,\n};\n\n/** In-memory budget tracker with periodic resets. */\nexport class Budget {\n private spent = 0;\n private periodStart = Date.now();\n private readonly totalSats: number | undefined;\n private readonly periodMs: number | undefined;\n\n constructor(options: L402ClientOptions) {\n this.totalSats = options.budgetSats;\n if (options.budgetPeriod) {\n this.periodMs = PERIOD_MS[options.budgetPeriod];\n }\n }\n\n private maybeReset(): void {\n if (this.periodMs && Date.now() - this.periodStart >= this.periodMs) {\n this.spent = 0;\n this.periodStart = Date.now();\n }\n }\n\n /** Throws L402BudgetExceededError if spending `price` would exceed the budget. */\n check(price: number): void {\n if (this.totalSats === undefined) return;\n this.maybeReset();\n if (this.spent + price > this.totalSats) {\n throw new L402BudgetExceededError(\n `Payment of ${price} sats would exceed budget (${this.spent}/${this.totalSats} sats spent)`,\n );\n }\n }\n\n /** Record a successful payment. */\n record(price: number): void {\n this.maybeReset();\n this.spent += price;\n }\n}\n","import type { TokenStore, L402Token } from \"../types.js\";\n\n/** Strip query params, hash, and trailing slashes for consistent cache keys. */\nfunction normalizeUrl(url: string): string {\n try {\n const u = new URL(url);\n u.search = \"\";\n u.hash = \"\";\n return u.toString().replace(/\\/+$/, \"\");\n } catch {\n return url;\n }\n}\n\n/** Default in-memory token cache backed by a Map. */\nexport class MemoryStore implements TokenStore {\n private tokens = new Map<string, L402Token>();\n\n async get(url: string): Promise<L402Token | null> {\n return this.tokens.get(normalizeUrl(url)) ?? null;\n }\n\n async set(url: string, token: L402Token): Promise<void> {\n this.tokens.set(normalizeUrl(url), token);\n }\n\n async delete(url: string): Promise<void> {\n this.tokens.delete(normalizeUrl(url));\n }\n}\n\n/** No-op store — never caches, every request pays fresh. */\nexport class NoStore implements TokenStore {\n async get(): Promise<null> {\n return null;\n }\n async set(): Promise<void> {}\n async delete(): Promise<void> {}\n}\n\n/** Resolve the `store` option into a concrete TokenStore. */\nexport function resolveStore(\n store?: \"memory\" | \"none\" | TokenStore,\n): TokenStore {\n if (!store || store === \"memory\") return new MemoryStore();\n if (store === \"none\") return new NoStore();\n return store;\n}\n","import type { LnBot } from \"@lnbot/sdk\";\nimport type { L402ClientOptions } from \"../types.js\";\nimport {\n L402Error,\n L402PaymentFailedError,\n} from \"../errors.js\";\nimport { parseChallenge, parseAuthorization } from \"../server/headers.js\";\nimport { Budget } from \"./budget.js\";\nimport { resolveStore } from \"./store.js\";\n\n/** An L402-aware HTTP client that transparently pays Lightning invoices on 402 responses. */\nexport interface L402Client {\n /** L402-aware fetch — pays 402 challenges automatically. */\n fetch(url: string, init?: RequestInit): Promise<Response>;\n /** GET + JSON parse with automatic L402 payment. */\n get(url: string, init?: RequestInit): Promise<unknown>;\n /** POST + JSON parse with automatic L402 payment. */\n post(url: string, init?: RequestInit): Promise<unknown>;\n}\n\n/**\n * Create an L402-aware HTTP client.\n *\n * One SDK call: `ln.l402.pay()` — pays the invoice and returns the Authorization token.\n */\nexport function client(ln: LnBot, options: L402ClientOptions = {}): L402Client {\n const store = resolveStore(options.store);\n const budget = new Budget(options);\n const maxPrice = options.maxPrice ?? 1000;\n\n async function l402Fetch(\n url: string,\n init?: RequestInit,\n ): Promise<Response> {\n // Step 1: Check token cache\n const cached = await store.get(url);\n if (cached) {\n const isExpired =\n cached.expiresAt && cached.expiresAt.getTime() <= Date.now();\n if (!isExpired) {\n const headers = new Headers(init?.headers);\n headers.set(\"Authorization\", cached.authorization);\n const res = await globalThis.fetch(url, { ...init, headers });\n // If the cached token was rejected (expired server-side), fall through\n if (res.status !== 402) return res;\n await store.delete(url);\n } else {\n await store.delete(url);\n }\n }\n\n // Step 2: Make request without auth\n const res = await globalThis.fetch(url, init);\n if (res.status !== 402) return res;\n\n // Step 3: Parse the 402 challenge\n const wwwAuth = res.headers.get(\"www-authenticate\");\n if (!wwwAuth)\n throw new L402Error(\"402 response missing WWW-Authenticate header\");\n\n const challenge = parseChallenge(wwwAuth);\n if (!challenge) throw new L402Error(\"Could not parse L402 challenge\");\n\n // Parse body for price info\n const body = await res.json().catch(() => null);\n const price: number = body?.price ?? 0;\n\n // Step 4: Budget checks\n if (price > maxPrice) {\n throw new L402Error(\n `Price ${price} sats exceeds maxPrice ${maxPrice}`,\n );\n }\n budget.check(price);\n\n // Step 5: Pay via SDK\n const payment = await ln.l402.pay({ wwwAuthenticate: wwwAuth });\n\n if (payment.status === \"failed\") {\n throw new L402PaymentFailedError(\"L402 payment failed\");\n }\n if (!payment.authorization) {\n throw new L402PaymentFailedError(\n \"Payment did not return authorization token\",\n );\n }\n\n // Step 6: Cache the token\n const parsed = parseAuthorization(payment.authorization);\n await store.set(url, {\n macaroon: parsed?.macaroon ?? challenge.macaroon,\n preimage: payment.preimage ?? parsed?.preimage ?? \"\",\n authorization: payment.authorization,\n paidAt: new Date(),\n expiresAt: body?.expiresAt\n ? new Date(body.expiresAt)\n : undefined,\n });\n\n budget.record(price);\n\n // Step 7: Retry with L402 Authorization\n const retryHeaders = new Headers(init?.headers);\n retryHeaders.set(\"Authorization\", payment.authorization);\n return globalThis.fetch(url, { ...init, headers: retryHeaders });\n }\n\n return {\n fetch: l402Fetch,\n async get(url: string, init?: RequestInit) {\n const res = await l402Fetch(url, { ...init, method: \"GET\" });\n return res.json();\n },\n async post(url: string, init?: RequestInit) {\n const res = await l402Fetch(url, { ...init, method: \"POST\" });\n return res.json();\n },\n };\n}\n"],"mappings":"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","names":["res"]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export { L as L402Client, c as client } from '../fetch-B0tuycqO.cjs';
|
|
2
|
+
import { L as L402ClientOptions, T as TokenStore, c as L402Token } from '../types-FRMMn5ej.cjs';
|
|
3
|
+
import '@lnbot/sdk';
|
|
4
|
+
import 'express';
|
|
5
|
+
|
|
6
|
+
/** In-memory budget tracker with periodic resets. */
|
|
7
|
+
declare class Budget {
|
|
8
|
+
private spent;
|
|
9
|
+
private periodStart;
|
|
10
|
+
private readonly totalSats;
|
|
11
|
+
private readonly periodMs;
|
|
12
|
+
constructor(options: L402ClientOptions);
|
|
13
|
+
private maybeReset;
|
|
14
|
+
/** Throws L402BudgetExceededError if spending `price` would exceed the budget. */
|
|
15
|
+
check(price: number): void;
|
|
16
|
+
/** Record a successful payment. */
|
|
17
|
+
record(price: number): void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Default in-memory token cache backed by a Map. */
|
|
21
|
+
declare class MemoryStore implements TokenStore {
|
|
22
|
+
private tokens;
|
|
23
|
+
get(url: string): Promise<L402Token | null>;
|
|
24
|
+
set(url: string, token: L402Token): Promise<void>;
|
|
25
|
+
delete(url: string): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
/** No-op store — never caches, every request pays fresh. */
|
|
28
|
+
declare class NoStore implements TokenStore {
|
|
29
|
+
get(): Promise<null>;
|
|
30
|
+
set(): Promise<void>;
|
|
31
|
+
delete(): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
/** Resolve the `store` option into a concrete TokenStore. */
|
|
34
|
+
declare function resolveStore(store?: "memory" | "none" | TokenStore): TokenStore;
|
|
35
|
+
|
|
36
|
+
export { Budget, MemoryStore, NoStore, resolveStore };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export { L as L402Client, c as client } from '../fetch-BPQpEg8M.js';
|
|
2
|
+
import { L as L402ClientOptions, T as TokenStore, c as L402Token } from '../types-FRMMn5ej.js';
|
|
3
|
+
import '@lnbot/sdk';
|
|
4
|
+
import 'express';
|
|
5
|
+
|
|
6
|
+
/** In-memory budget tracker with periodic resets. */
|
|
7
|
+
declare class Budget {
|
|
8
|
+
private spent;
|
|
9
|
+
private periodStart;
|
|
10
|
+
private readonly totalSats;
|
|
11
|
+
private readonly periodMs;
|
|
12
|
+
constructor(options: L402ClientOptions);
|
|
13
|
+
private maybeReset;
|
|
14
|
+
/** Throws L402BudgetExceededError if spending `price` would exceed the budget. */
|
|
15
|
+
check(price: number): void;
|
|
16
|
+
/** Record a successful payment. */
|
|
17
|
+
record(price: number): void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Default in-memory token cache backed by a Map. */
|
|
21
|
+
declare class MemoryStore implements TokenStore {
|
|
22
|
+
private tokens;
|
|
23
|
+
get(url: string): Promise<L402Token | null>;
|
|
24
|
+
set(url: string, token: L402Token): Promise<void>;
|
|
25
|
+
delete(url: string): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
/** No-op store — never caches, every request pays fresh. */
|
|
28
|
+
declare class NoStore implements TokenStore {
|
|
29
|
+
get(): Promise<null>;
|
|
30
|
+
set(): Promise<void>;
|
|
31
|
+
delete(): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
/** Resolve the `store` option into a concrete TokenStore. */
|
|
34
|
+
declare function resolveStore(store?: "memory" | "none" | TokenStore): TokenStore;
|
|
35
|
+
|
|
36
|
+
export { Budget, MemoryStore, NoStore, resolveStore };
|