satonomous 0.2.1 → 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/dist/index.cjs +148 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -33
- package/dist/index.d.ts +7 -33
- package/dist/index.js +148 -58
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -26,6 +26,55 @@ __export(index_exports, {
|
|
|
26
26
|
module.exports = __toCommonJS(index_exports);
|
|
27
27
|
|
|
28
28
|
// src/client.ts
|
|
29
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
30
|
+
var HASH_RE = /^[0-9a-f]{64}$/i;
|
|
31
|
+
function validateId(value, label) {
|
|
32
|
+
if (!value || typeof value !== "string") {
|
|
33
|
+
throw new Error(`${label} is required`);
|
|
34
|
+
}
|
|
35
|
+
if (!UUID_RE.test(value)) {
|
|
36
|
+
throw new Error(`${label} must be a valid UUID, got: ${value.slice(0, 50)}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function validateHash(value, label) {
|
|
40
|
+
if (!value || typeof value !== "string") {
|
|
41
|
+
throw new Error(`${label} is required`);
|
|
42
|
+
}
|
|
43
|
+
if (!HASH_RE.test(value)) {
|
|
44
|
+
throw new Error(`${label} must be a valid 64-char hex hash`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function validateSats(amount, label) {
|
|
48
|
+
if (typeof amount !== "number" || !Number.isFinite(amount) || !Number.isInteger(amount) || amount < 1) {
|
|
49
|
+
throw new Error(`${label} must be a positive integer, got: ${amount}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function validateUrl(url) {
|
|
53
|
+
try {
|
|
54
|
+
const parsed = new URL(url);
|
|
55
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
56
|
+
throw new Error("URL must use https:// or http://");
|
|
57
|
+
}
|
|
58
|
+
} catch (e) {
|
|
59
|
+
throw new Error(`Invalid API URL: ${e.message}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function enforceHttps(url) {
|
|
63
|
+
const parsed = new URL(url);
|
|
64
|
+
if (parsed.protocol !== "https:") {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`API URL must use HTTPS to protect your API key in transit. Got: ${parsed.protocol}//... Use http:// only for local development (localhost/127.0.0.1).`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function isLocalhost(url) {
|
|
71
|
+
try {
|
|
72
|
+
const parsed = new URL(url);
|
|
73
|
+
return parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "::1";
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
29
78
|
var L402Error = class extends Error {
|
|
30
79
|
constructor(message, status, code) {
|
|
31
80
|
super(message);
|
|
@@ -36,14 +85,19 @@ var L402Error = class extends Error {
|
|
|
36
85
|
};
|
|
37
86
|
var L402Agent = class {
|
|
38
87
|
constructor(options) {
|
|
39
|
-
if (!options.apiKey) {
|
|
88
|
+
if (!options.apiKey || typeof options.apiKey !== "string") {
|
|
40
89
|
throw new Error("apiKey is required");
|
|
41
90
|
}
|
|
91
|
+
const apiUrl = options.apiUrl ?? "https://l402gw.nosaltres2.info";
|
|
92
|
+
validateUrl(apiUrl);
|
|
93
|
+
if (!isLocalhost(apiUrl)) {
|
|
94
|
+
enforceHttps(apiUrl);
|
|
95
|
+
}
|
|
42
96
|
this.apiKey = options.apiKey;
|
|
43
|
-
this.apiUrl =
|
|
97
|
+
this.apiUrl = apiUrl.replace(/\/+$/, "");
|
|
44
98
|
this.onPaymentNeeded = options.onPaymentNeeded;
|
|
45
99
|
this.paymentTimeoutMs = options.paymentTimeoutMs ?? 3e5;
|
|
46
|
-
this.paymentPollIntervalMs = options.paymentPollIntervalMs ?? 5e3;
|
|
100
|
+
this.paymentPollIntervalMs = Math.max(options.paymentPollIntervalMs ?? 5e3, 1e3);
|
|
47
101
|
}
|
|
48
102
|
async request(method, path, body) {
|
|
49
103
|
const url = `${this.apiUrl}${path}`;
|
|
@@ -63,40 +117,49 @@ var L402Agent = class {
|
|
|
63
117
|
let errorCode;
|
|
64
118
|
try {
|
|
65
119
|
const data = await res.json();
|
|
66
|
-
|
|
67
|
-
|
|
120
|
+
if (data.error && typeof data.error === "string") {
|
|
121
|
+
errorMsg = data.error;
|
|
122
|
+
}
|
|
123
|
+
if (data.code && typeof data.code === "string") {
|
|
124
|
+
errorCode = data.code;
|
|
125
|
+
}
|
|
68
126
|
} catch {
|
|
69
127
|
}
|
|
70
128
|
throw new L402Error(errorMsg, res.status, errorCode);
|
|
71
129
|
}
|
|
72
130
|
return res.json();
|
|
73
131
|
}
|
|
74
|
-
// Static: register
|
|
132
|
+
// ─── Static: register ────────────────────────────────────────────────────
|
|
75
133
|
static async register(options) {
|
|
76
|
-
|
|
134
|
+
if (!options.name || typeof options.name !== "string" || options.name.trim().length < 1) {
|
|
135
|
+
throw new Error("name is required (non-empty string)");
|
|
136
|
+
}
|
|
137
|
+
const apiUrl = (options.apiUrl ?? "https://l402gw.nosaltres2.info").replace(/\/+$/, "");
|
|
138
|
+
validateUrl(apiUrl);
|
|
139
|
+
if (!isLocalhost(apiUrl)) enforceHttps(apiUrl);
|
|
77
140
|
const url = `${apiUrl}/api/v1/agents/register`;
|
|
78
141
|
const res = await fetch(url, {
|
|
79
142
|
method: "POST",
|
|
80
143
|
headers: { "Content-Type": "application/json" },
|
|
81
144
|
body: JSON.stringify({
|
|
82
|
-
name: options.name,
|
|
83
|
-
description: options.description,
|
|
145
|
+
name: options.name.trim(),
|
|
146
|
+
description: options.description?.trim(),
|
|
84
147
|
wallet_type: options.wallet_type ?? "custodial",
|
|
85
|
-
lightning_address: options.lightning_address
|
|
148
|
+
lightning_address: options.lightning_address?.trim()
|
|
86
149
|
})
|
|
87
150
|
});
|
|
88
151
|
if (!res.ok) {
|
|
89
152
|
let errorMsg = `HTTP ${res.status}`;
|
|
90
153
|
try {
|
|
91
154
|
const data = await res.json();
|
|
92
|
-
|
|
155
|
+
if (data.error && typeof data.error === "string") errorMsg = data.error;
|
|
93
156
|
} catch {
|
|
94
157
|
}
|
|
95
158
|
throw new L402Error(errorMsg, res.status);
|
|
96
159
|
}
|
|
97
160
|
return res.json();
|
|
98
161
|
}
|
|
99
|
-
// Wallet
|
|
162
|
+
// ─── Wallet ──────────────────────────────────────────────────────────────
|
|
100
163
|
async getBalance() {
|
|
101
164
|
return this.request("GET", "/api/v1/wallet/balance");
|
|
102
165
|
}
|
|
@@ -105,6 +168,7 @@ var L402Agent = class {
|
|
|
105
168
|
* Most agents should use `deposit()` instead, which notifies the human and waits.
|
|
106
169
|
*/
|
|
107
170
|
async createDeposit(amount_sats) {
|
|
171
|
+
validateSats(amount_sats, "amount_sats");
|
|
108
172
|
const result = await this.request("POST", "/api/v1/wallet/deposit", { amount_sats });
|
|
109
173
|
return {
|
|
110
174
|
...result,
|
|
@@ -115,42 +179,24 @@ var L402Agent = class {
|
|
|
115
179
|
* Check if a deposit invoice has been paid.
|
|
116
180
|
*/
|
|
117
181
|
async checkDeposit(paymentHash) {
|
|
182
|
+
validateHash(paymentHash, "paymentHash");
|
|
118
183
|
return this.request("GET", `/api/v1/wallet/deposit/${paymentHash}`);
|
|
119
184
|
}
|
|
120
185
|
/**
|
|
121
186
|
* High-level deposit: creates invoice, notifies human, waits for payment.
|
|
122
187
|
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
188
|
+
* AI agents don't have Lightning wallets. When an agent needs sats, it must
|
|
189
|
+
* ask a human to pay. This method handles the full flow:
|
|
190
|
+
* 1. Creates a Lightning invoice
|
|
191
|
+
* 2. Calls `onPaymentNeeded` to notify the human
|
|
192
|
+
* 3. Polls until paid or timeout
|
|
125
193
|
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* 2. Calls `onPaymentNeeded` so you can notify the human (chat, email, UI)
|
|
129
|
-
* 3. Polls until the invoice is paid or times out
|
|
130
|
-
* 4. Returns the confirmed deposit status
|
|
131
|
-
*
|
|
132
|
-
* If `onPaymentNeeded` is not configured, throws with the invoice so the
|
|
133
|
-
* caller can handle notification manually.
|
|
134
|
-
*
|
|
135
|
-
* @param amount_sats - Amount to deposit
|
|
136
|
-
* @param reason - Human-readable reason (shown in the notification)
|
|
137
|
-
* @returns Confirmed deposit status
|
|
194
|
+
* @param amount_sats - Amount to deposit (positive integer)
|
|
195
|
+
* @param reason - Human-readable reason (shown in notification)
|
|
138
196
|
* @throws {L402Error} if payment times out or fails
|
|
139
|
-
*
|
|
140
|
-
* @example
|
|
141
|
-
* const agent = new L402Agent({
|
|
142
|
-
* apiKey: 'sk_...',
|
|
143
|
-
* onPaymentNeeded: async (invoice) => {
|
|
144
|
-
* // Send to Slack, Discord, Signal, email, etc.
|
|
145
|
-
* await notify(`Pay ${invoice.amount_sats} sats: ${invoice.invoice}`);
|
|
146
|
-
* },
|
|
147
|
-
* });
|
|
148
|
-
*
|
|
149
|
-
* // Agent requests funding and waits
|
|
150
|
-
* const deposit = await agent.deposit(1000, 'Need funds to accept code-review offer');
|
|
151
|
-
* console.log(`Funded! Balance: ${deposit.amount_sats} sats`);
|
|
152
197
|
*/
|
|
153
198
|
async deposit(amount_sats, reason) {
|
|
199
|
+
validateSats(amount_sats, "amount_sats");
|
|
154
200
|
const invoice = await this.createDeposit(amount_sats);
|
|
155
201
|
if (!this.onPaymentNeeded) {
|
|
156
202
|
throw new L402Error(
|
|
@@ -170,18 +216,31 @@ var L402Agent = class {
|
|
|
170
216
|
invoice.invoice
|
|
171
217
|
].join("\n")
|
|
172
218
|
};
|
|
173
|
-
|
|
219
|
+
const callbackTimeout = 3e4;
|
|
220
|
+
try {
|
|
221
|
+
await Promise.race([
|
|
222
|
+
this.onPaymentNeeded(enrichedInvoice),
|
|
223
|
+
new Promise(
|
|
224
|
+
(_, reject) => setTimeout(() => reject(new Error("onPaymentNeeded callback timed out after 30s")), callbackTimeout)
|
|
225
|
+
)
|
|
226
|
+
]);
|
|
227
|
+
} catch (err) {
|
|
228
|
+
console.warn(`[satonomous] Payment notification warning: ${err.message}`);
|
|
229
|
+
}
|
|
174
230
|
if (this.paymentTimeoutMs === 0) {
|
|
175
231
|
return { status: "pending", amount_sats, paid_at: null };
|
|
176
232
|
}
|
|
177
233
|
const deadline = Date.now() + this.paymentTimeoutMs;
|
|
234
|
+
let pollInterval = this.paymentPollIntervalMs;
|
|
235
|
+
const maxInterval = 3e4;
|
|
178
236
|
while (Date.now() < deadline) {
|
|
179
|
-
await new Promise((r) => setTimeout(r,
|
|
237
|
+
await new Promise((r) => setTimeout(r, pollInterval));
|
|
180
238
|
const status = await this.checkDeposit(invoice.payment_hash);
|
|
181
239
|
if (status.status === "paid") return status;
|
|
182
240
|
if (status.status === "expired") {
|
|
183
241
|
throw new L402Error("Deposit invoice expired before payment", 408, "PAYMENT_EXPIRED");
|
|
184
242
|
}
|
|
243
|
+
pollInterval = Math.min(pollInterval * 1.5, maxInterval);
|
|
185
244
|
}
|
|
186
245
|
throw new L402Error(
|
|
187
246
|
`Payment not received within ${this.paymentTimeoutMs / 1e3}s. Invoice may still be valid \u2014 check with checkDeposit('${invoice.payment_hash}')`,
|
|
@@ -191,14 +250,9 @@ var L402Agent = class {
|
|
|
191
250
|
}
|
|
192
251
|
/**
|
|
193
252
|
* Ensure the agent has at least `minBalance` sats. If not, request a deposit.
|
|
194
|
-
* Convenience method that checks balance first, then deposits the difference.
|
|
195
|
-
*
|
|
196
|
-
* @example
|
|
197
|
-
* // Before accepting an offer, make sure you can pay
|
|
198
|
-
* await agent.ensureBalance(500, 'Need funds for code-review contract');
|
|
199
|
-
* await agent.fundContract(contractId);
|
|
200
253
|
*/
|
|
201
254
|
async ensureBalance(minBalance, reason) {
|
|
255
|
+
validateSats(minBalance, "minBalance");
|
|
202
256
|
const current = await this.getBalance();
|
|
203
257
|
if (current.balance_sats >= minBalance) return current;
|
|
204
258
|
const needed = minBalance - current.balance_sats;
|
|
@@ -206,63 +260,99 @@ var L402Agent = class {
|
|
|
206
260
|
return this.getBalance();
|
|
207
261
|
}
|
|
208
262
|
async withdraw(amount_sats) {
|
|
263
|
+
if (amount_sats !== void 0) validateSats(amount_sats, "amount_sats");
|
|
209
264
|
return this.request("POST", "/api/v1/wallet/withdraw", amount_sats ? { amount_sats } : {});
|
|
210
265
|
}
|
|
211
|
-
// Offers
|
|
266
|
+
// ─── Offers ──────────────────────────────────────────────────────────────
|
|
212
267
|
async createOffer(params) {
|
|
213
|
-
|
|
268
|
+
if (!params.title || typeof params.title !== "string") throw new Error("title is required");
|
|
269
|
+
if (!params.service_type || typeof params.service_type !== "string") throw new Error("service_type is required");
|
|
270
|
+
validateSats(params.price_sats, "price_sats");
|
|
271
|
+
const { sla_minutes, dispute_window_minutes, ...rest } = params;
|
|
272
|
+
return this.request("POST", "/api/v1/offers", {
|
|
273
|
+
...rest,
|
|
274
|
+
terms: {
|
|
275
|
+
sla_minutes: sla_minutes ?? 30,
|
|
276
|
+
dispute_window_minutes: dispute_window_minutes ?? 1440
|
|
277
|
+
}
|
|
278
|
+
});
|
|
214
279
|
}
|
|
215
280
|
async listOffers() {
|
|
216
281
|
const result = await this.request("GET", "/api/v1/offers");
|
|
217
|
-
return result.offers
|
|
282
|
+
return Array.isArray(result.offers) ? result.offers : [];
|
|
218
283
|
}
|
|
219
284
|
async getOffer(offerId) {
|
|
285
|
+
validateId(offerId, "offerId");
|
|
220
286
|
return this.request("GET", `/api/v1/offers/${offerId}`);
|
|
221
287
|
}
|
|
222
288
|
async updateOffer(offerId, active) {
|
|
289
|
+
validateId(offerId, "offerId");
|
|
290
|
+
if (typeof active !== "boolean") throw new Error("active must be a boolean");
|
|
223
291
|
return this.request("PATCH", `/api/v1/offers/${offerId}`, { active });
|
|
224
292
|
}
|
|
225
|
-
// Contracts
|
|
293
|
+
// ─── Contracts ───────────────────────────────────────────────────────────
|
|
226
294
|
async acceptOffer(offerId) {
|
|
295
|
+
validateId(offerId, "offerId");
|
|
227
296
|
return this.request("POST", "/api/v1/contracts", { offer_id: offerId });
|
|
228
297
|
}
|
|
229
298
|
async fundContract(contractId) {
|
|
299
|
+
validateId(contractId, "contractId");
|
|
230
300
|
return this.request("POST", `/api/v1/contracts/${contractId}/fund`, {});
|
|
231
301
|
}
|
|
232
302
|
async listContracts(filters) {
|
|
233
303
|
let path = "/api/v1/contracts";
|
|
234
304
|
const params = new URLSearchParams();
|
|
235
|
-
if (filters?.role)
|
|
236
|
-
|
|
305
|
+
if (filters?.role) {
|
|
306
|
+
if (filters.role !== "buyer" && filters.role !== "seller") {
|
|
307
|
+
throw new Error("role must be 'buyer' or 'seller'");
|
|
308
|
+
}
|
|
309
|
+
params.append("role", filters.role);
|
|
310
|
+
}
|
|
311
|
+
if (filters?.status) {
|
|
312
|
+
if (typeof filters.status !== "string") throw new Error("status must be a string");
|
|
313
|
+
params.append("status", filters.status);
|
|
314
|
+
}
|
|
237
315
|
if (params.toString()) path += "?" + params.toString();
|
|
238
316
|
const result = await this.request("GET", path);
|
|
239
|
-
return result.contracts
|
|
317
|
+
return Array.isArray(result.contracts) ? result.contracts : [];
|
|
240
318
|
}
|
|
241
319
|
async getContract(contractId) {
|
|
320
|
+
validateId(contractId, "contractId");
|
|
242
321
|
return this.request("GET", `/api/v1/contracts/${contractId}`);
|
|
243
322
|
}
|
|
244
|
-
// Delivery
|
|
323
|
+
// ─── Delivery ────────────────────────────────────────────────────────────
|
|
245
324
|
async submitDelivery(contractId, proofUrl, proofData) {
|
|
325
|
+
validateId(contractId, "contractId");
|
|
326
|
+
if (!proofUrl || typeof proofUrl !== "string") throw new Error("proofUrl is required");
|
|
246
327
|
return this.request("POST", `/api/v1/contracts/${contractId}/deliver`, {
|
|
247
328
|
proof_url: proofUrl,
|
|
248
329
|
proof_data: proofData
|
|
249
330
|
});
|
|
250
331
|
}
|
|
251
332
|
async confirmDelivery(contractId) {
|
|
333
|
+
validateId(contractId, "contractId");
|
|
252
334
|
return this.request("POST", `/api/v1/contracts/${contractId}/confirm`, {});
|
|
253
335
|
}
|
|
254
336
|
async disputeDelivery(contractId, reason, evidenceUrl) {
|
|
337
|
+
validateId(contractId, "contractId");
|
|
338
|
+
if (!reason || typeof reason !== "string") throw new Error("reason is required");
|
|
255
339
|
return this.request("POST", `/api/v1/contracts/${contractId}/dispute`, {
|
|
256
340
|
reason,
|
|
257
341
|
evidence_url: evidenceUrl
|
|
258
342
|
});
|
|
259
343
|
}
|
|
260
|
-
// Ledger
|
|
344
|
+
// ─── Ledger ──────────────────────────────────────────────────────────────
|
|
261
345
|
async getLedger(limit, offset) {
|
|
262
346
|
let path = "/api/v1/ledger";
|
|
263
347
|
const params = new URLSearchParams();
|
|
264
|
-
if (limit
|
|
265
|
-
|
|
348
|
+
if (limit !== void 0) {
|
|
349
|
+
if (!Number.isInteger(limit) || limit < 1) throw new Error("limit must be a positive integer");
|
|
350
|
+
params.append("limit", String(limit));
|
|
351
|
+
}
|
|
352
|
+
if (offset !== void 0) {
|
|
353
|
+
if (!Number.isInteger(offset) || offset < 0) throw new Error("offset must be a non-negative integer");
|
|
354
|
+
params.append("offset", String(offset));
|
|
355
|
+
}
|
|
266
356
|
if (params.toString()) path += "?" + params.toString();
|
|
267
357
|
return this.request("GET", path);
|
|
268
358
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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 const result = await this.request<DepositInvoice>('POST', '/api/v1/wallet/deposit', { amount_sats });\n // Add pay_url for easy wallet linking\n return {\n ...result,\n pay_url: `lightning:${result.invoice}`,\n };\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 with clear payment instructions\n const enrichedInvoice: DepositInvoice = {\n ...invoice,\n message: [\n reason\n ? `⚡ Agent needs ${amount_sats} sats: ${reason}`\n : `⚡ Agent needs ${amount_sats} sats deposited`,\n '',\n `📱 Tap to pay: ${invoice.pay_url}`,\n '',\n `Or paste this invoice into any Lightning wallet:`,\n invoice.invoice,\n ].join('\\n'),\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":[]}
|
|
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\n// ─── Validation helpers ──────────────────────────────────────────────────────\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst HASH_RE = /^[0-9a-f]{64}$/i;\n\nfunction validateId(value: string, label: string): void {\n if (!value || typeof value !== 'string') {\n throw new Error(`${label} is required`);\n }\n if (!UUID_RE.test(value)) {\n throw new Error(`${label} must be a valid UUID, got: ${value.slice(0, 50)}`);\n }\n}\n\nfunction validateHash(value: string, label: string): void {\n if (!value || typeof value !== 'string') {\n throw new Error(`${label} is required`);\n }\n if (!HASH_RE.test(value)) {\n throw new Error(`${label} must be a valid 64-char hex hash`);\n }\n}\n\nfunction validateSats(amount: number, label: string): void {\n if (typeof amount !== 'number' || !Number.isFinite(amount) || !Number.isInteger(amount) || amount < 1) {\n throw new Error(`${label} must be a positive integer, got: ${amount}`);\n }\n}\n\nfunction validateUrl(url: string): void {\n try {\n const parsed = new URL(url);\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {\n throw new Error('URL must use https:// or http://');\n }\n } catch (e: any) {\n throw new Error(`Invalid API URL: ${e.message}`);\n }\n}\n\nfunction enforceHttps(url: string): void {\n const parsed = new URL(url);\n if (parsed.protocol !== 'https:') {\n throw new Error(\n `API URL must use HTTPS to protect your API key in transit. Got: ${parsed.protocol}//... ` +\n `Use http:// only for local development (localhost/127.0.0.1).`\n );\n }\n}\n\nfunction isLocalhost(url: string): boolean {\n try {\n const parsed = new URL(url);\n return parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname === '::1';\n } catch {\n return false;\n }\n}\n\n// ─── Error class ─────────────────────────────────────────────────────────────\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\n// ─── Main client ─────────────────────────────────────────────────────────────\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 || typeof options.apiKey !== 'string') {\n throw new Error('apiKey is required');\n }\n\n const apiUrl = options.apiUrl ?? 'https://l402gw.nosaltres2.info';\n validateUrl(apiUrl);\n\n // Enforce HTTPS unless localhost (development)\n if (!isLocalhost(apiUrl)) {\n enforceHttps(apiUrl);\n }\n\n this.apiKey = options.apiKey;\n this.apiUrl = apiUrl.replace(/\\/+$/, ''); // strip trailing slashes\n this.onPaymentNeeded = options.onPaymentNeeded;\n this.paymentTimeoutMs = options.paymentTimeoutMs ?? 300_000;\n this.paymentPollIntervalMs = Math.max(options.paymentPollIntervalMs ?? 5_000, 1_000); // min 1s\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 if (data.error && typeof data.error === 'string') {\n errorMsg = data.error;\n }\n if (data.code && typeof data.code === 'string') {\n errorCode = data.code;\n }\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 ────────────────────────────────────────────────────\n\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 if (!options.name || typeof options.name !== 'string' || options.name.trim().length < 1) {\n throw new Error('name is required (non-empty string)');\n }\n\n const apiUrl = (options.apiUrl ?? 'https://l402gw.nosaltres2.info').replace(/\\/+$/, '');\n validateUrl(apiUrl);\n if (!isLocalhost(apiUrl)) enforceHttps(apiUrl);\n\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.trim(),\n description: options.description?.trim(),\n wallet_type: options.wallet_type ?? 'custodial',\n lightning_address: options.lightning_address?.trim(),\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 if (data.error && typeof data.error === 'string') errorMsg = data.error;\n } catch {\n // Use default\n }\n throw new L402Error(errorMsg, res.status);\n }\n\n return res.json() as Promise<AgentRegistration>;\n }\n\n // ─── Wallet ──────────────────────────────────────────────────────────────\n\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 validateSats(amount_sats, 'amount_sats');\n const result = await this.request<DepositInvoice>('POST', '/api/v1/wallet/deposit', { amount_sats });\n return {\n ...result,\n pay_url: `lightning:${result.invoice}`,\n };\n }\n\n /**\n * Check if a deposit invoice has been paid.\n */\n async checkDeposit(paymentHash: string): Promise<DepositStatus> {\n validateHash(paymentHash, 'paymentHash');\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 * AI agents don't have Lightning wallets. When an agent needs sats, it must\n * ask a human to pay. This method handles the full flow:\n * 1. Creates a Lightning invoice\n * 2. Calls `onPaymentNeeded` to notify the human\n * 3. Polls until paid or timeout\n *\n * @param amount_sats - Amount to deposit (positive integer)\n * @param reason - Human-readable reason (shown in notification)\n * @throws {L402Error} if payment times out or fails\n */\n async deposit(amount_sats: number, reason?: string): Promise<DepositStatus> {\n validateSats(amount_sats, 'amount_sats');\n const invoice = await this.createDeposit(amount_sats);\n\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 with payment instructions\n const enrichedInvoice: DepositInvoice = {\n ...invoice,\n message: [\n reason\n ? `⚡ Agent needs ${amount_sats} sats: ${reason}`\n : `⚡ Agent needs ${amount_sats} sats deposited`,\n '',\n `📱 Tap to pay: ${invoice.pay_url}`,\n '',\n `Or paste this invoice into any Lightning wallet:`,\n invoice.invoice,\n ].join('\\n'),\n };\n\n // Timeout-protect the callback (30s max)\n const callbackTimeout = 30_000;\n try {\n await Promise.race([\n this.onPaymentNeeded(enrichedInvoice),\n new Promise((_, reject) =>\n setTimeout(() => reject(new Error('onPaymentNeeded callback timed out after 30s')), callbackTimeout)\n ),\n ]);\n } catch (err: any) {\n // Log but don't fail — the invoice is still valid, just notification may have failed\n console.warn(`[satonomous] Payment notification warning: ${err.message}`);\n }\n\n // Poll for payment with exponential backoff\n if (this.paymentTimeoutMs === 0) {\n return { status: 'pending', amount_sats, paid_at: null };\n }\n\n const deadline = Date.now() + this.paymentTimeoutMs;\n let pollInterval = this.paymentPollIntervalMs;\n const maxInterval = 30_000; // cap at 30s\n\n while (Date.now() < deadline) {\n await new Promise(r => setTimeout(r, pollInterval));\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 // Exponential backoff: 5s → 7.5s → 11.25s → ... → 30s max\n pollInterval = Math.min(pollInterval * 1.5, maxInterval);\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 */\n async ensureBalance(minBalance: number, reason?: string): Promise<BalanceInfo> {\n validateSats(minBalance, 'minBalance');\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 if (amount_sats !== undefined) validateSats(amount_sats, 'amount_sats');\n return this.request('POST', '/api/v1/wallet/withdraw', amount_sats ? { amount_sats } : {});\n }\n\n // ─── Offers ──────────────────────────────────────────────────────────────\n\n async createOffer(params: CreateOfferParams): Promise<Offer> {\n if (!params.title || typeof params.title !== 'string') throw new Error('title is required');\n if (!params.service_type || typeof params.service_type !== 'string') throw new Error('service_type is required');\n validateSats(params.price_sats, 'price_sats');\n\n const { sla_minutes, dispute_window_minutes, ...rest } = params;\n return this.request('POST', '/api/v1/offers', {\n ...rest,\n terms: {\n sla_minutes: sla_minutes ?? 30,\n dispute_window_minutes: dispute_window_minutes ?? 1440,\n },\n });\n }\n\n async listOffers(): Promise<Offer[]> {\n const result = await this.request<{ offers: Offer[] }>('GET', '/api/v1/offers');\n return Array.isArray(result.offers) ? result.offers : [];\n }\n\n async getOffer(offerId: string): Promise<Offer> {\n validateId(offerId, 'offerId');\n return this.request('GET', `/api/v1/offers/${offerId}`);\n }\n\n async updateOffer(offerId: string, active: boolean): Promise<Offer> {\n validateId(offerId, 'offerId');\n if (typeof active !== 'boolean') throw new Error('active must be a boolean');\n return this.request('PATCH', `/api/v1/offers/${offerId}`, { active });\n }\n\n // ─── Contracts ───────────────────────────────────────────────────────────\n\n async acceptOffer(offerId: string): Promise<Contract> {\n validateId(offerId, 'offerId');\n return this.request('POST', '/api/v1/contracts', { offer_id: offerId });\n }\n\n async fundContract(contractId: string): Promise<FundResult> {\n validateId(contractId, 'contractId');\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) {\n if (filters.role !== 'buyer' && filters.role !== 'seller') {\n throw new Error(\"role must be 'buyer' or 'seller'\");\n }\n params.append('role', filters.role);\n }\n if (filters?.status) {\n if (typeof filters.status !== 'string') throw new Error('status must be a string');\n params.append('status', filters.status);\n }\n if (params.toString()) path += '?' + params.toString();\n const result = await this.request<{ contracts: Contract[] }>('GET', path);\n return Array.isArray(result.contracts) ? result.contracts : [];\n }\n\n async getContract(contractId: string): Promise<Contract> {\n validateId(contractId, 'contractId');\n return this.request('GET', `/api/v1/contracts/${contractId}`);\n }\n\n // ─── Delivery ────────────────────────────────────────────────────────────\n\n async submitDelivery(contractId: string, proofUrl: string, proofData?: any): Promise<Contract> {\n validateId(contractId, 'contractId');\n if (!proofUrl || typeof proofUrl !== 'string') throw new Error('proofUrl is required');\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 validateId(contractId, 'contractId');\n return this.request('POST', `/api/v1/contracts/${contractId}/confirm`, {});\n }\n\n async disputeDelivery(contractId: string, reason: string, evidenceUrl?: string): Promise<Contract> {\n validateId(contractId, 'contractId');\n if (!reason || typeof reason !== 'string') throw new Error('reason is required');\n return this.request('POST', `/api/v1/contracts/${contractId}/dispute`, {\n reason,\n evidence_url: evidenceUrl,\n });\n }\n\n // ─── Ledger ──────────────────────────────────────────────────────────────\n\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 !== undefined) {\n if (!Number.isInteger(limit) || limit < 1) throw new Error('limit must be a positive integer');\n params.append('limit', String(limit));\n }\n if (offset !== undefined) {\n if (!Number.isInteger(offset) || offset < 0) throw new Error('offset must be a non-negative integer');\n params.append('offset', String(offset));\n }\n if (params.toString()) path += '?' + params.toString();\n return this.request('GET', path);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACiBA,IAAM,UAAU;AAChB,IAAM,UAAU;AAEhB,SAAS,WAAW,OAAe,OAAqB;AACtD,MAAI,CAAC,SAAS,OAAO,UAAU,UAAU;AACvC,UAAM,IAAI,MAAM,GAAG,KAAK,cAAc;AAAA,EACxC;AACA,MAAI,CAAC,QAAQ,KAAK,KAAK,GAAG;AACxB,UAAM,IAAI,MAAM,GAAG,KAAK,+BAA+B,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE;AAAA,EAC7E;AACF;AAEA,SAAS,aAAa,OAAe,OAAqB;AACxD,MAAI,CAAC,SAAS,OAAO,UAAU,UAAU;AACvC,UAAM,IAAI,MAAM,GAAG,KAAK,cAAc;AAAA,EACxC;AACA,MAAI,CAAC,QAAQ,KAAK,KAAK,GAAG;AACxB,UAAM,IAAI,MAAM,GAAG,KAAK,mCAAmC;AAAA,EAC7D;AACF;AAEA,SAAS,aAAa,QAAgB,OAAqB;AACzD,MAAI,OAAO,WAAW,YAAY,CAAC,OAAO,SAAS,MAAM,KAAK,CAAC,OAAO,UAAU,MAAM,KAAK,SAAS,GAAG;AACrG,UAAM,IAAI,MAAM,GAAG,KAAK,qCAAqC,MAAM,EAAE;AAAA,EACvE;AACF;AAEA,SAAS,YAAY,KAAmB;AACtC,MAAI;AACF,UAAM,SAAS,IAAI,IAAI,GAAG;AAC1B,QAAI,OAAO,aAAa,YAAY,OAAO,aAAa,SAAS;AAC/D,YAAM,IAAI,MAAM,kCAAkC;AAAA,IACpD;AAAA,EACF,SAAS,GAAQ;AACf,UAAM,IAAI,MAAM,oBAAoB,EAAE,OAAO,EAAE;AAAA,EACjD;AACF;AAEA,SAAS,aAAa,KAAmB;AACvC,QAAM,SAAS,IAAI,IAAI,GAAG;AAC1B,MAAI,OAAO,aAAa,UAAU;AAChC,UAAM,IAAI;AAAA,MACR,mEAAmE,OAAO,QAAQ;AAAA,IAEpF;AAAA,EACF;AACF;AAEA,SAAS,YAAY,KAAsB;AACzC,MAAI;AACF,UAAM,SAAS,IAAI,IAAI,GAAG;AAC1B,WAAO,OAAO,aAAa,eAAe,OAAO,aAAa,eAAe,OAAO,aAAa;AAAA,EACnG,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAIO,IAAM,YAAN,cAAwB,MAAM;AAAA,EAInC,YAAY,SAAiB,QAAgB,MAAe;AAC1D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AAAA,EACd;AACF;AAIO,IAAM,YAAN,MAAgB;AAAA,EAQrB,YAAY,SAA2B;AACrC,QAAI,CAAC,QAAQ,UAAU,OAAO,QAAQ,WAAW,UAAU;AACzD,YAAM,IAAI,MAAM,oBAAoB;AAAA,IACtC;AAEA,UAAM,SAAS,QAAQ,UAAU;AACjC,gBAAY,MAAM;AAGlB,QAAI,CAAC,YAAY,MAAM,GAAG;AACxB,mBAAa,MAAM;AAAA,IACrB;AAEA,SAAK,SAAS,QAAQ;AACtB,SAAK,SAAS,OAAO,QAAQ,QAAQ,EAAE;AACvC,SAAK,kBAAkB,QAAQ;AAC/B,SAAK,mBAAmB,QAAQ,oBAAoB;AACpD,SAAK,wBAAwB,KAAK,IAAI,QAAQ,yBAAyB,KAAO,GAAK;AAAA,EACrF;AAAA,EAEA,MAAc,QAAW,QAAgB,MAAc,MAAwB;AAC7E,UAAM,MAAM,GAAG,KAAK,MAAM,GAAG,IAAI;AACjC,UAAM,UAAuB;AAAA,MAC3B;AAAA,MACA,SAAS;AAAA,QACP,cAAc,KAAK;AAAA,QACnB,gBAAgB;AAAA,MAClB;AAAA,IACF;AAEA,QAAI,MAAM;AACR,cAAQ,OAAO,KAAK,UAAU,IAAI;AAAA,IACpC;AAEA,UAAM,MAAM,MAAM,MAAM,KAAK,OAAO;AAEpC,QAAI,CAAC,IAAI,IAAI;AACX,UAAI,WAAW,QAAQ,IAAI,MAAM;AACjC,UAAI;AAEJ,UAAI;AACF,cAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,YAAI,KAAK,SAAS,OAAO,KAAK,UAAU,UAAU;AAChD,qBAAW,KAAK;AAAA,QAClB;AACA,YAAI,KAAK,QAAQ,OAAO,KAAK,SAAS,UAAU;AAC9C,sBAAY,KAAK;AAAA,QACnB;AAAA,MACF,QAAQ;AAAA,MAER;AAEA,YAAM,IAAI,UAAU,UAAU,IAAI,QAAQ,SAAS;AAAA,IACrD;AAEA,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA,EAIA,aAAa,SAAS,SAMS;AAC7B,QAAI,CAAC,QAAQ,QAAQ,OAAO,QAAQ,SAAS,YAAY,QAAQ,KAAK,KAAK,EAAE,SAAS,GAAG;AACvF,YAAM,IAAI,MAAM,qCAAqC;AAAA,IACvD;AAEA,UAAM,UAAU,QAAQ,UAAU,kCAAkC,QAAQ,QAAQ,EAAE;AACtF,gBAAY,MAAM;AAClB,QAAI,CAAC,YAAY,MAAM,EAAG,cAAa,MAAM;AAE7C,UAAM,MAAM,GAAG,MAAM;AAErB,UAAM,MAAM,MAAM,MAAM,KAAK;AAAA,MAC3B,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU;AAAA,QACnB,MAAM,QAAQ,KAAK,KAAK;AAAA,QACxB,aAAa,QAAQ,aAAa,KAAK;AAAA,QACvC,aAAa,QAAQ,eAAe;AAAA,QACpC,mBAAmB,QAAQ,mBAAmB,KAAK;AAAA,MACrD,CAAC;AAAA,IACH,CAAC;AAED,QAAI,CAAC,IAAI,IAAI;AACX,UAAI,WAAW,QAAQ,IAAI,MAAM;AACjC,UAAI;AACF,cAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,YAAI,KAAK,SAAS,OAAO,KAAK,UAAU,SAAU,YAAW,KAAK;AAAA,MACpE,QAAQ;AAAA,MAER;AACA,YAAM,IAAI,UAAU,UAAU,IAAI,MAAM;AAAA,IAC1C;AAEA,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA,EAIA,MAAM,aAAmC;AACvC,WAAO,KAAK,QAAQ,OAAO,wBAAwB;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,cAAc,aAA8C;AAChE,iBAAa,aAAa,aAAa;AACvC,UAAM,SAAS,MAAM,KAAK,QAAwB,QAAQ,0BAA0B,EAAE,YAAY,CAAC;AACnG,WAAO;AAAA,MACL,GAAG;AAAA,MACH,SAAS,aAAa,OAAO,OAAO;AAAA,IACtC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,aAA6C;AAC9D,iBAAa,aAAa,aAAa;AACvC,WAAO,KAAK,QAAQ,OAAO,0BAA0B,WAAW,EAAE;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAM,QAAQ,aAAqB,QAAyC;AAC1E,iBAAa,aAAa,aAAa;AACvC,UAAM,UAAU,MAAM,KAAK,cAAc,WAAW;AAEpD,QAAI,CAAC,KAAK,iBAAiB;AACzB,YAAM,IAAI;AAAA,QACR,mBAAmB,WAAW,mBAClB,QAAQ,OAAO,oGAED,QAAQ,YAAY;AAAA,QAC9C;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAGA,UAAM,kBAAkC;AAAA,MACtC,GAAG;AAAA,MACH,SAAS;AAAA,QACP,SACI,sBAAiB,WAAW,UAAU,MAAM,KAC5C,sBAAiB,WAAW;AAAA,QAChC;AAAA,QACA,yBAAkB,QAAQ,OAAO;AAAA,QACjC;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,MACV,EAAE,KAAK,IAAI;AAAA,IACb;AAGA,UAAM,kBAAkB;AACxB,QAAI;AACF,YAAM,QAAQ,KAAK;AAAA,QACjB,KAAK,gBAAgB,eAAe;AAAA,QACpC,IAAI;AAAA,UAAQ,CAAC,GAAG,WACd,WAAW,MAAM,OAAO,IAAI,MAAM,8CAA8C,CAAC,GAAG,eAAe;AAAA,QACrG;AAAA,MACF,CAAC;AAAA,IACH,SAAS,KAAU;AAEjB,cAAQ,KAAK,8CAA8C,IAAI,OAAO,EAAE;AAAA,IAC1E;AAGA,QAAI,KAAK,qBAAqB,GAAG;AAC/B,aAAO,EAAE,QAAQ,WAAW,aAAa,SAAS,KAAK;AAAA,IACzD;AAEA,UAAM,WAAW,KAAK,IAAI,IAAI,KAAK;AACnC,QAAI,eAAe,KAAK;AACxB,UAAM,cAAc;AAEpB,WAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,YAAM,IAAI,QAAQ,OAAK,WAAW,GAAG,YAAY,CAAC;AAClD,YAAM,SAAS,MAAM,KAAK,aAAa,QAAQ,YAAY;AAC3D,UAAI,OAAO,WAAW,OAAQ,QAAO;AACrC,UAAI,OAAO,WAAW,WAAW;AAC/B,cAAM,IAAI,UAAU,0CAA0C,KAAK,iBAAiB;AAAA,MACtF;AAEA,qBAAe,KAAK,IAAI,eAAe,KAAK,WAAW;AAAA,IACzD;AAEA,UAAM,IAAI;AAAA,MACR,+BAA+B,KAAK,mBAAmB,GAAI,iEACF,QAAQ,YAAY;AAAA,MAC7E;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAc,YAAoB,QAAuC;AAC7E,iBAAa,YAAY,YAAY;AACrC,UAAM,UAAU,MAAM,KAAK,WAAW;AACtC,QAAI,QAAQ,gBAAgB,WAAY,QAAO;AAE/C,UAAM,SAAS,aAAa,QAAQ;AACpC,UAAM,KAAK,QAAQ,QAAQ,UAAU,QAAQ,MAAM,oBAAoB,QAAQ,YAAY,UAAU,UAAU,GAAG;AAClH,WAAO,KAAK,WAAW;AAAA,EACzB;AAAA,EAEA,MAAM,SAAS,aAA+C;AAC5D,QAAI,gBAAgB,OAAW,cAAa,aAAa,aAAa;AACtE,WAAO,KAAK,QAAQ,QAAQ,2BAA2B,cAAc,EAAE,YAAY,IAAI,CAAC,CAAC;AAAA,EAC3F;AAAA;AAAA,EAIA,MAAM,YAAY,QAA2C;AAC3D,QAAI,CAAC,OAAO,SAAS,OAAO,OAAO,UAAU,SAAU,OAAM,IAAI,MAAM,mBAAmB;AAC1F,QAAI,CAAC,OAAO,gBAAgB,OAAO,OAAO,iBAAiB,SAAU,OAAM,IAAI,MAAM,0BAA0B;AAC/G,iBAAa,OAAO,YAAY,YAAY;AAE5C,UAAM,EAAE,aAAa,wBAAwB,GAAG,KAAK,IAAI;AACzD,WAAO,KAAK,QAAQ,QAAQ,kBAAkB;AAAA,MAC5C,GAAG;AAAA,MACH,OAAO;AAAA,QACL,aAAa,eAAe;AAAA,QAC5B,wBAAwB,0BAA0B;AAAA,MACpD;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,aAA+B;AACnC,UAAM,SAAS,MAAM,KAAK,QAA6B,OAAO,gBAAgB;AAC9E,WAAO,MAAM,QAAQ,OAAO,MAAM,IAAI,OAAO,SAAS,CAAC;AAAA,EACzD;AAAA,EAEA,MAAM,SAAS,SAAiC;AAC9C,eAAW,SAAS,SAAS;AAC7B,WAAO,KAAK,QAAQ,OAAO,kBAAkB,OAAO,EAAE;AAAA,EACxD;AAAA,EAEA,MAAM,YAAY,SAAiB,QAAiC;AAClE,eAAW,SAAS,SAAS;AAC7B,QAAI,OAAO,WAAW,UAAW,OAAM,IAAI,MAAM,0BAA0B;AAC3E,WAAO,KAAK,QAAQ,SAAS,kBAAkB,OAAO,IAAI,EAAE,OAAO,CAAC;AAAA,EACtE;AAAA;AAAA,EAIA,MAAM,YAAY,SAAoC;AACpD,eAAW,SAAS,SAAS;AAC7B,WAAO,KAAK,QAAQ,QAAQ,qBAAqB,EAAE,UAAU,QAAQ,CAAC;AAAA,EACxE;AAAA,EAEA,MAAM,aAAa,YAAyC;AAC1D,eAAW,YAAY,YAAY;AACnC,WAAO,KAAK,QAAQ,QAAQ,qBAAqB,UAAU,SAAS,CAAC,CAAC;AAAA,EACxE;AAAA,EAEA,MAAM,cAAc,SAA+E;AACjG,QAAI,OAAO;AACX,UAAM,SAAS,IAAI,gBAAgB;AACnC,QAAI,SAAS,MAAM;AACjB,UAAI,QAAQ,SAAS,WAAW,QAAQ,SAAS,UAAU;AACzD,cAAM,IAAI,MAAM,kCAAkC;AAAA,MACpD;AACA,aAAO,OAAO,QAAQ,QAAQ,IAAI;AAAA,IACpC;AACA,QAAI,SAAS,QAAQ;AACnB,UAAI,OAAO,QAAQ,WAAW,SAAU,OAAM,IAAI,MAAM,yBAAyB;AACjF,aAAO,OAAO,UAAU,QAAQ,MAAM;AAAA,IACxC;AACA,QAAI,OAAO,SAAS,EAAG,SAAQ,MAAM,OAAO,SAAS;AACrD,UAAM,SAAS,MAAM,KAAK,QAAmC,OAAO,IAAI;AACxE,WAAO,MAAM,QAAQ,OAAO,SAAS,IAAI,OAAO,YAAY,CAAC;AAAA,EAC/D;AAAA,EAEA,MAAM,YAAY,YAAuC;AACvD,eAAW,YAAY,YAAY;AACnC,WAAO,KAAK,QAAQ,OAAO,qBAAqB,UAAU,EAAE;AAAA,EAC9D;AAAA;AAAA,EAIA,MAAM,eAAe,YAAoB,UAAkB,WAAoC;AAC7F,eAAW,YAAY,YAAY;AACnC,QAAI,CAAC,YAAY,OAAO,aAAa,SAAU,OAAM,IAAI,MAAM,sBAAsB;AACrF,WAAO,KAAK,QAAQ,QAAQ,qBAAqB,UAAU,YAAY;AAAA,MACrE,WAAW;AAAA,MACX,YAAY;AAAA,IACd,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,gBAAgB,YAAuC;AAC3D,eAAW,YAAY,YAAY;AACnC,WAAO,KAAK,QAAQ,QAAQ,qBAAqB,UAAU,YAAY,CAAC,CAAC;AAAA,EAC3E;AAAA,EAEA,MAAM,gBAAgB,YAAoB,QAAgB,aAAyC;AACjG,eAAW,YAAY,YAAY;AACnC,QAAI,CAAC,UAAU,OAAO,WAAW,SAAU,OAAM,IAAI,MAAM,oBAAoB;AAC/E,WAAO,KAAK,QAAQ,QAAQ,qBAAqB,UAAU,YAAY;AAAA,MACrE;AAAA,MACA,cAAc;AAAA,IAChB,CAAC;AAAA,EACH;AAAA;AAAA,EAIA,MAAM,UAAU,OAAgB,QAA4E;AAC1G,QAAI,OAAO;AACX,UAAM,SAAS,IAAI,gBAAgB;AACnC,QAAI,UAAU,QAAW;AACvB,UAAI,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,EAAG,OAAM,IAAI,MAAM,kCAAkC;AAC7F,aAAO,OAAO,SAAS,OAAO,KAAK,CAAC;AAAA,IACtC;AACA,QAAI,WAAW,QAAW;AACxB,UAAI,CAAC,OAAO,UAAU,MAAM,KAAK,SAAS,EAAG,OAAM,IAAI,MAAM,uCAAuC;AACpG,aAAO,OAAO,UAAU,OAAO,MAAM,CAAC;AAAA,IACxC;AACA,QAAI,OAAO,SAAS,EAAG,SAAQ,MAAM,OAAO,SAAS;AACrD,WAAO,KAAK,QAAQ,OAAO,IAAI;AAAA,EACjC;AACF;","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -176,45 +176,19 @@ declare class L402Agent {
|
|
|
176
176
|
/**
|
|
177
177
|
* High-level deposit: creates invoice, notifies human, waits for payment.
|
|
178
178
|
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
179
|
+
* AI agents don't have Lightning wallets. When an agent needs sats, it must
|
|
180
|
+
* ask a human to pay. This method handles the full flow:
|
|
181
|
+
* 1. Creates a Lightning invoice
|
|
182
|
+
* 2. Calls `onPaymentNeeded` to notify the human
|
|
183
|
+
* 3. Polls until paid or timeout
|
|
181
184
|
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
* 2. Calls `onPaymentNeeded` so you can notify the human (chat, email, UI)
|
|
185
|
-
* 3. Polls until the invoice is paid or times out
|
|
186
|
-
* 4. Returns the confirmed deposit status
|
|
187
|
-
*
|
|
188
|
-
* If `onPaymentNeeded` is not configured, throws with the invoice so the
|
|
189
|
-
* caller can handle notification manually.
|
|
190
|
-
*
|
|
191
|
-
* @param amount_sats - Amount to deposit
|
|
192
|
-
* @param reason - Human-readable reason (shown in the notification)
|
|
193
|
-
* @returns Confirmed deposit status
|
|
185
|
+
* @param amount_sats - Amount to deposit (positive integer)
|
|
186
|
+
* @param reason - Human-readable reason (shown in notification)
|
|
194
187
|
* @throws {L402Error} if payment times out or fails
|
|
195
|
-
*
|
|
196
|
-
* @example
|
|
197
|
-
* const agent = new L402Agent({
|
|
198
|
-
* apiKey: 'sk_...',
|
|
199
|
-
* onPaymentNeeded: async (invoice) => {
|
|
200
|
-
* // Send to Slack, Discord, Signal, email, etc.
|
|
201
|
-
* await notify(`Pay ${invoice.amount_sats} sats: ${invoice.invoice}`);
|
|
202
|
-
* },
|
|
203
|
-
* });
|
|
204
|
-
*
|
|
205
|
-
* // Agent requests funding and waits
|
|
206
|
-
* const deposit = await agent.deposit(1000, 'Need funds to accept code-review offer');
|
|
207
|
-
* console.log(`Funded! Balance: ${deposit.amount_sats} sats`);
|
|
208
188
|
*/
|
|
209
189
|
deposit(amount_sats: number, reason?: string): Promise<DepositStatus>;
|
|
210
190
|
/**
|
|
211
191
|
* Ensure the agent has at least `minBalance` sats. If not, request a deposit.
|
|
212
|
-
* Convenience method that checks balance first, then deposits the difference.
|
|
213
|
-
*
|
|
214
|
-
* @example
|
|
215
|
-
* // Before accepting an offer, make sure you can pay
|
|
216
|
-
* await agent.ensureBalance(500, 'Need funds for code-review contract');
|
|
217
|
-
* await agent.fundContract(contractId);
|
|
218
192
|
*/
|
|
219
193
|
ensureBalance(minBalance: number, reason?: string): Promise<BalanceInfo>;
|
|
220
194
|
withdraw(amount_sats?: number): Promise<WithdrawResult>;
|
package/dist/index.d.ts
CHANGED
|
@@ -176,45 +176,19 @@ declare class L402Agent {
|
|
|
176
176
|
/**
|
|
177
177
|
* High-level deposit: creates invoice, notifies human, waits for payment.
|
|
178
178
|
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
179
|
+
* AI agents don't have Lightning wallets. When an agent needs sats, it must
|
|
180
|
+
* ask a human to pay. This method handles the full flow:
|
|
181
|
+
* 1. Creates a Lightning invoice
|
|
182
|
+
* 2. Calls `onPaymentNeeded` to notify the human
|
|
183
|
+
* 3. Polls until paid or timeout
|
|
181
184
|
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
* 2. Calls `onPaymentNeeded` so you can notify the human (chat, email, UI)
|
|
185
|
-
* 3. Polls until the invoice is paid or times out
|
|
186
|
-
* 4. Returns the confirmed deposit status
|
|
187
|
-
*
|
|
188
|
-
* If `onPaymentNeeded` is not configured, throws with the invoice so the
|
|
189
|
-
* caller can handle notification manually.
|
|
190
|
-
*
|
|
191
|
-
* @param amount_sats - Amount to deposit
|
|
192
|
-
* @param reason - Human-readable reason (shown in the notification)
|
|
193
|
-
* @returns Confirmed deposit status
|
|
185
|
+
* @param amount_sats - Amount to deposit (positive integer)
|
|
186
|
+
* @param reason - Human-readable reason (shown in notification)
|
|
194
187
|
* @throws {L402Error} if payment times out or fails
|
|
195
|
-
*
|
|
196
|
-
* @example
|
|
197
|
-
* const agent = new L402Agent({
|
|
198
|
-
* apiKey: 'sk_...',
|
|
199
|
-
* onPaymentNeeded: async (invoice) => {
|
|
200
|
-
* // Send to Slack, Discord, Signal, email, etc.
|
|
201
|
-
* await notify(`Pay ${invoice.amount_sats} sats: ${invoice.invoice}`);
|
|
202
|
-
* },
|
|
203
|
-
* });
|
|
204
|
-
*
|
|
205
|
-
* // Agent requests funding and waits
|
|
206
|
-
* const deposit = await agent.deposit(1000, 'Need funds to accept code-review offer');
|
|
207
|
-
* console.log(`Funded! Balance: ${deposit.amount_sats} sats`);
|
|
208
188
|
*/
|
|
209
189
|
deposit(amount_sats: number, reason?: string): Promise<DepositStatus>;
|
|
210
190
|
/**
|
|
211
191
|
* Ensure the agent has at least `minBalance` sats. If not, request a deposit.
|
|
212
|
-
* Convenience method that checks balance first, then deposits the difference.
|
|
213
|
-
*
|
|
214
|
-
* @example
|
|
215
|
-
* // Before accepting an offer, make sure you can pay
|
|
216
|
-
* await agent.ensureBalance(500, 'Need funds for code-review contract');
|
|
217
|
-
* await agent.fundContract(contractId);
|
|
218
192
|
*/
|
|
219
193
|
ensureBalance(minBalance: number, reason?: string): Promise<BalanceInfo>;
|
|
220
194
|
withdraw(amount_sats?: number): Promise<WithdrawResult>;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,53 @@
|
|
|
1
1
|
// src/client.ts
|
|
2
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3
|
+
var HASH_RE = /^[0-9a-f]{64}$/i;
|
|
4
|
+
function validateId(value, label) {
|
|
5
|
+
if (!value || typeof value !== "string") {
|
|
6
|
+
throw new Error(`${label} is required`);
|
|
7
|
+
}
|
|
8
|
+
if (!UUID_RE.test(value)) {
|
|
9
|
+
throw new Error(`${label} must be a valid UUID, got: ${value.slice(0, 50)}`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function validateHash(value, label) {
|
|
13
|
+
if (!value || typeof value !== "string") {
|
|
14
|
+
throw new Error(`${label} is required`);
|
|
15
|
+
}
|
|
16
|
+
if (!HASH_RE.test(value)) {
|
|
17
|
+
throw new Error(`${label} must be a valid 64-char hex hash`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function validateSats(amount, label) {
|
|
21
|
+
if (typeof amount !== "number" || !Number.isFinite(amount) || !Number.isInteger(amount) || amount < 1) {
|
|
22
|
+
throw new Error(`${label} must be a positive integer, got: ${amount}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function validateUrl(url) {
|
|
26
|
+
try {
|
|
27
|
+
const parsed = new URL(url);
|
|
28
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
29
|
+
throw new Error("URL must use https:// or http://");
|
|
30
|
+
}
|
|
31
|
+
} catch (e) {
|
|
32
|
+
throw new Error(`Invalid API URL: ${e.message}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function enforceHttps(url) {
|
|
36
|
+
const parsed = new URL(url);
|
|
37
|
+
if (parsed.protocol !== "https:") {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`API URL must use HTTPS to protect your API key in transit. Got: ${parsed.protocol}//... Use http:// only for local development (localhost/127.0.0.1).`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function isLocalhost(url) {
|
|
44
|
+
try {
|
|
45
|
+
const parsed = new URL(url);
|
|
46
|
+
return parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "::1";
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
2
51
|
var L402Error = class extends Error {
|
|
3
52
|
constructor(message, status, code) {
|
|
4
53
|
super(message);
|
|
@@ -9,14 +58,19 @@ var L402Error = class extends Error {
|
|
|
9
58
|
};
|
|
10
59
|
var L402Agent = class {
|
|
11
60
|
constructor(options) {
|
|
12
|
-
if (!options.apiKey) {
|
|
61
|
+
if (!options.apiKey || typeof options.apiKey !== "string") {
|
|
13
62
|
throw new Error("apiKey is required");
|
|
14
63
|
}
|
|
64
|
+
const apiUrl = options.apiUrl ?? "https://l402gw.nosaltres2.info";
|
|
65
|
+
validateUrl(apiUrl);
|
|
66
|
+
if (!isLocalhost(apiUrl)) {
|
|
67
|
+
enforceHttps(apiUrl);
|
|
68
|
+
}
|
|
15
69
|
this.apiKey = options.apiKey;
|
|
16
|
-
this.apiUrl =
|
|
70
|
+
this.apiUrl = apiUrl.replace(/\/+$/, "");
|
|
17
71
|
this.onPaymentNeeded = options.onPaymentNeeded;
|
|
18
72
|
this.paymentTimeoutMs = options.paymentTimeoutMs ?? 3e5;
|
|
19
|
-
this.paymentPollIntervalMs = options.paymentPollIntervalMs ?? 5e3;
|
|
73
|
+
this.paymentPollIntervalMs = Math.max(options.paymentPollIntervalMs ?? 5e3, 1e3);
|
|
20
74
|
}
|
|
21
75
|
async request(method, path, body) {
|
|
22
76
|
const url = `${this.apiUrl}${path}`;
|
|
@@ -36,40 +90,49 @@ var L402Agent = class {
|
|
|
36
90
|
let errorCode;
|
|
37
91
|
try {
|
|
38
92
|
const data = await res.json();
|
|
39
|
-
|
|
40
|
-
|
|
93
|
+
if (data.error && typeof data.error === "string") {
|
|
94
|
+
errorMsg = data.error;
|
|
95
|
+
}
|
|
96
|
+
if (data.code && typeof data.code === "string") {
|
|
97
|
+
errorCode = data.code;
|
|
98
|
+
}
|
|
41
99
|
} catch {
|
|
42
100
|
}
|
|
43
101
|
throw new L402Error(errorMsg, res.status, errorCode);
|
|
44
102
|
}
|
|
45
103
|
return res.json();
|
|
46
104
|
}
|
|
47
|
-
// Static: register
|
|
105
|
+
// ─── Static: register ────────────────────────────────────────────────────
|
|
48
106
|
static async register(options) {
|
|
49
|
-
|
|
107
|
+
if (!options.name || typeof options.name !== "string" || options.name.trim().length < 1) {
|
|
108
|
+
throw new Error("name is required (non-empty string)");
|
|
109
|
+
}
|
|
110
|
+
const apiUrl = (options.apiUrl ?? "https://l402gw.nosaltres2.info").replace(/\/+$/, "");
|
|
111
|
+
validateUrl(apiUrl);
|
|
112
|
+
if (!isLocalhost(apiUrl)) enforceHttps(apiUrl);
|
|
50
113
|
const url = `${apiUrl}/api/v1/agents/register`;
|
|
51
114
|
const res = await fetch(url, {
|
|
52
115
|
method: "POST",
|
|
53
116
|
headers: { "Content-Type": "application/json" },
|
|
54
117
|
body: JSON.stringify({
|
|
55
|
-
name: options.name,
|
|
56
|
-
description: options.description,
|
|
118
|
+
name: options.name.trim(),
|
|
119
|
+
description: options.description?.trim(),
|
|
57
120
|
wallet_type: options.wallet_type ?? "custodial",
|
|
58
|
-
lightning_address: options.lightning_address
|
|
121
|
+
lightning_address: options.lightning_address?.trim()
|
|
59
122
|
})
|
|
60
123
|
});
|
|
61
124
|
if (!res.ok) {
|
|
62
125
|
let errorMsg = `HTTP ${res.status}`;
|
|
63
126
|
try {
|
|
64
127
|
const data = await res.json();
|
|
65
|
-
|
|
128
|
+
if (data.error && typeof data.error === "string") errorMsg = data.error;
|
|
66
129
|
} catch {
|
|
67
130
|
}
|
|
68
131
|
throw new L402Error(errorMsg, res.status);
|
|
69
132
|
}
|
|
70
133
|
return res.json();
|
|
71
134
|
}
|
|
72
|
-
// Wallet
|
|
135
|
+
// ─── Wallet ──────────────────────────────────────────────────────────────
|
|
73
136
|
async getBalance() {
|
|
74
137
|
return this.request("GET", "/api/v1/wallet/balance");
|
|
75
138
|
}
|
|
@@ -78,6 +141,7 @@ var L402Agent = class {
|
|
|
78
141
|
* Most agents should use `deposit()` instead, which notifies the human and waits.
|
|
79
142
|
*/
|
|
80
143
|
async createDeposit(amount_sats) {
|
|
144
|
+
validateSats(amount_sats, "amount_sats");
|
|
81
145
|
const result = await this.request("POST", "/api/v1/wallet/deposit", { amount_sats });
|
|
82
146
|
return {
|
|
83
147
|
...result,
|
|
@@ -88,42 +152,24 @@ var L402Agent = class {
|
|
|
88
152
|
* Check if a deposit invoice has been paid.
|
|
89
153
|
*/
|
|
90
154
|
async checkDeposit(paymentHash) {
|
|
155
|
+
validateHash(paymentHash, "paymentHash");
|
|
91
156
|
return this.request("GET", `/api/v1/wallet/deposit/${paymentHash}`);
|
|
92
157
|
}
|
|
93
158
|
/**
|
|
94
159
|
* High-level deposit: creates invoice, notifies human, waits for payment.
|
|
95
160
|
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
161
|
+
* AI agents don't have Lightning wallets. When an agent needs sats, it must
|
|
162
|
+
* ask a human to pay. This method handles the full flow:
|
|
163
|
+
* 1. Creates a Lightning invoice
|
|
164
|
+
* 2. Calls `onPaymentNeeded` to notify the human
|
|
165
|
+
* 3. Polls until paid or timeout
|
|
98
166
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* 2. Calls `onPaymentNeeded` so you can notify the human (chat, email, UI)
|
|
102
|
-
* 3. Polls until the invoice is paid or times out
|
|
103
|
-
* 4. Returns the confirmed deposit status
|
|
104
|
-
*
|
|
105
|
-
* If `onPaymentNeeded` is not configured, throws with the invoice so the
|
|
106
|
-
* caller can handle notification manually.
|
|
107
|
-
*
|
|
108
|
-
* @param amount_sats - Amount to deposit
|
|
109
|
-
* @param reason - Human-readable reason (shown in the notification)
|
|
110
|
-
* @returns Confirmed deposit status
|
|
167
|
+
* @param amount_sats - Amount to deposit (positive integer)
|
|
168
|
+
* @param reason - Human-readable reason (shown in notification)
|
|
111
169
|
* @throws {L402Error} if payment times out or fails
|
|
112
|
-
*
|
|
113
|
-
* @example
|
|
114
|
-
* const agent = new L402Agent({
|
|
115
|
-
* apiKey: 'sk_...',
|
|
116
|
-
* onPaymentNeeded: async (invoice) => {
|
|
117
|
-
* // Send to Slack, Discord, Signal, email, etc.
|
|
118
|
-
* await notify(`Pay ${invoice.amount_sats} sats: ${invoice.invoice}`);
|
|
119
|
-
* },
|
|
120
|
-
* });
|
|
121
|
-
*
|
|
122
|
-
* // Agent requests funding and waits
|
|
123
|
-
* const deposit = await agent.deposit(1000, 'Need funds to accept code-review offer');
|
|
124
|
-
* console.log(`Funded! Balance: ${deposit.amount_sats} sats`);
|
|
125
170
|
*/
|
|
126
171
|
async deposit(amount_sats, reason) {
|
|
172
|
+
validateSats(amount_sats, "amount_sats");
|
|
127
173
|
const invoice = await this.createDeposit(amount_sats);
|
|
128
174
|
if (!this.onPaymentNeeded) {
|
|
129
175
|
throw new L402Error(
|
|
@@ -143,18 +189,31 @@ var L402Agent = class {
|
|
|
143
189
|
invoice.invoice
|
|
144
190
|
].join("\n")
|
|
145
191
|
};
|
|
146
|
-
|
|
192
|
+
const callbackTimeout = 3e4;
|
|
193
|
+
try {
|
|
194
|
+
await Promise.race([
|
|
195
|
+
this.onPaymentNeeded(enrichedInvoice),
|
|
196
|
+
new Promise(
|
|
197
|
+
(_, reject) => setTimeout(() => reject(new Error("onPaymentNeeded callback timed out after 30s")), callbackTimeout)
|
|
198
|
+
)
|
|
199
|
+
]);
|
|
200
|
+
} catch (err) {
|
|
201
|
+
console.warn(`[satonomous] Payment notification warning: ${err.message}`);
|
|
202
|
+
}
|
|
147
203
|
if (this.paymentTimeoutMs === 0) {
|
|
148
204
|
return { status: "pending", amount_sats, paid_at: null };
|
|
149
205
|
}
|
|
150
206
|
const deadline = Date.now() + this.paymentTimeoutMs;
|
|
207
|
+
let pollInterval = this.paymentPollIntervalMs;
|
|
208
|
+
const maxInterval = 3e4;
|
|
151
209
|
while (Date.now() < deadline) {
|
|
152
|
-
await new Promise((r) => setTimeout(r,
|
|
210
|
+
await new Promise((r) => setTimeout(r, pollInterval));
|
|
153
211
|
const status = await this.checkDeposit(invoice.payment_hash);
|
|
154
212
|
if (status.status === "paid") return status;
|
|
155
213
|
if (status.status === "expired") {
|
|
156
214
|
throw new L402Error("Deposit invoice expired before payment", 408, "PAYMENT_EXPIRED");
|
|
157
215
|
}
|
|
216
|
+
pollInterval = Math.min(pollInterval * 1.5, maxInterval);
|
|
158
217
|
}
|
|
159
218
|
throw new L402Error(
|
|
160
219
|
`Payment not received within ${this.paymentTimeoutMs / 1e3}s. Invoice may still be valid \u2014 check with checkDeposit('${invoice.payment_hash}')`,
|
|
@@ -164,14 +223,9 @@ var L402Agent = class {
|
|
|
164
223
|
}
|
|
165
224
|
/**
|
|
166
225
|
* Ensure the agent has at least `minBalance` sats. If not, request a deposit.
|
|
167
|
-
* Convenience method that checks balance first, then deposits the difference.
|
|
168
|
-
*
|
|
169
|
-
* @example
|
|
170
|
-
* // Before accepting an offer, make sure you can pay
|
|
171
|
-
* await agent.ensureBalance(500, 'Need funds for code-review contract');
|
|
172
|
-
* await agent.fundContract(contractId);
|
|
173
226
|
*/
|
|
174
227
|
async ensureBalance(minBalance, reason) {
|
|
228
|
+
validateSats(minBalance, "minBalance");
|
|
175
229
|
const current = await this.getBalance();
|
|
176
230
|
if (current.balance_sats >= minBalance) return current;
|
|
177
231
|
const needed = minBalance - current.balance_sats;
|
|
@@ -179,63 +233,99 @@ var L402Agent = class {
|
|
|
179
233
|
return this.getBalance();
|
|
180
234
|
}
|
|
181
235
|
async withdraw(amount_sats) {
|
|
236
|
+
if (amount_sats !== void 0) validateSats(amount_sats, "amount_sats");
|
|
182
237
|
return this.request("POST", "/api/v1/wallet/withdraw", amount_sats ? { amount_sats } : {});
|
|
183
238
|
}
|
|
184
|
-
// Offers
|
|
239
|
+
// ─── Offers ──────────────────────────────────────────────────────────────
|
|
185
240
|
async createOffer(params) {
|
|
186
|
-
|
|
241
|
+
if (!params.title || typeof params.title !== "string") throw new Error("title is required");
|
|
242
|
+
if (!params.service_type || typeof params.service_type !== "string") throw new Error("service_type is required");
|
|
243
|
+
validateSats(params.price_sats, "price_sats");
|
|
244
|
+
const { sla_minutes, dispute_window_minutes, ...rest } = params;
|
|
245
|
+
return this.request("POST", "/api/v1/offers", {
|
|
246
|
+
...rest,
|
|
247
|
+
terms: {
|
|
248
|
+
sla_minutes: sla_minutes ?? 30,
|
|
249
|
+
dispute_window_minutes: dispute_window_minutes ?? 1440
|
|
250
|
+
}
|
|
251
|
+
});
|
|
187
252
|
}
|
|
188
253
|
async listOffers() {
|
|
189
254
|
const result = await this.request("GET", "/api/v1/offers");
|
|
190
|
-
return result.offers
|
|
255
|
+
return Array.isArray(result.offers) ? result.offers : [];
|
|
191
256
|
}
|
|
192
257
|
async getOffer(offerId) {
|
|
258
|
+
validateId(offerId, "offerId");
|
|
193
259
|
return this.request("GET", `/api/v1/offers/${offerId}`);
|
|
194
260
|
}
|
|
195
261
|
async updateOffer(offerId, active) {
|
|
262
|
+
validateId(offerId, "offerId");
|
|
263
|
+
if (typeof active !== "boolean") throw new Error("active must be a boolean");
|
|
196
264
|
return this.request("PATCH", `/api/v1/offers/${offerId}`, { active });
|
|
197
265
|
}
|
|
198
|
-
// Contracts
|
|
266
|
+
// ─── Contracts ───────────────────────────────────────────────────────────
|
|
199
267
|
async acceptOffer(offerId) {
|
|
268
|
+
validateId(offerId, "offerId");
|
|
200
269
|
return this.request("POST", "/api/v1/contracts", { offer_id: offerId });
|
|
201
270
|
}
|
|
202
271
|
async fundContract(contractId) {
|
|
272
|
+
validateId(contractId, "contractId");
|
|
203
273
|
return this.request("POST", `/api/v1/contracts/${contractId}/fund`, {});
|
|
204
274
|
}
|
|
205
275
|
async listContracts(filters) {
|
|
206
276
|
let path = "/api/v1/contracts";
|
|
207
277
|
const params = new URLSearchParams();
|
|
208
|
-
if (filters?.role)
|
|
209
|
-
|
|
278
|
+
if (filters?.role) {
|
|
279
|
+
if (filters.role !== "buyer" && filters.role !== "seller") {
|
|
280
|
+
throw new Error("role must be 'buyer' or 'seller'");
|
|
281
|
+
}
|
|
282
|
+
params.append("role", filters.role);
|
|
283
|
+
}
|
|
284
|
+
if (filters?.status) {
|
|
285
|
+
if (typeof filters.status !== "string") throw new Error("status must be a string");
|
|
286
|
+
params.append("status", filters.status);
|
|
287
|
+
}
|
|
210
288
|
if (params.toString()) path += "?" + params.toString();
|
|
211
289
|
const result = await this.request("GET", path);
|
|
212
|
-
return result.contracts
|
|
290
|
+
return Array.isArray(result.contracts) ? result.contracts : [];
|
|
213
291
|
}
|
|
214
292
|
async getContract(contractId) {
|
|
293
|
+
validateId(contractId, "contractId");
|
|
215
294
|
return this.request("GET", `/api/v1/contracts/${contractId}`);
|
|
216
295
|
}
|
|
217
|
-
// Delivery
|
|
296
|
+
// ─── Delivery ────────────────────────────────────────────────────────────
|
|
218
297
|
async submitDelivery(contractId, proofUrl, proofData) {
|
|
298
|
+
validateId(contractId, "contractId");
|
|
299
|
+
if (!proofUrl || typeof proofUrl !== "string") throw new Error("proofUrl is required");
|
|
219
300
|
return this.request("POST", `/api/v1/contracts/${contractId}/deliver`, {
|
|
220
301
|
proof_url: proofUrl,
|
|
221
302
|
proof_data: proofData
|
|
222
303
|
});
|
|
223
304
|
}
|
|
224
305
|
async confirmDelivery(contractId) {
|
|
306
|
+
validateId(contractId, "contractId");
|
|
225
307
|
return this.request("POST", `/api/v1/contracts/${contractId}/confirm`, {});
|
|
226
308
|
}
|
|
227
309
|
async disputeDelivery(contractId, reason, evidenceUrl) {
|
|
310
|
+
validateId(contractId, "contractId");
|
|
311
|
+
if (!reason || typeof reason !== "string") throw new Error("reason is required");
|
|
228
312
|
return this.request("POST", `/api/v1/contracts/${contractId}/dispute`, {
|
|
229
313
|
reason,
|
|
230
314
|
evidence_url: evidenceUrl
|
|
231
315
|
});
|
|
232
316
|
}
|
|
233
|
-
// Ledger
|
|
317
|
+
// ─── Ledger ──────────────────────────────────────────────────────────────
|
|
234
318
|
async getLedger(limit, offset) {
|
|
235
319
|
let path = "/api/v1/ledger";
|
|
236
320
|
const params = new URLSearchParams();
|
|
237
|
-
if (limit
|
|
238
|
-
|
|
321
|
+
if (limit !== void 0) {
|
|
322
|
+
if (!Number.isInteger(limit) || limit < 1) throw new Error("limit must be a positive integer");
|
|
323
|
+
params.append("limit", String(limit));
|
|
324
|
+
}
|
|
325
|
+
if (offset !== void 0) {
|
|
326
|
+
if (!Number.isInteger(offset) || offset < 0) throw new Error("offset must be a non-negative integer");
|
|
327
|
+
params.append("offset", String(offset));
|
|
328
|
+
}
|
|
239
329
|
if (params.toString()) path += "?" + params.toString();
|
|
240
330
|
return this.request("GET", path);
|
|
241
331
|
}
|
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 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 const result = await this.request<DepositInvoice>('POST', '/api/v1/wallet/deposit', { amount_sats });\n // Add pay_url for easy wallet linking\n return {\n ...result,\n pay_url: `lightning:${result.invoice}`,\n };\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 with clear payment instructions\n const enrichedInvoice: DepositInvoice = {\n ...invoice,\n message: [\n reason\n ? `⚡ Agent needs ${amount_sats} sats: ${reason}`\n : `⚡ Agent needs ${amount_sats} sats deposited`,\n '',\n `📱 Tap to pay: ${invoice.pay_url}`,\n '',\n `Or paste this invoice into any Lightning wallet:`,\n invoice.invoice,\n ].join('\\n'),\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":";AAeO,IAAM,YAAN,cAAwB,MAAM;AAAA,EAInC,YAAY,SAAiB,QAAgB,MAAe;AAC1D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,YAAN,MAAgB;AAAA,EAQrB,YAAY,SAA2B;AACrC,QAAI,CAAC,QAAQ,QAAQ;AACnB,YAAM,IAAI,MAAM,oBAAoB;AAAA,IACtC;AACA,SAAK,SAAS,QAAQ;AACtB,SAAK,SAAS,QAAQ,UAAU;AAChC,SAAK,kBAAkB,QAAQ;AAC/B,SAAK,mBAAmB,QAAQ,oBAAoB;AACpD,SAAK,wBAAwB,QAAQ,yBAAyB;AAAA,EAChE;AAAA,EAEA,MAAc,QAAW,QAAgB,MAAc,MAAwB;AAC7E,UAAM,MAAM,GAAG,KAAK,MAAM,GAAG,IAAI;AACjC,UAAM,UAAuB;AAAA,MAC3B;AAAA,MACA,SAAS;AAAA,QACP,cAAc,KAAK;AAAA,QACnB,gBAAgB;AAAA,MAClB;AAAA,IACF;AAEA,QAAI,MAAM;AACR,cAAQ,OAAO,KAAK,UAAU,IAAI;AAAA,IACpC;AAEA,UAAM,MAAM,MAAM,MAAM,KAAK,OAAO;AAEpC,QAAI,CAAC,IAAI,IAAI;AACX,UAAI,WAAW,QAAQ,IAAI,MAAM;AACjC,UAAI;AAEJ,UAAI;AACF,cAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,mBAAW,KAAK,SAAS;AACzB,oBAAY,KAAK;AAAA,MACnB,QAAQ;AAAA,MAER;AAEA,YAAM,IAAI,UAAU,UAAU,IAAI,QAAQ,SAAS;AAAA,IACrD;AAEA,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA,EAGA,aAAa,SAAS,SAMS;AAC7B,UAAM,SAAS,QAAQ,UAAU;AACjC,UAAM,MAAM,GAAG,MAAM;AAErB,UAAM,MAAM,MAAM,MAAM,KAAK;AAAA,MAC3B,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU;AAAA,QACnB,MAAM,QAAQ;AAAA,QACd,aAAa,QAAQ;AAAA,QACrB,aAAa,QAAQ,eAAe;AAAA,QACpC,mBAAmB,QAAQ;AAAA,MAC7B,CAAC;AAAA,IACH,CAAC;AAED,QAAI,CAAC,IAAI,IAAI;AACX,UAAI,WAAW,QAAQ,IAAI,MAAM;AACjC,UAAI;AACF,cAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,mBAAW,KAAK,SAAS;AAAA,MAC3B,QAAQ;AAAA,MAER;AACA,YAAM,IAAI,UAAU,UAAU,IAAI,MAAM;AAAA,IAC1C;AAEA,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA,EAGA,MAAM,aAAmC;AACvC,WAAO,KAAK,QAAQ,OAAO,wBAAwB;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,cAAc,aAA8C;AAChE,UAAM,SAAS,MAAM,KAAK,QAAwB,QAAQ,0BAA0B,EAAE,YAAY,CAAC;AAEnG,WAAO;AAAA,MACL,GAAG;AAAA,MACH,SAAS,aAAa,OAAO,OAAO;AAAA,IACtC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,aAA6C;AAC9D,WAAO,KAAK,QAAQ,OAAO,0BAA0B,WAAW,EAAE;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmCA,MAAM,QAAQ,aAAqB,QAAyC;AAC1E,UAAM,UAAU,MAAM,KAAK,cAAc,WAAW;AAGpD,QAAI,CAAC,KAAK,iBAAiB;AACzB,YAAM,IAAI;AAAA,QACR,mBAAmB,WAAW,mBAClB,QAAQ,OAAO,oGAED,QAAQ,YAAY;AAAA,QAC9C;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAGA,UAAM,kBAAkC;AAAA,MACtC,GAAG;AAAA,MACH,SAAS;AAAA,QACP,SACI,sBAAiB,WAAW,UAAU,MAAM,KAC5C,sBAAiB,WAAW;AAAA,QAChC;AAAA,QACA,yBAAkB,QAAQ,OAAO;AAAA,QACjC;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,MACV,EAAE,KAAK,IAAI;AAAA,IACb;AACA,UAAM,KAAK,gBAAgB,eAAe;AAG1C,QAAI,KAAK,qBAAqB,GAAG;AAC/B,aAAO,EAAE,QAAQ,WAAW,aAAa,SAAS,KAAK;AAAA,IACzD;AAEA,UAAM,WAAW,KAAK,IAAI,IAAI,KAAK;AACnC,WAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,YAAM,IAAI,QAAQ,OAAK,WAAW,GAAG,KAAK,qBAAqB,CAAC;AAChE,YAAM,SAAS,MAAM,KAAK,aAAa,QAAQ,YAAY;AAC3D,UAAI,OAAO,WAAW,OAAQ,QAAO;AACrC,UAAI,OAAO,WAAW,WAAW;AAC/B,cAAM,IAAI,UAAU,0CAA0C,KAAK,iBAAiB;AAAA,MACtF;AAAA,IACF;AAEA,UAAM,IAAI;AAAA,MACR,+BAA+B,KAAK,mBAAmB,GAAI,iEACF,QAAQ,YAAY;AAAA,MAC7E;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,cAAc,YAAoB,QAAuC;AAC7E,UAAM,UAAU,MAAM,KAAK,WAAW;AACtC,QAAI,QAAQ,gBAAgB,WAAY,QAAO;AAE/C,UAAM,SAAS,aAAa,QAAQ;AACpC,UAAM,KAAK,QAAQ,QAAQ,UAAU,QAAQ,MAAM,oBAAoB,QAAQ,YAAY,UAAU,UAAU,GAAG;AAClH,WAAO,KAAK,WAAW;AAAA,EACzB;AAAA,EAEA,MAAM,SAAS,aAA+C;AAC5D,WAAO,KAAK,QAAQ,QAAQ,2BAA2B,cAAc,EAAE,YAAY,IAAI,CAAC,CAAC;AAAA,EAC3F;AAAA;AAAA,EAGA,MAAM,YAAY,QAA2C;AAC3D,WAAO,KAAK,QAAQ,QAAQ,kBAAkB,MAAM;AAAA,EACtD;AAAA,EAEA,MAAM,aAA+B;AACnC,UAAM,SAAS,MAAM,KAAK,QAA6B,OAAO,gBAAgB;AAC9E,WAAO,OAAO,UAAU,CAAC;AAAA,EAC3B;AAAA,EAEA,MAAM,SAAS,SAAiC;AAC9C,WAAO,KAAK,QAAQ,OAAO,kBAAkB,OAAO,EAAE;AAAA,EACxD;AAAA,EAEA,MAAM,YAAY,SAAiB,QAAiC;AAClE,WAAO,KAAK,QAAQ,SAAS,kBAAkB,OAAO,IAAI,EAAE,OAAO,CAAC;AAAA,EACtE;AAAA;AAAA,EAGA,MAAM,YAAY,SAAoC;AACpD,WAAO,KAAK,QAAQ,QAAQ,qBAAqB,EAAE,UAAU,QAAQ,CAAC;AAAA,EACxE;AAAA,EAEA,MAAM,aAAa,YAAyC;AAC1D,WAAO,KAAK,QAAQ,QAAQ,qBAAqB,UAAU,SAAS,CAAC,CAAC;AAAA,EACxE;AAAA,EAEA,MAAM,cAAc,SAA+E;AACjG,QAAI,OAAO;AACX,UAAM,SAAS,IAAI,gBAAgB;AACnC,QAAI,SAAS,KAAM,QAAO,OAAO,QAAQ,QAAQ,IAAI;AACrD,QAAI,SAAS,OAAQ,QAAO,OAAO,UAAU,QAAQ,MAAM;AAC3D,QAAI,OAAO,SAAS,EAAG,SAAQ,MAAM,OAAO,SAAS;AACrD,UAAM,SAAS,MAAM,KAAK,QAAmC,OAAO,IAAI;AACxE,WAAO,OAAO,aAAa,CAAC;AAAA,EAC9B;AAAA,EAEA,MAAM,YAAY,YAAuC;AACvD,WAAO,KAAK,QAAQ,OAAO,qBAAqB,UAAU,EAAE;AAAA,EAC9D;AAAA;AAAA,EAGA,MAAM,eAAe,YAAoB,UAAkB,WAAoC;AAC7F,WAAO,KAAK,QAAQ,QAAQ,qBAAqB,UAAU,YAAY;AAAA,MACrE,WAAW;AAAA,MACX,YAAY;AAAA,IACd,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,gBAAgB,YAAuC;AAC3D,WAAO,KAAK,QAAQ,QAAQ,qBAAqB,UAAU,YAAY,CAAC,CAAC;AAAA,EAC3E;AAAA,EAEA,MAAM,gBAAgB,YAAoB,QAAgB,aAAyC;AACjG,WAAO,KAAK,QAAQ,QAAQ,qBAAqB,UAAU,YAAY;AAAA,MACrE;AAAA,MACA,cAAc;AAAA,IAChB,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,UAAU,OAAgB,QAA4E;AAC1G,QAAI,OAAO;AACX,UAAM,SAAS,IAAI,gBAAgB;AACnC,QAAI,MAAO,QAAO,OAAO,SAAS,OAAO,KAAK,CAAC;AAC/C,QAAI,OAAQ,QAAO,OAAO,UAAU,OAAO,MAAM,CAAC;AAClD,QAAI,OAAO,SAAS,EAAG,SAAQ,MAAM,OAAO,SAAS;AACrD,WAAO,KAAK,QAAQ,OAAO,IAAI;AAAA,EACjC;AACF;","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\n// ─── Validation helpers ──────────────────────────────────────────────────────\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst HASH_RE = /^[0-9a-f]{64}$/i;\n\nfunction validateId(value: string, label: string): void {\n if (!value || typeof value !== 'string') {\n throw new Error(`${label} is required`);\n }\n if (!UUID_RE.test(value)) {\n throw new Error(`${label} must be a valid UUID, got: ${value.slice(0, 50)}`);\n }\n}\n\nfunction validateHash(value: string, label: string): void {\n if (!value || typeof value !== 'string') {\n throw new Error(`${label} is required`);\n }\n if (!HASH_RE.test(value)) {\n throw new Error(`${label} must be a valid 64-char hex hash`);\n }\n}\n\nfunction validateSats(amount: number, label: string): void {\n if (typeof amount !== 'number' || !Number.isFinite(amount) || !Number.isInteger(amount) || amount < 1) {\n throw new Error(`${label} must be a positive integer, got: ${amount}`);\n }\n}\n\nfunction validateUrl(url: string): void {\n try {\n const parsed = new URL(url);\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {\n throw new Error('URL must use https:// or http://');\n }\n } catch (e: any) {\n throw new Error(`Invalid API URL: ${e.message}`);\n }\n}\n\nfunction enforceHttps(url: string): void {\n const parsed = new URL(url);\n if (parsed.protocol !== 'https:') {\n throw new Error(\n `API URL must use HTTPS to protect your API key in transit. Got: ${parsed.protocol}//... ` +\n `Use http:// only for local development (localhost/127.0.0.1).`\n );\n }\n}\n\nfunction isLocalhost(url: string): boolean {\n try {\n const parsed = new URL(url);\n return parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname === '::1';\n } catch {\n return false;\n }\n}\n\n// ─── Error class ─────────────────────────────────────────────────────────────\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\n// ─── Main client ─────────────────────────────────────────────────────────────\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 || typeof options.apiKey !== 'string') {\n throw new Error('apiKey is required');\n }\n\n const apiUrl = options.apiUrl ?? 'https://l402gw.nosaltres2.info';\n validateUrl(apiUrl);\n\n // Enforce HTTPS unless localhost (development)\n if (!isLocalhost(apiUrl)) {\n enforceHttps(apiUrl);\n }\n\n this.apiKey = options.apiKey;\n this.apiUrl = apiUrl.replace(/\\/+$/, ''); // strip trailing slashes\n this.onPaymentNeeded = options.onPaymentNeeded;\n this.paymentTimeoutMs = options.paymentTimeoutMs ?? 300_000;\n this.paymentPollIntervalMs = Math.max(options.paymentPollIntervalMs ?? 5_000, 1_000); // min 1s\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 if (data.error && typeof data.error === 'string') {\n errorMsg = data.error;\n }\n if (data.code && typeof data.code === 'string') {\n errorCode = data.code;\n }\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 ────────────────────────────────────────────────────\n\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 if (!options.name || typeof options.name !== 'string' || options.name.trim().length < 1) {\n throw new Error('name is required (non-empty string)');\n }\n\n const apiUrl = (options.apiUrl ?? 'https://l402gw.nosaltres2.info').replace(/\\/+$/, '');\n validateUrl(apiUrl);\n if (!isLocalhost(apiUrl)) enforceHttps(apiUrl);\n\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.trim(),\n description: options.description?.trim(),\n wallet_type: options.wallet_type ?? 'custodial',\n lightning_address: options.lightning_address?.trim(),\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 if (data.error && typeof data.error === 'string') errorMsg = data.error;\n } catch {\n // Use default\n }\n throw new L402Error(errorMsg, res.status);\n }\n\n return res.json() as Promise<AgentRegistration>;\n }\n\n // ─── Wallet ──────────────────────────────────────────────────────────────\n\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 validateSats(amount_sats, 'amount_sats');\n const result = await this.request<DepositInvoice>('POST', '/api/v1/wallet/deposit', { amount_sats });\n return {\n ...result,\n pay_url: `lightning:${result.invoice}`,\n };\n }\n\n /**\n * Check if a deposit invoice has been paid.\n */\n async checkDeposit(paymentHash: string): Promise<DepositStatus> {\n validateHash(paymentHash, 'paymentHash');\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 * AI agents don't have Lightning wallets. When an agent needs sats, it must\n * ask a human to pay. This method handles the full flow:\n * 1. Creates a Lightning invoice\n * 2. Calls `onPaymentNeeded` to notify the human\n * 3. Polls until paid or timeout\n *\n * @param amount_sats - Amount to deposit (positive integer)\n * @param reason - Human-readable reason (shown in notification)\n * @throws {L402Error} if payment times out or fails\n */\n async deposit(amount_sats: number, reason?: string): Promise<DepositStatus> {\n validateSats(amount_sats, 'amount_sats');\n const invoice = await this.createDeposit(amount_sats);\n\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 with payment instructions\n const enrichedInvoice: DepositInvoice = {\n ...invoice,\n message: [\n reason\n ? `⚡ Agent needs ${amount_sats} sats: ${reason}`\n : `⚡ Agent needs ${amount_sats} sats deposited`,\n '',\n `📱 Tap to pay: ${invoice.pay_url}`,\n '',\n `Or paste this invoice into any Lightning wallet:`,\n invoice.invoice,\n ].join('\\n'),\n };\n\n // Timeout-protect the callback (30s max)\n const callbackTimeout = 30_000;\n try {\n await Promise.race([\n this.onPaymentNeeded(enrichedInvoice),\n new Promise((_, reject) =>\n setTimeout(() => reject(new Error('onPaymentNeeded callback timed out after 30s')), callbackTimeout)\n ),\n ]);\n } catch (err: any) {\n // Log but don't fail — the invoice is still valid, just notification may have failed\n console.warn(`[satonomous] Payment notification warning: ${err.message}`);\n }\n\n // Poll for payment with exponential backoff\n if (this.paymentTimeoutMs === 0) {\n return { status: 'pending', amount_sats, paid_at: null };\n }\n\n const deadline = Date.now() + this.paymentTimeoutMs;\n let pollInterval = this.paymentPollIntervalMs;\n const maxInterval = 30_000; // cap at 30s\n\n while (Date.now() < deadline) {\n await new Promise(r => setTimeout(r, pollInterval));\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 // Exponential backoff: 5s → 7.5s → 11.25s → ... → 30s max\n pollInterval = Math.min(pollInterval * 1.5, maxInterval);\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 */\n async ensureBalance(minBalance: number, reason?: string): Promise<BalanceInfo> {\n validateSats(minBalance, 'minBalance');\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 if (amount_sats !== undefined) validateSats(amount_sats, 'amount_sats');\n return this.request('POST', '/api/v1/wallet/withdraw', amount_sats ? { amount_sats } : {});\n }\n\n // ─── Offers ──────────────────────────────────────────────────────────────\n\n async createOffer(params: CreateOfferParams): Promise<Offer> {\n if (!params.title || typeof params.title !== 'string') throw new Error('title is required');\n if (!params.service_type || typeof params.service_type !== 'string') throw new Error('service_type is required');\n validateSats(params.price_sats, 'price_sats');\n\n const { sla_minutes, dispute_window_minutes, ...rest } = params;\n return this.request('POST', '/api/v1/offers', {\n ...rest,\n terms: {\n sla_minutes: sla_minutes ?? 30,\n dispute_window_minutes: dispute_window_minutes ?? 1440,\n },\n });\n }\n\n async listOffers(): Promise<Offer[]> {\n const result = await this.request<{ offers: Offer[] }>('GET', '/api/v1/offers');\n return Array.isArray(result.offers) ? result.offers : [];\n }\n\n async getOffer(offerId: string): Promise<Offer> {\n validateId(offerId, 'offerId');\n return this.request('GET', `/api/v1/offers/${offerId}`);\n }\n\n async updateOffer(offerId: string, active: boolean): Promise<Offer> {\n validateId(offerId, 'offerId');\n if (typeof active !== 'boolean') throw new Error('active must be a boolean');\n return this.request('PATCH', `/api/v1/offers/${offerId}`, { active });\n }\n\n // ─── Contracts ───────────────────────────────────────────────────────────\n\n async acceptOffer(offerId: string): Promise<Contract> {\n validateId(offerId, 'offerId');\n return this.request('POST', '/api/v1/contracts', { offer_id: offerId });\n }\n\n async fundContract(contractId: string): Promise<FundResult> {\n validateId(contractId, 'contractId');\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) {\n if (filters.role !== 'buyer' && filters.role !== 'seller') {\n throw new Error(\"role must be 'buyer' or 'seller'\");\n }\n params.append('role', filters.role);\n }\n if (filters?.status) {\n if (typeof filters.status !== 'string') throw new Error('status must be a string');\n params.append('status', filters.status);\n }\n if (params.toString()) path += '?' + params.toString();\n const result = await this.request<{ contracts: Contract[] }>('GET', path);\n return Array.isArray(result.contracts) ? result.contracts : [];\n }\n\n async getContract(contractId: string): Promise<Contract> {\n validateId(contractId, 'contractId');\n return this.request('GET', `/api/v1/contracts/${contractId}`);\n }\n\n // ─── Delivery ────────────────────────────────────────────────────────────\n\n async submitDelivery(contractId: string, proofUrl: string, proofData?: any): Promise<Contract> {\n validateId(contractId, 'contractId');\n if (!proofUrl || typeof proofUrl !== 'string') throw new Error('proofUrl is required');\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 validateId(contractId, 'contractId');\n return this.request('POST', `/api/v1/contracts/${contractId}/confirm`, {});\n }\n\n async disputeDelivery(contractId: string, reason: string, evidenceUrl?: string): Promise<Contract> {\n validateId(contractId, 'contractId');\n if (!reason || typeof reason !== 'string') throw new Error('reason is required');\n return this.request('POST', `/api/v1/contracts/${contractId}/dispute`, {\n reason,\n evidence_url: evidenceUrl,\n });\n }\n\n // ─── Ledger ──────────────────────────────────────────────────────────────\n\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 !== undefined) {\n if (!Number.isInteger(limit) || limit < 1) throw new Error('limit must be a positive integer');\n params.append('limit', String(limit));\n }\n if (offset !== undefined) {\n if (!Number.isInteger(offset) || offset < 0) throw new Error('offset must be a non-negative integer');\n params.append('offset', String(offset));\n }\n if (params.toString()) path += '?' + params.toString();\n return this.request('GET', path);\n }\n}\n"],"mappings":"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","names":[]}
|