satonomous 0.2.2 → 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 +140 -57
- 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 +140 -57
- 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
|
-
*
|
|
125
|
-
*
|
|
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.
|
|
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
|
|
134
193
|
*
|
|
135
|
-
* @param amount_sats - Amount to deposit
|
|
136
|
-
* @param reason - Human-readable reason (shown in
|
|
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,10 +260,14 @@ 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) {
|
|
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");
|
|
213
271
|
const { sla_minutes, dispute_window_minutes, ...rest } = params;
|
|
214
272
|
return this.request("POST", "/api/v1/offers", {
|
|
215
273
|
...rest,
|
|
@@ -221,55 +279,80 @@ var L402Agent = class {
|
|
|
221
279
|
}
|
|
222
280
|
async listOffers() {
|
|
223
281
|
const result = await this.request("GET", "/api/v1/offers");
|
|
224
|
-
return result.offers
|
|
282
|
+
return Array.isArray(result.offers) ? result.offers : [];
|
|
225
283
|
}
|
|
226
284
|
async getOffer(offerId) {
|
|
285
|
+
validateId(offerId, "offerId");
|
|
227
286
|
return this.request("GET", `/api/v1/offers/${offerId}`);
|
|
228
287
|
}
|
|
229
288
|
async updateOffer(offerId, active) {
|
|
289
|
+
validateId(offerId, "offerId");
|
|
290
|
+
if (typeof active !== "boolean") throw new Error("active must be a boolean");
|
|
230
291
|
return this.request("PATCH", `/api/v1/offers/${offerId}`, { active });
|
|
231
292
|
}
|
|
232
|
-
// Contracts
|
|
293
|
+
// ─── Contracts ───────────────────────────────────────────────────────────
|
|
233
294
|
async acceptOffer(offerId) {
|
|
295
|
+
validateId(offerId, "offerId");
|
|
234
296
|
return this.request("POST", "/api/v1/contracts", { offer_id: offerId });
|
|
235
297
|
}
|
|
236
298
|
async fundContract(contractId) {
|
|
299
|
+
validateId(contractId, "contractId");
|
|
237
300
|
return this.request("POST", `/api/v1/contracts/${contractId}/fund`, {});
|
|
238
301
|
}
|
|
239
302
|
async listContracts(filters) {
|
|
240
303
|
let path = "/api/v1/contracts";
|
|
241
304
|
const params = new URLSearchParams();
|
|
242
|
-
if (filters?.role)
|
|
243
|
-
|
|
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
|
+
}
|
|
244
315
|
if (params.toString()) path += "?" + params.toString();
|
|
245
316
|
const result = await this.request("GET", path);
|
|
246
|
-
return result.contracts
|
|
317
|
+
return Array.isArray(result.contracts) ? result.contracts : [];
|
|
247
318
|
}
|
|
248
319
|
async getContract(contractId) {
|
|
320
|
+
validateId(contractId, "contractId");
|
|
249
321
|
return this.request("GET", `/api/v1/contracts/${contractId}`);
|
|
250
322
|
}
|
|
251
|
-
// Delivery
|
|
323
|
+
// ─── Delivery ────────────────────────────────────────────────────────────
|
|
252
324
|
async submitDelivery(contractId, proofUrl, proofData) {
|
|
325
|
+
validateId(contractId, "contractId");
|
|
326
|
+
if (!proofUrl || typeof proofUrl !== "string") throw new Error("proofUrl is required");
|
|
253
327
|
return this.request("POST", `/api/v1/contracts/${contractId}/deliver`, {
|
|
254
328
|
proof_url: proofUrl,
|
|
255
329
|
proof_data: proofData
|
|
256
330
|
});
|
|
257
331
|
}
|
|
258
332
|
async confirmDelivery(contractId) {
|
|
333
|
+
validateId(contractId, "contractId");
|
|
259
334
|
return this.request("POST", `/api/v1/contracts/${contractId}/confirm`, {});
|
|
260
335
|
}
|
|
261
336
|
async disputeDelivery(contractId, reason, evidenceUrl) {
|
|
337
|
+
validateId(contractId, "contractId");
|
|
338
|
+
if (!reason || typeof reason !== "string") throw new Error("reason is required");
|
|
262
339
|
return this.request("POST", `/api/v1/contracts/${contractId}/dispute`, {
|
|
263
340
|
reason,
|
|
264
341
|
evidence_url: evidenceUrl
|
|
265
342
|
});
|
|
266
343
|
}
|
|
267
|
-
// Ledger
|
|
344
|
+
// ─── Ledger ──────────────────────────────────────────────────────────────
|
|
268
345
|
async getLedger(limit, offset) {
|
|
269
346
|
let path = "/api/v1/ledger";
|
|
270
347
|
const params = new URLSearchParams();
|
|
271
|
-
if (limit
|
|
272
|
-
|
|
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
|
+
}
|
|
273
356
|
if (params.toString()) path += "?" + params.toString();
|
|
274
357
|
return this.request("GET", path);
|
|
275
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 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 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":"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","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
|
-
*
|
|
98
|
-
*
|
|
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.
|
|
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
|
|
107
166
|
*
|
|
108
|
-
* @param amount_sats - Amount to deposit
|
|
109
|
-
* @param reason - Human-readable reason (shown in
|
|
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,10 +233,14 @@ 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) {
|
|
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");
|
|
186
244
|
const { sla_minutes, dispute_window_minutes, ...rest } = params;
|
|
187
245
|
return this.request("POST", "/api/v1/offers", {
|
|
188
246
|
...rest,
|
|
@@ -194,55 +252,80 @@ var L402Agent = class {
|
|
|
194
252
|
}
|
|
195
253
|
async listOffers() {
|
|
196
254
|
const result = await this.request("GET", "/api/v1/offers");
|
|
197
|
-
return result.offers
|
|
255
|
+
return Array.isArray(result.offers) ? result.offers : [];
|
|
198
256
|
}
|
|
199
257
|
async getOffer(offerId) {
|
|
258
|
+
validateId(offerId, "offerId");
|
|
200
259
|
return this.request("GET", `/api/v1/offers/${offerId}`);
|
|
201
260
|
}
|
|
202
261
|
async updateOffer(offerId, active) {
|
|
262
|
+
validateId(offerId, "offerId");
|
|
263
|
+
if (typeof active !== "boolean") throw new Error("active must be a boolean");
|
|
203
264
|
return this.request("PATCH", `/api/v1/offers/${offerId}`, { active });
|
|
204
265
|
}
|
|
205
|
-
// Contracts
|
|
266
|
+
// ─── Contracts ───────────────────────────────────────────────────────────
|
|
206
267
|
async acceptOffer(offerId) {
|
|
268
|
+
validateId(offerId, "offerId");
|
|
207
269
|
return this.request("POST", "/api/v1/contracts", { offer_id: offerId });
|
|
208
270
|
}
|
|
209
271
|
async fundContract(contractId) {
|
|
272
|
+
validateId(contractId, "contractId");
|
|
210
273
|
return this.request("POST", `/api/v1/contracts/${contractId}/fund`, {});
|
|
211
274
|
}
|
|
212
275
|
async listContracts(filters) {
|
|
213
276
|
let path = "/api/v1/contracts";
|
|
214
277
|
const params = new URLSearchParams();
|
|
215
|
-
if (filters?.role)
|
|
216
|
-
|
|
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
|
+
}
|
|
217
288
|
if (params.toString()) path += "?" + params.toString();
|
|
218
289
|
const result = await this.request("GET", path);
|
|
219
|
-
return result.contracts
|
|
290
|
+
return Array.isArray(result.contracts) ? result.contracts : [];
|
|
220
291
|
}
|
|
221
292
|
async getContract(contractId) {
|
|
293
|
+
validateId(contractId, "contractId");
|
|
222
294
|
return this.request("GET", `/api/v1/contracts/${contractId}`);
|
|
223
295
|
}
|
|
224
|
-
// Delivery
|
|
296
|
+
// ─── Delivery ────────────────────────────────────────────────────────────
|
|
225
297
|
async submitDelivery(contractId, proofUrl, proofData) {
|
|
298
|
+
validateId(contractId, "contractId");
|
|
299
|
+
if (!proofUrl || typeof proofUrl !== "string") throw new Error("proofUrl is required");
|
|
226
300
|
return this.request("POST", `/api/v1/contracts/${contractId}/deliver`, {
|
|
227
301
|
proof_url: proofUrl,
|
|
228
302
|
proof_data: proofData
|
|
229
303
|
});
|
|
230
304
|
}
|
|
231
305
|
async confirmDelivery(contractId) {
|
|
306
|
+
validateId(contractId, "contractId");
|
|
232
307
|
return this.request("POST", `/api/v1/contracts/${contractId}/confirm`, {});
|
|
233
308
|
}
|
|
234
309
|
async disputeDelivery(contractId, reason, evidenceUrl) {
|
|
310
|
+
validateId(contractId, "contractId");
|
|
311
|
+
if (!reason || typeof reason !== "string") throw new Error("reason is required");
|
|
235
312
|
return this.request("POST", `/api/v1/contracts/${contractId}/dispute`, {
|
|
236
313
|
reason,
|
|
237
314
|
evidence_url: evidenceUrl
|
|
238
315
|
});
|
|
239
316
|
}
|
|
240
|
-
// Ledger
|
|
317
|
+
// ─── Ledger ──────────────────────────────────────────────────────────────
|
|
241
318
|
async getLedger(limit, offset) {
|
|
242
319
|
let path = "/api/v1/ledger";
|
|
243
320
|
const params = new URLSearchParams();
|
|
244
|
-
if (limit
|
|
245
|
-
|
|
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
|
+
}
|
|
246
329
|
if (params.toString()) path += "?" + params.toString();
|
|
247
330
|
return this.request("GET", path);
|
|
248
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 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 result.offers || [];\n }\n\n async getOffer(offerId: string): Promise<Offer> {\n return this.request('GET', `/api/v1/offers/${offerId}`);\n }\n\n async updateOffer(offerId: string, active: boolean): Promise<Offer> {\n return this.request('PATCH', `/api/v1/offers/${offerId}`, { active });\n }\n\n // Contracts\n async acceptOffer(offerId: string): Promise<Contract> {\n return this.request('POST', '/api/v1/contracts', { offer_id: offerId });\n }\n\n async fundContract(contractId: string): Promise<FundResult> {\n return this.request('POST', `/api/v1/contracts/${contractId}/fund`, {});\n }\n\n async listContracts(filters?: { role?: 'buyer' | 'seller'; status?: string }): Promise<Contract[]> {\n let path = '/api/v1/contracts';\n const params = new URLSearchParams();\n if (filters?.role) params.append('role', filters.role);\n if (filters?.status) params.append('status', filters.status);\n if (params.toString()) path += '?' + params.toString();\n const result = await this.request<{ contracts: Contract[] }>('GET', path);\n return result.contracts || [];\n }\n\n async getContract(contractId: string): Promise<Contract> {\n return this.request('GET', `/api/v1/contracts/${contractId}`);\n }\n\n // Delivery\n async submitDelivery(contractId: string, proofUrl: string, proofData?: any): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/deliver`, {\n proof_url: proofUrl,\n proof_data: proofData,\n });\n }\n\n async confirmDelivery(contractId: string): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/confirm`, {});\n }\n\n async disputeDelivery(contractId: string, reason: string, evidenceUrl?: string): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/dispute`, {\n reason,\n evidence_url: evidenceUrl,\n });\n }\n\n // Ledger\n async getLedger(limit?: number, offset?: number): Promise<{ balance_sats: number; entries: LedgerEntry[] }> {\n let path = '/api/v1/ledger';\n const params = new URLSearchParams();\n if (limit) params.append('limit', String(limit));\n if (offset) params.append('offset', String(offset));\n if (params.toString()) path += '?' + params.toString();\n return this.request('GET', path);\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type {\n L402AgentOptions,\n BalanceInfo,\n Offer,\n CreateOfferParams,\n Contract,\n FundResult,\n DepositInvoice,\n DepositStatus,\n LedgerEntry,\n WithdrawResult,\n AgentRegistration,\n PaymentNeededCallback,\n} from './types.js';\n\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":[]}
|