satonomous 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs 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.
188
+ * AI agents don't have Lightning wallets. When an agent needs sats, it must
189
+ * ask a human to pay. This method handles the full flow:
190
+ * 1. Creates a Lightning invoice
191
+ * 2. Calls `onPaymentNeeded` to notify the human
192
+ * 3. Polls until paid or timeout
125
193
  *
126
- * 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.
134
- *
135
- * @param amount_sats - Amount to deposit
136
- * @param reason - Human-readable reason (shown in the notification)
137
- * @returns Confirmed deposit status
194
+ * @param amount_sats - Amount to deposit (positive integer)
195
+ * @param reason - Human-readable reason (shown in notification)
138
196
  * @throws {L402Error} if payment times out or fails
139
- *
140
- * @example
141
- * const agent = new L402Agent({
142
- * apiKey: 'sk_...',
143
- * onPaymentNeeded: async (invoice) => {
144
- * // Send to Slack, Discord, Signal, email, etc.
145
- * await notify(`Pay ${invoice.amount_sats} sats: ${invoice.invoice}`);
146
- * },
147
- * });
148
- *
149
- * // Agent requests funding and waits
150
- * const deposit = await agent.deposit(1000, 'Need funds to accept code-review offer');
151
- * console.log(`Funded! Balance: ${deposit.amount_sats} sats`);
152
197
  */
