lightning-agent 0.2.0 → 0.3.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 +193 -103
- package/lib/auth.js +380 -0
- package/lib/escrow.js +332 -0
- package/lib/index.js +18 -1
- package/lib/stream.js +477 -0
- package/package.json +3 -3
- package/test-v030.js +282 -0
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# ⚡ lightning-agent
|
|
2
2
|
|
|
3
|
-
Lightning
|
|
3
|
+
Lightning toolkit for AI agents. Payments, auth, escrow, and streaming micropayments.
|
|
4
4
|
|
|
5
|
-
A
|
|
5
|
+
A SDK that gives AI agents the ability to transact, authenticate, escrow work, and stream content for sats — all over [Nostr Wallet Connect (NWC)](https://nwc.dev). No browser, no UI, no bank accounts. Connect to any NWC-compatible wallet (Alby Hub, Mutiny, etc.) and start building the agent economy.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -10,7 +10,18 @@ A tiny SDK that gives any AI agent the ability to send and receive Bitcoin Light
|
|
|
10
10
|
npm install lightning-agent
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## What's in the box
|
|
14
|
+
|
|
15
|
+
| Module | What it does | Since |
|
|
16
|
+
|--------|-------------|-------|
|
|
17
|
+
| **Wallet** | Send/receive Lightning payments, decode invoices | v0.1.0 |
|
|
18
|
+
| **Auth** | LNURL-auth — login with your Lightning wallet | v0.3.0 |
|
|
19
|
+
| **Escrow** | Hold funds until work is verified, then release or refund | v0.3.0 |
|
|
20
|
+
| **Stream** | Pay-per-token streaming micropayments | v0.3.0 |
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Quick Start: Payments
|
|
14
25
|
|
|
15
26
|
```javascript
|
|
16
27
|
const { createWallet } = require('lightning-agent');
|
|
@@ -19,158 +30,248 @@ const wallet = createWallet('nostr+walletconnect://...');
|
|
|
19
30
|
|
|
20
31
|
// Check balance
|
|
21
32
|
const { balanceSats } = await wallet.getBalance();
|
|
22
|
-
console.log(`Balance: ${balanceSats} sats`);
|
|
23
33
|
|
|
24
34
|
// Create an invoice (get paid)
|
|
25
35
|
const { invoice, paymentHash } = await wallet.createInvoice({
|
|
26
36
|
amountSats: 50,
|
|
27
37
|
description: 'Text generation query'
|
|
28
38
|
});
|
|
29
|
-
console.log(`Pay me: ${invoice}`);
|
|
30
39
|
|
|
31
40
|
// Wait for payment
|
|
32
|
-
const { paid } = await wallet.waitForPayment(paymentHash
|
|
41
|
+
const { paid } = await wallet.waitForPayment(paymentHash);
|
|
33
42
|
|
|
34
|
-
// Pay an invoice
|
|
43
|
+
// Pay an invoice
|
|
35
44
|
const { preimage } = await wallet.payInvoice(someInvoice);
|
|
36
45
|
|
|
37
|
-
//
|
|
46
|
+
// Pay a Lightning address directly
|
|
38
47
|
await wallet.payAddress('alice@getalby.com', { amountSats: 10 });
|
|
39
48
|
|
|
40
|
-
// Done
|
|
41
49
|
wallet.close();
|
|
42
50
|
```
|
|
43
51
|
|
|
44
|
-
|
|
52
|
+
---
|
|
45
53
|
|
|
46
|
-
|
|
54
|
+
## Auth (LNURL-auth)
|
|
47
55
|
|
|
48
|
-
|
|
56
|
+
Login with a Lightning wallet. No passwords, no OAuth — just a signed cryptographic challenge.
|
|
57
|
+
|
|
58
|
+
### Server side
|
|
49
59
|
|
|
50
60
|
```javascript
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
61
|
+
const { createAuthServer, signAuth } = require('lightning-agent');
|
|
62
|
+
|
|
63
|
+
const auth = createAuthServer({
|
|
64
|
+
callbackUrl: 'https://api.example.com/auth',
|
|
65
|
+
challengeTtlMs: 300000 // 5 minutes
|
|
66
|
+
});
|
|
56
67
|
|
|
57
|
-
|
|
68
|
+
// Generate a challenge
|
|
69
|
+
const { k1, lnurl } = auth.createChallenge();
|
|
70
|
+
// → lnurl can be rendered as QR for wallet apps
|
|
71
|
+
// → k1 can be sent directly to agent clients
|
|
58
72
|
|
|
59
|
-
|
|
73
|
+
// Verify a signed response
|
|
74
|
+
const result = auth.verify(k1, sig, key);
|
|
75
|
+
// → { valid: true, pubkey: '03abc...' }
|
|
60
76
|
|
|
61
|
-
|
|
62
|
-
|
|
77
|
+
// Or use as Express middleware
|
|
78
|
+
app.get('/auth', auth.middleware((pubkey, req, res) => {
|
|
79
|
+
console.log('Authenticated:', pubkey);
|
|
80
|
+
}));
|
|
63
81
|
```
|
|
64
82
|
|
|
65
|
-
|
|
83
|
+
### Client side (agent)
|
|
66
84
|
|
|
67
|
-
|
|
85
|
+
```javascript
|
|
86
|
+
const { signAuth, authenticate } = require('lightning-agent');
|
|
68
87
|
|
|
69
|
-
|
|
88
|
+
// Sign a challenge manually
|
|
89
|
+
const { sig, key } = signAuth(k1, myPrivateKeyHex);
|
|
90
|
+
// → Send sig + key back to server
|
|
70
91
|
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
description: 'API call fee',
|
|
75
|
-
expiry: 3600, // optional, seconds
|
|
76
|
-
timeoutMs: 15000 // optional
|
|
77
|
-
});
|
|
92
|
+
// Or complete the full flow automatically
|
|
93
|
+
const result = await authenticate('lnurl1...', myPrivateKeyHex);
|
|
94
|
+
if (result.success) console.log('Logged in as', result.pubkey);
|
|
78
95
|
```
|
|
79
96
|
|
|
80
|
-
###
|
|
97
|
+
### API
|
|
81
98
|
|
|
82
|
-
|
|
99
|
+
#### `createAuthServer(opts?)`
|
|
100
|
+
- `opts.challengeTtlMs` — Challenge validity in ms (default 300000)
|
|
101
|
+
- `opts.callbackUrl` — Full URL for LNURL generation
|
|
83
102
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
103
|
+
Returns: `{ createChallenge(), verify(k1, sig, key), middleware(onAuth), activeChallenges }`
|
|
104
|
+
|
|
105
|
+
#### `signAuth(k1, privateKey)`
|
|
106
|
+
Sign a challenge with a secp256k1 private key. Returns `{ sig, key }` (DER signature + compressed pubkey).
|
|
87
107
|
|
|
88
|
-
|
|
108
|
+
#### `authenticate(lnurlOrUrl, privateKey)`
|
|
109
|
+
Complete an LNURL-auth flow: fetch challenge → sign → submit. Returns `{ success, pubkey, error? }`.
|
|
89
110
|
|
|
90
|
-
|
|
111
|
+
---
|
|
91
112
|
|
|
92
|
-
|
|
113
|
+
## Escrow
|
|
114
|
+
|
|
115
|
+
Hold funds until work is verified. Client pays into escrow → worker delivers → escrow releases payment. If the worker doesn't deliver, funds are refunded.
|
|
93
116
|
|
|
94
117
|
```javascript
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
118
|
+
const { createWallet, createEscrowManager } = require('lightning-agent');
|
|
119
|
+
|
|
120
|
+
const escrowWallet = createWallet('nostr+walletconnect://...');
|
|
121
|
+
const mgr = createEscrowManager(escrowWallet, {
|
|
122
|
+
onStateChange: (id, from, to) => console.log(`${id}: ${from} → ${to}`)
|
|
99
123
|
});
|
|
100
|
-
// { preimage, paymentHash, invoice, amountSats }
|
|
101
|
-
```
|
|
102
124
|
|
|
103
|
-
|
|
125
|
+
// 1. Create escrow
|
|
126
|
+
const escrow = await mgr.create({
|
|
127
|
+
amountSats: 500,
|
|
128
|
+
workerAddress: 'worker@getalby.com',
|
|
129
|
+
description: 'Translate 200 words EN→ES',
|
|
130
|
+
deadlineMs: 3600000 // 1 hour
|
|
131
|
+
});
|
|
132
|
+
console.log('Client should pay:', escrow.invoice);
|
|
104
133
|
|
|
105
|
-
|
|
134
|
+
// 2. Confirm funding (waits for payment)
|
|
135
|
+
await mgr.fund(escrow.id);
|
|
106
136
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
timeoutMs: 60000, // total wait (default 60s)
|
|
110
|
-
pollIntervalMs: 2000 // poll frequency (default 2s)
|
|
111
|
-
});
|
|
112
|
-
```
|
|
137
|
+
// 3. Worker delivers proof
|
|
138
|
+
mgr.deliver(escrow.id, { url: 'https://example.com/result', hash: 'sha256...' });
|
|
113
139
|
|
|
114
|
-
|
|
140
|
+
// 4a. Release to worker
|
|
141
|
+
await mgr.release(escrow.id);
|
|
115
142
|
|
|
116
|
-
|
|
143
|
+
// 4b. Or refund to client
|
|
144
|
+
await mgr.refund(escrow.id, 'client@getalby.com', 'Worker no-show');
|
|
117
145
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
// { amountSats: 5000, network: 'mainnet', description: null, paymentHash: null }
|
|
146
|
+
// 4c. Or dispute
|
|
147
|
+
mgr.dispute(escrow.id, 'Quality insufficient', 'client');
|
|
121
148
|
```
|
|
122
149
|
|
|
123
|
-
###
|
|
150
|
+
### Escrow states
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
CREATED → FUNDED → DELIVERED → RELEASED
|
|
154
|
+
→ REFUNDED
|
|
155
|
+
→ EXPIRED (auto, on deadline)
|
|
156
|
+
→ DISPUTED
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### API
|
|
160
|
+
|
|
161
|
+
#### `createEscrowManager(wallet, opts?)`
|
|
162
|
+
- `wallet` — NWC wallet that holds escrowed funds
|
|
163
|
+
- `opts.onStateChange(id, oldState, newState, escrow)` — State change callback
|
|
164
|
+
- `opts.defaultDeadlineMs` — Default deadline (default 3600000)
|
|
165
|
+
|
|
166
|
+
Returns: `{ create(config), fund(id, opts?), deliver(id, proof), release(id), refund(id, address, reason?), dispute(id, reason, raisedBy), get(id), list(state?), close() }`
|
|
124
167
|
|
|
125
|
-
|
|
168
|
+
#### `EscrowState`
|
|
169
|
+
Enum: `CREATED`, `FUNDED`, `DELIVERED`, `RELEASED`, `REFUNDED`, `EXPIRED`, `DISPUTED`
|
|
126
170
|
|
|
127
|
-
|
|
171
|
+
---
|
|
128
172
|
|
|
129
|
-
|
|
173
|
+
## Streaming Payments
|
|
174
|
+
|
|
175
|
+
Pay-per-token micropayments. Lightning is the only payment system that can handle fractions-of-a-cent per word.
|
|
176
|
+
|
|
177
|
+
### Provider (server)
|
|
130
178
|
|
|
131
179
|
```javascript
|
|
132
|
-
const {
|
|
133
|
-
const
|
|
134
|
-
|
|
180
|
+
const { createWallet, createStreamProvider } = require('lightning-agent');
|
|
181
|
+
const http = require('http');
|
|
182
|
+
|
|
183
|
+
const wallet = createWallet('nostr+walletconnect://...');
|
|
184
|
+
const provider = createStreamProvider(wallet, {
|
|
185
|
+
satsPerBatch: 2, // charge 2 sats per batch
|
|
186
|
+
tokensPerBatch: 100, // 100 tokens per batch
|
|
187
|
+
maxBatches: 50 // cap at 50 batches (100 sats max)
|
|
188
|
+
});
|
|
135
189
|
|
|
136
|
-
|
|
190
|
+
http.createServer(async (req, res) => {
|
|
191
|
+
await provider.handleRequest(req, res, async function* () {
|
|
192
|
+
// Your generator yields tokens/strings
|
|
193
|
+
for (const word of myTextGenerator(req)) {
|
|
194
|
+
yield word + ' ';
|
|
195
|
+
}
|
|
196
|
+
}, { firstBatchFree: true });
|
|
197
|
+
}).listen(8080);
|
|
198
|
+
```
|
|
137
199
|
|
|
138
|
-
|
|
200
|
+
### Client (consumer)
|
|
139
201
|
|
|
140
202
|
```javascript
|
|
141
|
-
const {
|
|
142
|
-
|
|
143
|
-
|
|
203
|
+
const { createWallet, createStreamClient } = require('lightning-agent');
|
|
204
|
+
|
|
205
|
+
const wallet = createWallet('nostr+walletconnect://...');
|
|
206
|
+
const client = createStreamClient(wallet, { maxSats: 200 });
|
|
207
|
+
|
|
208
|
+
for await (const text of client.stream('https://api.example.com/generate', {
|
|
209
|
+
body: { prompt: 'Explain Lightning Network in 500 words' },
|
|
210
|
+
maxSats: 100 // budget for this stream
|
|
211
|
+
})) {
|
|
212
|
+
process.stdout.write(text);
|
|
213
|
+
}
|
|
144
214
|
```
|
|
145
215
|
|
|
146
|
-
###
|
|
216
|
+
### SSE Protocol
|
|
147
217
|
|
|
148
|
-
|
|
218
|
+
The provider uses Server-Sent Events:
|
|
149
219
|
|
|
150
|
-
```javascript
|
|
151
|
-
const { parseNwcUrl } = require('lightning-agent');
|
|
152
|
-
const { walletPubkey, relay, secret } = parseNwcUrl('nostr+walletconnect://...');
|
|
153
220
|
```
|
|
221
|
+
event: session data: { "sessionId": "abc..." }
|
|
222
|
+
event: content data: { "tokens": "Hello world...", "batchIndex": 1 }
|
|
223
|
+
event: invoice data: { "invoice": "lnbc...", "sats": 2, "batchIndex": 2 }
|
|
224
|
+
event: content data: { "tokens": "more text...", "batchIndex": 2 }
|
|
225
|
+
event: done data: { "totalBatches": 5, "totalSats": 8, "totalTokens": 500 }
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Client proves payment by POSTing `{ sessionId, preimage }` to the same URL.
|
|
229
|
+
|
|
230
|
+
### API
|
|
231
|
+
|
|
232
|
+
#### `createStreamProvider(wallet, opts?)`
|
|
233
|
+
- `opts.satsPerBatch` — Sats per batch (default 1)
|
|
234
|
+
- `opts.tokensPerBatch` — Tokens per batch (default 50)
|
|
235
|
+
- `opts.maxBatches` — Max batches per stream (default 100)
|
|
236
|
+
- `opts.paymentTimeoutMs` — Payment wait timeout (default 30000)
|
|
237
|
+
|
|
238
|
+
Returns: `{ handleRequest(req, res, generator, opts?), activeSessions }`
|
|
239
|
+
|
|
240
|
+
#### `createStreamClient(wallet, opts?)`
|
|
241
|
+
- `opts.maxSats` — Budget cap (default 1000)
|
|
242
|
+
- `opts.autoPay` — Auto-pay invoices (default true)
|
|
243
|
+
|
|
244
|
+
Returns: `{ stream(url, opts?), budget }`
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Wallet API Reference
|
|
249
|
+
|
|
250
|
+
### `createWallet(nwcUrl?)`
|
|
251
|
+
Create a wallet instance. Pass NWC URL directly or set `NWC_URL` env var.
|
|
252
|
+
|
|
253
|
+
### `wallet.getBalance(opts?)` → `{ balanceSats, balanceMsats }`
|
|
254
|
+
### `wallet.createInvoice(opts)` → `{ invoice, paymentHash, amountSats }`
|
|
255
|
+
### `wallet.payInvoice(invoice, opts?)` → `{ preimage, paymentHash }`
|
|
256
|
+
### `wallet.payAddress(address, opts)` → `{ preimage, paymentHash, invoice, amountSats }`
|
|
257
|
+
### `wallet.waitForPayment(hash, opts?)` → `{ paid, preimage, settledAt }`
|
|
258
|
+
### `wallet.decodeInvoice(invoice)` → `{ amountSats, network }`
|
|
259
|
+
### `wallet.close()`
|
|
260
|
+
|
|
261
|
+
### Standalone helpers
|
|
262
|
+
- `resolveLightningAddress(address, amountSats, comment?)` — Resolve without paying
|
|
263
|
+
- `decodeBolt11(invoice)` — Offline bolt11 decoder
|
|
264
|
+
- `parseNwcUrl(url)` — Parse NWC URL into components
|
|
154
265
|
|
|
155
266
|
## CLI
|
|
156
267
|
|
|
157
268
|
```bash
|
|
158
|
-
# Set your NWC URL
|
|
159
269
|
export NWC_URL="nostr+walletconnect://..."
|
|
160
270
|
|
|
161
|
-
# Check balance
|
|
162
271
|
lightning-agent balance
|
|
163
|
-
|
|
164
|
-
# Create an invoice for 50 sats
|
|
165
|
-
lightning-agent invoice 50 "Text generation query"
|
|
166
|
-
|
|
167
|
-
# Pay an invoice
|
|
272
|
+
lightning-agent invoice 50 "API call fee"
|
|
168
273
|
lightning-agent pay lnbc50u1p...
|
|
169
|
-
|
|
170
|
-
# Decode an invoice (offline)
|
|
171
274
|
lightning-agent decode lnbc50u1p...
|
|
172
|
-
|
|
173
|
-
# Wait for a payment
|
|
174
275
|
lightning-agent wait <payment_hash> [timeout_ms]
|
|
175
276
|
```
|
|
176
277
|
|
|
@@ -178,31 +279,20 @@ lightning-agent wait <payment_hash> [timeout_ms]
|
|
|
178
279
|
|
|
179
280
|
You need a Nostr Wallet Connect URL from a compatible wallet:
|
|
180
281
|
|
|
181
|
-
- **[Alby Hub](https://albyhub.com)** — Self-hosted Lightning node with NWC. Recommended
|
|
282
|
+
- **[Alby Hub](https://albyhub.com)** — Self-hosted Lightning node with NWC. Recommended.
|
|
182
283
|
- **[Mutiny Wallet](https://mutinywallet.com)** — Mobile-first with NWC support.
|
|
183
284
|
- **[Coinos](https://coinos.io)** — Web wallet with NWC.
|
|
184
285
|
|
|
185
|
-
The URL looks like: `nostr+walletconnect://<wallet_pubkey>?relay=wss://...&secret=<hex>`
|
|
186
|
-
|
|
187
|
-
## How It Works
|
|
188
|
-
|
|
189
|
-
lightning-agent uses the [NWC protocol (NIP-47)](https://github.com/nostr-protocol/nips/blob/master/47.md):
|
|
190
|
-
|
|
191
|
-
1. Your agent signs NWC requests (kind 23194) with the secret from the NWC URL
|
|
192
|
-
2. Requests are encrypted with NIP-04 and sent to the wallet's relay
|
|
193
|
-
3. The wallet service processes the request and returns an encrypted response (kind 23195)
|
|
194
|
-
4. All communication happens over Nostr relays — no direct connection to the wallet needed
|
|
195
|
-
|
|
196
286
|
## Design Philosophy
|
|
197
287
|
|
|
198
|
-
|
|
288
|
+
Built for AI agents, not humans:
|
|
199
289
|
|
|
200
290
|
- **Minimal deps** — just `nostr-tools` and `ws`
|
|
201
|
-
- **No UI** — pure code,
|
|
202
|
-
- **
|
|
291
|
+
- **No UI** — pure code, any Node.js environment
|
|
292
|
+
- **Fresh connections** — new relay connection per request for reliability
|
|
203
293
|
- **Timeouts everywhere** — agents can't afford to hang
|
|
204
|
-
- **
|
|
294
|
+
- **Composable** — auth + escrow + streaming + payments work together
|
|
205
295
|
|
|
206
296
|
## License
|
|
207
297
|
|
|
208
|
-
MIT
|
|
298
|
+
MIT — Built by [Jeletor](https://jeletor.com)
|