@satpath/gateless 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 satpath
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,251 @@
1
+ # Gateless
2
+
3
+ Sovereign L402 payments for AI agents on Lightning.
4
+
5
+ Gateless is a JavaScript/TypeScript library that lets AI agents in Node.js autonomously pay for L402-protected web resources over the Lightning Network. Supports both the classic L402 protocol (macaroon + invoice) and Fewsats L402 v0.2 (offers + payment request). You run your own node. Your agent handles payments within spending limits you define. No accounts, no API keys, no identity - payment is authentication.
6
+
7
+ ## Why
8
+
9
+ AI agents need to pay for things. The emerging solutions either lock you into custodial stablecoins ([x402](https://www.x402.org/)) or require shell access to Go CLI tools ([lnget](https://github.com/lightninglabs/lightning-agent-tools)). Neither works for a web developer building an agent in TypeScript.
10
+
11
+ Gateless fills the gap: a self-contained JS/TS toolkit that connects to **your own LND node** and handles L402 payments programmatically. No custodian, no corporate infrastructure, your keys.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install gateless
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ```typescript
22
+ import { LndClient, L402Client } from "gateless";
23
+
24
+ const lnd = new LndClient({
25
+ host: "127.0.0.1",
26
+ port: 8080,
27
+ tlsCertPath: "./creds/tls.cert",
28
+ macaroonPath: "./creds/admin.macaroon",
29
+ });
30
+
31
+ const client = new L402Client({
32
+ paymentProvider: lnd,
33
+ maxPaymentSats: 100,
34
+ spendingLimits: {
35
+ maxPerPaymentSats: 50,
36
+ maxTotalSats: 1000,
37
+ maxPaymentsPerMinute: 10,
38
+ },
39
+ });
40
+
41
+ // Use it like fetch - L402 payments happen automatically
42
+ const response = await client.fetch("https://api.example.com/paid-resource");
43
+ const data = await response.json();
44
+ ```
45
+
46
+ If the server returns `402 Payment Required`, Gateless automatically detects the protocol version and handles payment:
47
+
48
+ **Classic L402** - Parses the `WWW-Authenticate` header, pays the invoice, caches the macaroon+preimage token, and retries with `Authorization: L402`. Second requests reuse the cached token.
49
+
50
+ **Fewsats v0.2** - Parses the JSON offers body, selects an offer, fetches a Lightning invoice from the payment request endpoint, pays it, and retries with the original request headers (Bearer token). The server credits your account after payment.
51
+
52
+ ### Fewsats v0.2
53
+
54
+ Most live L402 endpoints today use the Fewsats v0.2 protocol. Your Bearer token (obtained separately) is your credential - Gateless handles the payment when the server returns 402:
55
+
56
+ ```typescript
57
+ const client = new L402Client({
58
+ paymentProvider: lnd,
59
+ maxPaymentSats: 100,
60
+ });
61
+
62
+ // Bearer token is passed through - Gateless pays when the server demands it
63
+ const response = await client.fetch("https://api.example.com/paid-resource", {
64
+ headers: { Authorization: "Bearer your-api-token" },
65
+ });
66
+ const data = await response.json();
67
+ ```
68
+
69
+ You can customize which offer is selected when multiple are available:
70
+
71
+ ```typescript
72
+ import { L402Client, type OfferStrategy } from "gateless";
73
+
74
+ const pickMostCredits: OfferStrategy = (offers) => {
75
+ const lightning = offers.filter((o) =>
76
+ o.payment_methods.includes("lightning"),
77
+ );
78
+ if (lightning.length === 0) throw new Error("No lightning offers");
79
+ lightning.sort((a, b) => b.balance - a.balance);
80
+ return lightning[0]!;
81
+ };
82
+
83
+ const client = new L402Client({
84
+ paymentProvider: lnd,
85
+ maxPaymentSats: 100,
86
+ offerStrategy: pickMostCredits, // default: cheapest lightning-compatible offer
87
+ });
88
+ ```
89
+
90
+ ## Features
91
+
92
+ **L402 Client** - Drop-in `fetch` wrapper that handles the full 402 → pay → retry flow automatically. Supports both classic L402 and Fewsats v0.2.
93
+
94
+ **Fewsats v0.2** - Automatic offer selection, payment request negotiation, and Bearer token auth. Pluggable offer strategy (default: cheapest lightning-compatible).
95
+
96
+ **Token Cache** - Stores paid macaroon+preimage pairs for classic L402. Avoids double-paying for the same resource. Supports optional TTL expiry.
97
+
98
+ **Spending Controls** - Set per-payment limits, total budgets, and rate limits. Applies to both classic and v0.2 flows. An AI agent physically cannot exceed the budget you define.
99
+
100
+ **Payment Provider Interface** - Ships with an LND client over REST. Bring your own provider by implementing a simple interface:
101
+
102
+ ```typescript
103
+ interface PaymentProvider {
104
+ payInvoice(paymentRequest: string): Promise<PaymentResult>;
105
+ }
106
+ ```
107
+
108
+ ## How L402 Works
109
+
110
+ L402 is a protocol that uses Lightning Network payments for authentication. Gateless supports two variants:
111
+
112
+ ### Classic L402
113
+
114
+ The server returns a macaroon and invoice in the `WWW-Authenticate` header. After payment, the preimage proves you paid.
115
+
116
+ ```
117
+ Agent Server
118
+ | GET /resource |
119
+ |----------------------------->|
120
+ | 402 + WWW-Authenticate: |
121
+ | L402 macaroon="...", |
122
+ | invoice="lnbc..." |
123
+ |<-----------------------------|
124
+ | [pays Lightning invoice] |
125
+ | GET /resource |
126
+ | Authorization: L402 |
127
+ | <macaroon>:<preimage> |
128
+ |----------------------------->|
129
+ | 200 OK { data } |
130
+ |<-----------------------------|
131
+ ```
132
+
133
+ ### Fewsats v0.2
134
+
135
+ The server returns a JSON body with offers. The agent selects an offer, fetches a Lightning invoice, pays it, and retries with the original Bearer token.
136
+
137
+ ```
138
+ Agent Server Payment Endpoint
139
+ | GET /resource | |
140
+ | Authorization: Bearer ... | |
141
+ |----------------------------->| |
142
+ | 402 + JSON body: | |
143
+ | { offers, payment_ | |
144
+ | context_token, | |
145
+ | payment_request_url } | |
146
+ |<-----------------------------| |
147
+ | POST /payment-request | |
148
+ | { offer_id, |-------------------->|
149
+ | payment_method, | |
150
+ | payment_context_token } | |
151
+ | { lightning_invoice } |<--------------------|
152
+ |<-----------------------------| |
153
+ | [pays Lightning invoice] | |
154
+ | GET /resource | |
155
+ | Authorization: Bearer ... | |
156
+ |----------------------------->| |
157
+ | 200 OK { data } | |
158
+ |<-----------------------------| |
159
+ ```
160
+
161
+ No accounts. No passwords. No tracking. Payment is the authentication.
162
+
163
+ ## Architecture
164
+
165
+ ```
166
+ ┌─────────────────────────────────────────┐
167
+ │ Your Application │
168
+ │ │
169
+ │ const res = await client.fetch(url) │
170
+ └────────────────┬────────────────────────┘
171
+
172
+ ┌────────────────▼────────────────────────┐
173
+ │ L402Client │
174
+ │ │
175
+ │ ┌──────────┐ ┌──────────┐ ┌─────────┐ │
176
+ │ │ Token │ │ Spending │ │ Invoice │ │
177
+ │ │ Cache │ │ Tracker │ │ Decoder │ │
178
+ │ └──────────┘ └──────────┘ └─────────┘ │
179
+ └────────────────┬────────────────────────┘
180
+
181
+ ┌────────────────▼────────────────────────┐
182
+ │ PaymentProvider │
183
+ │ │
184
+ │ LndClient │ (NWC - planned) │
185
+ │ REST API │ (LNC - planned) │
186
+ └────────────────┬────────────────────────┘
187
+
188
+ ┌────────────────▼────────────────────────┐
189
+ │ Your LND Node │
190
+ │ (your keys, your node) │
191
+ └─────────────────────────────────────────┘
192
+ ```
193
+
194
+ ## Comparison
195
+
196
+ | | Gateless | lnget (Lightning Labs) | x402 (Coinbase) |
197
+ | ----------------- | ------------------- | ---------------------- | ---------------------- |
198
+ | Language | TypeScript | Go | Multiple |
199
+ | Runtime | Node.js | CLI only | Server SDKs |
200
+ | Payment rail | Lightning (Bitcoin) | Lightning (Bitcoin) | USDC (stablecoins) |
201
+ | Node | Your own LND | Your own LND | Coinbase custody |
202
+ | Identity required | No | No | Yes (Coinbase account) |
203
+ | npm install | Yes | No | Yes |
204
+ | Self-sovereign | Yes | Yes | No |
205
+
206
+ Gateless and lnget are complementary. lnget is for terminal-based agents (Claude Code, Codex). Gateless is for web developers building agents in TypeScript.
207
+
208
+ ## Roadmap
209
+
210
+ - ✅ LND REST payment provider
211
+ - ✅ L402 fetch client with automatic payment
212
+ - ✅ Token caching
213
+ - ✅ Spending limits and rate controls
214
+ - ✅ Fewsats L402 v0.2 support (offers, payment requests, pluggable offer strategy)
215
+ - ⬜ Nostr Wallet Connect (NWC) payment provider
216
+ - ⬜ Lightning Node Connect (LNC) provider
217
+ - ⬜ Nostr endpoint discovery
218
+ - ⬜ React hooks (`useL402Fetch`)
219
+ - ⬜ Macaroon attenuation and inspection
220
+ - ⬜ Server-side middleware (Aperture alternative in JS)
221
+
222
+ ## Requirements
223
+
224
+ - Node.js 18+
225
+ - An LND node (v0.16+) with REST API enabled
226
+ - A funded Lightning channel
227
+
228
+ ### LND Credentials
229
+
230
+ Gateless needs two files from your LND node:
231
+
232
+ - **TLS certificate** — usually at `~/.lnd/tls.cert`
233
+ - **Admin macaroon** — usually at `~/.lnd/data/chain/bitcoin/mainnet/admin.macaroon`
234
+
235
+ Copy them to your project (e.g. a `creds/` directory) and point `LndClient` at them. If your node is on a different machine, use an SSH tunnel to forward the REST port:
236
+
237
+ ```bash
238
+ ssh -L 8080:127.0.0.1:8080 user@your-node-ip
239
+ ```
240
+
241
+ ## ⚠️ Disclaimer
242
+
243
+ Gateless is experimental software. It interacts with real Bitcoin on the Lightning Network. By using this software you accept full responsibility for any funds sent or lost. Always start with small amounts, use spending limits, and test thoroughly before deploying in any production environment. This software is provided as-is with no warranty of any kind.
244
+
245
+ ## License
246
+
247
+ MIT
248
+
249
+ ## Author
250
+
251
+ [satpath](https://github.com/satpathdev)
@@ -0,0 +1,13 @@
1
+ /** The invoice amount or total spend would exceed configured limits */
2
+ export declare class L402BudgetError extends Error {
3
+ name: "L402BudgetError";
4
+ }
5
+ /** The Lightning payment failed (routing failure, insufficient funds, etc.) */
6
+ export declare class L402PaymentError extends Error {
7
+ name: "L402PaymentError";
8
+ }
9
+ /** The server's L402 challenge was malformed or the invoice could not be parsed */
10
+ export declare class L402ProtocolError extends Error {
11
+ name: "L402ProtocolError";
12
+ }
13
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,qBAAa,eAAgB,SAAQ,KAAK;IAC/B,IAAI,EAAG,iBAAiB,CAAU;CAC5C;AAED,+EAA+E;AAC/E,qBAAa,gBAAiB,SAAQ,KAAK;IAChC,IAAI,EAAG,kBAAkB,CAAU;CAC7C;AAED,mFAAmF;AACnF,qBAAa,iBAAkB,SAAQ,KAAK;IACjC,IAAI,EAAG,mBAAmB,CAAU;CAC9C"}
package/dist/errors.js ADDED
@@ -0,0 +1,13 @@
1
+ /** The invoice amount or total spend would exceed configured limits */
2
+ export class L402BudgetError extends Error {
3
+ name = "L402BudgetError";
4
+ }
5
+ /** The Lightning payment failed (routing failure, insufficient funds, etc.) */
6
+ export class L402PaymentError extends Error {
7
+ name = "L402PaymentError";
8
+ }
9
+ /** The server's L402 challenge was malformed or the invoice could not be parsed */
10
+ export class L402ProtocolError extends Error {
11
+ name = "L402ProtocolError";
12
+ }
13
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAC/B,IAAI,GAAG,iBAA0B,CAAC;CAC5C;AAED,+EAA+E;AAC/E,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAChC,IAAI,GAAG,kBAA2B,CAAC;CAC7C;AAED,mFAAmF;AACnF,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACjC,IAAI,GAAG,mBAA4B,CAAC;CAC9C"}
@@ -0,0 +1,41 @@
1
+ /** A single offer from the Fewsats v0.2 402 JSON body */
2
+ export interface FewsatsOffer {
3
+ offer_id: string;
4
+ title: string;
5
+ description: string;
6
+ amount: number;
7
+ balance: number;
8
+ currency: string;
9
+ payment_methods: string[];
10
+ type: string;
11
+ }
12
+ /** The JSON body of a Fewsats v0.2 402 response */
13
+ export interface FewsatsPaymentRequired {
14
+ offers: FewsatsOffer[];
15
+ payment_context_token: string;
16
+ payment_request_url: string;
17
+ version: string;
18
+ }
19
+ /** The response from POSTing to the payment_request_url */
20
+ export interface FewsatsPaymentRequestResponse {
21
+ payment_request: {
22
+ lightning_invoice: string;
23
+ };
24
+ expires_at: string;
25
+ offer_id: string;
26
+ version: string;
27
+ }
28
+ /** Strategy function for selecting an offer from the array */
29
+ export type OfferStrategy = (offers: FewsatsOffer[]) => FewsatsOffer;
30
+ /**
31
+ * Attempts to parse an unknown value as a Fewsats v0.2 payment-required payload.
32
+ * Returns the parsed payload if valid, or undefined if the shape doesn't match.
33
+ * Does not throw.
34
+ */
35
+ export declare function parseFewsatsPaymentRequired(body: unknown): FewsatsPaymentRequired | undefined;
36
+ /**
37
+ * Default offer strategy: selects the cheapest offer that supports lightning.
38
+ * Throws L402ProtocolError if no lightning-compatible offer exists.
39
+ */
40
+ export declare function selectCheapestLightningOffer(offers: FewsatsOffer[]): FewsatsOffer;
41
+ //# sourceMappingURL=fewsats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fewsats.d.ts","sourceRoot":"","sources":["../src/fewsats.ts"],"names":[],"mappings":"AAEA,yDAAyD;AACzD,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,mDAAmD;AACnD,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,2DAA2D;AAC3D,MAAM,WAAW,6BAA6B;IAC5C,eAAe,EAAE;QAAE,iBAAiB,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,8DAA8D;AAC9D,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,YAAY,CAAC;AAErE;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,OAAO,GACZ,sBAAsB,GAAG,SAAS,CAqCpC;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,YAAY,EAAE,GACrB,YAAY,CAad"}
@@ -0,0 +1,47 @@
1
+ import { L402ProtocolError } from "./errors.js";
2
+ /**
3
+ * Attempts to parse an unknown value as a Fewsats v0.2 payment-required payload.
4
+ * Returns the parsed payload if valid, or undefined if the shape doesn't match.
5
+ * Does not throw.
6
+ */
7
+ export function parseFewsatsPaymentRequired(body) {
8
+ if (typeof body !== "object" ||
9
+ body === null ||
10
+ !("offers" in body) ||
11
+ !("payment_context_token" in body) ||
12
+ !("payment_request_url" in body) ||
13
+ !("version" in body)) {
14
+ return undefined;
15
+ }
16
+ const obj = body;
17
+ if (!Array.isArray(obj["offers"]) ||
18
+ obj["offers"].length === 0 ||
19
+ typeof obj["payment_context_token"] !== "string" ||
20
+ typeof obj["payment_request_url"] !== "string" ||
21
+ typeof obj["version"] !== "string") {
22
+ return undefined;
23
+ }
24
+ for (const offer of obj["offers"]) {
25
+ if (typeof offer !== "object" ||
26
+ offer === null ||
27
+ typeof offer["offer_id"] !== "string" ||
28
+ typeof offer["amount"] !== "number" ||
29
+ !Array.isArray(offer["payment_methods"])) {
30
+ return undefined;
31
+ }
32
+ }
33
+ return body;
34
+ }
35
+ /**
36
+ * Default offer strategy: selects the cheapest offer that supports lightning.
37
+ * Throws L402ProtocolError if no lightning-compatible offer exists.
38
+ */
39
+ export function selectCheapestLightningOffer(offers) {
40
+ const lightningOffers = offers.filter((o) => o.payment_methods.includes("lightning"));
41
+ if (lightningOffers.length === 0) {
42
+ throw new L402ProtocolError("No offers support lightning payment method");
43
+ }
44
+ lightningOffers.sort((a, b) => a.amount - b.amount);
45
+ return lightningOffers[0];
46
+ }
47
+ //# sourceMappingURL=fewsats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fewsats.js","sourceRoot":"","sources":["../src/fewsats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAiChD;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CACzC,IAAa;IAEb,IACE,OAAO,IAAI,KAAK,QAAQ;QACxB,IAAI,KAAK,IAAI;QACb,CAAC,CAAC,QAAQ,IAAI,IAAI,CAAC;QACnB,CAAC,CAAC,uBAAuB,IAAI,IAAI,CAAC;QAClC,CAAC,CAAC,qBAAqB,IAAI,IAAI,CAAC;QAChC,CAAC,CAAC,SAAS,IAAI,IAAI,CAAC,EACpB,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,GAAG,GAAG,IAA+B,CAAC;IAE5C,IACE,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,GAAG,CAAC,QAAQ,CAAC,CAAC,MAAM,KAAK,CAAC;QAC1B,OAAO,GAAG,CAAC,uBAAuB,CAAC,KAAK,QAAQ;QAChD,OAAO,GAAG,CAAC,qBAAqB,CAAC,KAAK,QAAQ;QAC9C,OAAO,GAAG,CAAC,SAAS,CAAC,KAAK,QAAQ,EAClC,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,QAAQ,CAAc,EAAE,CAAC;QAC/C,IACE,OAAO,KAAK,KAAK,QAAQ;YACzB,KAAK,KAAK,IAAI;YACd,OAAQ,KAAiC,CAAC,UAAU,CAAC,KAAK,QAAQ;YAClE,OAAQ,KAAiC,CAAC,QAAQ,CAAC,KAAK,QAAQ;YAChE,CAAC,KAAK,CAAC,OAAO,CAAE,KAAiC,CAAC,iBAAiB,CAAC,CAAC,EACrE,CAAC;YACD,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAED,OAAO,IAA8B,CAAC;AACxC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,4BAA4B,CAC1C,MAAsB;IAEtB,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAC1C,CAAC,CAAC,eAAe,CAAC,QAAQ,CAAC,WAAW,CAAC,CACxC,CAAC;IAEF,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,iBAAiB,CACzB,4CAA4C,CAC7C,CAAC;IACJ,CAAC;IAED,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;IACpD,OAAO,eAAe,CAAC,CAAC,CAAE,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,12 @@
1
+ export { LndClient } from "./lnd.js";
2
+ export type { LndConfig } from "./lnd.js";
3
+ export { L402Client } from "./l402.js";
4
+ export type { L402ClientConfig } from "./l402.js";
5
+ export { TokenCache } from "./token-cache.js";
6
+ export type { CachedToken } from "./token-cache.js";
7
+ export type { PaymentProvider, PaymentResult } from "./payment-provider.js";
8
+ export type { SpendingLimit, SpendingRecord } from "./spending.js";
9
+ export { L402BudgetError, L402PaymentError, L402ProtocolError } from "./errors.js";
10
+ export type { FewsatsOffer, FewsatsPaymentRequired, FewsatsPaymentRequestResponse, OfferStrategy, } from "./fewsats.js";
11
+ export { selectCheapestLightningOffer } from "./fewsats.js";
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,YAAY,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC5E,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACnF,YAAY,EACV,YAAY,EACZ,sBAAsB,EACtB,6BAA6B,EAC7B,aAAa,GACd,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,4BAA4B,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { LndClient } from "./lnd.js";
2
+ export { L402Client } from "./l402.js";
3
+ export { TokenCache } from "./token-cache.js";
4
+ export { L402BudgetError, L402PaymentError, L402ProtocolError } from "./errors.js";
5
+ export { selectCheapestLightningOffer } from "./fewsats.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAI9C,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAOnF,OAAO,EAAE,4BAA4B,EAAE,MAAM,cAAc,CAAC"}
package/dist/l402.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ import { type SpendingLimit } from "./spending.js";
2
+ import type { PaymentProvider } from "./payment-provider.js";
3
+ import { type OfferStrategy } from "./fewsats.js";
4
+ export interface L402ClientConfig {
5
+ paymentProvider: PaymentProvider;
6
+ /**
7
+ * Maximum sats allowed per individual payment (default: 1000).
8
+ * This is a simple safety cap. For full budget control, use spendingLimits
9
+ * which provides per-payment, total budget, and rate limiting together.
10
+ */
11
+ maxPaymentSats?: number;
12
+ /** Optional spending limits for budget and rate control */
13
+ spendingLimits?: SpendingLimit;
14
+ /** Strategy for selecting from Fewsats v0.2 offers (default: cheapest lightning-compatible) */
15
+ offerStrategy?: OfferStrategy;
16
+ }
17
+ export declare class L402Client {
18
+ private paymentProvider;
19
+ private maxPaymentSats;
20
+ private cache;
21
+ private spending;
22
+ private inflightPayments;
23
+ private offerStrategy;
24
+ constructor(config: L402ClientConfig);
25
+ /** Remove all cached tokens */
26
+ clearCache(): void;
27
+ fetch(url: string, init?: RequestInit): Promise<Response>;
28
+ private handleClassicL402;
29
+ private handleFewsatsV02;
30
+ private parseWwwAuthenticate;
31
+ /**
32
+ * Local BOLT11 amount parser. Used only as a fallback when the payment
33
+ * provider does not implement getInvoiceAmountSats(). Supports mainnet
34
+ * (lnbc), testnet (lntb), and regtest (lnbcrt) invoices.
35
+ */
36
+ private decodeInvoiceAmountLocal;
37
+ }
38
+ //# sourceMappingURL=l402.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"l402.d.ts","sourceRoot":"","sources":["../src/l402.ts"],"names":[],"mappings":"AACA,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAM7D,OAAO,EAGL,KAAK,aAAa,EAGnB,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,eAAe,CAAC;IACjC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,2DAA2D;IAC3D,cAAc,CAAC,EAAE,aAAa,CAAC;IAC/B,+FAA+F;IAC/F,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,gBAAgB,CAAoC;IAC5D,OAAO,CAAC,aAAa,CAAgB;gBAEzB,MAAM,EAAE,gBAAgB;IAUpC,+BAA+B;IAC/B,UAAU,IAAI,IAAI;IAIZ,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC;YAoEjD,iBAAiB;YA4CjB,gBAAgB;IAyF9B,OAAO,CAAC,oBAAoB;IAiB5B;;;;OAIG;IACH,OAAO,CAAC,wBAAwB;CA8CjC"}
package/dist/l402.js ADDED
@@ -0,0 +1,224 @@
1
+ import { TokenCache } from "./token-cache.js";
2
+ import { SpendingTracker } from "./spending.js";
3
+ import { L402BudgetError, L402PaymentError, L402ProtocolError, } from "./errors.js";
4
+ import { parseFewsatsPaymentRequired, selectCheapestLightningOffer, } from "./fewsats.js";
5
+ export class L402Client {
6
+ paymentProvider;
7
+ maxPaymentSats;
8
+ cache;
9
+ spending;
10
+ inflightPayments = new Map();
11
+ offerStrategy;
12
+ constructor(config) {
13
+ this.paymentProvider = config.paymentProvider;
14
+ this.maxPaymentSats = config.maxPaymentSats ?? 1000;
15
+ this.cache = new TokenCache();
16
+ this.offerStrategy = config.offerStrategy ?? selectCheapestLightningOffer;
17
+ if (config.spendingLimits) {
18
+ this.spending = new SpendingTracker(config.spendingLimits);
19
+ }
20
+ }
21
+ /** Remove all cached tokens */
22
+ clearCache() {
23
+ this.cache.clear();
24
+ }
25
+ async fetch(url, init) {
26
+ const cached = this.cache.get(url);
27
+ if (cached) {
28
+ const headers = new Headers(init?.headers);
29
+ headers.set("Authorization", `L402 ${cached.macaroon}:${cached.preimage}`);
30
+ const cachedResponse = await fetch(url, { ...init, headers });
31
+ if (cachedResponse.status !== 402) {
32
+ return cachedResponse;
33
+ }
34
+ // Token expired server-side - discard and fall through to payment flow
35
+ this.cache.delete(url);
36
+ }
37
+ // If another call is already paying for this URL, wait for it then use the cache
38
+ const inflight = this.inflightPayments.get(url);
39
+ if (inflight) {
40
+ await inflight;
41
+ const token = this.cache.get(url);
42
+ if (token) {
43
+ const headers = new Headers(init?.headers);
44
+ headers.set("Authorization", `L402 ${token.macaroon}:${token.preimage}`);
45
+ return fetch(url, { ...init, headers });
46
+ }
47
+ }
48
+ const response = await fetch(url, init);
49
+ if (response.status !== 402) {
50
+ return response;
51
+ }
52
+ // Detect which L402 flavor was returned
53
+ const wwwAuth = response.headers.get("www-authenticate");
54
+ if (wwwAuth) {
55
+ // Classic L402: WWW-Authenticate header present
56
+ await response.body?.cancel();
57
+ return this.handleClassicL402(url, init, wwwAuth);
58
+ }
59
+ // Try Fewsats v0.2: parse JSON body
60
+ let body;
61
+ try {
62
+ body = await response.json();
63
+ }
64
+ catch {
65
+ throw new L402ProtocolError("402 response has no WWW-Authenticate header and body is not valid JSON");
66
+ }
67
+ const fewsatsPayload = parseFewsatsPaymentRequired(body);
68
+ if (!fewsatsPayload) {
69
+ throw new L402ProtocolError("402 response has no WWW-Authenticate header and body does not match Fewsats v0.2 format");
70
+ }
71
+ return this.handleFewsatsV02(url, init, fewsatsPayload);
72
+ }
73
+ async handleClassicL402(url, init, wwwAuth) {
74
+ const { macaroon, invoice } = this.parseWwwAuthenticate(wwwAuth);
75
+ const amountSats = this.paymentProvider.getInvoiceAmountSats
76
+ ? await this.paymentProvider.getInvoiceAmountSats(invoice)
77
+ : this.decodeInvoiceAmountLocal(invoice);
78
+ if (amountSats > this.maxPaymentSats) {
79
+ throw new L402BudgetError(`Invoice amount ${amountSats} sats exceeds limit of ${this.maxPaymentSats} sats`);
80
+ }
81
+ this.spending?.check(amountSats);
82
+ let resolveInflight;
83
+ const paymentPromise = new Promise((r) => {
84
+ resolveInflight = r;
85
+ });
86
+ this.inflightPayments.set(url, paymentPromise);
87
+ try {
88
+ const payment = await this.paymentProvider.payInvoice(invoice);
89
+ this.spending?.record(amountSats, url);
90
+ this.cache.set(url, macaroon, payment.preimage);
91
+ const headers = new Headers(init?.headers);
92
+ headers.set("Authorization", `L402 ${macaroon}:${payment.preimage}`);
93
+ return fetch(url, { ...init, headers });
94
+ }
95
+ catch (error) {
96
+ const message = error instanceof Error ? error.message : String(error);
97
+ throw new L402PaymentError(`Payment failed: ${message}`);
98
+ }
99
+ finally {
100
+ this.inflightPayments.delete(url);
101
+ resolveInflight();
102
+ }
103
+ }
104
+ async handleFewsatsV02(url, init, payload) {
105
+ const selectedOffer = this.offerStrategy(payload.offers);
106
+ // POST to payment_request_url to get a lightning invoice
107
+ let paymentRequestResponse;
108
+ try {
109
+ paymentRequestResponse = await fetch(payload.payment_request_url, {
110
+ method: "POST",
111
+ headers: { "Content-Type": "application/json" },
112
+ body: JSON.stringify({
113
+ offer_id: selectedOffer.offer_id,
114
+ payment_method: "lightning",
115
+ payment_context_token: payload.payment_context_token,
116
+ }),
117
+ });
118
+ }
119
+ catch (error) {
120
+ const message = error instanceof Error ? error.message : String(error);
121
+ throw new L402PaymentError(`Failed to fetch payment request: ${message}`);
122
+ }
123
+ if (!paymentRequestResponse.ok) {
124
+ const errorText = await paymentRequestResponse
125
+ .text()
126
+ .catch(() => "unknown error");
127
+ throw new L402PaymentError(`Payment request endpoint returned ${paymentRequestResponse.status}: ${errorText}`);
128
+ }
129
+ let paymentRequestData;
130
+ try {
131
+ paymentRequestData =
132
+ (await paymentRequestResponse.json());
133
+ }
134
+ catch {
135
+ throw new L402ProtocolError("Payment request response is not valid JSON");
136
+ }
137
+ const invoice = paymentRequestData.payment_request?.lightning_invoice;
138
+ if (!invoice) {
139
+ throw new L402ProtocolError("Payment request response missing lightning_invoice");
140
+ }
141
+ // Validate invoice amount against spending limits
142
+ const amountSats = this.paymentProvider.getInvoiceAmountSats
143
+ ? await this.paymentProvider.getInvoiceAmountSats(invoice)
144
+ : this.decodeInvoiceAmountLocal(invoice);
145
+ if (amountSats > this.maxPaymentSats) {
146
+ throw new L402BudgetError(`Invoice amount ${amountSats} sats exceeds limit of ${this.maxPaymentSats} sats`);
147
+ }
148
+ this.spending?.check(amountSats);
149
+ try {
150
+ await this.paymentProvider.payInvoice(invoice);
151
+ }
152
+ catch (error) {
153
+ const message = error instanceof Error ? error.message : String(error);
154
+ throw new L402PaymentError(`Payment failed: ${message}`);
155
+ }
156
+ this.spending?.record(amountSats, url);
157
+ const maxRetries = 5;
158
+ const baseDelayMs = 500;
159
+ for (let i = 0; i < maxRetries; i++) {
160
+ await new Promise((resolve) => setTimeout(resolve, baseDelayMs * (i + 1)));
161
+ const retryResponse = await fetch(url, init);
162
+ if (retryResponse.status !== 402) {
163
+ return retryResponse;
164
+ }
165
+ // Still 402 — server hasn't credited yet, try again
166
+ await retryResponse.body?.cancel();
167
+ }
168
+ // All retries exhausted — return whatever the server gives
169
+ return fetch(url, init);
170
+ }
171
+ parseWwwAuthenticate(header) {
172
+ const macaroonMatch = header.match(/macaroon="([^"]+)"/);
173
+ const invoiceMatch = header.match(/invoice="([^"]+)"/);
174
+ if (!macaroonMatch?.[1] || !invoiceMatch?.[1]) {
175
+ throw new L402ProtocolError(`Invalid WWW-Authenticate header: ${header}`);
176
+ }
177
+ return {
178
+ macaroon: macaroonMatch[1],
179
+ invoice: invoiceMatch[1],
180
+ };
181
+ }
182
+ /**
183
+ * Local BOLT11 amount parser. Used only as a fallback when the payment
184
+ * provider does not implement getInvoiceAmountSats(). Supports mainnet
185
+ * (lnbc), testnet (lntb), and regtest (lnbcrt) invoices.
186
+ */
187
+ decodeInvoiceAmountLocal(invoice) {
188
+ const lower = invoice.toLowerCase();
189
+ // Find amount start after the network prefix
190
+ let amountStart;
191
+ if (lower.startsWith("lnbcrt")) {
192
+ amountStart = 6;
193
+ }
194
+ else if (lower.startsWith("lntb") || lower.startsWith("lnbc")) {
195
+ amountStart = 4;
196
+ }
197
+ else {
198
+ throw new L402ProtocolError("Cannot decode invoice: unrecognized network prefix");
199
+ }
200
+ // The human-readable part ends at the last '1' before the data section
201
+ const separatorIndex = lower.lastIndexOf("1");
202
+ if (separatorIndex <= amountStart) {
203
+ throw new L402ProtocolError("Cannot decode invoice amount: no amount specified");
204
+ }
205
+ const amountPart = lower.slice(amountStart, separatorIndex);
206
+ const match = amountPart.match(/^(\d+)([munp])$/);
207
+ if (!match?.[1] || !match[2]) {
208
+ throw new L402ProtocolError("Cannot decode invoice amount: invalid format");
209
+ }
210
+ const amount = parseInt(match[1], 10);
211
+ const multipliers = {
212
+ m: 100_000,
213
+ u: 100,
214
+ n: 0.1,
215
+ p: 0.0001,
216
+ };
217
+ const mult = multipliers[match[2]];
218
+ if (mult === undefined) {
219
+ throw new L402ProtocolError("Cannot decode invoice amount");
220
+ }
221
+ return Math.round(amount * mult);
222
+ }
223
+ }
224
+ //# sourceMappingURL=l402.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"l402.js","sourceRoot":"","sources":["../src/l402.ts"],"names":[],"mappings":"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"}
package/dist/lnd.d.ts ADDED
@@ -0,0 +1,45 @@
1
+ import type { PaymentProvider, PaymentResult } from "./payment-provider.js";
2
+ export type { PaymentResult } from "./payment-provider.js";
3
+ export interface LndConfig {
4
+ /** Hostname or IP of the LND REST API */
5
+ host: string;
6
+ /** REST API port (default LND: 8080) */
7
+ port: number;
8
+ /** Path to LND's TLS certificate (tls.cert) */
9
+ tlsCertPath: string;
10
+ /** Path to a macaroon file for authentication (e.g. admin.macaroon) */
11
+ macaroonPath: string;
12
+ /** Request timeout in milliseconds (default: 30000) */
13
+ timeoutMs?: number;
14
+ }
15
+ /**
16
+ * Connects to an LND node via its REST API.
17
+ * Reads the TLS cert and macaroon from disk once at construction.
18
+ * The macaroon is sent as a hex-encoded header on every request.
19
+ */
20
+ export declare class LndClient implements PaymentProvider {
21
+ private config;
22
+ private tlsCert;
23
+ private macaroon;
24
+ private baseUrl;
25
+ private timeoutMs;
26
+ constructor(config: LndConfig);
27
+ /**
28
+ * Makes an authenticated HTTPS request to the LND REST API.
29
+ * The TLS cert is used as a CA to verify the self-signed certificate.
30
+ */
31
+ private request;
32
+ /** Returns general information about the node (alias, sync status, block height, etc.) */
33
+ getInfo(): Promise<Record<string, unknown>>;
34
+ /**
35
+ * Pays a BOLT11 Lightning invoice.
36
+ * LND returns the preimage as base64 - we convert it to hex
37
+ * because the L402 Authorization header requires hex encoding.
38
+ */
39
+ payInvoice(paymentRequest: string): Promise<PaymentResult>;
40
+ /** Returns the invoice amount in satoshis by decoding via LND's REST API */
41
+ getInvoiceAmountSats(paymentRequest: string): Promise<number>;
42
+ /** Decodes a BOLT11 invoice without paying it. Returns amount, destination, description, etc. */
43
+ decodeInvoice(paymentRequest: string): Promise<Record<string, unknown>>;
44
+ }
45
+ //# sourceMappingURL=lnd.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lnd.d.ts","sourceRoot":"","sources":["../src/lnd.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE5E,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,MAAM,WAAW,SAAS;IACxB,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,YAAY,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,qBAAa,SAAU,YAAW,eAAe;IAMnC,OAAO,CAAC,MAAM;IAL1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAS;gBAEN,MAAM,EAAE,SAAS;IAOrC;;;OAGG;YACW,OAAO;IA8CrB,0FAA0F;IACpF,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAIjD;;;;OAIG;IACG,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IA6BhE,4EAA4E;IACtE,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAQnE,iGAAiG;IAC3F,aAAa,CACjB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAGpC"}
package/dist/lnd.js ADDED
@@ -0,0 +1,97 @@
1
+ import fs from "node:fs";
2
+ import https from "node:https";
3
+ /**
4
+ * Connects to an LND node via its REST API.
5
+ * Reads the TLS cert and macaroon from disk once at construction.
6
+ * The macaroon is sent as a hex-encoded header on every request.
7
+ */
8
+ export class LndClient {
9
+ config;
10
+ tlsCert;
11
+ macaroon;
12
+ baseUrl;
13
+ timeoutMs;
14
+ constructor(config) {
15
+ this.config = config;
16
+ this.tlsCert = fs.readFileSync(config.tlsCertPath);
17
+ this.macaroon = fs.readFileSync(config.macaroonPath).toString("hex");
18
+ this.baseUrl = `https://${config.host}:${config.port}`;
19
+ this.timeoutMs = config.timeoutMs ?? 30_000;
20
+ }
21
+ /**
22
+ * Makes an authenticated HTTPS request to the LND REST API.
23
+ * The TLS cert is used as a CA to verify the self-signed certificate.
24
+ */
25
+ async request(method, path, body) {
26
+ const url = new URL(path, this.baseUrl);
27
+ const options = {
28
+ method,
29
+ ca: this.tlsCert,
30
+ headers: {
31
+ "Grpc-Metadata-macaroon": this.macaroon,
32
+ "Content-Type": "application/json",
33
+ },
34
+ };
35
+ return new Promise((resolve, reject) => {
36
+ const req = https.request(url, options, (res) => {
37
+ let data = "";
38
+ res.on("data", (chunk) => {
39
+ data += chunk.toString();
40
+ });
41
+ res.on("end", () => {
42
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
43
+ resolve(JSON.parse(data));
44
+ }
45
+ else {
46
+ reject(new Error(`LND API error ${res.statusCode}: ${data}`));
47
+ }
48
+ });
49
+ });
50
+ req.setTimeout(this.timeoutMs, () => {
51
+ req.destroy();
52
+ reject(new Error(`LND request timed out after ${this.timeoutMs}ms`));
53
+ });
54
+ req.on("error", reject);
55
+ if (body) {
56
+ req.write(JSON.stringify(body));
57
+ }
58
+ req.end();
59
+ });
60
+ }
61
+ /** Returns general information about the node (alias, sync status, block height, etc.) */
62
+ async getInfo() {
63
+ return this.request("GET", "/v1/getinfo");
64
+ }
65
+ /**
66
+ * Pays a BOLT11 Lightning invoice.
67
+ * LND returns the preimage as base64 - we convert it to hex
68
+ * because the L402 Authorization header requires hex encoding.
69
+ */
70
+ async payInvoice(paymentRequest) {
71
+ const response = await this.request("POST", "/v1/channels/transactions", {
72
+ payment_request: paymentRequest,
73
+ });
74
+ if (response.payment_error) {
75
+ throw new Error(`Payment failed: ${response.payment_error}`);
76
+ }
77
+ if (typeof response.payment_preimage !== "string" ||
78
+ !response.payment_preimage) {
79
+ throw new Error("LND response missing payment_preimage");
80
+ }
81
+ return {
82
+ preimage: Buffer.from(response.payment_preimage, "base64").toString("hex"),
83
+ paymentHash: Buffer.from(response.payment_hash, "base64").toString("hex"),
84
+ status: "SUCCEEDED",
85
+ };
86
+ }
87
+ /** Returns the invoice amount in satoshis by decoding via LND's REST API */
88
+ async getInvoiceAmountSats(paymentRequest) {
89
+ const result = await this.request("GET", `/v1/payreq/${paymentRequest}`);
90
+ return parseInt(result.num_satoshis, 10);
91
+ }
92
+ /** Decodes a BOLT11 invoice without paying it. Returns amount, destination, description, etc. */
93
+ async decodeInvoice(paymentRequest) {
94
+ return this.request("GET", `/v1/payreq/${paymentRequest}`);
95
+ }
96
+ }
97
+ //# sourceMappingURL=lnd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lnd.js","sourceRoot":"","sources":["../src/lnd.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ export interface PaymentResult {
2
+ /** Hex-encoded preimage proving payment was made */
3
+ preimage: string;
4
+ /** Hex-encoded payment hash identifying this payment */
5
+ paymentHash: string;
6
+ status: string;
7
+ }
8
+ /**
9
+ * Interface for any Lightning payment backend.
10
+ * Implement this to add support for payment methods beyond LND,
11
+ * such as Nostr Wallet Connect (NWC) or Lightning Node Connect (LNC).
12
+ */
13
+ export interface PaymentProvider {
14
+ /** Pays a BOLT11 Lightning invoice and returns the preimage as proof of payment */
15
+ payInvoice(paymentRequest: string): Promise<PaymentResult>;
16
+ /**
17
+ * Returns the invoice amount in satoshis by decoding the BOLT11 invoice.
18
+ * Used for pre-payment amount validation. If not implemented, the client
19
+ * falls back to local BOLT11 parsing (less reliable).
20
+ */
21
+ getInvoiceAmountSats?(paymentRequest: string): Promise<number>;
22
+ }
23
+ //# sourceMappingURL=payment-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-provider.d.ts","sourceRoot":"","sources":["../src/payment-provider.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,mFAAmF;IACnF,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAE3D;;;;OAIG;IACH,oBAAoB,CAAC,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAChE"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=payment-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-provider.js","sourceRoot":"","sources":["../src/payment-provider.ts"],"names":[],"mappings":""}
@@ -0,0 +1,23 @@
1
+ export interface SpendingLimit {
2
+ maxPerPaymentSats: number;
3
+ maxTotalSats: number;
4
+ maxPaymentsPerMinute: number;
5
+ }
6
+ export interface SpendingRecord {
7
+ amount: number;
8
+ url: string;
9
+ timestamp: number;
10
+ }
11
+ export declare class SpendingTracker {
12
+ private limits;
13
+ private history;
14
+ private totalSpent;
15
+ constructor(limits: SpendingLimit);
16
+ check(amountSats: number): void;
17
+ record(amountSats: number, url: string): void;
18
+ get spent(): number;
19
+ get remaining(): number;
20
+ get paymentCount(): number;
21
+ reset(): void;
22
+ }
23
+ //# sourceMappingURL=spending.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spending.d.ts","sourceRoot":"","sources":["../src/spending.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa;IAC5B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,eAAe;IAId,OAAO,CAAC,MAAM;IAH1B,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,UAAU,CAAK;gBAEH,MAAM,EAAE,aAAa;IAEzC,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAwB/B,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAmB7C,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,KAAK,IAAI,IAAI;CAId"}
@@ -0,0 +1,51 @@
1
+ import { L402BudgetError } from "./errors.js";
2
+ export class SpendingTracker {
3
+ limits;
4
+ history = [];
5
+ totalSpent = 0;
6
+ constructor(limits) {
7
+ this.limits = limits;
8
+ }
9
+ check(amountSats) {
10
+ if (amountSats > this.limits.maxPerPaymentSats) {
11
+ throw new L402BudgetError(`Payment of ${amountSats} sats exceeds per-payment limit of ${this.limits.maxPerPaymentSats} sats`);
12
+ }
13
+ if (this.totalSpent + amountSats > this.limits.maxTotalSats) {
14
+ throw new L402BudgetError(`Payment of ${amountSats} sats would exceed total budget. Spent: ${this.totalSpent}/${this.limits.maxTotalSats} sats`);
15
+ }
16
+ const oneMinuteAgo = Date.now() - 60_000;
17
+ const recentPayments = this.history.filter((r) => r.timestamp > oneMinuteAgo);
18
+ if (recentPayments.length >= this.limits.maxPaymentsPerMinute) {
19
+ throw new L402BudgetError(`Rate limit: ${this.limits.maxPaymentsPerMinute} payments per minute exceeded`);
20
+ }
21
+ }
22
+ record(amountSats, url) {
23
+ const now = Date.now();
24
+ this.history.push({
25
+ amount: amountSats,
26
+ url,
27
+ timestamp: now,
28
+ });
29
+ this.totalSpent += amountSats;
30
+ // Prune records older than 1 minute (only needed for rate limiting)
31
+ const oneMinuteAgo = now - 60_000;
32
+ const firstRecentIndex = this.history.findIndex((r) => r.timestamp > oneMinuteAgo);
33
+ if (firstRecentIndex > 0) {
34
+ this.history = this.history.slice(firstRecentIndex);
35
+ }
36
+ }
37
+ get spent() {
38
+ return this.totalSpent;
39
+ }
40
+ get remaining() {
41
+ return this.limits.maxTotalSats - this.totalSpent;
42
+ }
43
+ get paymentCount() {
44
+ return this.history.length;
45
+ }
46
+ reset() {
47
+ this.history = [];
48
+ this.totalSpent = 0;
49
+ }
50
+ }
51
+ //# sourceMappingURL=spending.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spending.js","sourceRoot":"","sources":["../src/spending.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAc9C,MAAM,OAAO,eAAe;IAIN;IAHZ,OAAO,GAAqB,EAAE,CAAC;IAC/B,UAAU,GAAG,CAAC,CAAC;IAEvB,YAAoB,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;IAAG,CAAC;IAE7C,KAAK,CAAC,UAAkB;QACtB,IAAI,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC;YAC/C,MAAM,IAAI,eAAe,CACvB,cAAc,UAAU,sCAAsC,IAAI,CAAC,MAAM,CAAC,iBAAiB,OAAO,CACnG,CAAC;QACJ,CAAC;QAED,IAAI,IAAI,CAAC,UAAU,GAAG,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;YAC5D,MAAM,IAAI,eAAe,CACvB,cAAc,UAAU,2CAA2C,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,MAAM,CAAC,YAAY,OAAO,CACtH,CAAC;QACJ,CAAC;QAED,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC;QACzC,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CACxC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,YAAY,CAClC,CAAC;QACF,IAAI,cAAc,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,oBAAoB,EAAE,CAAC;YAC9D,MAAM,IAAI,eAAe,CACvB,eAAe,IAAI,CAAC,MAAM,CAAC,oBAAoB,+BAA+B,CAC/E,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,CAAC,UAAkB,EAAE,GAAW;QACpC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;YAChB,MAAM,EAAE,UAAU;YAClB,GAAG;YACH,SAAS,EAAE,GAAG;SACf,CAAC,CAAC;QACH,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC;QAE9B,oEAAoE;QACpE,MAAM,YAAY,GAAG,GAAG,GAAG,MAAM,CAAC;QAClC,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAC7C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,YAAY,CAClC,CAAC;QACF,IAAI,gBAAgB,GAAG,CAAC,EAAE,CAAC;YACzB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;IAED,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,GAAG,IAAI,CAAC,UAAU,CAAC;IACpD,CAAC;IAED,IAAI,YAAY;QACd,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;IAC7B,CAAC;IAED,KAAK;QACH,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;QAClB,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;IACtB,CAAC;CACF"}
@@ -0,0 +1,16 @@
1
+ export interface CachedToken {
2
+ macaroon: string;
3
+ preimage: string;
4
+ url: string;
5
+ createdAt: number;
6
+ expiresAt?: number;
7
+ }
8
+ export declare class TokenCache {
9
+ private tokens;
10
+ get(url: string): CachedToken | undefined;
11
+ set(url: string, macaroon: string, preimage: string, ttlMs?: number): void;
12
+ delete(url: string): boolean;
13
+ clear(): void;
14
+ get size(): number;
15
+ }
16
+ //# sourceMappingURL=token-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-cache.d.ts","sourceRoot":"","sources":["../src/token-cache.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAkC;IAEhD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAYzC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI;IAe1E,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAI5B,KAAK,IAAI,IAAI;IAIb,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
@@ -0,0 +1,35 @@
1
+ export class TokenCache {
2
+ tokens = new Map();
3
+ get(url) {
4
+ const token = this.tokens.get(url);
5
+ if (!token)
6
+ return undefined;
7
+ if (token.expiresAt && Date.now() > token.expiresAt) {
8
+ this.tokens.delete(url);
9
+ return undefined;
10
+ }
11
+ return token;
12
+ }
13
+ set(url, macaroon, preimage, ttlMs) {
14
+ const token = {
15
+ macaroon,
16
+ preimage,
17
+ url,
18
+ createdAt: Date.now(),
19
+ };
20
+ if (ttlMs) {
21
+ token.expiresAt = Date.now() + ttlMs;
22
+ }
23
+ this.tokens.set(url, token);
24
+ }
25
+ delete(url) {
26
+ return this.tokens.delete(url);
27
+ }
28
+ clear() {
29
+ this.tokens.clear();
30
+ }
31
+ get size() {
32
+ return this.tokens.size;
33
+ }
34
+ }
35
+ //# sourceMappingURL=token-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-cache.js","sourceRoot":"","sources":["../src/token-cache.ts"],"names":[],"mappings":"AAQA,MAAM,OAAO,UAAU;IACb,MAAM,GAAG,IAAI,GAAG,EAAuB,CAAC;IAEhD,GAAG,CAAC,GAAW;QACb,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAE7B,IAAI,KAAK,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;YACpD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACxB,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;IAED,GAAG,CAAC,GAAW,EAAE,QAAgB,EAAE,QAAgB,EAAE,KAAc;QACjE,MAAM,KAAK,GAAgB;YACzB,QAAQ;YACR,QAAQ;YACR,GAAG;YACH,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB,CAAC;QAEF,IAAI,KAAK,EAAE,CAAC;YACV,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;QACvC,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED,MAAM,CAAC,GAAW;QAChB,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACjC,CAAC;IAED,KAAK;QACH,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;IAC1B,CAAC;CACF"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@satpath/gateless",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Sovereign L402 payments for AI agents on Lightning",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "scripts": {
20
+ "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
21
+ "build": "npm run clean && tsc -p tsconfig.build.json",
22
+ "test": "tsc && node dist/test/test-e2e.js",
23
+ "prepublishOnly": "npm run build"
24
+ },
25
+ "keywords": [
26
+ "lightning",
27
+ "bitcoin",
28
+ "l402",
29
+ "lsat",
30
+ "macaroon",
31
+ "ai-agent",
32
+ "payments",
33
+ "lnd",
34
+ "micropayments"
35
+ ],
36
+ "author": "satpath",
37
+ "license": "MIT",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/satpathdev/gateless"
41
+ },
42
+ "engines": {
43
+ "node": ">=18"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^25.3.0",
47
+ "typescript": "^5.9.3"
48
+ }
49
+ }