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 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 = options.apiUrl ?? "https://l402gw.nosaltres2.info";
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
- errorMsg = data.error || errorMsg;
67
- errorCode = data.code;
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 a new agent (no auth needed)
132
+ // ─── Static: register ────────────────────────────────────────────────────
75
133
  static async register(options) {
76
- const apiUrl = options.apiUrl ?? "https://l402gw.nosaltres2.info";
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
- errorMsg = data.error || errorMsg;
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
- * WHY THIS EXISTS: AI agents don't have Lightning wallets. They can't pay
124
- * invoices. When an agent needs sats, it must ask a human to pay.
125
- *
126
- * This method:
127
- * 1. Creates a Lightning invoice for the requested amount
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 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
- await this.onPaymentNeeded(enrichedInvoice);
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, this.paymentPollIntervalMs));
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) params.append("role", filters.role);
243
- if (filters?.status) params.append("status", filters.status);
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) params.append("limit", String(limit));
272
- if (offset) params.append("offset", String(offset));
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
  }
@@ -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
- * WHY THIS EXISTS: AI agents don't have Lightning wallets. They can't pay
180
- * invoices. When an agent needs sats, it must ask a human to pay.
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
- * This method:
183
- * 1. Creates a Lightning invoice for the requested amount
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
- * WHY THIS EXISTS: AI agents don't have Lightning wallets. They can't pay
180
- * invoices. When an agent needs sats, it must ask a human to pay.
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
- * This method:
183
- * 1. Creates a Lightning invoice for the requested amount
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 = options.apiUrl ?? "https://l402gw.nosaltres2.info";
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
- errorMsg = data.error || errorMsg;
40
- errorCode = data.code;
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 a new agent (no auth needed)
105
+ // ─── Static: register ────────────────────────────────────────────────────
48
106
  static async register(options) {
49
- const apiUrl = options.apiUrl ?? "https://l402gw.nosaltres2.info";
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
- errorMsg = data.error || errorMsg;
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
- * WHY THIS EXISTS: AI agents don't have Lightning wallets. They can't pay
97
- * invoices. When an agent needs sats, it must ask a human to pay.
98
- *
99
- * This method:
100
- * 1. Creates a Lightning invoice for the requested amount
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 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
- await this.onPaymentNeeded(enrichedInvoice);
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, this.paymentPollIntervalMs));
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) params.append("role", filters.role);
216
- if (filters?.status) params.append("status", filters.status);
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) params.append("limit", String(limit));
245
- if (offset) params.append("offset", String(offset));
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":";AAiBA,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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "satonomous",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "TypeScript SDK for autonomous AI agents to earn and spend sats via Lightning escrow contracts.",
5
5
  "keywords": [
6
6
  "l402",