153
198
  async deposit(amount_sats, reason) {
199
+ validateSats(amount_sats, "amount_sats");
154
200
  const invoice = await this.createDeposit(amount_sats);
155
201
  if (!this.onPaymentNeeded) {
156
202
  throw new L402Error(
@@ -170,18 +216,31 @@ var L402Agent = class {
170
216
  invoice.invoice
171
217
  ].join("\n")
172
218
  };
173
- 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,63 +260,99 @@ var L402Agent = class {
206
260
  return this.getBalance();
207
261
  }
208
262
  async withdraw(amount_sats) {
263
+ if (amount_sats !== void 0) validateSats(amount_sats, "amount_sats");
209
264
  return this.request("POST", "/api/v1/wallet/withdraw", amount_sats ? { amount_sats } : {});
210
265
  }
211
- // Offers
266
+ // ─── Offers ──────────────────────────────────────────────────────────────
212
267
  async createOffer(params) {
213
- return this.request("POST", "/api/v1/offers", 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");
271
+ const { sla_minutes, dispute_window_minutes, ...rest } = params;
272
+ return this.request("POST", "/api/v1/offers", {
273
+ ...rest,
274
+ terms: {
275
+ sla_minutes: sla_minutes ?? 30,
276
+ dispute_window_minutes: dispute_window_minutes ?? 1440
277
+ }
278
+ });
214
279
  }
215
280
  async listOffers() {
216
281
  const result = await this.request("GET", "/api/v1/offers");
217
- return result.offers || [];
282
+ return Array.isArray(result.offers) ? result.offers : [];
218
283
  }
219
284
  async getOffer(offerId) {
285
+ validateId(offerId, "offerId");
220
286
  return this.request("GET", `/api/v1/offers/${offerId}`);
221
287
  }
222
288
  async updateOffer(offerId, active) {
289
+ validateId(offerId, "offerId");
290
+ if (typeof active !== "boolean") throw new Error("active must be a boolean");
223
291
  return this.request("PATCH", `/api/v1/offers/${offerId}`, { active });
224
292
  }
225
- // Contracts
293
+ // ─── Contracts ───────────────────────────────────────────────────────────
226
294
  async acceptOffer(offerId) {
295
+ validateId(offerId, "offerId");
227
296
  return this.request("POST", "/api/v1/contracts", { offer_id: offerId });
228
297
  }
229
298
  async fundContract(contractId) {
299
+ validateId(contractId, "contractId");
230
300
  return this.request("POST", `/api/v1/contracts/${contractId}/fund`, {});
231
301
  }
232
302
  async listContracts(filters) {
233
303
  let path = "/api/v1/contracts";
234
304
  const params = new URLSearchParams();
235
- if (filters?.role) params.append("role", filters.role);
236
- 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
+ }
237
315
  if (params.toString()) path += "?" + params.toString();
238
316
  const result = await this.request("GET", path);
239
- return result.contracts || [];
317
+ return Array.isArray(result.contracts) ? result.contracts : [];
240
318
  }
241
319
  async getContract(contractId) {
320
+ validateId(contractId, "contractId");
242
321
  return this.request("GET", `/api/v1/contracts/${contractId}`);
243
322
  }
244
- // Delivery
323
+ // ─── Delivery ────────────────────────────────────────────────────────────
245
324
  async submitDelivery(contractId, proofUrl, proofData) {
325
+ validateId(contractId, "contractId");
326
+ if (!proofUrl || typeof proofUrl !== "string") throw new Error("proofUrl is required");
246
327
  return this.request("POST", `/api/v1/contracts/${contractId}/deliver`, {
247
328
  proof_url: proofUrl,
248
329
  proof_data: proofData
249
330
  });
250
331
  }
251
332
  async confirmDelivery(contractId) {
333
+ validateId(contractId, "contractId");
252
334
  return this.request("POST", `/api/v1/contracts/${contractId}/confirm`, {});
253
335
  }
254
336
  async disputeDelivery(contractId, reason, evidenceUrl) {
337
+ validateId(contractId, "contractId");
338
+ if (!reason || typeof reason !== "string") throw new Error("reason is required");
255
339
  return this.request("POST", `/api/v1/contracts/${contractId}/dispute`, {
256
340
  reason,
257
341
  evidence_url: evidenceUrl
258
342
  });
259
343
  }
260
- // Ledger
344
+ // ─── Ledger ──────────────────────────────────────────────────────────────
261
345
  async getLedger(limit, offset) {
262
346
  let path = "/api/v1/ledger";
263
347
  const params = new URLSearchParams();
264
- if (limit) params.append("limit", String(limit));
265
- 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
+ }
266
356
  if (params.toString()) path += "?" + params.toString();
267
357
  return this.request("GET", path);
268
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 return this.request('POST', '/api/v1/offers', params);\n }\n\n async listOffers(): Promise<Offer[]> {\n const result = await this.request<{ offers: Offer[] }>('GET', '/api/v1/offers');\n return result.offers || [];\n }\n\n async getOffer(offerId: string): Promise<Offer> {\n return this.request('GET', `/api/v1/offers/${offerId}`);\n }\n\n async updateOffer(offerId: string, active: boolean): Promise<Offer> {\n return this.request('PATCH', `/api/v1/offers/${offerId}`, { active });\n }\n\n // Contracts\n async acceptOffer(offerId: string): Promise<Contract> {\n return this.request('POST', '/api/v1/contracts', { offer_id: offerId });\n }\n\n async fundContract(contractId: string): Promise<FundResult> {\n return this.request('POST', `/api/v1/contracts/${contractId}/fund`, {});\n }\n\n async listContracts(filters?: { role?: 'buyer' | 'seller'; status?: string }): Promise<Contract[]> {\n let path = '/api/v1/contracts';\n const params = new URLSearchParams();\n if (filters?.role) params.append('role', filters.role);\n if (filters?.status) params.append('status', filters.status);\n if (params.toString()) path += '?' + params.toString();\n const result = await this.request<{ contracts: Contract[] }>('GET', path);\n return result.contracts || [];\n }\n\n async getContract(contractId: string): Promise<Contract> {\n return this.request('GET', `/api/v1/contracts/${contractId}`);\n }\n\n // Delivery\n async submitDelivery(contractId: string, proofUrl: string, proofData?: any): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/deliver`, {\n proof_url: proofUrl,\n proof_data: proofData,\n });\n }\n\n async confirmDelivery(contractId: string): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/confirm`, {});\n }\n\n async disputeDelivery(contractId: string, reason: string, evidenceUrl?: string): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/dispute`, {\n reason,\n evidence_url: evidenceUrl,\n });\n }\n\n // Ledger\n async getLedger(limit?: number, offset?: number): Promise<{ balance_sats: number; entries: LedgerEntry[] }> {\n let path = '/api/v1/ledger';\n const params = new URLSearchParams();\n if (limit) params.append('limit', String(limit));\n if (offset) params.append('offset', String(offset));\n if (params.toString()) path += '?' + params.toString();\n return this.request('GET', path);\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts"],"sourcesContent":["/**\n * satonomous — SDK for autonomous AI agents to earn and spend sats\n *\n * AI agents can't pay Lightning invoices — they don't have wallets.\n * This SDK bridges that gap: when an agent needs funds, it notifies\n * a human (via callback) and waits for payment.\n *\n * @example\n * ```ts\n * import { L402Agent } from 'satonomous';\n *\n * const agent = new L402Agent({\n * apiKey: 'sk_...',\n * // This is the key part: how the agent asks a human for money\n * onPaymentNeeded: async (invoice) => {\n * await sendSlackMessage(`⚡ Pay ${invoice.amount_sats} sats: ${invoice.invoice}`);\n * },\n * });\n *\n * // Agent ensures it has funds before working\n * await agent.ensureBalance(500, 'Need funds for code-review contract');\n * ```\n *\n * @module\n */\n\nexport { L402Agent, L402Error } from './client.js';\nexport type {\n L402AgentOptions,\n BalanceInfo,\n Offer,\n CreateOfferParams,\n Contract,\n FundResult,\n DepositInvoice,\n DepositStatus,\n LedgerEntry,\n WithdrawResult,\n AgentRegistration,\n PaymentNeededCallback,\n} from './types.js';\n","import type {\n L402AgentOptions,\n BalanceInfo,\n Offer,\n CreateOfferParams,\n Contract,\n FundResult,\n DepositInvoice,\n DepositStatus,\n LedgerEntry,\n WithdrawResult,\n AgentRegistration,\n PaymentNeededCallback,\n} from './types.js';\n\n// ─── Validation helpers ──────────────────────────────────────────────────────\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst HASH_RE = /^[0-9a-f]{64}$/i;\n\nfunction validateId(value: string, label: string): void {\n if (!value || typeof value !== 'string') {\n throw new Error(`${label} is required`);\n }\n if (!UUID_RE.test(value)) {\n throw new Error(`${label} must be a valid UUID, got: ${value.slice(0, 50)}`);\n }\n}\n\nfunction validateHash(value: string, label: string): void {\n if (!value || typeof value !== 'string') {\n throw new Error(`${label} is required`);\n }\n if (!HASH_RE.test(value)) {\n throw new Error(`${label} must be a valid 64-char hex hash`);\n }\n}\n\nfunction validateSats(amount: number, label: string): void {\n if (typeof amount !== 'number' || !Number.isFinite(amount) || !Number.isInteger(amount) || amount < 1) {\n throw new Error(`${label} must be a positive integer, got: ${amount}`);\n }\n}\n\nfunction validateUrl(url: string): void {\n try {\n const parsed = new URL(url);\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {\n throw new Error('URL must use https:// or http://');\n }\n } catch (e: any) {\n throw new Error(`Invalid API URL: ${e.message}`);\n }\n}\n\nfunction enforceHttps(url: string): void {\n const parsed = new URL(url);\n if (parsed.protocol !== 'https:') {\n throw new Error(\n `API URL must use HTTPS to protect your API key in transit. Got: ${parsed.protocol}//... ` +\n `Use http:// only for local development (localhost/127.0.0.1).`\n );\n }\n}\n\nfunction isLocalhost(url: string): boolean {\n try {\n const parsed = new URL(url);\n return parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname === '::1';\n } catch {\n return false;\n }\n}\n\n// ─── Error class ─────────────────────────────────────────────────────────────\n\nexport class L402Error extends Error {\n status: number;\n code?: string;\n\n constructor(message: string, status: number, code?: string) {\n super(message);\n this.name = 'L402Error';\n this.status = status;\n this.code = code;\n }\n}\n\n// ─── Main client ─────────────────────────────────────────────────────────────\n\nexport class L402Agent {\n private apiKey: string;\n private apiUrl: string;\n\n private onPaymentNeeded?: PaymentNeededCallback;\n private paymentTimeoutMs: number;\n private paymentPollIntervalMs: number;\n\n constructor(options: L402AgentOptions) {\n if (!options.apiKey || typeof options.apiKey !== 'string') {\n throw new Error('apiKey is required');\n }\n\n const apiUrl = options.apiUrl ?? 'https://l402gw.nosaltres2.info';\n validateUrl(apiUrl);\n\n // Enforce HTTPS unless localhost (development)\n if (!isLocalhost(apiUrl)) {\n enforceHttps(apiUrl);\n }\n\n this.apiKey = options.apiKey;\n this.apiUrl = apiUrl.replace(/\\/+$/, ''); // strip trailing slashes\n this.onPaymentNeeded = options.onPaymentNeeded;\n this.paymentTimeoutMs = options.paymentTimeoutMs ?? 300_000;\n this.paymentPollIntervalMs = Math.max(options.paymentPollIntervalMs ?? 5_000, 1_000); // min 1s\n }\n\n private async request<T>(method: string, path: string, body?: any): Promise<T> {\n const url = `${this.apiUrl}${path}`;\n const options: RequestInit = {\n method,\n headers: {\n 'X-L402-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n };\n\n if (body) {\n options.body = JSON.stringify(body);\n }\n\n const res = await fetch(url, options);\n\n if (!res.ok) {\n let errorMsg = `HTTP ${res.status}`;\n let errorCode: string | undefined;\n\n try {\n const data = await res.json() as { error?: string; code?: string };\n if (data.error && typeof data.error === 'string') {\n errorMsg = data.error;\n }\n if (data.code && typeof data.code === 'string') {\n errorCode = data.code;\n }\n } catch {\n // Use default error message\n }\n\n throw new L402Error(errorMsg, res.status, errorCode);\n }\n\n return res.json() as Promise<T>;\n }\n\n // ─── Static: register ────────────────────────────────────────────────────\n\n static async register(options: {\n name: string;\n description?: string;\n wallet_type?: 'custodial' | 'external';\n lightning_address?: string;\n apiUrl?: string;\n }): Promise<AgentRegistration> {\n if (!options.name || typeof options.name !== 'string' || options.name.trim().length < 1) {\n throw new Error('name is required (non-empty string)');\n }\n\n const apiUrl = (options.apiUrl ?? 'https://l402gw.nosaltres2.info').replace(/\\/+$/, '');\n validateUrl(apiUrl);\n if (!isLocalhost(apiUrl)) enforceHttps(apiUrl);\n\n const url = `${apiUrl}/api/v1/agents/register`;\n\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n name: options.name.trim(),\n description: options.description?.trim(),\n wallet_type: options.wallet_type ?? 'custodial',\n lightning_address: options.lightning_address?.trim(),\n }),\n });\n\n if (!res.ok) {\n let errorMsg = `HTTP ${res.status}`;\n try {\n const data = await res.json() as { error?: string };\n if (data.error && typeof data.error === 'string') errorMsg = data.error;\n } catch {\n // Use default\n }\n throw new L402Error(errorMsg, res.status);\n }\n\n return res.json() as Promise<AgentRegistration>;\n }\n\n // ─── Wallet ──────────────────────────────────────────────────────────────\n\n async getBalance(): Promise<BalanceInfo> {\n return this.request('GET', '/api/v1/wallet/balance');\n }\n\n /**\n * Low-level: create a deposit invoice. Returns the invoice for manual handling.\n * Most agents should use `deposit()` instead, which notifies the human and waits.\n */\n async createDeposit(amount_sats: number): Promise<DepositInvoice> {\n validateSats(amount_sats, 'amount_sats');\n const result = await this.request<DepositInvoice>('POST', '/api/v1/wallet/deposit', { amount_sats });\n return {\n ...result,\n pay_url: `lightning:${result.invoice}`,\n };\n }\n\n /**\n * Check if a deposit invoice has been paid.\n */\n async checkDeposit(paymentHash: string): Promise<DepositStatus> {\n validateHash(paymentHash, 'paymentHash');\n return this.request('GET', `/api/v1/wallet/deposit/${paymentHash}`);\n }\n\n /**\n * High-level deposit: creates invoice, notifies human, waits for payment.\n *\n * AI agents don't have Lightning wallets. When an agent needs sats, it must\n * ask a human to pay. This method handles the full flow:\n * 1. Creates a Lightning invoice\n * 2. Calls `onPaymentNeeded` to notify the human\n * 3. Polls until paid or timeout\n *\n * @param amount_sats - Amount to deposit (positive integer)\n * @param reason - Human-readable reason (shown in notification)\n * @throws {L402Error} if payment times out or fails\n */\n async deposit(amount_sats: number, reason?: string): Promise<DepositStatus> {\n validateSats(amount_sats, 'amount_sats');\n const invoice = await this.createDeposit(amount_sats);\n\n if (!this.onPaymentNeeded) {\n throw new L402Error(\n `Payment needed: ${amount_sats} sats. ` +\n `Invoice: ${invoice.invoice}. ` +\n `No onPaymentNeeded callback configured — pay this invoice manually ` +\n `and call checkDeposit('${invoice.payment_hash}') to confirm.`,\n 402,\n 'PAYMENT_NEEDED'\n );\n }\n\n // Notify the human with payment instructions\n const enrichedInvoice: DepositInvoice = {\n ...invoice,\n message: [\n reason\n ? `⚡ Agent needs ${amount_sats} sats: ${reason}`\n : `⚡ Agent needs ${amount_sats} sats deposited`,\n '',\n `📱 Tap to pay: ${invoice.pay_url}`,\n '',\n `Or paste this invoice into any Lightning wallet:`,\n invoice.invoice,\n ].join('\\n'),\n };\n\n // Timeout-protect the callback (30s max)\n const callbackTimeout = 30_000;\n try {\n await Promise.race([\n this.onPaymentNeeded(enrichedInvoice),\n new Promise((_, reject) =>\n setTimeout(() => reject(new Error('onPaymentNeeded callback timed out after 30s')), callbackTimeout)\n ),\n ]);\n } catch (err: any) {\n // Log but don't fail — the invoice is still valid, just notification may have failed\n console.warn(`[satonomous] Payment notification warning: ${err.message}`);\n }\n\n // Poll for payment with exponential backoff\n if (this.paymentTimeoutMs === 0) {\n return { status: 'pending', amount_sats, paid_at: null };\n }\n\n const deadline = Date.now() + this.paymentTimeoutMs;\n let pollInterval = this.paymentPollIntervalMs;\n const maxInterval = 30_000; // cap at 30s\n\n while (Date.now() < deadline) {\n await new Promise(r => setTimeout(r, pollInterval));\n const status = await this.checkDeposit(invoice.payment_hash);\n if (status.status === 'paid') return status;\n if (status.status === 'expired') {\n throw new L402Error('Deposit invoice expired before payment', 408, 'PAYMENT_EXPIRED');\n }\n // Exponential backoff: 5s → 7.5s → 11.25s → ... → 30s max\n pollInterval = Math.min(pollInterval * 1.5, maxInterval);\n }\n\n throw new L402Error(\n `Payment not received within ${this.paymentTimeoutMs / 1000}s. ` +\n `Invoice may still be valid — check with checkDeposit('${invoice.payment_hash}')`,\n 408,\n 'PAYMENT_TIMEOUT'\n );\n }\n\n /**\n * Ensure the agent has at least `minBalance` sats. If not, request a deposit.\n */\n async ensureBalance(minBalance: number, reason?: string): Promise<BalanceInfo> {\n validateSats(minBalance, 'minBalance');\n const current = await this.getBalance();\n if (current.balance_sats >= minBalance) return current;\n\n const needed = minBalance - current.balance_sats;\n await this.deposit(needed, reason ?? `Need ${needed} more sats (have ${current.balance_sats}, need ${minBalance})`);\n return this.getBalance();\n }\n\n async withdraw(amount_sats?: number): Promise<WithdrawResult> {\n if (amount_sats !== undefined) validateSats(amount_sats, 'amount_sats');\n return this.request('POST', '/api/v1/wallet/withdraw', amount_sats ? { amount_sats } : {});\n }\n\n // ─── Offers ──────────────────────────────────────────────────────────────\n\n async createOffer(params: CreateOfferParams): Promise<Offer> {\n if (!params.title || typeof params.title !== 'string') throw new Error('title is required');\n if (!params.service_type || typeof params.service_type !== 'string') throw new Error('service_type is required');\n validateSats(params.price_sats, 'price_sats');\n\n const { sla_minutes, dispute_window_minutes, ...rest } = params;\n return this.request('POST', '/api/v1/offers', {\n ...rest,\n terms: {\n sla_minutes: sla_minutes ?? 30,\n dispute_window_minutes: dispute_window_minutes ?? 1440,\n },\n });\n }\n\n async listOffers(): Promise<Offer[]> {\n const result = await this.request<{ offers: Offer[] }>('GET', '/api/v1/offers');\n return Array.isArray(result.offers) ? result.offers : [];\n }\n\n async getOffer(offerId: string): Promise<Offer> {\n validateId(offerId, 'offerId');\n return this.request('GET', `/api/v1/offers/${offerId}`);\n }\n\n async updateOffer(offerId: string, active: boolean): Promise<Offer> {\n validateId(offerId, 'offerId');\n if (typeof active !== 'boolean') throw new Error('active must be a boolean');\n return this.request('PATCH', `/api/v1/offers/${offerId}`, { active });\n }\n\n // ─── Contracts ───────────────────────────────────────────────────────────\n\n async acceptOffer(offerId: string): Promise<Contract> {\n validateId(offerId, 'offerId');\n return this.request('POST', '/api/v1/contracts', { offer_id: offerId });\n }\n\n async fundContract(contractId: string): Promise<FundResult> {\n validateId(contractId, 'contractId');\n return this.request('POST', `/api/v1/contracts/${contractId}/fund`, {});\n }\n\n async listContracts(filters?: { role?: 'buyer' | 'seller'; status?: string }): Promise<Contract[]> {\n let path = '/api/v1/contracts';\n const params = new URLSearchParams();\n if (filters?.role) {\n if (filters.role !== 'buyer' && filters.role !== 'seller') {\n throw new Error(\"role must be 'buyer' or 'seller'\");\n }\n params.append('role', filters.role);\n }\n if (filters?.status) {\n if (typeof filters.status !== 'string') throw new Error('status must be a string');\n params.append('status', filters.status);\n }\n if (params.toString()) path += '?' + params.toString();\n const result = await this.request<{ contracts: Contract[] }>('GET', path);\n return Array.isArray(result.contracts) ? result.contracts : [];\n }\n\n async getContract(contractId: string): Promise<Contract> {\n validateId(contractId, 'contractId');\n return this.request('GET', `/api/v1/contracts/${contractId}`);\n }\n\n // ─── Delivery ────────────────────────────────────────────────────────────\n\n async submitDelivery(contractId: string, proofUrl: string, proofData?: any): Promise<Contract> {\n validateId(contractId, 'contractId');\n if (!proofUrl || typeof proofUrl !== 'string') throw new Error('proofUrl is required');\n return this.request('POST', `/api/v1/contracts/${contractId}/deliver`, {\n proof_url: proofUrl,\n proof_data: proofData,\n });\n }\n\n async confirmDelivery(contractId: string): Promise<Contract> {\n validateId(contractId, 'contractId');\n return this.request('POST', `/api/v1/contracts/${contractId}/confirm`, {});\n }\n\n async disputeDelivery(contractId: string, reason: string, evidenceUrl?: string): Promise<Contract> {\n validateId(contractId, 'contractId');\n if (!reason || typeof reason !== 'string') throw new Error('reason is required');\n return this.request('POST', `/api/v1/contracts/${contractId}/dispute`, {\n reason,\n evidence_url: evidenceUrl,\n });\n }\n\n // ─── Ledger ──────────────────────────────────────────────────────────────\n\n async getLedger(limit?: number, offset?: number): Promise<{ balance_sats: number; entries: LedgerEntry[] }> {\n let path = '/api/v1/ledger';\n const params = new URLSearchParams();\n if (limit !== undefined) {\n if (!Number.isInteger(limit) || limit < 1) throw new Error('limit must be a positive integer');\n params.append('limit', String(limit));\n }\n if (offset !== undefined) {\n if (!Number.isInteger(offset) || offset < 0) throw new Error('offset must be a non-negative integer');\n params.append('offset', String(offset));\n }\n if (params.toString()) path += '?' + params.toString();\n return this.request('GET', path);\n }\n}\n"],"mappings":"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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.
161
+ * AI agents don't have Lightning wallets. When an agent needs sats, it must
162
+ * ask a human to pay. This method handles the full flow:
163
+ * 1. Creates a Lightning invoice
164
+ * 2. Calls `onPaymentNeeded` to notify the human
165
+ * 3. Polls until paid or timeout
98
166
  *
99
- * 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.
107
- *
108
- * @param amount_sats - Amount to deposit
109
- * @param reason - Human-readable reason (shown in the notification)
110
- * @returns Confirmed deposit status
167
+ * @param amount_sats - Amount to deposit (positive integer)
168
+ * @param reason - Human-readable reason (shown in notification)
111
169
  * @throws {L402Error} if payment times out or fails
112
- *
113
- * @example
114
- * const agent = new L402Agent({
115
- * apiKey: 'sk_...',
116
- * onPaymentNeeded: async (invoice) => {
117
- * // Send to Slack, Discord, Signal, email, etc.
118
- * await notify(`Pay ${invoice.amount_sats} sats: ${invoice.invoice}`);
119
- * },
120
- * });
121
- *
122
- * // Agent requests funding and waits
123
- * const deposit = await agent.deposit(1000, 'Need funds to accept code-review offer');
124
- * console.log(`Funded! Balance: ${deposit.amount_sats} sats`);
125
170
  */
126
171
  async deposit(amount_sats, reason) {
172
+ validateSats(amount_sats, "amount_sats");
127
173
  const invoice = await this.createDeposit(amount_sats);
128
174
  if (!this.onPaymentNeeded) {
129
175
  throw new L402Error(
@@ -143,18 +189,31 @@ var L402Agent = class {
143
189
  invoice.invoice
144
190
  ].join("\n")
145
191
  };
146
- 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,63 +233,99 @@ var L402Agent = class {
179
233
  return this.getBalance();
180
234
  }
181
235
  async withdraw(amount_sats) {
236
+ if (amount_sats !== void 0) validateSats(amount_sats, "amount_sats");
182
237
  return this.request("POST", "/api/v1/wallet/withdraw", amount_sats ? { amount_sats } : {});
183
238
  }
184
- // Offers
239
+ // ─── Offers ──────────────────────────────────────────────────────────────
185
240
  async createOffer(params) {
186
- return this.request("POST", "/api/v1/offers", 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");
244
+ const { sla_minutes, dispute_window_minutes, ...rest } = params;
245
+ return this.request("POST", "/api/v1/offers", {
246
+ ...rest,
247
+ terms: {
248
+ sla_minutes: sla_minutes ?? 30,
249
+ dispute_window_minutes: dispute_window_minutes ?? 1440
250
+ }
251
+ });
187
252
  }
188
253
  async listOffers() {
189
254
  const result = await this.request("GET", "/api/v1/offers");
190
- return result.offers || [];
255
+ return Array.isArray(result.offers) ? result.offers : [];
191
256
  }
192
257
  async getOffer(offerId) {
258
+ validateId(offerId, "offerId");
193
259
  return this.request("GET", `/api/v1/offers/${offerId}`);
194
260
  }
195
261
  async updateOffer(offerId, active) {
262
+ validateId(offerId, "offerId");
263
+ if (typeof active !== "boolean") throw new Error("active must be a boolean");
196
264
  return this.request("PATCH", `/api/v1/offers/${offerId}`, { active });
197
265
  }
198
- // Contracts
266
+ // ─── Contracts ───────────────────────────────────────────────────────────
199
267
  async acceptOffer(offerId) {
268
+ validateId(offerId, "offerId");
200
269
  return this.request("POST", "/api/v1/contracts", { offer_id: offerId });
201
270
  }
202
271
  async fundContract(contractId) {
272
+ validateId(contractId, "contractId");
203
273
  return this.request("POST", `/api/v1/contracts/${contractId}/fund`, {});
204
274
  }
205
275
  async listContracts(filters) {
206
276
  let path = "/api/v1/contracts";
207
277
  const params = new URLSearchParams();
208
- if (filters?.role) params.append("role", filters.role);
209
- 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
+ }
210
288
  if (params.toString()) path += "?" + params.toString();
211
289
  const result = await this.request("GET", path);
212
- return result.contracts || [];
290
+ return Array.isArray(result.contracts) ? result.contracts : [];
213
291
  }
214
292
  async getContract(contractId) {
293
+ validateId(contractId, "contractId");
215
294
  return this.request("GET", `/api/v1/contracts/${contractId}`);
216
295
  }
217
- // Delivery
296
+ // ─── Delivery ────────────────────────────────────────────────────────────
218
297
  async submitDelivery(contractId, proofUrl, proofData) {
298
+ validateId(contractId, "contractId");
299
+ if (!proofUrl || typeof proofUrl !== "string") throw new Error("proofUrl is required");
219
300
  return this.request("POST", `/api/v1/contracts/${contractId}/deliver`, {
220
301
  proof_url: proofUrl,
221
302
  proof_data: proofData
222
303
  });
223
304
  }
224
305
  async confirmDelivery(contractId) {
306
+ validateId(contractId, "contractId");
225
307
  return this.request("POST", `/api/v1/contracts/${contractId}/confirm`, {});
226
308
  }
227
309
  async disputeDelivery(contractId, reason, evidenceUrl) {
310
+ validateId(contractId, "contractId");
311
+ if (!reason || typeof reason !== "string") throw new Error("reason is required");
228
312
  return this.request("POST", `/api/v1/contracts/${contractId}/dispute`, {
229
313
  reason,
230
314
  evidence_url: evidenceUrl
231
315
  });
232
316
  }
233
- // Ledger
317
+ // ─── Ledger ──────────────────────────────────────────────────────────────
234
318
  async getLedger(limit, offset) {
235
319
  let path = "/api/v1/ledger";
236
320
  const params = new URLSearchParams();
237
- if (limit) params.append("limit", String(limit));
238
- 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
+ }
239
329
  if (params.toString()) path += "?" + params.toString();
240
330
  return this.request("GET", path);
241
331
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type {\n L402AgentOptions,\n BalanceInfo,\n Offer,\n CreateOfferParams,\n Contract,\n FundResult,\n DepositInvoice,\n DepositStatus,\n LedgerEntry,\n WithdrawResult,\n AgentRegistration,\n PaymentNeededCallback,\n} from './types.js';\n\nexport class L402Error extends Error {\n status: number;\n code?: string;\n\n constructor(message: string, status: number, code?: string) {\n super(message);\n this.name = 'L402Error';\n this.status = status;\n this.code = code;\n }\n}\n\nexport class L402Agent {\n private apiKey: string;\n private apiUrl: string;\n\n private onPaymentNeeded?: PaymentNeededCallback;\n private paymentTimeoutMs: number;\n private paymentPollIntervalMs: number;\n\n constructor(options: L402AgentOptions) {\n if (!options.apiKey) {\n throw new Error('apiKey is required');\n }\n this.apiKey = options.apiKey;\n this.apiUrl = options.apiUrl ?? 'https://l402gw.nosaltres2.info';\n this.onPaymentNeeded = options.onPaymentNeeded;\n this.paymentTimeoutMs = options.paymentTimeoutMs ?? 300_000;\n this.paymentPollIntervalMs = options.paymentPollIntervalMs ?? 5_000;\n }\n\n private async request<T>(method: string, path: string, body?: any): Promise<T> {\n const url = `${this.apiUrl}${path}`;\n const options: RequestInit = {\n method,\n headers: {\n 'X-L402-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n };\n\n if (body) {\n options.body = JSON.stringify(body);\n }\n\n const res = await fetch(url, options);\n\n if (!res.ok) {\n let errorMsg = `HTTP ${res.status}`;\n let errorCode: string | undefined;\n\n try {\n const data = await res.json() as { error?: string; code?: string };\n errorMsg = data.error || errorMsg;\n errorCode = data.code;\n } catch {\n // Use default error message\n }\n\n throw new L402Error(errorMsg, res.status, errorCode);\n }\n\n return res.json() as Promise<T>;\n }\n\n // Static: register a new agent (no auth needed)\n static async register(options: {\n name: string;\n description?: string;\n wallet_type?: 'custodial' | 'external';\n lightning_address?: string;\n apiUrl?: string;\n }): Promise<AgentRegistration> {\n const apiUrl = options.apiUrl ?? 'https://l402gw.nosaltres2.info';\n const url = `${apiUrl}/api/v1/agents/register`;\n\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n name: options.name,\n description: options.description,\n wallet_type: options.wallet_type ?? 'custodial',\n lightning_address: options.lightning_address,\n }),\n });\n\n if (!res.ok) {\n let errorMsg = `HTTP ${res.status}`;\n try {\n const data = await res.json() as { error?: string };\n errorMsg = data.error || errorMsg;\n } catch {\n // Use default error message\n }\n throw new L402Error(errorMsg, res.status);\n }\n\n return res.json() as Promise<AgentRegistration>;\n }\n\n // Wallet\n async getBalance(): Promise<BalanceInfo> {\n return this.request('GET', '/api/v1/wallet/balance');\n }\n\n /**\n * Low-level: create a deposit invoice. Returns the invoice for manual handling.\n * Most agents should use `deposit()` instead, which notifies the human and waits.\n */\n async createDeposit(amount_sats: number): Promise<DepositInvoice> {\n const result = await this.request<DepositInvoice>('POST', '/api/v1/wallet/deposit', { amount_sats });\n // Add pay_url for easy wallet linking\n return {\n ...result,\n pay_url: `lightning:${result.invoice}`,\n };\n }\n\n /**\n * Check if a deposit invoice has been paid.\n */\n async checkDeposit(paymentHash: string): Promise<DepositStatus> {\n return this.request('GET', `/api/v1/wallet/deposit/${paymentHash}`);\n }\n\n /**\n * High-level deposit: creates invoice, notifies human, waits for payment.\n *\n * WHY THIS EXISTS: AI agents don't have Lightning wallets. They can't pay\n * invoices. When an agent needs sats, it must ask a human to pay.\n *\n * This method:\n * 1. Creates a Lightning invoice for the requested amount\n * 2. Calls `onPaymentNeeded` so you can notify the human (chat, email, UI)\n * 3. Polls until the invoice is paid or times out\n * 4. Returns the confirmed deposit status\n *\n * If `onPaymentNeeded` is not configured, throws with the invoice so the\n * caller can handle notification manually.\n *\n * @param amount_sats - Amount to deposit\n * @param reason - Human-readable reason (shown in the notification)\n * @returns Confirmed deposit status\n * @throws {L402Error} if payment times out or fails\n *\n * @example\n * const agent = new L402Agent({\n * apiKey: 'sk_...',\n * onPaymentNeeded: async (invoice) => {\n * // Send to Slack, Discord, Signal, email, etc.\n * await notify(`Pay ${invoice.amount_sats} sats: ${invoice.invoice}`);\n * },\n * });\n *\n * // Agent requests funding and waits\n * const deposit = await agent.deposit(1000, 'Need funds to accept code-review offer');\n * console.log(`Funded! Balance: ${deposit.amount_sats} sats`);\n */\n async deposit(amount_sats: number, reason?: string): Promise<DepositStatus> {\n const invoice = await this.createDeposit(amount_sats);\n\n // If no callback, throw with invoice details so caller can handle it\n if (!this.onPaymentNeeded) {\n throw new L402Error(\n `Payment needed: ${amount_sats} sats. ` +\n `Invoice: ${invoice.invoice}. ` +\n `No onPaymentNeeded callback configured — pay this invoice manually ` +\n `and call checkDeposit('${invoice.payment_hash}') to confirm.`,\n 402,\n 'PAYMENT_NEEDED'\n );\n }\n\n // Notify the human with clear payment instructions\n const enrichedInvoice: DepositInvoice = {\n ...invoice,\n message: [\n reason\n ? `⚡ Agent needs ${amount_sats} sats: ${reason}`\n : `⚡ Agent needs ${amount_sats} sats deposited`,\n '',\n `📱 Tap to pay: ${invoice.pay_url}`,\n '',\n `Or paste this invoice into any Lightning wallet:`,\n invoice.invoice,\n ].join('\\n'),\n };\n await this.onPaymentNeeded(enrichedInvoice);\n\n // Poll for payment\n if (this.paymentTimeoutMs === 0) {\n return { status: 'pending', amount_sats, paid_at: null };\n }\n\n const deadline = Date.now() + this.paymentTimeoutMs;\n while (Date.now() < deadline) {\n await new Promise(r => setTimeout(r, this.paymentPollIntervalMs));\n const status = await this.checkDeposit(invoice.payment_hash);\n if (status.status === 'paid') return status;\n if (status.status === 'expired') {\n throw new L402Error('Deposit invoice expired before payment', 408, 'PAYMENT_EXPIRED');\n }\n }\n\n throw new L402Error(\n `Payment not received within ${this.paymentTimeoutMs / 1000}s. ` +\n `Invoice may still be valid — check with checkDeposit('${invoice.payment_hash}')`,\n 408,\n 'PAYMENT_TIMEOUT'\n );\n }\n\n /**\n * Ensure the agent has at least `minBalance` sats. If not, request a deposit.\n * Convenience method that checks balance first, then deposits the difference.\n *\n * @example\n * // Before accepting an offer, make sure you can pay\n * await agent.ensureBalance(500, 'Need funds for code-review contract');\n * await agent.fundContract(contractId);\n */\n async ensureBalance(minBalance: number, reason?: string): Promise<BalanceInfo> {\n const current = await this.getBalance();\n if (current.balance_sats >= minBalance) return current;\n\n const needed = minBalance - current.balance_sats;\n await this.deposit(needed, reason ?? `Need ${needed} more sats (have ${current.balance_sats}, need ${minBalance})`);\n return this.getBalance();\n }\n\n async withdraw(amount_sats?: number): Promise<WithdrawResult> {\n return this.request('POST', '/api/v1/wallet/withdraw', amount_sats ? { amount_sats } : {});\n }\n\n // Offers\n async createOffer(params: CreateOfferParams): Promise<Offer> {\n return this.request('POST', '/api/v1/offers', params);\n }\n\n async listOffers(): Promise<Offer[]> {\n const result = await this.request<{ offers: Offer[] }>('GET', '/api/v1/offers');\n return result.offers || [];\n }\n\n async getOffer(offerId: string): Promise<Offer> {\n return this.request('GET', `/api/v1/offers/${offerId}`);\n }\n\n async updateOffer(offerId: string, active: boolean): Promise<Offer> {\n return this.request('PATCH', `/api/v1/offers/${offerId}`, { active });\n }\n\n // Contracts\n async acceptOffer(offerId: string): Promise<Contract> {\n return this.request('POST', '/api/v1/contracts', { offer_id: offerId });\n }\n\n async fundContract(contractId: string): Promise<FundResult> {\n return this.request('POST', `/api/v1/contracts/${contractId}/fund`, {});\n }\n\n async listContracts(filters?: { role?: 'buyer' | 'seller'; status?: string }): Promise<Contract[]> {\n let path = '/api/v1/contracts';\n const params = new URLSearchParams();\n if (filters?.role) params.append('role', filters.role);\n if (filters?.status) params.append('status', filters.status);\n if (params.toString()) path += '?' + params.toString();\n const result = await this.request<{ contracts: Contract[] }>('GET', path);\n return result.contracts || [];\n }\n\n async getContract(contractId: string): Promise<Contract> {\n return this.request('GET', `/api/v1/contracts/${contractId}`);\n }\n\n // Delivery\n async submitDelivery(contractId: string, proofUrl: string, proofData?: any): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/deliver`, {\n proof_url: proofUrl,\n proof_data: proofData,\n });\n }\n\n async confirmDelivery(contractId: string): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/confirm`, {});\n }\n\n async disputeDelivery(contractId: string, reason: string, evidenceUrl?: string): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/dispute`, {\n reason,\n evidence_url: evidenceUrl,\n });\n }\n\n // Ledger\n async getLedger(limit?: number, offset?: number): Promise<{ balance_sats: number; entries: LedgerEntry[] }> {\n let path = '/api/v1/ledger';\n const params = new URLSearchParams();\n if (limit) params.append('limit', String(limit));\n if (offset) params.append('offset', String(offset));\n if (params.toString()) path += '?' + params.toString();\n return this.request('GET', path);\n }\n}\n"],"mappings":"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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.1",
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",