satonomous 0.1.0 → 0.2.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 +125 -243
- package/dist/index.cjs +92 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +102 -1
- package/dist/index.d.ts +102 -1
- package/dist/index.js +92 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,303 +1,185 @@
|
|
|
1
|
-
#
|
|
1
|
+
# satonomous
|
|
2
2
|
|
|
3
|
-
TypeScript SDK for
|
|
3
|
+
TypeScript SDK for autonomous AI agents to earn and spend sats via Lightning escrow contracts.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## The Problem: AI Agents Can't Pay
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
AI agents don't have Lightning wallets. When an agent needs sats to fund a contract, **it must ask a human to pay**. This SDK makes that explicit:
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
npm install l402-agent
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Quick Example
|
|
9
|
+
```typescript
|
|
10
|
+
import { L402Agent } from 'satonomous';
|
|
16
11
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const agent = new L402Agent({ apiKey });
|
|
30
|
-
|
|
31
|
-
// 3. Check balance
|
|
32
|
-
const balance = await agent.getBalance();
|
|
33
|
-
console.log('Balance:', balance.balance_sats, 'sats');
|
|
34
|
-
|
|
35
|
-
// 4. Create a deposit invoice (to add sats)
|
|
36
|
-
const invoice = await agent.createDeposit(10000);
|
|
37
|
-
console.log('Pay this invoice:', invoice.invoice);
|
|
38
|
-
|
|
39
|
-
// 5. Create an offer (publish a service for sale)
|
|
40
|
-
const offer = await agent.createOffer({
|
|
41
|
-
title: 'Data Analysis',
|
|
42
|
-
description: 'I will analyze your dataset',
|
|
43
|
-
price_sats: 1000,
|
|
44
|
-
service_type: 'analysis',
|
|
45
|
-
sla_minutes: 60,
|
|
46
|
-
dispute_window_minutes: 1440,
|
|
12
|
+
const agent = new L402Agent({
|
|
13
|
+
apiKey: process.env.L402_API_KEY!,
|
|
14
|
+
|
|
15
|
+
// 👇 This is how your agent asks for money
|
|
16
|
+
onPaymentNeeded: async (invoice) => {
|
|
17
|
+
// Send to Slack, Discord, Signal, email — whatever reaches the human
|
|
18
|
+
await sendMessage(channel, [
|
|
19
|
+
`⚡ Agent needs ${invoice.amount_sats} sats`,
|
|
20
|
+
`Reason: ${invoice.message}`,
|
|
21
|
+
`Invoice: ${invoice.invoice}`,
|
|
22
|
+
].join('\n'));
|
|
23
|
+
},
|
|
47
24
|
});
|
|
48
|
-
console.log('Offer created:', offer.id);
|
|
49
|
-
|
|
50
|
-
// 6. Accept another agent's offer (become buyer)
|
|
51
|
-
const contract = await agent.acceptOffer(someOfferId);
|
|
52
|
-
console.log('Contract created:', contract.id);
|
|
53
|
-
|
|
54
|
-
// 7. Fund the contract from your balance
|
|
55
|
-
const funded = await agent.fundContract(contract.id);
|
|
56
|
-
console.log('Contract funded:', funded.contract.status);
|
|
57
|
-
|
|
58
|
-
// 8. Submit delivery proof (as seller)
|
|
59
|
-
const delivered = await agent.submitDelivery(
|
|
60
|
-
contractId,
|
|
61
|
-
'https://example.com/results.json',
|
|
62
|
-
{ analysis: 'results here' }
|
|
63
|
-
);
|
|
64
|
-
console.log('Delivery submitted:', delivered.status);
|
|
65
|
-
|
|
66
|
-
// 9. Confirm delivery to release funds (as buyer)
|
|
67
|
-
const confirmed = await agent.confirmDelivery(contractId);
|
|
68
|
-
console.log('Funds released to seller:', confirmed.status);
|
|
69
|
-
|
|
70
|
-
// 10. View ledger
|
|
71
|
-
const ledger = await agent.getLedger(50, 0);
|
|
72
|
-
console.log('Current balance:', ledger.balance_sats);
|
|
73
|
-
console.log('Recent entries:', ledger.entries);
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
## API Reference
|
|
77
25
|
|
|
78
|
-
|
|
26
|
+
// Agent ensures it has funds — notifies human if balance is low
|
|
27
|
+
await agent.ensureBalance(500, 'Need funds to accept a code-review contract');
|
|
79
28
|
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
apiUrl: 'https://...', // Optional: default is l402gw.nosaltres2.info
|
|
84
|
-
});
|
|
29
|
+
// Now the agent can work autonomously
|
|
30
|
+
const contract = await agent.acceptOffer(offerId);
|
|
31
|
+
await agent.fundContract(contract.id);
|
|
85
32
|
```
|
|
86
33
|
|
|
87
|
-
###
|
|
88
|
-
|
|
89
|
-
#### `L402Agent.register(options)`
|
|
34
|
+
### Or: Bring Your Own Wallet
|
|
90
35
|
|
|
91
|
-
|
|
36
|
+
If the agent has access to a Lightning wallet (LND, CLN, LNbits), register with `wallet_type: 'external'` and handle payments directly:
|
|
92
37
|
|
|
93
|
-
```
|
|
38
|
+
```typescript
|
|
94
39
|
const reg = await L402Agent.register({
|
|
95
|
-
name: '
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
lightning_address: 'name@..', // Optional: your Lightning address
|
|
99
|
-
apiUrl: 'https://...', // Optional: override default URL
|
|
40
|
+
name: 'Self-funded Bot',
|
|
41
|
+
wallet_type: 'external',
|
|
42
|
+
lightning_address: 'bot@getalby.com',
|
|
100
43
|
});
|
|
101
|
-
// Returns: { tenant_id, api_key, name, description, wallet_type, lightning_address, balance_sats }
|
|
102
44
|
```
|
|
103
45
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
#### Wallet
|
|
107
|
-
|
|
108
|
-
**`getBalance(): Promise<BalanceInfo>`**
|
|
109
|
-
|
|
110
|
-
Get your current balance.
|
|
46
|
+
## Install
|
|
111
47
|
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
// { balance_sats: 50000 }
|
|
48
|
+
```bash
|
|
49
|
+
npm install satonomous
|
|
115
50
|
```
|
|
116
51
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
Create a Lightning invoice to add sats to your balance.
|
|
52
|
+
## Quick Start
|
|
120
53
|
|
|
121
|
-
|
|
122
|
-
const invoice = await agent.createDeposit(10000);
|
|
123
|
-
// { payment_hash: '...', invoice: 'lnbc...', amount_sats: 10000 }
|
|
124
|
-
```
|
|
54
|
+
### 1. Register an agent
|
|
125
55
|
|
|
126
|
-
|
|
56
|
+
```typescript
|
|
57
|
+
import { L402Agent } from 'satonomous';
|
|
127
58
|
|
|
128
|
-
|
|
59
|
+
// No auth needed — creates a new agent account
|
|
60
|
+
const reg = await L402Agent.register({
|
|
61
|
+
name: 'MyAgent',
|
|
62
|
+
description: 'Code review bot',
|
|
63
|
+
});
|
|
129
64
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
//
|
|
65
|
+
console.log(reg.api_key); // l402_sk_...
|
|
66
|
+
console.log(reg.tenant_id); // uuid
|
|
67
|
+
// Store this API key securely!
|
|
133
68
|
```
|
|
134
69
|
|
|
135
|
-
|
|
70
|
+
### 2. Fund the agent (human-in-the-loop)
|
|
136
71
|
|
|
137
|
-
|
|
72
|
+
```typescript
|
|
73
|
+
const agent = new L402Agent({
|
|
74
|
+
apiKey: reg.api_key,
|
|
75
|
+
onPaymentNeeded: async (invoice) => {
|
|
76
|
+
// YOUR notification logic — Slack, Discord, email, etc.
|
|
77
|
+
console.log(`⚡ Please pay: ${invoice.invoice}`);
|
|
78
|
+
},
|
|
79
|
+
paymentTimeoutMs: 300_000, // wait up to 5 min for human to pay
|
|
80
|
+
});
|
|
138
81
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// { lnurl: 'lnurl1...', qr_data: 'data:image/png;...', k1: '...', amount_sats: 5000, balance_sats: 45000 }
|
|
82
|
+
// This will: create invoice → notify human → poll until paid
|
|
83
|
+
await agent.deposit(1000, 'Initial funding for agent operations');
|
|
142
84
|
```
|
|
143
85
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
**`createOffer(params: CreateOfferParams): Promise<Offer>`**
|
|
86
|
+
### 3. Full contract lifecycle
|
|
147
87
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const offer = await agent.createOffer({
|
|
88
|
+
```typescript
|
|
89
|
+
// Seller: create an offer
|
|
90
|
+
const offer = await seller.createOffer({
|
|
152
91
|
title: 'Code Review',
|
|
153
|
-
description: '
|
|
154
|
-
price_sats:
|
|
155
|
-
service_type: '
|
|
156
|
-
sla_minutes:
|
|
157
|
-
dispute_window_minutes: 2880, // Dispute period after delivery
|
|
92
|
+
description: 'Review your PR within 1 hour',
|
|
93
|
+
price_sats: 500,
|
|
94
|
+
service_type: 'code_review',
|
|
95
|
+
sla_minutes: 60,
|
|
158
96
|
});
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
**`listOffers(): Promise<Offer[]>`**
|
|
162
|
-
|
|
163
|
-
List all offers you've created.
|
|
164
|
-
|
|
165
|
-
```ts
|
|
166
|
-
const offers = await agent.listOffers();
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
**`getOffer(offerId: string): Promise<Offer>`**
|
|
170
|
-
|
|
171
|
-
Get details of a specific offer (by any agent).
|
|
172
|
-
|
|
173
|
-
```ts
|
|
174
|
-
const offer = await agent.getOffer(offerId);
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
**`updateOffer(offerId: string, active: boolean): Promise<Offer>`**
|
|
178
|
-
|
|
179
|
-
Activate or deactivate an offer.
|
|
180
|
-
|
|
181
|
-
```ts
|
|
182
|
-
await agent.updateOffer(offerId, false); // Deactivate
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
#### Contracts
|
|
186
|
-
|
|
187
|
-
**`acceptOffer(offerId: string): Promise<Contract>`**
|
|
188
|
-
|
|
189
|
-
Accept another agent's offer to create a contract.
|
|
190
97
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
98
|
+
// Buyer: accept and fund
|
|
99
|
+
await buyer.ensureBalance(offer.price_sats + 10, 'Funding for code review');
|
|
100
|
+
const contract = await buyer.acceptOffer(offer.id);
|
|
101
|
+
const funded = await buyer.fundContract(contract.id);
|
|
195
102
|
|
|
196
|
-
|
|
103
|
+
// Seller: deliver
|
|
104
|
+
await seller.submitDelivery(contract.id, 'https://github.com/pr/123#review');
|
|
197
105
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
```ts
|
|
201
|
-
const result = await agent.fundContract(contractId);
|
|
202
|
-
// { success: true, contract: {...}, message: '...' }
|
|
106
|
+
// Buyer: confirm → funds released to seller
|
|
107
|
+
await buyer.confirmDelivery(contract.id);
|
|
203
108
|
```
|
|
204
109
|
|
|
205
|
-
|
|
110
|
+
## API Reference
|
|
206
111
|
|
|
207
|
-
|
|
112
|
+
### Constructor
|
|
208
113
|
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
|
|
114
|
+
```typescript
|
|
115
|
+
new L402Agent(options: {
|
|
116
|
+
apiKey: string; // Required: your L402 API key
|
|
117
|
+
apiUrl?: string; // Default: https://l402gw.nosaltres2.info
|
|
118
|
+
onPaymentNeeded?: (invoice: DepositInvoice) => Promise<void> | void;
|
|
119
|
+
paymentTimeoutMs?: number; // Default: 300_000 (5 min)
|
|
120
|
+
paymentPollIntervalMs?: number; // Default: 5_000 (5 sec)
|
|
121
|
+
})
|
|
212
122
|
```
|
|
213
123
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
Get full details of a contract.
|
|
217
|
-
|
|
218
|
-
```ts
|
|
219
|
-
const contract = await agent.getContract(contractId);
|
|
220
|
-
```
|
|
124
|
+
### Static Methods
|
|
221
125
|
|
|
222
|
-
|
|
126
|
+
| Method | Description |
|
|
127
|
+
|--------|-------------|
|
|
128
|
+
| `L402Agent.register(opts)` | Register a new agent (no auth needed) |
|
|
223
129
|
|
|
224
|
-
|
|
130
|
+
### Wallet
|
|
225
131
|
|
|
226
|
-
|
|
132
|
+
| Method | Description |
|
|
133
|
+
|--------|-------------|
|
|
134
|
+
| `getBalance()` | Check current sats balance |
|
|
135
|
+
| `deposit(amount, reason?)` | **High-level**: create invoice → notify human → wait for payment |
|
|
136
|
+
| `ensureBalance(min, reason?)` | Deposit only if balance is below minimum |
|
|
137
|
+
| `createDeposit(amount)` | Low-level: just create the invoice |
|
|
138
|
+
| `checkDeposit(hash)` | Check if an invoice was paid |
|
|
139
|
+
| `withdraw(amount?)` | Create LNURL-withdraw link |
|
|
227
140
|
|
|
228
|
-
|
|
229
|
-
const contract = await agent.submitDelivery(
|
|
230
|
-
contractId,
|
|
231
|
-
'https://example.com/delivery.json',
|
|
232
|
-
{ analysis: 'results', timestamp: Date.now() }
|
|
233
|
-
);
|
|
234
|
-
```
|
|
141
|
+
### Offers
|
|
235
142
|
|
|
236
|
-
|
|
143
|
+
| Method | Description |
|
|
144
|
+
|--------|-------------|
|
|
145
|
+
| `createOffer(params)` | Publish a service offer |
|
|
146
|
+
| `listOffers()` | Browse available offers |
|
|
147
|
+
| `getOffer(id)` | Get offer details |
|
|
148
|
+
| `updateOffer(id, active)` | Activate/deactivate offer |
|
|
237
149
|
|
|
238
|
-
|
|
150
|
+
### Contracts
|
|
239
151
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
152
|
+
| Method | Description |
|
|
153
|
+
|--------|-------------|
|
|
154
|
+
| `acceptOffer(offerId)` | Accept offer → create contract |
|
|
155
|
+
| `fundContract(contractId)` | Fund contract from balance |
|
|
156
|
+
| `listContracts(filters?)` | List your contracts |
|
|
157
|
+
| `getContract(contractId)` | Get contract details |
|
|
244
158
|
|
|
245
|
-
|
|
159
|
+
### Delivery
|
|
246
160
|
|
|
247
|
-
|
|
161
|
+
| Method | Description |
|
|
162
|
+
|--------|-------------|
|
|
163
|
+
| `submitDelivery(contractId, proofUrl, proofData?)` | Submit proof of delivery |
|
|
164
|
+
| `confirmDelivery(contractId)` | Confirm delivery → release funds |
|
|
165
|
+
| `disputeDelivery(contractId, reason, evidenceUrl?)` | Dispute a delivery |
|
|
248
166
|
|
|
249
|
-
|
|
250
|
-
const contract = await agent.disputeDelivery(
|
|
251
|
-
contractId,
|
|
252
|
-
'Delivery does not meet requirements',
|
|
253
|
-
'https://example.com/evidence.json'
|
|
254
|
-
);
|
|
255
|
-
// status: 'disputed'
|
|
256
|
-
```
|
|
167
|
+
### Ledger
|
|
257
168
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
View your transaction ledger.
|
|
263
|
-
|
|
264
|
-
```ts
|
|
265
|
-
const { balance_sats, entries } = await agent.getLedger(50, 0);
|
|
266
|
-
// entries: [
|
|
267
|
-
// {
|
|
268
|
-
// id: 1,
|
|
269
|
-
// type: 'credit',
|
|
270
|
-
// amount_sats: 10000,
|
|
271
|
-
// source: 'deposit',
|
|
272
|
-
// reference_id: 'payment_hash',
|
|
273
|
-
// balance_after: 50000,
|
|
274
|
-
// created_at: '2026-04-03T...',
|
|
275
|
-
// },
|
|
276
|
-
// ...
|
|
277
|
-
// ]
|
|
278
|
-
```
|
|
169
|
+
| Method | Description |
|
|
170
|
+
|--------|-------------|
|
|
171
|
+
| `getLedger(limit?, offset?)` | View transaction history |
|
|
279
172
|
|
|
280
173
|
## Environment Variables
|
|
281
174
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
175
|
+
| Variable | Description | Default |
|
|
176
|
+
|----------|-------------|---------|
|
|
177
|
+
| `L402_API_KEY` | Your agent's API key | — |
|
|
178
|
+
| `L402_API_URL` | Gateway URL | `https://l402gw.nosaltres2.info` |
|
|
286
179
|
|
|
287
|
-
|
|
180
|
+
## Why "Satonomous"?
|
|
288
181
|
|
|
289
|
-
|
|
290
|
-
import { L402Error } from 'l402-agent';
|
|
291
|
-
|
|
292
|
-
try {
|
|
293
|
-
await agent.fundContract(contractId);
|
|
294
|
-
} catch (err) {
|
|
295
|
-
if (err instanceof L402Error) {
|
|
296
|
-
console.error(`HTTP ${err.status}: ${err.message}`);
|
|
297
|
-
console.error(`Code: ${err.code}`);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
```
|
|
182
|
+
**Sat** (satoshi) + **autonomous**. AI agents that earn and spend sats autonomously — with human approval for the money part, because that's how trust works.
|
|
301
183
|
|
|
302
184
|
## License
|
|
303
185
|
|
package/dist/index.cjs
CHANGED
|
@@ -41,6 +41,9 @@ var L402Agent = class {
|
|
|
41
41
|
}
|
|
42
42
|
this.apiKey = options.apiKey;
|
|
43
43
|
this.apiUrl = options.apiUrl ?? "https://l402gw.nosaltres2.info";
|
|
44
|
+
this.onPaymentNeeded = options.onPaymentNeeded;
|
|
45
|
+
this.paymentTimeoutMs = options.paymentTimeoutMs ?? 3e5;
|
|
46
|
+
this.paymentPollIntervalMs = options.paymentPollIntervalMs ?? 5e3;
|
|
44
47
|
}
|
|
45
48
|
async request(method, path, body) {
|
|
46
49
|
const url = `${this.apiUrl}${path}`;
|
|
@@ -71,7 +74,7 @@ var L402Agent = class {
|
|
|
71
74
|
// Static: register a new agent (no auth needed)
|
|
72
75
|
static async register(options) {
|
|
73
76
|
const apiUrl = options.apiUrl ?? "https://l402gw.nosaltres2.info";
|
|
74
|
-
const url = `${apiUrl}/api/v1/register`;
|
|
77
|
+
const url = `${apiUrl}/api/v1/agents/register`;
|
|
75
78
|
const res = await fetch(url, {
|
|
76
79
|
method: "POST",
|
|
77
80
|
headers: { "Content-Type": "application/json" },
|
|
@@ -97,12 +100,100 @@ var L402Agent = class {
|
|
|
97
100
|
async getBalance() {
|
|
98
101
|
return this.request("GET", "/api/v1/wallet/balance");
|
|
99
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Low-level: create a deposit invoice. Returns the invoice for manual handling.
|
|
105
|
+
* Most agents should use `deposit()` instead, which notifies the human and waits.
|
|
106
|
+
*/
|
|
100
107
|
async createDeposit(amount_sats) {
|
|
101
108
|
return this.request("POST", "/api/v1/wallet/deposit", { amount_sats });
|
|
102
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Check if a deposit invoice has been paid.
|
|
112
|
+
*/
|
|
103
113
|
async checkDeposit(paymentHash) {
|
|
104
114
|
return this.request("GET", `/api/v1/wallet/deposit/${paymentHash}`);
|
|
105
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* High-level deposit: creates invoice, notifies human, waits for payment.
|
|
118
|
+
*
|
|
119
|
+
* WHY THIS EXISTS: AI agents don't have Lightning wallets. They can't pay
|
|
120
|
+
* invoices. When an agent needs sats, it must ask a human to pay.
|
|
121
|
+
*
|
|
122
|
+
* This method:
|
|
123
|
+
* 1. Creates a Lightning invoice for the requested amount
|
|
124
|
+
* 2. Calls `onPaymentNeeded` so you can notify the human (chat, email, UI)
|
|
125
|
+
* 3. Polls until the invoice is paid or times out
|
|
126
|
+
* 4. Returns the confirmed deposit status
|
|
127
|
+
*
|
|
128
|
+
* If `onPaymentNeeded` is not configured, throws with the invoice so the
|
|
129
|
+
* caller can handle notification manually.
|
|
130
|
+
*
|
|
131
|
+
* @param amount_sats - Amount to deposit
|
|
132
|
+
* @param reason - Human-readable reason (shown in the notification)
|
|
133
|
+
* @returns Confirmed deposit status
|
|
134
|
+
* @throws {L402Error} if payment times out or fails
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* const agent = new L402Agent({
|
|
138
|
+
* apiKey: 'sk_...',
|
|
139
|
+
* onPaymentNeeded: async (invoice) => {
|
|
140
|
+
* // Send to Slack, Discord, Signal, email, etc.
|
|
141
|
+
* await notify(`Pay ${invoice.amount_sats} sats: ${invoice.invoice}`);
|
|
142
|
+
* },
|
|
143
|
+
* });
|
|
144
|
+
*
|
|
145
|
+
* // Agent requests funding and waits
|
|
146
|
+
* const deposit = await agent.deposit(1000, 'Need funds to accept code-review offer');
|
|
147
|
+
* console.log(`Funded! Balance: ${deposit.amount_sats} sats`);
|
|
148
|
+
*/
|
|
149
|
+
async deposit(amount_sats, reason) {
|
|
150
|
+
const invoice = await this.createDeposit(amount_sats);
|
|
151
|
+
if (!this.onPaymentNeeded) {
|
|
152
|
+
throw new L402Error(
|
|
153
|
+
`Payment needed: ${amount_sats} sats. Invoice: ${invoice.invoice}. No onPaymentNeeded callback configured \u2014 pay this invoice manually and call checkDeposit('${invoice.payment_hash}') to confirm.`,
|
|
154
|
+
402,
|
|
155
|
+
"PAYMENT_NEEDED"
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
const enrichedInvoice = {
|
|
159
|
+
...invoice,
|
|
160
|
+
message: reason ? `\u26A1 Agent needs ${amount_sats} sats: ${reason}` : `\u26A1 Agent needs ${amount_sats} sats deposited`
|
|
161
|
+
};
|
|
162
|
+
await this.onPaymentNeeded(enrichedInvoice);
|
|
163
|
+
if (this.paymentTimeoutMs === 0) {
|
|
164
|
+
return { status: "pending", amount_sats, paid_at: null };
|
|
165
|
+
}
|
|
166
|
+
const deadline = Date.now() + this.paymentTimeoutMs;
|
|
167
|
+
while (Date.now() < deadline) {
|
|
168
|
+
await new Promise((r) => setTimeout(r, this.paymentPollIntervalMs));
|
|
169
|
+
const status = await this.checkDeposit(invoice.payment_hash);
|
|
170
|
+
if (status.status === "paid") return status;
|
|
171
|
+
if (status.status === "expired") {
|
|
172
|
+
throw new L402Error("Deposit invoice expired before payment", 408, "PAYMENT_EXPIRED");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
throw new L402Error(
|
|
176
|
+
`Payment not received within ${this.paymentTimeoutMs / 1e3}s. Invoice may still be valid \u2014 check with checkDeposit('${invoice.payment_hash}')`,
|
|
177
|
+
408,
|
|
178
|
+
"PAYMENT_TIMEOUT"
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Ensure the agent has at least `minBalance` sats. If not, request a deposit.
|
|
183
|
+
* Convenience method that checks balance first, then deposits the difference.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* // Before accepting an offer, make sure you can pay
|
|
187
|
+
* await agent.ensureBalance(500, 'Need funds for code-review contract');
|
|
188
|
+
* await agent.fundContract(contractId);
|
|
189
|
+
*/
|
|
190
|
+
async ensureBalance(minBalance, reason) {
|
|
191
|
+
const current = await this.getBalance();
|
|
192
|
+
if (current.balance_sats >= minBalance) return current;
|
|
193
|
+
const needed = minBalance - current.balance_sats;
|
|
194
|
+
await this.deposit(needed, reason ?? `Need ${needed} more sats (have ${current.balance_sats}, need ${minBalance})`);
|
|
195
|
+
return this.getBalance();
|
|
196
|
+
}
|
|
106
197
|
async withdraw(amount_sats) {
|
|
107
198
|
return this.request("POST", "/api/v1/wallet/withdraw", amount_sats ? { amount_sats } : {});
|
|
108
199
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/client.ts"],"sourcesContent":["/**\n * l402-agent — SDK for the L402 Gateway API\n *\n * @example\n * ```ts\n * import { L402Agent } from 'l402-agent';\n *\n * // Register a new agent\n * const reg = await L402Agent.register({\n * name: 'MyService',\n * description: 'A helpful service',\n * });\n * console.log(reg.api_key);\n *\n * // Create client with API key\n * const agent = new L402Agent({ apiKey: reg.api_key });\n * const balance = await agent.getBalance();\n * console.log(balance);\n * ```\n *\n * @module\n */\n\nexport { L402Agent, L402Error } from './client.js';\nexport type {\n L402AgentOptions,\n BalanceInfo,\n Offer,\n CreateOfferParams,\n Contract,\n FundResult,\n DepositInvoice,\n DepositStatus,\n LedgerEntry,\n WithdrawResult,\n AgentRegistration,\n} from './types.js';\n","import type {\n L402AgentOptions,\n BalanceInfo,\n Offer,\n CreateOfferParams,\n Contract,\n FundResult,\n DepositInvoice,\n DepositStatus,\n LedgerEntry,\n WithdrawResult,\n AgentRegistration,\n} from './types.js';\n\nexport class L402Error extends Error {\n status: number;\n code?: string;\n\n constructor(message: string, status: number, code?: string) {\n super(message);\n this.name = 'L402Error';\n this.status = status;\n this.code = code;\n }\n}\n\nexport class L402Agent {\n private apiKey: string;\n private apiUrl: string;\n\n constructor(options: L402AgentOptions) {\n if (!options.apiKey) {\n throw new Error('apiKey is required');\n }\n this.apiKey = options.apiKey;\n this.apiUrl = options.apiUrl ?? 'https://l402gw.nosaltres2.info';\n }\n\n private async request<T>(method: string, path: string, body?: any): Promise<T> {\n const url = `${this.apiUrl}${path}`;\n const options: RequestInit = {\n method,\n headers: {\n 'X-L402-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n };\n\n if (body) {\n options.body = JSON.stringify(body);\n }\n\n const res = await fetch(url, options);\n\n if (!res.ok) {\n let errorMsg = `HTTP ${res.status}`;\n let errorCode: string | undefined;\n\n try {\n const data = await res.json() as { error?: string; code?: string };\n errorMsg = data.error || errorMsg;\n errorCode = data.code;\n } catch {\n // Use default error message\n }\n\n throw new L402Error(errorMsg, res.status, errorCode);\n }\n\n return res.json() as Promise<T>;\n }\n\n // Static: register a new agent (no auth needed)\n static async register(options: {\n name: string;\n description?: string;\n wallet_type?: 'custodial' | 'external';\n lightning_address?: string;\n apiUrl?: string;\n }): Promise<AgentRegistration> {\n const apiUrl = options.apiUrl ?? 'https://l402gw.nosaltres2.info';\n const url = `${apiUrl}/api/v1/register`;\n\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n name: options.name,\n description: options.description,\n wallet_type: options.wallet_type ?? 'custodial',\n lightning_address: options.lightning_address,\n }),\n });\n\n if (!res.ok) {\n let errorMsg = `HTTP ${res.status}`;\n try {\n const data = await res.json() as { error?: string };\n errorMsg = data.error || errorMsg;\n } catch {\n // Use default error message\n }\n throw new L402Error(errorMsg, res.status);\n }\n\n return res.json() as Promise<AgentRegistration>;\n }\n\n // Wallet\n async getBalance(): Promise<BalanceInfo> {\n return this.request('GET', '/api/v1/wallet/balance');\n }\n\n async createDeposit(amount_sats: number): Promise<DepositInvoice> {\n return this.request('POST', '/api/v1/wallet/deposit', { amount_sats });\n }\n\n async checkDeposit(paymentHash: string): Promise<DepositStatus> {\n return this.request('GET', `/api/v1/wallet/deposit/${paymentHash}`);\n }\n\n async withdraw(amount_sats?: number): Promise<WithdrawResult> {\n return this.request('POST', '/api/v1/wallet/withdraw', amount_sats ? { amount_sats } : {});\n }\n\n // Offers\n async createOffer(params: CreateOfferParams): Promise<Offer> {\n return this.request('POST', '/api/v1/offers', params);\n }\n\n async listOffers(): Promise<Offer[]> {\n const result = await this.request<{ offers: Offer[] }>('GET', '/api/v1/offers');\n return result.offers || [];\n }\n\n async getOffer(offerId: string): Promise<Offer> {\n return this.request('GET', `/api/v1/offers/${offerId}`);\n }\n\n async updateOffer(offerId: string, active: boolean): Promise<Offer> {\n return this.request('PATCH', `/api/v1/offers/${offerId}`, { active });\n }\n\n // Contracts\n async acceptOffer(offerId: string): Promise<Contract> {\n return this.request('POST', '/api/v1/contracts', { offer_id: offerId });\n }\n\n async fundContract(contractId: string): Promise<FundResult> {\n return this.request('POST', `/api/v1/contracts/${contractId}/fund`, {});\n }\n\n async listContracts(filters?: { role?: 'buyer' | 'seller'; status?: string }): Promise<Contract[]> {\n let path = '/api/v1/contracts';\n const params = new URLSearchParams();\n if (filters?.role) params.append('role', filters.role);\n if (filters?.status) params.append('status', filters.status);\n if (params.toString()) path += '?' + params.toString();\n const result = await this.request<{ contracts: Contract[] }>('GET', path);\n return result.contracts || [];\n }\n\n async getContract(contractId: string): Promise<Contract> {\n return this.request('GET', `/api/v1/contracts/${contractId}`);\n }\n\n // Delivery\n async submitDelivery(contractId: string, proofUrl: string, proofData?: any): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/deliver`, {\n proof_url: proofUrl,\n proof_data: proofData,\n });\n }\n\n async confirmDelivery(contractId: string): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/confirm`, {});\n }\n\n async disputeDelivery(contractId: string, reason: string, evidenceUrl?: string): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/dispute`, {\n reason,\n evidence_url: evidenceUrl,\n });\n }\n\n // Ledger\n async getLedger(limit?: number, offset?: number): Promise<{ balance_sats: number; entries: LedgerEntry[] }> {\n let path = '/api/v1/ledger';\n const params = new URLSearchParams();\n if (limit) params.append('limit', String(limit));\n if (offset) params.append('offset', String(offset));\n if (params.toString()) path += '?' + params.toString();\n return this.request('GET', path);\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts"],"sourcesContent":["/**\n * satonomous — SDK for autonomous AI agents to earn and spend sats\n *\n * AI agents can't pay Lightning invoices — they don't have wallets.\n * This SDK bridges that gap: when an agent needs funds, it notifies\n * a human (via callback) and waits for payment.\n *\n * @example\n * ```ts\n * import { L402Agent } from 'satonomous';\n *\n * const agent = new L402Agent({\n * apiKey: 'sk_...',\n * // This is the key part: how the agent asks a human for money\n * onPaymentNeeded: async (invoice) => {\n * await sendSlackMessage(`⚡ Pay ${invoice.amount_sats} sats: ${invoice.invoice}`);\n * },\n * });\n *\n * // Agent ensures it has funds before working\n * await agent.ensureBalance(500, 'Need funds for code-review contract');\n * ```\n *\n * @module\n */\n\nexport { L402Agent, L402Error } from './client.js';\nexport type {\n L402AgentOptions,\n BalanceInfo,\n Offer,\n CreateOfferParams,\n Contract,\n FundResult,\n DepositInvoice,\n DepositStatus,\n LedgerEntry,\n WithdrawResult,\n AgentRegistration,\n PaymentNeededCallback,\n} from './types.js';\n","import type {\n L402AgentOptions,\n BalanceInfo,\n Offer,\n CreateOfferParams,\n Contract,\n FundResult,\n DepositInvoice,\n DepositStatus,\n LedgerEntry,\n WithdrawResult,\n AgentRegistration,\n PaymentNeededCallback,\n} from './types.js';\n\nexport class L402Error extends Error {\n status: number;\n code?: string;\n\n constructor(message: string, status: number, code?: string) {\n super(message);\n this.name = 'L402Error';\n this.status = status;\n this.code = code;\n }\n}\n\nexport class L402Agent {\n private apiKey: string;\n private apiUrl: string;\n\n private onPaymentNeeded?: PaymentNeededCallback;\n private paymentTimeoutMs: number;\n private paymentPollIntervalMs: number;\n\n constructor(options: L402AgentOptions) {\n if (!options.apiKey) {\n throw new Error('apiKey is required');\n }\n this.apiKey = options.apiKey;\n this.apiUrl = options.apiUrl ?? 'https://l402gw.nosaltres2.info';\n this.onPaymentNeeded = options.onPaymentNeeded;\n this.paymentTimeoutMs = options.paymentTimeoutMs ?? 300_000;\n this.paymentPollIntervalMs = options.paymentPollIntervalMs ?? 5_000;\n }\n\n private async request<T>(method: string, path: string, body?: any): Promise<T> {\n const url = `${this.apiUrl}${path}`;\n const options: RequestInit = {\n method,\n headers: {\n 'X-L402-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n };\n\n if (body) {\n options.body = JSON.stringify(body);\n }\n\n const res = await fetch(url, options);\n\n if (!res.ok) {\n let errorMsg = `HTTP ${res.status}`;\n let errorCode: string | undefined;\n\n try {\n const data = await res.json() as { error?: string; code?: string };\n errorMsg = data.error || errorMsg;\n errorCode = data.code;\n } catch {\n // Use default error message\n }\n\n throw new L402Error(errorMsg, res.status, errorCode);\n }\n\n return res.json() as Promise<T>;\n }\n\n // Static: register a new agent (no auth needed)\n static async register(options: {\n name: string;\n description?: string;\n wallet_type?: 'custodial' | 'external';\n lightning_address?: string;\n apiUrl?: string;\n }): Promise<AgentRegistration> {\n const apiUrl = options.apiUrl ?? 'https://l402gw.nosaltres2.info';\n const url = `${apiUrl}/api/v1/agents/register`;\n\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n name: options.name,\n description: options.description,\n wallet_type: options.wallet_type ?? 'custodial',\n lightning_address: options.lightning_address,\n }),\n });\n\n if (!res.ok) {\n let errorMsg = `HTTP ${res.status}`;\n try {\n const data = await res.json() as { error?: string };\n errorMsg = data.error || errorMsg;\n } catch {\n // Use default error message\n }\n throw new L402Error(errorMsg, res.status);\n }\n\n return res.json() as Promise<AgentRegistration>;\n }\n\n // Wallet\n async getBalance(): Promise<BalanceInfo> {\n return this.request('GET', '/api/v1/wallet/balance');\n }\n\n /**\n * Low-level: create a deposit invoice. Returns the invoice for manual handling.\n * Most agents should use `deposit()` instead, which notifies the human and waits.\n */\n async createDeposit(amount_sats: number): Promise<DepositInvoice> {\n return this.request('POST', '/api/v1/wallet/deposit', { amount_sats });\n }\n\n /**\n * Check if a deposit invoice has been paid.\n */\n async checkDeposit(paymentHash: string): Promise<DepositStatus> {\n return this.request('GET', `/api/v1/wallet/deposit/${paymentHash}`);\n }\n\n /**\n * High-level deposit: creates invoice, notifies human, waits for payment.\n *\n * WHY THIS EXISTS: AI agents don't have Lightning wallets. They can't pay\n * invoices. When an agent needs sats, it must ask a human to pay.\n *\n * This method:\n * 1. Creates a Lightning invoice for the requested amount\n * 2. Calls `onPaymentNeeded` so you can notify the human (chat, email, UI)\n * 3. Polls until the invoice is paid or times out\n * 4. Returns the confirmed deposit status\n *\n * If `onPaymentNeeded` is not configured, throws with the invoice so the\n * caller can handle notification manually.\n *\n * @param amount_sats - Amount to deposit\n * @param reason - Human-readable reason (shown in the notification)\n * @returns Confirmed deposit status\n * @throws {L402Error} if payment times out or fails\n *\n * @example\n * const agent = new L402Agent({\n * apiKey: 'sk_...',\n * onPaymentNeeded: async (invoice) => {\n * // Send to Slack, Discord, Signal, email, etc.\n * await notify(`Pay ${invoice.amount_sats} sats: ${invoice.invoice}`);\n * },\n * });\n *\n * // Agent requests funding and waits\n * const deposit = await agent.deposit(1000, 'Need funds to accept code-review offer');\n * console.log(`Funded! Balance: ${deposit.amount_sats} sats`);\n */\n async deposit(amount_sats: number, reason?: string): Promise<DepositStatus> {\n const invoice = await this.createDeposit(amount_sats);\n\n // If no callback, throw with invoice details so caller can handle it\n if (!this.onPaymentNeeded) {\n throw new L402Error(\n `Payment needed: ${amount_sats} sats. ` +\n `Invoice: ${invoice.invoice}. ` +\n `No onPaymentNeeded callback configured — pay this invoice manually ` +\n `and call checkDeposit('${invoice.payment_hash}') to confirm.`,\n 402,\n 'PAYMENT_NEEDED'\n );\n }\n\n // Notify the human\n const enrichedInvoice: DepositInvoice = {\n ...invoice,\n message: reason\n ? `⚡ Agent needs ${amount_sats} sats: ${reason}`\n : `⚡ Agent needs ${amount_sats} sats deposited`,\n };\n await this.onPaymentNeeded(enrichedInvoice);\n\n // Poll for payment\n if (this.paymentTimeoutMs === 0) {\n return { status: 'pending', amount_sats, paid_at: null };\n }\n\n const deadline = Date.now() + this.paymentTimeoutMs;\n while (Date.now() < deadline) {\n await new Promise(r => setTimeout(r, this.paymentPollIntervalMs));\n const status = await this.checkDeposit(invoice.payment_hash);\n if (status.status === 'paid') return status;\n if (status.status === 'expired') {\n throw new L402Error('Deposit invoice expired before payment', 408, 'PAYMENT_EXPIRED');\n }\n }\n\n throw new L402Error(\n `Payment not received within ${this.paymentTimeoutMs / 1000}s. ` +\n `Invoice may still be valid — check with checkDeposit('${invoice.payment_hash}')`,\n 408,\n 'PAYMENT_TIMEOUT'\n );\n }\n\n /**\n * Ensure the agent has at least `minBalance` sats. If not, request a deposit.\n * Convenience method that checks balance first, then deposits the difference.\n *\n * @example\n * // Before accepting an offer, make sure you can pay\n * await agent.ensureBalance(500, 'Need funds for code-review contract');\n * await agent.fundContract(contractId);\n */\n async ensureBalance(minBalance: number, reason?: string): Promise<BalanceInfo> {\n const current = await this.getBalance();\n if (current.balance_sats >= minBalance) return current;\n\n const needed = minBalance - current.balance_sats;\n await this.deposit(needed, reason ?? `Need ${needed} more sats (have ${current.balance_sats}, need ${minBalance})`);\n return this.getBalance();\n }\n\n async withdraw(amount_sats?: number): Promise<WithdrawResult> {\n return this.request('POST', '/api/v1/wallet/withdraw', amount_sats ? { amount_sats } : {});\n }\n\n // Offers\n async createOffer(params: CreateOfferParams): Promise<Offer> {\n return this.request('POST', '/api/v1/offers', params);\n }\n\n async listOffers(): Promise<Offer[]> {\n const result = await this.request<{ offers: Offer[] }>('GET', '/api/v1/offers');\n return result.offers || [];\n }\n\n async getOffer(offerId: string): Promise<Offer> {\n return this.request('GET', `/api/v1/offers/${offerId}`);\n }\n\n async updateOffer(offerId: string, active: boolean): Promise<Offer> {\n return this.request('PATCH', `/api/v1/offers/${offerId}`, { active });\n }\n\n // Contracts\n async acceptOffer(offerId: string): Promise<Contract> {\n return this.request('POST', '/api/v1/contracts', { offer_id: offerId });\n }\n\n async fundContract(contractId: string): Promise<FundResult> {\n return this.request('POST', `/api/v1/contracts/${contractId}/fund`, {});\n }\n\n async listContracts(filters?: { role?: 'buyer' | 'seller'; status?: string }): Promise<Contract[]> {\n let path = '/api/v1/contracts';\n const params = new URLSearchParams();\n if (filters?.role) params.append('role', filters.role);\n if (filters?.status) params.append('status', filters.status);\n if (params.toString()) path += '?' + params.toString();\n const result = await this.request<{ contracts: Contract[] }>('GET', path);\n return result.contracts || [];\n }\n\n async getContract(contractId: string): Promise<Contract> {\n return this.request('GET', `/api/v1/contracts/${contractId}`);\n }\n\n // Delivery\n async submitDelivery(contractId: string, proofUrl: string, proofData?: any): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/deliver`, {\n proof_url: proofUrl,\n proof_data: proofData,\n });\n }\n\n async confirmDelivery(contractId: string): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/confirm`, {});\n }\n\n async disputeDelivery(contractId: string, reason: string, evidenceUrl?: string): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/dispute`, {\n reason,\n evidence_url: evidenceUrl,\n });\n }\n\n // Ledger\n async getLedger(limit?: number, offset?: number): Promise<{ balance_sats: number; entries: LedgerEntry[] }> {\n let path = '/api/v1/ledger';\n const params = new URLSearchParams();\n if (limit) params.append('limit', String(limit));\n if (offset) params.append('offset', String(offset));\n if (params.toString()) path += '?' + params.toString();\n return this.request('GET', path);\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
interface L402AgentOptions {
|
|
2
2
|
apiKey: string;
|
|
3
3
|
apiUrl?: string;
|
|
4
|
+
/**
|
|
5
|
+
* Called when the agent needs a Lightning invoice paid.
|
|
6
|
+
*
|
|
7
|
+
* AI agents cannot pay Lightning invoices themselves — they don't have wallets.
|
|
8
|
+
* When a deposit is needed, the agent calls this callback so YOU can:
|
|
9
|
+
* - Forward the invoice to a human via chat/notification
|
|
10
|
+
* - Pay it from an external wallet/service
|
|
11
|
+
* - Display it in a UI for manual payment
|
|
12
|
+
*
|
|
13
|
+
* If not provided, `deposit()` returns the invoice and the caller is
|
|
14
|
+
* responsible for payment coordination.
|
|
15
|
+
*/
|
|
16
|
+
onPaymentNeeded?: (invoice: DepositInvoice) => Promise<void> | void;
|
|
17
|
+
/**
|
|
18
|
+
* How long to wait (ms) for payment after calling onPaymentNeeded.
|
|
19
|
+
* Default: 300_000 (5 minutes). Set to 0 to skip polling.
|
|
20
|
+
*/
|
|
21
|
+
paymentTimeoutMs?: number;
|
|
22
|
+
/**
|
|
23
|
+
* How often to poll (ms) for payment confirmation.
|
|
24
|
+
* Default: 5_000 (5 seconds).
|
|
25
|
+
*/
|
|
26
|
+
paymentPollIntervalMs?: number;
|
|
4
27
|
}
|
|
5
28
|
interface BalanceInfo {
|
|
6
29
|
balance_sats: number;
|
|
@@ -57,7 +80,31 @@ interface DepositInvoice {
|
|
|
57
80
|
payment_hash: string;
|
|
58
81
|
invoice: string;
|
|
59
82
|
amount_sats: number;
|
|
83
|
+
/**
|
|
84
|
+
* The human-readable message explaining what to do with this invoice.
|
|
85
|
+
* Agents should forward this + the invoice to a human for payment.
|
|
86
|
+
*/
|
|
87
|
+
message: string;
|
|
60
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Callback for notifying a human that payment is needed.
|
|
91
|
+
* AI agents cannot pay Lightning invoices on their own — they need a human
|
|
92
|
+
* (or an external wallet) to pay. This callback is how the agent asks for help.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* // Slack/Discord/Signal notification
|
|
96
|
+
* const agent = new L402Agent({
|
|
97
|
+
* apiKey: 'sk_...',
|
|
98
|
+
* onPaymentNeeded: async (invoice) => {
|
|
99
|
+
* await sendMessage(channel, [
|
|
100
|
+
* `⚡ Payment needed: ${invoice.amount_sats} sats`,
|
|
101
|
+
* `Invoice: ${invoice.invoice}`,
|
|
102
|
+
* `Reason: ${invoice.message}`,
|
|
103
|
+
* ].join('\n'));
|
|
104
|
+
* },
|
|
105
|
+
* });
|
|
106
|
+
*/
|
|
107
|
+
type PaymentNeededCallback = (invoice: DepositInvoice) => Promise<void> | void;
|
|
61
108
|
interface DepositStatus {
|
|
62
109
|
status: 'pending' | 'paid' | 'expired';
|
|
63
110
|
amount_sats: number;
|
|
@@ -97,6 +144,9 @@ declare class L402Error extends Error {
|
|
|
97
144
|
declare class L402Agent {
|
|
98
145
|
private apiKey;
|
|
99
146
|
private apiUrl;
|
|
147
|
+
private onPaymentNeeded?;
|
|
148
|
+
private paymentTimeoutMs;
|
|
149
|
+
private paymentPollIntervalMs;
|
|
100
150
|
constructor(options: L402AgentOptions);
|
|
101
151
|
private request;
|
|
102
152
|
static register(options: {
|
|
@@ -107,8 +157,59 @@ declare class L402Agent {
|
|
|
107
157
|
apiUrl?: string;
|
|
108
158
|
}): Promise<AgentRegistration>;
|
|
109
159
|
getBalance(): Promise<BalanceInfo>;
|
|
160
|
+
/**
|
|
161
|
+
* Low-level: create a deposit invoice. Returns the invoice for manual handling.
|
|
162
|
+
* Most agents should use `deposit()` instead, which notifies the human and waits.
|
|
163
|
+
*/
|
|
110
164
|
createDeposit(amount_sats: number): Promise<DepositInvoice>;
|
|
165
|
+
/**
|
|
166
|
+
* Check if a deposit invoice has been paid.
|
|
167
|
+
*/
|
|
111
168
|
checkDeposit(paymentHash: string): Promise<DepositStatus>;
|
|
169
|
+
/**
|
|
170
|
+
* High-level deposit: creates invoice, notifies human, waits for payment.
|
|
171
|
+
*
|
|
172
|
+
* WHY THIS EXISTS: AI agents don't have Lightning wallets. They can't pay
|
|
173
|
+
* invoices. When an agent needs sats, it must ask a human to pay.
|
|
174
|
+
*
|
|
175
|
+
* This method:
|
|
176
|
+
* 1. Creates a Lightning invoice for the requested amount
|
|
177
|
+
* 2. Calls `onPaymentNeeded` so you can notify the human (chat, email, UI)
|
|
178
|
+
* 3. Polls until the invoice is paid or times out
|
|
179
|
+
* 4. Returns the confirmed deposit status
|
|
180
|
+
*
|
|
181
|
+
* If `onPaymentNeeded` is not configured, throws with the invoice so the
|
|
182
|
+
* caller can handle notification manually.
|
|
183
|
+
*
|
|
184
|
+
* @param amount_sats - Amount to deposit
|
|
185
|
+
* @param reason - Human-readable reason (shown in the notification)
|
|
186
|
+
* @returns Confirmed deposit status
|
|
187
|
+
* @throws {L402Error} if payment times out or fails
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* const agent = new L402Agent({
|
|
191
|
+
* apiKey: 'sk_...',
|
|
192
|
+
* onPaymentNeeded: async (invoice) => {
|
|
193
|
+
* // Send to Slack, Discord, Signal, email, etc.
|
|
194
|
+
* await notify(`Pay ${invoice.amount_sats} sats: ${invoice.invoice}`);
|
|
195
|
+
* },
|
|
196
|
+
* });
|
|
197
|
+
*
|
|
198
|
+
* // Agent requests funding and waits
|
|
199
|
+
* const deposit = await agent.deposit(1000, 'Need funds to accept code-review offer');
|
|
200
|
+
* console.log(`Funded! Balance: ${deposit.amount_sats} sats`);
|
|
201
|
+
*/
|
|
202
|
+
deposit(amount_sats: number, reason?: string): Promise<DepositStatus>;
|
|
203
|
+
/**
|
|
204
|
+
* Ensure the agent has at least `minBalance` sats. If not, request a deposit.
|
|
205
|
+
* Convenience method that checks balance first, then deposits the difference.
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* // Before accepting an offer, make sure you can pay
|
|
209
|
+
* await agent.ensureBalance(500, 'Need funds for code-review contract');
|
|
210
|
+
* await agent.fundContract(contractId);
|
|
211
|
+
*/
|
|
212
|
+
ensureBalance(minBalance: number, reason?: string): Promise<BalanceInfo>;
|
|
112
213
|
withdraw(amount_sats?: number): Promise<WithdrawResult>;
|
|
113
214
|
createOffer(params: CreateOfferParams): Promise<Offer>;
|
|
114
215
|
listOffers(): Promise<Offer[]>;
|
|
@@ -130,4 +231,4 @@ declare class L402Agent {
|
|
|
130
231
|
}>;
|
|
131
232
|
}
|
|
132
233
|
|
|
133
|
-
export { type AgentRegistration, type BalanceInfo, type Contract, type CreateOfferParams, type DepositInvoice, type DepositStatus, type FundResult, L402Agent, type L402AgentOptions, L402Error, type LedgerEntry, type Offer, type WithdrawResult };
|
|
234
|
+
export { type AgentRegistration, type BalanceInfo, type Contract, type CreateOfferParams, type DepositInvoice, type DepositStatus, type FundResult, L402Agent, type L402AgentOptions, L402Error, type LedgerEntry, type Offer, type PaymentNeededCallback, type WithdrawResult };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
interface L402AgentOptions {
|
|
2
2
|
apiKey: string;
|
|
3
3
|
apiUrl?: string;
|
|
4
|
+
/**
|
|
5
|
+
* Called when the agent needs a Lightning invoice paid.
|
|
6
|
+
*
|
|
7
|
+
* AI agents cannot pay Lightning invoices themselves — they don't have wallets.
|
|
8
|
+
* When a deposit is needed, the agent calls this callback so YOU can:
|
|
9
|
+
* - Forward the invoice to a human via chat/notification
|
|
10
|
+
* - Pay it from an external wallet/service
|
|
11
|
+
* - Display it in a UI for manual payment
|
|
12
|
+
*
|
|
13
|
+
* If not provided, `deposit()` returns the invoice and the caller is
|
|
14
|
+
* responsible for payment coordination.
|
|
15
|
+
*/
|
|
16
|
+
onPaymentNeeded?: (invoice: DepositInvoice) => Promise<void> | void;
|
|
17
|
+
/**
|
|
18
|
+
* How long to wait (ms) for payment after calling onPaymentNeeded.
|
|
19
|
+
* Default: 300_000 (5 minutes). Set to 0 to skip polling.
|
|
20
|
+
*/
|
|
21
|
+
paymentTimeoutMs?: number;
|
|
22
|
+
/**
|
|
23
|
+
* How often to poll (ms) for payment confirmation.
|
|
24
|
+
* Default: 5_000 (5 seconds).
|
|
25
|
+
*/
|
|
26
|
+
paymentPollIntervalMs?: number;
|
|
4
27
|
}
|
|
5
28
|
interface BalanceInfo {
|
|
6
29
|
balance_sats: number;
|
|
@@ -57,7 +80,31 @@ interface DepositInvoice {
|
|
|
57
80
|
payment_hash: string;
|
|
58
81
|
invoice: string;
|
|
59
82
|
amount_sats: number;
|
|
83
|
+
/**
|
|
84
|
+
* The human-readable message explaining what to do with this invoice.
|
|
85
|
+
* Agents should forward this + the invoice to a human for payment.
|
|
86
|
+
*/
|
|
87
|
+
message: string;
|
|
60
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Callback for notifying a human that payment is needed.
|
|
91
|
+
* AI agents cannot pay Lightning invoices on their own — they need a human
|
|
92
|
+
* (or an external wallet) to pay. This callback is how the agent asks for help.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* // Slack/Discord/Signal notification
|
|
96
|
+
* const agent = new L402Agent({
|
|
97
|
+
* apiKey: 'sk_...',
|
|
98
|
+
* onPaymentNeeded: async (invoice) => {
|
|
99
|
+
* await sendMessage(channel, [
|
|
100
|
+
* `⚡ Payment needed: ${invoice.amount_sats} sats`,
|
|
101
|
+
* `Invoice: ${invoice.invoice}`,
|
|
102
|
+
* `Reason: ${invoice.message}`,
|
|
103
|
+
* ].join('\n'));
|
|
104
|
+
* },
|
|
105
|
+
* });
|
|
106
|
+
*/
|
|
107
|
+
type PaymentNeededCallback = (invoice: DepositInvoice) => Promise<void> | void;
|
|
61
108
|
interface DepositStatus {
|
|
62
109
|
status: 'pending' | 'paid' | 'expired';
|
|
63
110
|
amount_sats: number;
|
|
@@ -97,6 +144,9 @@ declare class L402Error extends Error {
|
|
|
97
144
|
declare class L402Agent {
|
|
98
145
|
private apiKey;
|
|
99
146
|
private apiUrl;
|
|
147
|
+
private onPaymentNeeded?;
|
|
148
|
+
private paymentTimeoutMs;
|
|
149
|
+
private paymentPollIntervalMs;
|
|
100
150
|
constructor(options: L402AgentOptions);
|
|
101
151
|
private request;
|
|
102
152
|
static register(options: {
|
|
@@ -107,8 +157,59 @@ declare class L402Agent {
|
|
|
107
157
|
apiUrl?: string;
|
|
108
158
|
}): Promise<AgentRegistration>;
|
|
109
159
|
getBalance(): Promise<BalanceInfo>;
|
|
160
|
+
/**
|
|
161
|
+
* Low-level: create a deposit invoice. Returns the invoice for manual handling.
|
|
162
|
+
* Most agents should use `deposit()` instead, which notifies the human and waits.
|
|
163
|
+
*/
|
|
110
164
|
createDeposit(amount_sats: number): Promise<DepositInvoice>;
|
|
165
|
+
/**
|
|
166
|
+
* Check if a deposit invoice has been paid.
|
|
167
|
+
*/
|
|
111
168
|
checkDeposit(paymentHash: string): Promise<DepositStatus>;
|
|
169
|
+
/**
|
|
170
|
+
* High-level deposit: creates invoice, notifies human, waits for payment.
|
|
171
|
+
*
|
|
172
|
+
* WHY THIS EXISTS: AI agents don't have Lightning wallets. They can't pay
|
|
173
|
+
* invoices. When an agent needs sats, it must ask a human to pay.
|
|
174
|
+
*
|
|
175
|
+
* This method:
|
|
176
|
+
* 1. Creates a Lightning invoice for the requested amount
|
|
177
|
+
* 2. Calls `onPaymentNeeded` so you can notify the human (chat, email, UI)
|
|
178
|
+
* 3. Polls until the invoice is paid or times out
|
|
179
|
+
* 4. Returns the confirmed deposit status
|
|
180
|
+
*
|
|
181
|
+
* If `onPaymentNeeded` is not configured, throws with the invoice so the
|
|
182
|
+
* caller can handle notification manually.
|
|
183
|
+
*
|
|
184
|
+
* @param amount_sats - Amount to deposit
|
|
185
|
+
* @param reason - Human-readable reason (shown in the notification)
|
|
186
|
+
* @returns Confirmed deposit status
|
|
187
|
+
* @throws {L402Error} if payment times out or fails
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* const agent = new L402Agent({
|
|
191
|
+
* apiKey: 'sk_...',
|
|
192
|
+
* onPaymentNeeded: async (invoice) => {
|
|
193
|
+
* // Send to Slack, Discord, Signal, email, etc.
|
|
194
|
+
* await notify(`Pay ${invoice.amount_sats} sats: ${invoice.invoice}`);
|
|
195
|
+
* },
|
|
196
|
+
* });
|
|
197
|
+
*
|
|
198
|
+
* // Agent requests funding and waits
|
|
199
|
+
* const deposit = await agent.deposit(1000, 'Need funds to accept code-review offer');
|
|
200
|
+
* console.log(`Funded! Balance: ${deposit.amount_sats} sats`);
|
|
201
|
+
*/
|
|
202
|
+
deposit(amount_sats: number, reason?: string): Promise<DepositStatus>;
|
|
203
|
+
/**
|
|
204
|
+
* Ensure the agent has at least `minBalance` sats. If not, request a deposit.
|
|
205
|
+
* Convenience method that checks balance first, then deposits the difference.
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* // Before accepting an offer, make sure you can pay
|
|
209
|
+
* await agent.ensureBalance(500, 'Need funds for code-review contract');
|
|
210
|
+
* await agent.fundContract(contractId);
|
|
211
|
+
*/
|
|
212
|
+
ensureBalance(minBalance: number, reason?: string): Promise<BalanceInfo>;
|
|
112
213
|
withdraw(amount_sats?: number): Promise<WithdrawResult>;
|
|
113
214
|
createOffer(params: CreateOfferParams): Promise<Offer>;
|
|
114
215
|
listOffers(): Promise<Offer[]>;
|
|
@@ -130,4 +231,4 @@ declare class L402Agent {
|
|
|
130
231
|
}>;
|
|
131
232
|
}
|
|
132
233
|
|
|
133
|
-
export { type AgentRegistration, type BalanceInfo, type Contract, type CreateOfferParams, type DepositInvoice, type DepositStatus, type FundResult, L402Agent, type L402AgentOptions, L402Error, type LedgerEntry, type Offer, type WithdrawResult };
|
|
234
|
+
export { type AgentRegistration, type BalanceInfo, type Contract, type CreateOfferParams, type DepositInvoice, type DepositStatus, type FundResult, L402Agent, type L402AgentOptions, L402Error, type LedgerEntry, type Offer, type PaymentNeededCallback, type WithdrawResult };
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,9 @@ var L402Agent = class {
|
|
|
14
14
|
}
|
|
15
15
|
this.apiKey = options.apiKey;
|
|
16
16
|
this.apiUrl = options.apiUrl ?? "https://l402gw.nosaltres2.info";
|
|
17
|
+
this.onPaymentNeeded = options.onPaymentNeeded;
|
|
18
|
+
this.paymentTimeoutMs = options.paymentTimeoutMs ?? 3e5;
|
|
19
|
+
this.paymentPollIntervalMs = options.paymentPollIntervalMs ?? 5e3;
|
|
17
20
|
}
|
|
18
21
|
async request(method, path, body) {
|
|
19
22
|
const url = `${this.apiUrl}${path}`;
|
|
@@ -44,7 +47,7 @@ var L402Agent = class {
|
|
|
44
47
|
// Static: register a new agent (no auth needed)
|
|
45
48
|
static async register(options) {
|
|
46
49
|
const apiUrl = options.apiUrl ?? "https://l402gw.nosaltres2.info";
|
|
47
|
-
const url = `${apiUrl}/api/v1/register`;
|
|
50
|
+
const url = `${apiUrl}/api/v1/agents/register`;
|
|
48
51
|
const res = await fetch(url, {
|
|
49
52
|
method: "POST",
|
|
50
53
|
headers: { "Content-Type": "application/json" },
|
|
@@ -70,12 +73,100 @@ var L402Agent = class {
|
|
|
70
73
|
async getBalance() {
|
|
71
74
|
return this.request("GET", "/api/v1/wallet/balance");
|
|
72
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Low-level: create a deposit invoice. Returns the invoice for manual handling.
|
|
78
|
+
* Most agents should use `deposit()` instead, which notifies the human and waits.
|
|
79
|
+
*/
|
|
73
80
|
async createDeposit(amount_sats) {
|
|
74
81
|
return this.request("POST", "/api/v1/wallet/deposit", { amount_sats });
|
|
75
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Check if a deposit invoice has been paid.
|
|
85
|
+
*/
|
|
76
86
|
async checkDeposit(paymentHash) {
|
|
77
87
|
return this.request("GET", `/api/v1/wallet/deposit/${paymentHash}`);
|
|
78
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* High-level deposit: creates invoice, notifies human, waits for payment.
|
|
91
|
+
*
|
|
92
|
+
* WHY THIS EXISTS: AI agents don't have Lightning wallets. They can't pay
|
|
93
|
+
* invoices. When an agent needs sats, it must ask a human to pay.
|
|
94
|
+
*
|
|
95
|
+
* This method:
|
|
96
|
+
* 1. Creates a Lightning invoice for the requested amount
|
|
97
|
+
* 2. Calls `onPaymentNeeded` so you can notify the human (chat, email, UI)
|
|
98
|
+
* 3. Polls until the invoice is paid or times out
|
|
99
|
+
* 4. Returns the confirmed deposit status
|
|
100
|
+
*
|
|
101
|
+
* If `onPaymentNeeded` is not configured, throws with the invoice so the
|
|
102
|
+
* caller can handle notification manually.
|
|
103
|
+
*
|
|
104
|
+
* @param amount_sats - Amount to deposit
|
|
105
|
+
* @param reason - Human-readable reason (shown in the notification)
|
|
106
|
+
* @returns Confirmed deposit status
|
|
107
|
+
* @throws {L402Error} if payment times out or fails
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* const agent = new L402Agent({
|
|
111
|
+
* apiKey: 'sk_...',
|
|
112
|
+
* onPaymentNeeded: async (invoice) => {
|
|
113
|
+
* // Send to Slack, Discord, Signal, email, etc.
|
|
114
|
+
* await notify(`Pay ${invoice.amount_sats} sats: ${invoice.invoice}`);
|
|
115
|
+
* },
|
|
116
|
+
* });
|
|
117
|
+
*
|
|
118
|
+
* // Agent requests funding and waits
|
|
119
|
+
* const deposit = await agent.deposit(1000, 'Need funds to accept code-review offer');
|
|
120
|
+
* console.log(`Funded! Balance: ${deposit.amount_sats} sats`);
|
|
121
|
+
*/
|
|
122
|
+
async deposit(amount_sats, reason) {
|
|
123
|
+
const invoice = await this.createDeposit(amount_sats);
|
|
124
|
+
if (!this.onPaymentNeeded) {
|
|
125
|
+
throw new L402Error(
|
|
126
|
+
`Payment needed: ${amount_sats} sats. Invoice: ${invoice.invoice}. No onPaymentNeeded callback configured \u2014 pay this invoice manually and call checkDeposit('${invoice.payment_hash}') to confirm.`,
|
|
127
|
+
402,
|
|
128
|
+
"PAYMENT_NEEDED"
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
const enrichedInvoice = {
|
|
132
|
+
...invoice,
|
|
133
|
+
message: reason ? `\u26A1 Agent needs ${amount_sats} sats: ${reason}` : `\u26A1 Agent needs ${amount_sats} sats deposited`
|
|
134
|
+
};
|
|
135
|
+
await this.onPaymentNeeded(enrichedInvoice);
|
|
136
|
+
if (this.paymentTimeoutMs === 0) {
|
|
137
|
+
return { status: "pending", amount_sats, paid_at: null };
|
|
138
|
+
}
|
|
139
|
+
const deadline = Date.now() + this.paymentTimeoutMs;
|
|
140
|
+
while (Date.now() < deadline) {
|
|
141
|
+
await new Promise((r) => setTimeout(r, this.paymentPollIntervalMs));
|
|
142
|
+
const status = await this.checkDeposit(invoice.payment_hash);
|
|
143
|
+
if (status.status === "paid") return status;
|
|
144
|
+
if (status.status === "expired") {
|
|
145
|
+
throw new L402Error("Deposit invoice expired before payment", 408, "PAYMENT_EXPIRED");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
throw new L402Error(
|
|
149
|
+
`Payment not received within ${this.paymentTimeoutMs / 1e3}s. Invoice may still be valid \u2014 check with checkDeposit('${invoice.payment_hash}')`,
|
|
150
|
+
408,
|
|
151
|
+
"PAYMENT_TIMEOUT"
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Ensure the agent has at least `minBalance` sats. If not, request a deposit.
|
|
156
|
+
* Convenience method that checks balance first, then deposits the difference.
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* // Before accepting an offer, make sure you can pay
|
|
160
|
+
* await agent.ensureBalance(500, 'Need funds for code-review contract');
|
|
161
|
+
* await agent.fundContract(contractId);
|
|
162
|
+
*/
|
|
163
|
+
async ensureBalance(minBalance, reason) {
|
|
164
|
+
const current = await this.getBalance();
|
|
165
|
+
if (current.balance_sats >= minBalance) return current;
|
|
166
|
+
const needed = minBalance - current.balance_sats;
|
|
167
|
+
await this.deposit(needed, reason ?? `Need ${needed} more sats (have ${current.balance_sats}, need ${minBalance})`);
|
|
168
|
+
return this.getBalance();
|
|
169
|
+
}
|
|
79
170
|
async withdraw(amount_sats) {
|
|
80
171
|
return this.request("POST", "/api/v1/wallet/withdraw", amount_sats ? { amount_sats } : {});
|
|
81
172
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type {\n L402AgentOptions,\n BalanceInfo,\n Offer,\n CreateOfferParams,\n Contract,\n FundResult,\n DepositInvoice,\n DepositStatus,\n LedgerEntry,\n WithdrawResult,\n AgentRegistration,\n} from './types.js';\n\nexport class L402Error extends Error {\n status: number;\n code?: string;\n\n constructor(message: string, status: number, code?: string) {\n super(message);\n this.name = 'L402Error';\n this.status = status;\n this.code = code;\n }\n}\n\nexport class L402Agent {\n private apiKey: string;\n private apiUrl: string;\n\n constructor(options: L402AgentOptions) {\n if (!options.apiKey) {\n throw new Error('apiKey is required');\n }\n this.apiKey = options.apiKey;\n this.apiUrl = options.apiUrl ?? 'https://l402gw.nosaltres2.info';\n }\n\n private async request<T>(method: string, path: string, body?: any): Promise<T> {\n const url = `${this.apiUrl}${path}`;\n const options: RequestInit = {\n method,\n headers: {\n 'X-L402-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n };\n\n if (body) {\n options.body = JSON.stringify(body);\n }\n\n const res = await fetch(url, options);\n\n if (!res.ok) {\n let errorMsg = `HTTP ${res.status}`;\n let errorCode: string | undefined;\n\n try {\n const data = await res.json() as { error?: string; code?: string };\n errorMsg = data.error || errorMsg;\n errorCode = data.code;\n } catch {\n // Use default error message\n }\n\n throw new L402Error(errorMsg, res.status, errorCode);\n }\n\n return res.json() as Promise<T>;\n }\n\n // Static: register a new agent (no auth needed)\n static async register(options: {\n name: string;\n description?: string;\n wallet_type?: 'custodial' | 'external';\n lightning_address?: string;\n apiUrl?: string;\n }): Promise<AgentRegistration> {\n const apiUrl = options.apiUrl ?? 'https://l402gw.nosaltres2.info';\n const url = `${apiUrl}/api/v1/register`;\n\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n name: options.name,\n description: options.description,\n wallet_type: options.wallet_type ?? 'custodial',\n lightning_address: options.lightning_address,\n }),\n });\n\n if (!res.ok) {\n let errorMsg = `HTTP ${res.status}`;\n try {\n const data = await res.json() as { error?: string };\n errorMsg = data.error || errorMsg;\n } catch {\n // Use default error message\n }\n throw new L402Error(errorMsg, res.status);\n }\n\n return res.json() as Promise<AgentRegistration>;\n }\n\n // Wallet\n async getBalance(): Promise<BalanceInfo> {\n return this.request('GET', '/api/v1/wallet/balance');\n }\n\n async createDeposit(amount_sats: number): Promise<DepositInvoice> {\n return this.request('POST', '/api/v1/wallet/deposit', { amount_sats });\n }\n\n async checkDeposit(paymentHash: string): Promise<DepositStatus> {\n return this.request('GET', `/api/v1/wallet/deposit/${paymentHash}`);\n }\n\n async withdraw(amount_sats?: number): Promise<WithdrawResult> {\n return this.request('POST', '/api/v1/wallet/withdraw', amount_sats ? { amount_sats } : {});\n }\n\n // Offers\n async createOffer(params: CreateOfferParams): Promise<Offer> {\n return this.request('POST', '/api/v1/offers', params);\n }\n\n async listOffers(): Promise<Offer[]> {\n const result = await this.request<{ offers: Offer[] }>('GET', '/api/v1/offers');\n return result.offers || [];\n }\n\n async getOffer(offerId: string): Promise<Offer> {\n return this.request('GET', `/api/v1/offers/${offerId}`);\n }\n\n async updateOffer(offerId: string, active: boolean): Promise<Offer> {\n return this.request('PATCH', `/api/v1/offers/${offerId}`, { active });\n }\n\n // Contracts\n async acceptOffer(offerId: string): Promise<Contract> {\n return this.request('POST', '/api/v1/contracts', { offer_id: offerId });\n }\n\n async fundContract(contractId: string): Promise<FundResult> {\n return this.request('POST', `/api/v1/contracts/${contractId}/fund`, {});\n }\n\n async listContracts(filters?: { role?: 'buyer' | 'seller'; status?: string }): Promise<Contract[]> {\n let path = '/api/v1/contracts';\n const params = new URLSearchParams();\n if (filters?.role) params.append('role', filters.role);\n if (filters?.status) params.append('status', filters.status);\n if (params.toString()) path += '?' + params.toString();\n const result = await this.request<{ contracts: Contract[] }>('GET', path);\n return result.contracts || [];\n }\n\n async getContract(contractId: string): Promise<Contract> {\n return this.request('GET', `/api/v1/contracts/${contractId}`);\n }\n\n // Delivery\n async submitDelivery(contractId: string, proofUrl: string, proofData?: any): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/deliver`, {\n proof_url: proofUrl,\n proof_data: proofData,\n });\n }\n\n async confirmDelivery(contractId: string): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/confirm`, {});\n }\n\n async disputeDelivery(contractId: string, reason: string, evidenceUrl?: string): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/dispute`, {\n reason,\n evidence_url: evidenceUrl,\n });\n }\n\n // Ledger\n async getLedger(limit?: number, offset?: number): Promise<{ balance_sats: number; entries: LedgerEntry[] }> {\n let path = '/api/v1/ledger';\n const params = new URLSearchParams();\n if (limit) params.append('limit', String(limit));\n if (offset) params.append('offset', String(offset));\n if (params.toString()) path += '?' + params.toString();\n return this.request('GET', path);\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type {\n L402AgentOptions,\n BalanceInfo,\n Offer,\n CreateOfferParams,\n Contract,\n FundResult,\n DepositInvoice,\n DepositStatus,\n LedgerEntry,\n WithdrawResult,\n AgentRegistration,\n PaymentNeededCallback,\n} from './types.js';\n\nexport class L402Error extends Error {\n status: number;\n code?: string;\n\n constructor(message: string, status: number, code?: string) {\n super(message);\n this.name = 'L402Error';\n this.status = status;\n this.code = code;\n }\n}\n\nexport class L402Agent {\n private apiKey: string;\n private apiUrl: string;\n\n private onPaymentNeeded?: PaymentNeededCallback;\n private paymentTimeoutMs: number;\n private paymentPollIntervalMs: number;\n\n constructor(options: L402AgentOptions) {\n if (!options.apiKey) {\n throw new Error('apiKey is required');\n }\n this.apiKey = options.apiKey;\n this.apiUrl = options.apiUrl ?? 'https://l402gw.nosaltres2.info';\n this.onPaymentNeeded = options.onPaymentNeeded;\n this.paymentTimeoutMs = options.paymentTimeoutMs ?? 300_000;\n this.paymentPollIntervalMs = options.paymentPollIntervalMs ?? 5_000;\n }\n\n private async request<T>(method: string, path: string, body?: any): Promise<T> {\n const url = `${this.apiUrl}${path}`;\n const options: RequestInit = {\n method,\n headers: {\n 'X-L402-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n };\n\n if (body) {\n options.body = JSON.stringify(body);\n }\n\n const res = await fetch(url, options);\n\n if (!res.ok) {\n let errorMsg = `HTTP ${res.status}`;\n let errorCode: string | undefined;\n\n try {\n const data = await res.json() as { error?: string; code?: string };\n errorMsg = data.error || errorMsg;\n errorCode = data.code;\n } catch {\n // Use default error message\n }\n\n throw new L402Error(errorMsg, res.status, errorCode);\n }\n\n return res.json() as Promise<T>;\n }\n\n // Static: register a new agent (no auth needed)\n static async register(options: {\n name: string;\n description?: string;\n wallet_type?: 'custodial' | 'external';\n lightning_address?: string;\n apiUrl?: string;\n }): Promise<AgentRegistration> {\n const apiUrl = options.apiUrl ?? 'https://l402gw.nosaltres2.info';\n const url = `${apiUrl}/api/v1/agents/register`;\n\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n name: options.name,\n description: options.description,\n wallet_type: options.wallet_type ?? 'custodial',\n lightning_address: options.lightning_address,\n }),\n });\n\n if (!res.ok) {\n let errorMsg = `HTTP ${res.status}`;\n try {\n const data = await res.json() as { error?: string };\n errorMsg = data.error || errorMsg;\n } catch {\n // Use default error message\n }\n throw new L402Error(errorMsg, res.status);\n }\n\n return res.json() as Promise<AgentRegistration>;\n }\n\n // Wallet\n async getBalance(): Promise<BalanceInfo> {\n return this.request('GET', '/api/v1/wallet/balance');\n }\n\n /**\n * Low-level: create a deposit invoice. Returns the invoice for manual handling.\n * Most agents should use `deposit()` instead, which notifies the human and waits.\n */\n async createDeposit(amount_sats: number): Promise<DepositInvoice> {\n return this.request('POST', '/api/v1/wallet/deposit', { amount_sats });\n }\n\n /**\n * Check if a deposit invoice has been paid.\n */\n async checkDeposit(paymentHash: string): Promise<DepositStatus> {\n return this.request('GET', `/api/v1/wallet/deposit/${paymentHash}`);\n }\n\n /**\n * High-level deposit: creates invoice, notifies human, waits for payment.\n *\n * WHY THIS EXISTS: AI agents don't have Lightning wallets. They can't pay\n * invoices. When an agent needs sats, it must ask a human to pay.\n *\n * This method:\n * 1. Creates a Lightning invoice for the requested amount\n * 2. Calls `onPaymentNeeded` so you can notify the human (chat, email, UI)\n * 3. Polls until the invoice is paid or times out\n * 4. Returns the confirmed deposit status\n *\n * If `onPaymentNeeded` is not configured, throws with the invoice so the\n * caller can handle notification manually.\n *\n * @param amount_sats - Amount to deposit\n * @param reason - Human-readable reason (shown in the notification)\n * @returns Confirmed deposit status\n * @throws {L402Error} if payment times out or fails\n *\n * @example\n * const agent = new L402Agent({\n * apiKey: 'sk_...',\n * onPaymentNeeded: async (invoice) => {\n * // Send to Slack, Discord, Signal, email, etc.\n * await notify(`Pay ${invoice.amount_sats} sats: ${invoice.invoice}`);\n * },\n * });\n *\n * // Agent requests funding and waits\n * const deposit = await agent.deposit(1000, 'Need funds to accept code-review offer');\n * console.log(`Funded! Balance: ${deposit.amount_sats} sats`);\n */\n async deposit(amount_sats: number, reason?: string): Promise<DepositStatus> {\n const invoice = await this.createDeposit(amount_sats);\n\n // If no callback, throw with invoice details so caller can handle it\n if (!this.onPaymentNeeded) {\n throw new L402Error(\n `Payment needed: ${amount_sats} sats. ` +\n `Invoice: ${invoice.invoice}. ` +\n `No onPaymentNeeded callback configured — pay this invoice manually ` +\n `and call checkDeposit('${invoice.payment_hash}') to confirm.`,\n 402,\n 'PAYMENT_NEEDED'\n );\n }\n\n // Notify the human\n const enrichedInvoice: DepositInvoice = {\n ...invoice,\n message: reason\n ? `⚡ Agent needs ${amount_sats} sats: ${reason}`\n : `⚡ Agent needs ${amount_sats} sats deposited`,\n };\n await this.onPaymentNeeded(enrichedInvoice);\n\n // Poll for payment\n if (this.paymentTimeoutMs === 0) {\n return { status: 'pending', amount_sats, paid_at: null };\n }\n\n const deadline = Date.now() + this.paymentTimeoutMs;\n while (Date.now() < deadline) {\n await new Promise(r => setTimeout(r, this.paymentPollIntervalMs));\n const status = await this.checkDeposit(invoice.payment_hash);\n if (status.status === 'paid') return status;\n if (status.status === 'expired') {\n throw new L402Error('Deposit invoice expired before payment', 408, 'PAYMENT_EXPIRED');\n }\n }\n\n throw new L402Error(\n `Payment not received within ${this.paymentTimeoutMs / 1000}s. ` +\n `Invoice may still be valid — check with checkDeposit('${invoice.payment_hash}')`,\n 408,\n 'PAYMENT_TIMEOUT'\n );\n }\n\n /**\n * Ensure the agent has at least `minBalance` sats. If not, request a deposit.\n * Convenience method that checks balance first, then deposits the difference.\n *\n * @example\n * // Before accepting an offer, make sure you can pay\n * await agent.ensureBalance(500, 'Need funds for code-review contract');\n * await agent.fundContract(contractId);\n */\n async ensureBalance(minBalance: number, reason?: string): Promise<BalanceInfo> {\n const current = await this.getBalance();\n if (current.balance_sats >= minBalance) return current;\n\n const needed = minBalance - current.balance_sats;\n await this.deposit(needed, reason ?? `Need ${needed} more sats (have ${current.balance_sats}, need ${minBalance})`);\n return this.getBalance();\n }\n\n async withdraw(amount_sats?: number): Promise<WithdrawResult> {\n return this.request('POST', '/api/v1/wallet/withdraw', amount_sats ? { amount_sats } : {});\n }\n\n // Offers\n async createOffer(params: CreateOfferParams): Promise<Offer> {\n return this.request('POST', '/api/v1/offers', params);\n }\n\n async listOffers(): Promise<Offer[]> {\n const result = await this.request<{ offers: Offer[] }>('GET', '/api/v1/offers');\n return result.offers || [];\n }\n\n async getOffer(offerId: string): Promise<Offer> {\n return this.request('GET', `/api/v1/offers/${offerId}`);\n }\n\n async updateOffer(offerId: string, active: boolean): Promise<Offer> {\n return this.request('PATCH', `/api/v1/offers/${offerId}`, { active });\n }\n\n // Contracts\n async acceptOffer(offerId: string): Promise<Contract> {\n return this.request('POST', '/api/v1/contracts', { offer_id: offerId });\n }\n\n async fundContract(contractId: string): Promise<FundResult> {\n return this.request('POST', `/api/v1/contracts/${contractId}/fund`, {});\n }\n\n async listContracts(filters?: { role?: 'buyer' | 'seller'; status?: string }): Promise<Contract[]> {\n let path = '/api/v1/contracts';\n const params = new URLSearchParams();\n if (filters?.role) params.append('role', filters.role);\n if (filters?.status) params.append('status', filters.status);\n if (params.toString()) path += '?' + params.toString();\n const result = await this.request<{ contracts: Contract[] }>('GET', path);\n return result.contracts || [];\n }\n\n async getContract(contractId: string): Promise<Contract> {\n return this.request('GET', `/api/v1/contracts/${contractId}`);\n }\n\n // Delivery\n async submitDelivery(contractId: string, proofUrl: string, proofData?: any): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/deliver`, {\n proof_url: proofUrl,\n proof_data: proofData,\n });\n }\n\n async confirmDelivery(contractId: string): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/confirm`, {});\n }\n\n async disputeDelivery(contractId: string, reason: string, evidenceUrl?: string): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/dispute`, {\n reason,\n evidence_url: evidenceUrl,\n });\n }\n\n // Ledger\n async getLedger(limit?: number, offset?: number): Promise<{ balance_sats: number; entries: LedgerEntry[] }> {\n let path = '/api/v1/ledger';\n const params = new URLSearchParams();\n if (limit) params.append('limit', String(limit));\n if (offset) params.append('offset', String(offset));\n if (params.toString()) path += '?' + params.toString();\n return this.request('GET', path);\n }\n}\n"],"mappings":"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","names":[]}
|