satonomous 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,303 +1,185 @@
1
- # l402-agent
1
+ # satonomous
2
2
 
3
- TypeScript SDK for the **L402 Gateway API** Lightning paywall and agent-to-agent escrow service.
3
+ TypeScript SDK for autonomous AI agents to earn and spend sats via Lightning escrow contracts.
4
4
 
5
- ## What is it?
5
+ ## The Problem: AI Agents Can't Pay
6
6
 
7
- A lightweight HTTP client SDK for interacting with the L402 Gateway. Manage wallets, create and accept service offers, and execute escrow-secured contracts on the Lightning Network.
7
+ AI agents don't have Lightning wallets. When an agent needs sats to fund a contract, **it must ask a human to pay**. This SDK makes that explicit:
8
8
 
9
- ## Install
10
-
11
- ```bash
12
- npm install l402-agent
13
- ```
14
-
15
- ## Quick Example
9
+ ```typescript
10
+ import { L402Agent } from 'satonomous';
16
11
 
17
- ```ts
18
- import { L402Agent } from 'l402-agent';
19
-
20
- // 1. Register a new agent
21
- const reg = await L402Agent.register({
22
- name: 'MyService',
23
- description: 'A helpful service for humans',
24
- });
25
- const apiKey = reg.api_key;
26
- console.log('Registered:', reg.tenant_id);
27
-
28
- // 2. Create a client
29
- const agent = new L402Agent({ apiKey });
30
-
31
- // 3. Check balance
32
- const balance = await agent.getBalance();
33
- console.log('Balance:', balance.balance_sats, 'sats');
34
-
35
- // 4. Create a deposit invoice (to add sats)
36
- const invoice = await agent.createDeposit(10000);
37
- console.log('Pay this invoice:', invoice.invoice);
38
-
39
- // 5. Create an offer (publish a service for sale)
40
- const offer = await agent.createOffer({
41
- title: 'Data Analysis',
42
- description: 'I will analyze your dataset',
43
- price_sats: 1000,
44
- service_type: 'analysis',
45
- sla_minutes: 60,
46
- dispute_window_minutes: 1440,
12
+ const agent = new L402Agent({
13
+ apiKey: process.env.L402_API_KEY!,
14
+
15
+ // 👇 This is how your agent asks for money
16
+ onPaymentNeeded: async (invoice) => {
17
+ // Send to Slack, Discord, Signal, email — whatever reaches the human
18
+ await sendMessage(channel, [
19
+ `⚡ Agent needs ${invoice.amount_sats} sats`,
20
+ `Reason: ${invoice.message}`,
21
+ `Invoice: ${invoice.invoice}`,
22
+ ].join('\n'));
23
+ },
47
24
  });
48
- console.log('Offer created:', offer.id);
49
-
50
- // 6. Accept another agent's offer (become buyer)
51
- const contract = await agent.acceptOffer(someOfferId);
52
- console.log('Contract created:', contract.id);
53
-
54
- // 7. Fund the contract from your balance
55
- const funded = await agent.fundContract(contract.id);
56
- console.log('Contract funded:', funded.contract.status);
57
-
58
- // 8. Submit delivery proof (as seller)
59
- const delivered = await agent.submitDelivery(
60
- contractId,
61
- 'https://example.com/results.json',
62
- { analysis: 'results here' }
63
- );
64
- console.log('Delivery submitted:', delivered.status);
65
-
66
- // 9. Confirm delivery to release funds (as buyer)
67
- const confirmed = await agent.confirmDelivery(contractId);
68
- console.log('Funds released to seller:', confirmed.status);
69
-
70
- // 10. View ledger
71
- const ledger = await agent.getLedger(50, 0);
72
- console.log('Current balance:', ledger.balance_sats);
73
- console.log('Recent entries:', ledger.entries);
74
- ```
75
-
76
- ## API Reference
77
25
 
78
- ### Constructor
26
+ // Agent ensures it has funds — notifies human if balance is low
27
+ await agent.ensureBalance(500, 'Need funds to accept a code-review contract');
79
28
 
80
- ```ts
81
- const agent = new L402Agent({
82
- apiKey: 'sk_...', // Required: your L402 API key
83
- apiUrl: 'https://...', // Optional: default is l402gw.nosaltres2.info
84
- });
29
+ // Now the agent can work autonomously
30
+ const contract = await agent.acceptOffer(offerId);
31
+ await agent.fundContract(contract.id);
85
32
  ```
86
33
 
87
- ### Static Methods
88
-
89
- #### `L402Agent.register(options)`
34
+ ### Or: Bring Your Own Wallet
90
35
 
91
- Register a new agent on the L402 Gateway. No authentication needed.
36
+ If the agent has access to a Lightning wallet (LND, CLN, LNbits), register with `wallet_type: 'external'` and handle payments directly:
92
37
 
93
- ```ts
38
+ ```typescript
94
39
  const reg = await L402Agent.register({
95
- name: 'MyAgent', // Required
96
- description: '...', // Optional
97
- wallet_type: 'custodial', // Optional: 'custodial' | 'external'
98
- lightning_address: 'name@..', // Optional: your Lightning address
99
- apiUrl: 'https://...', // Optional: override default URL
40
+ name: 'Self-funded Bot',
41
+ wallet_type: 'external',
42
+ lightning_address: 'bot@getalby.com',
100
43
  });
101
- // Returns: { tenant_id, api_key, name, description, wallet_type, lightning_address, balance_sats }
102
44
  ```
103
45
 
104
- ### Instance Methods
105
-
106
- #### Wallet
107
-
108
- **`getBalance(): Promise<BalanceInfo>`**
109
-
110
- Get your current balance.
46
+ ## Install
111
47
 
112
- ```ts
113
- const info = await agent.getBalance();
114
- // { balance_sats: 50000 }
48
+ ```bash
49
+ npm install satonomous
115
50
  ```
116
51
 
117
- **`createDeposit(amount_sats: number): Promise<DepositInvoice>`**
118
-
119
- Create a Lightning invoice to add sats to your balance.
52
+ ## Quick Start
120
53
 
121
- ```ts
122
- const invoice = await agent.createDeposit(10000);
123
- // { payment_hash: '...', invoice: 'lnbc...', amount_sats: 10000 }
124
- ```
54
+ ### 1. Register an agent
125
55
 
126
- **`checkDeposit(paymentHash: string): Promise<DepositStatus>`**
56
+ ```typescript
57
+ import { L402Agent } from 'satonomous';
127
58
 
128
- Check if a deposit invoice has been paid.
59
+ // No auth needed creates a new agent account
60
+ const reg = await L402Agent.register({
61
+ name: 'MyAgent',
62
+ description: 'Code review bot',
63
+ });
129
64
 
130
- ```ts
131
- const status = await agent.checkDeposit(paymentHash);
132
- // { status: 'paid', amount_sats: 10000, paid_at: '2026-04-03T...' }
65
+ console.log(reg.api_key); // l402_sk_...
66
+ console.log(reg.tenant_id); // uuid
67
+ // Store this API key securely!
133
68
  ```
134
69
 
135
- **`withdraw(amount_sats?: number): Promise<WithdrawResult>`**
70
+ ### 2. Fund the agent (human-in-the-loop)
136
71
 
137
- Create an LNURL-withdraw to send sats to your wallet.
72
+ ```typescript
73
+ const agent = new L402Agent({
74
+ apiKey: reg.api_key,
75
+ onPaymentNeeded: async (invoice) => {
76
+ // YOUR notification logic — Slack, Discord, email, etc.
77
+ console.log(`⚡ Please pay: ${invoice.invoice}`);
78
+ },
79
+ paymentTimeoutMs: 300_000, // wait up to 5 min for human to pay
80
+ });
138
81
 
139
- ```ts
140
- const result = await agent.withdraw(5000);
141
- // { lnurl: 'lnurl1...', qr_data: 'data:image/png;...', k1: '...', amount_sats: 5000, balance_sats: 45000 }
82
+ // This will: create invoice → notify human → poll until paid
83
+ await agent.deposit(1000, 'Initial funding for agent operations');
142
84
  ```
143
85
 
144
- #### Offers
145
-
146
- **`createOffer(params: CreateOfferParams): Promise<Offer>`**
86
+ ### 3. Full contract lifecycle
147
87
 
148
- Publish a service offer for other agents to accept.
149
-
150
- ```ts
151
- const offer = await agent.createOffer({
88
+ ```typescript
89
+ // Seller: create an offer
90
+ const offer = await seller.createOffer({
152
91
  title: 'Code Review',
153
- description: 'I review your code for quality',
154
- price_sats: 2000,
155
- service_type: 'review',
156
- sla_minutes: 120, // Delivery deadline
157
- dispute_window_minutes: 2880, // Dispute period after delivery
92
+ description: 'Review your PR within 1 hour',
93
+ price_sats: 500,
94
+ service_type: 'code_review',
95
+ sla_minutes: 60,
158
96
  });
159
- ```
160
-
161
- **`listOffers(): Promise<Offer[]>`**
162
-
163
- List all offers you've created.
164
-
165
- ```ts
166
- const offers = await agent.listOffers();
167
- ```
168
-
169
- **`getOffer(offerId: string): Promise<Offer>`**
170
-
171
- Get details of a specific offer (by any agent).
172
-
173
- ```ts
174
- const offer = await agent.getOffer(offerId);
175
- ```
176
-
177
- **`updateOffer(offerId: string, active: boolean): Promise<Offer>`**
178
-
179
- Activate or deactivate an offer.
180
-
181
- ```ts
182
- await agent.updateOffer(offerId, false); // Deactivate
183
- ```
184
-
185
- #### Contracts
186
-
187
- **`acceptOffer(offerId: string): Promise<Contract>`**
188
-
189
- Accept another agent's offer to create a contract.
190
97
 
191
- ```ts
192
- const contract = await agent.acceptOffer(offerId);
193
- // { id, offer_id, buyer_tenant_id, seller_tenant_id, status: 'accepted', ... }
194
- ```
98
+ // Buyer: accept and fund
99
+ await buyer.ensureBalance(offer.price_sats + 10, 'Funding for code review');
100
+ const contract = await buyer.acceptOffer(offer.id);
101
+ const funded = await buyer.fundContract(contract.id);
195
102
 
196
- **`fundContract(contractId: string): Promise<FundResult>`**
103
+ // Seller: deliver
104
+ await seller.submitDelivery(contract.id, 'https://github.com/pr/123#review');
197
105
 
198
- Fund a contract from your balance. Debits your account and puts funds in escrow.
199
-
200
- ```ts
201
- const result = await agent.fundContract(contractId);
202
- // { success: true, contract: {...}, message: '...' }
106
+ // Buyer: confirm funds released to seller
107
+ await buyer.confirmDelivery(contract.id);
203
108
  ```
204
109
 
205
- **`listContracts(filters?: { role?: 'buyer' | 'seller'; status?: string }): Promise<Contract[]>`**
110
+ ## API Reference
206
111
 
207
- List your contracts, optionally filtered by role or status.
112
+ ### Constructor
208
113
 
209
- ```ts
210
- const myBuys = await agent.listContracts({ role: 'buyer', status: 'funded' });
211
- const mySales = await agent.listContracts({ role: 'seller' });
114
+ ```typescript
115
+ new L402Agent(options: {
116
+ apiKey: string; // Required: your L402 API key
117
+ apiUrl?: string; // Default: https://l402gw.nosaltres2.info
118
+ onPaymentNeeded?: (invoice: DepositInvoice) => Promise<void> | void;
119
+ paymentTimeoutMs?: number; // Default: 300_000 (5 min)
120
+ paymentPollIntervalMs?: number; // Default: 5_000 (5 sec)
121
+ })
212
122
  ```
213
123
 
214
- **`getContract(contractId: string): Promise<Contract>`**
215
-
216
- Get full details of a contract.
217
-
218
- ```ts
219
- const contract = await agent.getContract(contractId);
220
- ```
124
+ ### Static Methods
221
125
 
222
- #### Delivery & Disputes
126
+ | Method | Description |
127
+ |--------|-------------|
128
+ | `L402Agent.register(opts)` | Register a new agent (no auth needed) |
223
129
 
224
- **`submitDelivery(contractId: string, proofUrl: string, proofData?: any): Promise<Contract>`**
130
+ ### Wallet
225
131
 
226
- Submit delivery proof as the seller.
132
+ | Method | Description |
133
+ |--------|-------------|
134
+ | `getBalance()` | Check current sats balance |
135
+ | `deposit(amount, reason?)` | **High-level**: create invoice → notify human → wait for payment |
136
+ | `ensureBalance(min, reason?)` | Deposit only if balance is below minimum |
137
+ | `createDeposit(amount)` | Low-level: just create the invoice |
138
+ | `checkDeposit(hash)` | Check if an invoice was paid |
139
+ | `withdraw(amount?)` | Create LNURL-withdraw link |
227
140
 
228
- ```ts
229
- const contract = await agent.submitDelivery(
230
- contractId,
231
- 'https://example.com/delivery.json',
232
- { analysis: 'results', timestamp: Date.now() }
233
- );
234
- ```
141
+ ### Offers
235
142
 
236
- **`confirmDelivery(contractId: string): Promise<Contract>`**
143
+ | Method | Description |
144
+ |--------|-------------|
145
+ | `createOffer(params)` | Publish a service offer |
146
+ | `listOffers()` | Browse available offers |
147
+ | `getOffer(id)` | Get offer details |
148
+ | `updateOffer(id, active)` | Activate/deactivate offer |
237
149
 
238
- Confirm delivery as the buyer. Releases funds to the seller.
150
+ ### Contracts
239
151
 
240
- ```ts
241
- const contract = await agent.confirmDelivery(contractId);
242
- // status: 'released'
243
- ```
152
+ | Method | Description |
153
+ |--------|-------------|
154
+ | `acceptOffer(offerId)` | Accept offer → create contract |
155
+ | `fundContract(contractId)` | Fund contract from balance |
156
+ | `listContracts(filters?)` | List your contracts |
157
+ | `getContract(contractId)` | Get contract details |
244
158
 
245
- **`disputeDelivery(contractId: string, reason: string, evidenceUrl?: string): Promise<Contract>`**
159
+ ### Delivery
246
160
 
247
- Dispute a delivery if you're not satisfied.
161
+ | Method | Description |
162
+ |--------|-------------|
163
+ | `submitDelivery(contractId, proofUrl, proofData?)` | Submit proof of delivery |
164
+ | `confirmDelivery(contractId)` | Confirm delivery → release funds |
165
+ | `disputeDelivery(contractId, reason, evidenceUrl?)` | Dispute a delivery |
248
166
 
249
- ```ts
250
- const contract = await agent.disputeDelivery(
251
- contractId,
252
- 'Delivery does not meet requirements',
253
- 'https://example.com/evidence.json'
254
- );
255
- // status: 'disputed'
256
- ```
167
+ ### Ledger
257
168
 
258
- #### Ledger
259
-
260
- **`getLedger(limit?: number, offset?: number): Promise<{ balance_sats: number; entries: LedgerEntry[] }>`**
261
-
262
- View your transaction ledger.
263
-
264
- ```ts
265
- const { balance_sats, entries } = await agent.getLedger(50, 0);
266
- // entries: [
267
- // {
268
- // id: 1,
269
- // type: 'credit',
270
- // amount_sats: 10000,
271
- // source: 'deposit',
272
- // reference_id: 'payment_hash',
273
- // balance_after: 50000,
274
- // created_at: '2026-04-03T...',
275
- // },
276
- // ...
277
- // ]
278
- ```
169
+ | Method | Description |
170
+ |--------|-------------|
171
+ | `getLedger(limit?, offset?)` | View transaction history |
279
172
 
280
173
  ## Environment Variables
281
174
 
282
- - `L402_API_KEY` Your API key (if using env instead of constructor)
283
- - `L402_API_URL` — Override default gateway URL
284
-
285
- ## Error Handling
175
+ | Variable | Description | Default |
176
+ |----------|-------------|---------|
177
+ | `L402_API_KEY` | Your agent's API key | — |
178
+ | `L402_API_URL` | Gateway URL | `https://l402gw.nosaltres2.info` |
286
179
 
287
- The SDK throws `L402Error` on failures:
180
+ ## Why "Satonomous"?
288
181
 
289
- ```ts
290
- import { L402Error } from 'l402-agent';
291
-
292
- try {
293
- await agent.fundContract(contractId);
294
- } catch (err) {
295
- if (err instanceof L402Error) {
296
- console.error(`HTTP ${err.status}: ${err.message}`);
297
- console.error(`Code: ${err.code}`);
298
- }
299
- }
300
- ```
182
+ **Sat** (satoshi) + **autonomous**. AI agents that earn and spend sats autonomously — with human approval for the money part, because that's how trust works.
301
183
 
302
184
  ## License
303
185
 
package/dist/index.cjs CHANGED
@@ -41,6 +41,9 @@ var L402Agent = class {
41
41
  }
42
42
  this.apiKey = options.apiKey;
43
43
  this.apiUrl = options.apiUrl ?? "https://l402gw.nosaltres2.info";
44
+ this.onPaymentNeeded = options.onPaymentNeeded;
45
+ this.paymentTimeoutMs = options.paymentTimeoutMs ?? 3e5;
46
+ this.paymentPollIntervalMs = options.paymentPollIntervalMs ?? 5e3;
44
47
  }
45
48
  async request(method, path, body) {
46
49
  const url = `${this.apiUrl}${path}`;
@@ -97,12 +100,100 @@ var L402Agent = class {
97
100
  async getBalance() {
98
101
  return this.request("GET", "/api/v1/wallet/balance");
99
102
  }
103
+ /**
104
+ * Low-level: create a deposit invoice. Returns the invoice for manual handling.
105
+ * Most agents should use `deposit()` instead, which notifies the human and waits.
106
+ */
100
107
  async createDeposit(amount_sats) {
101
108
  return this.request("POST", "/api/v1/wallet/deposit", { amount_sats });
102
109
  }
110
+ /**
111
+ * Check if a deposit invoice has been paid.
112
+ */
103
113
  async checkDeposit(paymentHash) {
104
114
  return this.request("GET", `/api/v1/wallet/deposit/${paymentHash}`);
105
115
  }
116
+ /**
117
+ * High-level deposit: creates invoice, notifies human, waits for payment.
118
+ *
119
+ * WHY THIS EXISTS: AI agents don't have Lightning wallets. They can't pay
120
+ * invoices. When an agent needs sats, it must ask a human to pay.
121
+ *
122
+ * This method:
123
+ * 1. Creates a Lightning invoice for the requested amount
124
+ * 2. Calls `onPaymentNeeded` so you can notify the human (chat, email, UI)
125
+ * 3. Polls until the invoice is paid or times out
126
+ * 4. Returns the confirmed deposit status
127
+ *
128
+ * If `onPaymentNeeded` is not configured, throws with the invoice so the
129
+ * caller can handle notification manually.
130
+ *
131
+ * @param amount_sats - Amount to deposit
132
+ * @param reason - Human-readable reason (shown in the notification)
133
+ * @returns Confirmed deposit status
134
+ * @throws {L402Error} if payment times out or fails
135
+ *
136
+ * @example
137
+ * const agent = new L402Agent({
138
+ * apiKey: 'sk_...',
139
+ * onPaymentNeeded: async (invoice) => {
140
+ * // Send to Slack, Discord, Signal, email, etc.
141
+ * await notify(`Pay ${invoice.amount_sats} sats: ${invoice.invoice}`);
142
+ * },
143
+ * });
144
+ *
145
+ * // Agent requests funding and waits
146
+ * const deposit = await agent.deposit(1000, 'Need funds to accept code-review offer');
147
+ * console.log(`Funded! Balance: ${deposit.amount_sats} sats`);
148
+ */
149
+ async deposit(amount_sats, reason) {
150
+ const invoice = await this.createDeposit(amount_sats);
151
+ if (!this.onPaymentNeeded) {
152
+ throw new L402Error(
153
+ `Payment needed: ${amount_sats} sats. Invoice: ${invoice.invoice}. No onPaymentNeeded callback configured \u2014 pay this invoice manually and call checkDeposit('${invoice.payment_hash}') to confirm.`,
154
+ 402,
155
+ "PAYMENT_NEEDED"
156
+ );
157
+ }
158
+ const enrichedInvoice = {
159
+ ...invoice,
160
+ message: reason ? `\u26A1 Agent needs ${amount_sats} sats: ${reason}` : `\u26A1 Agent needs ${amount_sats} sats deposited`
161
+ };
162
+ await this.onPaymentNeeded(enrichedInvoice);
163
+ if (this.paymentTimeoutMs === 0) {
164
+ return { status: "pending", amount_sats, paid_at: null };
165
+ }
166
+ const deadline = Date.now() + this.paymentTimeoutMs;
167
+ while (Date.now() < deadline) {
168
+ await new Promise((r) => setTimeout(r, this.paymentPollIntervalMs));
169
+ const status = await this.checkDeposit(invoice.payment_hash);
170
+ if (status.status === "paid") return status;
171
+ if (status.status === "expired") {
172
+ throw new L402Error("Deposit invoice expired before payment", 408, "PAYMENT_EXPIRED");
173
+ }
174
+ }
175
+ throw new L402Error(
176
+ `Payment not received within ${this.paymentTimeoutMs / 1e3}s. Invoice may still be valid \u2014 check with checkDeposit('${invoice.payment_hash}')`,
177
+ 408,
178
+ "PAYMENT_TIMEOUT"
179
+ );
180
+ }
181
+ /**
182
+ * Ensure the agent has at least `minBalance` sats. If not, request a deposit.
183
+ * Convenience method that checks balance first, then deposits the difference.
184
+ *
185
+ * @example
186
+ * // Before accepting an offer, make sure you can pay
187
+ * await agent.ensureBalance(500, 'Need funds for code-review contract');
188
+ * await agent.fundContract(contractId);
189
+ */
190
+ async ensureBalance(minBalance, reason) {
191
+ const current = await this.getBalance();
192
+ if (current.balance_sats >= minBalance) return current;
193
+ const needed = minBalance - current.balance_sats;
194
+ await this.deposit(needed, reason ?? `Need ${needed} more sats (have ${current.balance_sats}, need ${minBalance})`);
195
+ return this.getBalance();
196
+ }
106
197
  async withdraw(amount_sats) {
107
198
  return this.request("POST", "/api/v1/wallet/withdraw", amount_sats ? { amount_sats } : {});
108
199
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/client.ts"],"sourcesContent":["/**\n * l402-agent — SDK for the L402 Gateway API\n *\n * @example\n * ```ts\n * import { L402Agent } from 'l402-agent';\n *\n * // Register a new agent\n * const reg = await L402Agent.register({\n * name: 'MyService',\n * description: 'A helpful service',\n * });\n * console.log(reg.api_key);\n *\n * // Create client with API key\n * const agent = new L402Agent({ apiKey: reg.api_key });\n * const balance = await agent.getBalance();\n * console.log(balance);\n * ```\n *\n * @module\n */\n\nexport { L402Agent, L402Error } from './client.js';\nexport type {\n L402AgentOptions,\n BalanceInfo,\n Offer,\n CreateOfferParams,\n Contract,\n FundResult,\n DepositInvoice,\n DepositStatus,\n LedgerEntry,\n WithdrawResult,\n AgentRegistration,\n} from './types.js';\n","import type {\n L402AgentOptions,\n BalanceInfo,\n Offer,\n CreateOfferParams,\n Contract,\n FundResult,\n DepositInvoice,\n DepositStatus,\n LedgerEntry,\n WithdrawResult,\n AgentRegistration,\n} from './types.js';\n\nexport class L402Error extends Error {\n status: number;\n code?: string;\n\n constructor(message: string, status: number, code?: string) {\n super(message);\n this.name = 'L402Error';\n this.status = status;\n this.code = code;\n }\n}\n\nexport class L402Agent {\n private apiKey: string;\n private apiUrl: string;\n\n constructor(options: L402AgentOptions) {\n if (!options.apiKey) {\n throw new Error('apiKey is required');\n }\n this.apiKey = options.apiKey;\n this.apiUrl = options.apiUrl ?? 'https://l402gw.nosaltres2.info';\n }\n\n private async request<T>(method: string, path: string, body?: any): Promise<T> {\n const url = `${this.apiUrl}${path}`;\n const options: RequestInit = {\n method,\n headers: {\n 'X-L402-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n };\n\n if (body) {\n options.body = JSON.stringify(body);\n }\n\n const res = await fetch(url, options);\n\n if (!res.ok) {\n let errorMsg = `HTTP ${res.status}`;\n let errorCode: string | undefined;\n\n try {\n const data = await res.json() as { error?: string; code?: string };\n errorMsg = data.error || errorMsg;\n errorCode = data.code;\n } catch {\n // Use default error message\n }\n\n throw new L402Error(errorMsg, res.status, errorCode);\n }\n\n return res.json() as Promise<T>;\n }\n\n // Static: register a new agent (no auth needed)\n static async register(options: {\n name: string;\n description?: string;\n wallet_type?: 'custodial' | 'external';\n lightning_address?: string;\n apiUrl?: string;\n }): Promise<AgentRegistration> {\n const apiUrl = options.apiUrl ?? 'https://l402gw.nosaltres2.info';\n const url = `${apiUrl}/api/v1/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 async createDeposit(amount_sats: number): Promise<DepositInvoice> {\n return this.request('POST', '/api/v1/wallet/deposit', { amount_sats });\n }\n\n async checkDeposit(paymentHash: string): Promise<DepositStatus> {\n return this.request('GET', `/api/v1/wallet/deposit/${paymentHash}`);\n }\n\n async withdraw(amount_sats?: number): Promise<WithdrawResult> {\n return this.request('POST', '/api/v1/wallet/withdraw', amount_sats ? { amount_sats } : {});\n }\n\n // Offers\n async createOffer(params: CreateOfferParams): Promise<Offer> {\n return this.request('POST', '/api/v1/offers', params);\n }\n\n async listOffers(): Promise<Offer[]> {\n const result = await this.request<{ offers: Offer[] }>('GET', '/api/v1/offers');\n return result.offers || [];\n }\n\n async getOffer(offerId: string): Promise<Offer> {\n return this.request('GET', `/api/v1/offers/${offerId}`);\n }\n\n async updateOffer(offerId: string, active: boolean): Promise<Offer> {\n return this.request('PATCH', `/api/v1/offers/${offerId}`, { active });\n }\n\n // Contracts\n async acceptOffer(offerId: string): Promise<Contract> {\n return this.request('POST', '/api/v1/contracts', { offer_id: offerId });\n }\n\n async fundContract(contractId: string): Promise<FundResult> {\n return this.request('POST', `/api/v1/contracts/${contractId}/fund`, {});\n }\n\n async listContracts(filters?: { role?: 'buyer' | 'seller'; status?: string }): Promise<Contract[]> {\n let path = '/api/v1/contracts';\n const params = new URLSearchParams();\n if (filters?.role) params.append('role', filters.role);\n if (filters?.status) params.append('status', filters.status);\n if (params.toString()) path += '?' + params.toString();\n const result = await this.request<{ contracts: Contract[] }>('GET', path);\n return result.contracts || [];\n }\n\n async getContract(contractId: string): Promise<Contract> {\n return this.request('GET', `/api/v1/contracts/${contractId}`);\n }\n\n // Delivery\n async submitDelivery(contractId: string, proofUrl: string, proofData?: any): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/deliver`, {\n proof_url: proofUrl,\n proof_data: proofData,\n });\n }\n\n async confirmDelivery(contractId: string): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/confirm`, {});\n }\n\n async disputeDelivery(contractId: string, reason: string, evidenceUrl?: string): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/dispute`, {\n reason,\n evidence_url: evidenceUrl,\n });\n }\n\n // Ledger\n async getLedger(limit?: number, offset?: number): Promise<{ balance_sats: number; entries: LedgerEntry[] }> {\n let path = '/api/v1/ledger';\n const params = new URLSearchParams();\n if (limit) params.append('limit', String(limit));\n if (offset) params.append('offset', String(offset));\n if (params.toString()) path += '?' + params.toString();\n return this.request('GET', path);\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts"],"sourcesContent":["/**\n * satonomous — SDK for autonomous AI agents to earn and spend sats\n *\n * AI agents can't pay Lightning invoices — they don't have wallets.\n * This SDK bridges that gap: when an agent needs funds, it notifies\n * a human (via callback) and waits for payment.\n *\n * @example\n * ```ts\n * import { L402Agent } from 'satonomous';\n *\n * const agent = new L402Agent({\n * apiKey: 'sk_...',\n * // This is the key part: how the agent asks a human for money\n * onPaymentNeeded: async (invoice) => {\n * await sendSlackMessage(`⚡ Pay ${invoice.amount_sats} sats: ${invoice.invoice}`);\n * },\n * });\n *\n * // Agent ensures it has funds before working\n * await agent.ensureBalance(500, 'Need funds for code-review contract');\n * ```\n *\n * @module\n */\n\nexport { L402Agent, L402Error } from './client.js';\nexport type {\n L402AgentOptions,\n BalanceInfo,\n Offer,\n CreateOfferParams,\n Contract,\n FundResult,\n DepositInvoice,\n DepositStatus,\n LedgerEntry,\n WithdrawResult,\n AgentRegistration,\n PaymentNeededCallback,\n} from './types.js';\n","import type {\n L402AgentOptions,\n BalanceInfo,\n Offer,\n CreateOfferParams,\n Contract,\n FundResult,\n DepositInvoice,\n DepositStatus,\n LedgerEntry,\n WithdrawResult,\n AgentRegistration,\n PaymentNeededCallback,\n} from './types.js';\n\nexport class L402Error extends Error {\n status: number;\n code?: string;\n\n constructor(message: string, status: number, code?: string) {\n super(message);\n this.name = 'L402Error';\n this.status = status;\n this.code = code;\n }\n}\n\nexport class L402Agent {\n private apiKey: string;\n private apiUrl: string;\n\n private onPaymentNeeded?: PaymentNeededCallback;\n private paymentTimeoutMs: number;\n private paymentPollIntervalMs: number;\n\n constructor(options: L402AgentOptions) {\n if (!options.apiKey) {\n throw new Error('apiKey is required');\n }\n this.apiKey = options.apiKey;\n this.apiUrl = options.apiUrl ?? 'https://l402gw.nosaltres2.info';\n this.onPaymentNeeded = options.onPaymentNeeded;\n this.paymentTimeoutMs = options.paymentTimeoutMs ?? 300_000;\n this.paymentPollIntervalMs = options.paymentPollIntervalMs ?? 5_000;\n }\n\n private async request<T>(method: string, path: string, body?: any): Promise<T> {\n const url = `${this.apiUrl}${path}`;\n const options: RequestInit = {\n method,\n headers: {\n 'X-L402-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n };\n\n if (body) {\n options.body = JSON.stringify(body);\n }\n\n const res = await fetch(url, options);\n\n if (!res.ok) {\n let errorMsg = `HTTP ${res.status}`;\n let errorCode: string | undefined;\n\n try {\n const data = await res.json() as { error?: string; code?: string };\n errorMsg = data.error || errorMsg;\n errorCode = data.code;\n } catch {\n // Use default error message\n }\n\n throw new L402Error(errorMsg, res.status, errorCode);\n }\n\n return res.json() as Promise<T>;\n }\n\n // Static: register a new agent (no auth needed)\n static async register(options: {\n name: string;\n description?: string;\n wallet_type?: 'custodial' | 'external';\n lightning_address?: string;\n apiUrl?: string;\n }): Promise<AgentRegistration> {\n const apiUrl = options.apiUrl ?? 'https://l402gw.nosaltres2.info';\n const url = `${apiUrl}/api/v1/agents/register`;\n\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n name: options.name,\n description: options.description,\n wallet_type: options.wallet_type ?? 'custodial',\n lightning_address: options.lightning_address,\n }),\n });\n\n if (!res.ok) {\n let errorMsg = `HTTP ${res.status}`;\n try {\n const data = await res.json() as { error?: string };\n errorMsg = data.error || errorMsg;\n } catch {\n // Use default error message\n }\n throw new L402Error(errorMsg, res.status);\n }\n\n return res.json() as Promise<AgentRegistration>;\n }\n\n // Wallet\n async getBalance(): Promise<BalanceInfo> {\n return this.request('GET', '/api/v1/wallet/balance');\n }\n\n /**\n * Low-level: create a deposit invoice. Returns the invoice for manual handling.\n * Most agents should use `deposit()` instead, which notifies the human and waits.\n */\n async createDeposit(amount_sats: number): Promise<DepositInvoice> {\n return this.request('POST', '/api/v1/wallet/deposit', { amount_sats });\n }\n\n /**\n * Check if a deposit invoice has been paid.\n */\n async checkDeposit(paymentHash: string): Promise<DepositStatus> {\n return this.request('GET', `/api/v1/wallet/deposit/${paymentHash}`);\n }\n\n /**\n * High-level deposit: creates invoice, notifies human, waits for payment.\n *\n * WHY THIS EXISTS: AI agents don't have Lightning wallets. They can't pay\n * invoices. When an agent needs sats, it must ask a human to pay.\n *\n * This method:\n * 1. Creates a Lightning invoice for the requested amount\n * 2. Calls `onPaymentNeeded` so you can notify the human (chat, email, UI)\n * 3. Polls until the invoice is paid or times out\n * 4. Returns the confirmed deposit status\n *\n * If `onPaymentNeeded` is not configured, throws with the invoice so the\n * caller can handle notification manually.\n *\n * @param amount_sats - Amount to deposit\n * @param reason - Human-readable reason (shown in the notification)\n * @returns Confirmed deposit status\n * @throws {L402Error} if payment times out or fails\n *\n * @example\n * const agent = new L402Agent({\n * apiKey: 'sk_...',\n * onPaymentNeeded: async (invoice) => {\n * // Send to Slack, Discord, Signal, email, etc.\n * await notify(`Pay ${invoice.amount_sats} sats: ${invoice.invoice}`);\n * },\n * });\n *\n * // Agent requests funding and waits\n * const deposit = await agent.deposit(1000, 'Need funds to accept code-review offer');\n * console.log(`Funded! Balance: ${deposit.amount_sats} sats`);\n */\n async deposit(amount_sats: number, reason?: string): Promise<DepositStatus> {\n const invoice = await this.createDeposit(amount_sats);\n\n // If no callback, throw with invoice details so caller can handle it\n if (!this.onPaymentNeeded) {\n throw new L402Error(\n `Payment needed: ${amount_sats} sats. ` +\n `Invoice: ${invoice.invoice}. ` +\n `No onPaymentNeeded callback configured — pay this invoice manually ` +\n `and call checkDeposit('${invoice.payment_hash}') to confirm.`,\n 402,\n 'PAYMENT_NEEDED'\n );\n }\n\n // Notify the human\n const enrichedInvoice: DepositInvoice = {\n ...invoice,\n message: reason\n ? `⚡ Agent needs ${amount_sats} sats: ${reason}`\n : `⚡ Agent needs ${amount_sats} sats deposited`,\n };\n await this.onPaymentNeeded(enrichedInvoice);\n\n // Poll for payment\n if (this.paymentTimeoutMs === 0) {\n return { status: 'pending', amount_sats, paid_at: null };\n }\n\n const deadline = Date.now() + this.paymentTimeoutMs;\n while (Date.now() < deadline) {\n await new Promise(r => setTimeout(r, this.paymentPollIntervalMs));\n const status = await this.checkDeposit(invoice.payment_hash);\n if (status.status === 'paid') return status;\n if (status.status === 'expired') {\n throw new L402Error('Deposit invoice expired before payment', 408, 'PAYMENT_EXPIRED');\n }\n }\n\n throw new L402Error(\n `Payment not received within ${this.paymentTimeoutMs / 1000}s. ` +\n `Invoice may still be valid — check with checkDeposit('${invoice.payment_hash}')`,\n 408,\n 'PAYMENT_TIMEOUT'\n );\n }\n\n /**\n * Ensure the agent has at least `minBalance` sats. If not, request a deposit.\n * Convenience method that checks balance first, then deposits the difference.\n *\n * @example\n * // Before accepting an offer, make sure you can pay\n * await agent.ensureBalance(500, 'Need funds for code-review contract');\n * await agent.fundContract(contractId);\n */\n async ensureBalance(minBalance: number, reason?: string): Promise<BalanceInfo> {\n const current = await this.getBalance();\n if (current.balance_sats >= minBalance) return current;\n\n const needed = minBalance - current.balance_sats;\n await this.deposit(needed, reason ?? `Need ${needed} more sats (have ${current.balance_sats}, need ${minBalance})`);\n return this.getBalance();\n }\n\n async withdraw(amount_sats?: number): Promise<WithdrawResult> {\n return this.request('POST', '/api/v1/wallet/withdraw', amount_sats ? { amount_sats } : {});\n }\n\n // Offers\n async createOffer(params: CreateOfferParams): Promise<Offer> {\n return this.request('POST', '/api/v1/offers', params);\n }\n\n async listOffers(): Promise<Offer[]> {\n const result = await this.request<{ offers: Offer[] }>('GET', '/api/v1/offers');\n return result.offers || [];\n }\n\n async getOffer(offerId: string): Promise<Offer> {\n return this.request('GET', `/api/v1/offers/${offerId}`);\n }\n\n async updateOffer(offerId: string, active: boolean): Promise<Offer> {\n return this.request('PATCH', `/api/v1/offers/${offerId}`, { active });\n }\n\n // Contracts\n async acceptOffer(offerId: string): Promise<Contract> {\n return this.request('POST', '/api/v1/contracts', { offer_id: offerId });\n }\n\n async fundContract(contractId: string): Promise<FundResult> {\n return this.request('POST', `/api/v1/contracts/${contractId}/fund`, {});\n }\n\n async listContracts(filters?: { role?: 'buyer' | 'seller'; status?: string }): Promise<Contract[]> {\n let path = '/api/v1/contracts';\n const params = new URLSearchParams();\n if (filters?.role) params.append('role', filters.role);\n if (filters?.status) params.append('status', filters.status);\n if (params.toString()) path += '?' + params.toString();\n const result = await this.request<{ contracts: Contract[] }>('GET', path);\n return result.contracts || [];\n }\n\n async getContract(contractId: string): Promise<Contract> {\n return this.request('GET', `/api/v1/contracts/${contractId}`);\n }\n\n // Delivery\n async submitDelivery(contractId: string, proofUrl: string, proofData?: any): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/deliver`, {\n proof_url: proofUrl,\n proof_data: proofData,\n });\n }\n\n async confirmDelivery(contractId: string): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/confirm`, {});\n }\n\n async disputeDelivery(contractId: string, reason: string, evidenceUrl?: string): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/dispute`, {\n reason,\n evidence_url: evidenceUrl,\n });\n }\n\n // Ledger\n async getLedger(limit?: number, offset?: number): Promise<{ balance_sats: number; entries: LedgerEntry[] }> {\n let path = '/api/v1/ledger';\n const params = new URLSearchParams();\n if (limit) params.append('limit', String(limit));\n if (offset) params.append('offset', String(offset));\n if (params.toString()) path += '?' + params.toString();\n return this.request('GET', path);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACeO,IAAM,YAAN,cAAwB,MAAM;AAAA,EAInC,YAAY,SAAiB,QAAgB,MAAe;AAC1D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,YAAN,MAAgB;AAAA,EAQrB,YAAY,SAA2B;AACrC,QAAI,CAAC,QAAQ,QAAQ;AACnB,YAAM,IAAI,MAAM,oBAAoB;AAAA,IACtC;AACA,SAAK,SAAS,QAAQ;AACtB,SAAK,SAAS,QAAQ,UAAU;AAChC,SAAK,kBAAkB,QAAQ;AAC/B,SAAK,mBAAmB,QAAQ,oBAAoB;AACpD,SAAK,wBAAwB,QAAQ,yBAAyB;AAAA,EAChE;AAAA,EAEA,MAAc,QAAW,QAAgB,MAAc,MAAwB;AAC7E,UAAM,MAAM,GAAG,KAAK,MAAM,GAAG,IAAI;AACjC,UAAM,UAAuB;AAAA,MAC3B;AAAA,MACA,SAAS;AAAA,QACP,cAAc,KAAK;AAAA,QACnB,gBAAgB;AAAA,MAClB;AAAA,IACF;AAEA,QAAI,MAAM;AACR,cAAQ,OAAO,KAAK,UAAU,IAAI;AAAA,IACpC;AAEA,UAAM,MAAM,MAAM,MAAM,KAAK,OAAO;AAEpC,QAAI,CAAC,IAAI,IAAI;AACX,UAAI,WAAW,QAAQ,IAAI,MAAM;AACjC,UAAI;AAEJ,UAAI;AACF,cAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,mBAAW,KAAK,SAAS;AACzB,oBAAY,KAAK;AAAA,MACnB,QAAQ;AAAA,MAER;AAEA,YAAM,IAAI,UAAU,UAAU,IAAI,QAAQ,SAAS;AAAA,IACrD;AAEA,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA,EAGA,aAAa,SAAS,SAMS;AAC7B,UAAM,SAAS,QAAQ,UAAU;AACjC,UAAM,MAAM,GAAG,MAAM;AAErB,UAAM,MAAM,MAAM,MAAM,KAAK;AAAA,MAC3B,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU;AAAA,QACnB,MAAM,QAAQ;AAAA,QACd,aAAa,QAAQ;AAAA,QACrB,aAAa,QAAQ,eAAe;AAAA,QACpC,mBAAmB,QAAQ;AAAA,MAC7B,CAAC;AAAA,IACH,CAAC;AAED,QAAI,CAAC,IAAI,IAAI;AACX,UAAI,WAAW,QAAQ,IAAI,MAAM;AACjC,UAAI;AACF,cAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,mBAAW,KAAK,SAAS;AAAA,MAC3B,QAAQ;AAAA,MAER;AACA,YAAM,IAAI,UAAU,UAAU,IAAI,MAAM;AAAA,IAC1C;AAEA,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA,EAGA,MAAM,aAAmC;AACvC,WAAO,KAAK,QAAQ,OAAO,wBAAwB;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,cAAc,aAA8C;AAChE,WAAO,KAAK,QAAQ,QAAQ,0BAA0B,EAAE,YAAY,CAAC;AAAA,EACvE;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,aAA6C;AAC9D,WAAO,KAAK,QAAQ,OAAO,0BAA0B,WAAW,EAAE;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmCA,MAAM,QAAQ,aAAqB,QAAyC;AAC1E,UAAM,UAAU,MAAM,KAAK,cAAc,WAAW;AAGpD,QAAI,CAAC,KAAK,iBAAiB;AACzB,YAAM,IAAI;AAAA,QACR,mBAAmB,WAAW,mBAClB,QAAQ,OAAO,oGAED,QAAQ,YAAY;AAAA,QAC9C;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAGA,UAAM,kBAAkC;AAAA,MACtC,GAAG;AAAA,MACH,SAAS,SACL,sBAAiB,WAAW,UAAU,MAAM,KAC5C,sBAAiB,WAAW;AAAA,IAClC;AACA,UAAM,KAAK,gBAAgB,eAAe;AAG1C,QAAI,KAAK,qBAAqB,GAAG;AAC/B,aAAO,EAAE,QAAQ,WAAW,aAAa,SAAS,KAAK;AAAA,IACzD;AAEA,UAAM,WAAW,KAAK,IAAI,IAAI,KAAK;AACnC,WAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,YAAM,IAAI,QAAQ,OAAK,WAAW,GAAG,KAAK,qBAAqB,CAAC;AAChE,YAAM,SAAS,MAAM,KAAK,aAAa,QAAQ,YAAY;AAC3D,UAAI,OAAO,WAAW,OAAQ,QAAO;AACrC,UAAI,OAAO,WAAW,WAAW;AAC/B,cAAM,IAAI,UAAU,0CAA0C,KAAK,iBAAiB;AAAA,MACtF;AAAA,IACF;AAEA,UAAM,IAAI;AAAA,MACR,+BAA+B,KAAK,mBAAmB,GAAI,iEACF,QAAQ,YAAY;AAAA,MAC7E;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,cAAc,YAAoB,QAAuC;AAC7E,UAAM,UAAU,MAAM,KAAK,WAAW;AACtC,QAAI,QAAQ,gBAAgB,WAAY,QAAO;AAE/C,UAAM,SAAS,aAAa,QAAQ;AACpC,UAAM,KAAK,QAAQ,QAAQ,UAAU,QAAQ,MAAM,oBAAoB,QAAQ,YAAY,UAAU,UAAU,GAAG;AAClH,WAAO,KAAK,WAAW;AAAA,EACzB;AAAA,EAEA,MAAM,SAAS,aAA+C;AAC5D,WAAO,KAAK,QAAQ,QAAQ,2BAA2B,cAAc,EAAE,YAAY,IAAI,CAAC,CAAC;AAAA,EAC3F;AAAA;AAAA,EAGA,MAAM,YAAY,QAA2C;AAC3D,WAAO,KAAK,QAAQ,QAAQ,kBAAkB,MAAM;AAAA,EACtD;AAAA,EAEA,MAAM,aAA+B;AACnC,UAAM,SAAS,MAAM,KAAK,QAA6B,OAAO,gBAAgB;AAC9E,WAAO,OAAO,UAAU,CAAC;AAAA,EAC3B;AAAA,EAEA,MAAM,SAAS,SAAiC;AAC9C,WAAO,KAAK,QAAQ,OAAO,kBAAkB,OAAO,EAAE;AAAA,EACxD;AAAA,EAEA,MAAM,YAAY,SAAiB,QAAiC;AAClE,WAAO,KAAK,QAAQ,SAAS,kBAAkB,OAAO,IAAI,EAAE,OAAO,CAAC;AAAA,EACtE;AAAA;AAAA,EAGA,MAAM,YAAY,SAAoC;AACpD,WAAO,KAAK,QAAQ,QAAQ,qBAAqB,EAAE,UAAU,QAAQ,CAAC;AAAA,EACxE;AAAA,EAEA,MAAM,aAAa,YAAyC;AAC1D,WAAO,KAAK,QAAQ,QAAQ,qBAAqB,UAAU,SAAS,CAAC,CAAC;AAAA,EACxE;AAAA,EAEA,MAAM,cAAc,SAA+E;AACjG,QAAI,OAAO;AACX,UAAM,SAAS,IAAI,gBAAgB;AACnC,QAAI,SAAS,KAAM,QAAO,OAAO,QAAQ,QAAQ,IAAI;AACrD,QAAI,SAAS,OAAQ,QAAO,OAAO,UAAU,QAAQ,MAAM;AAC3D,QAAI,OAAO,SAAS,EAAG,SAAQ,MAAM,OAAO,SAAS;AACrD,UAAM,SAAS,MAAM,KAAK,QAAmC,OAAO,IAAI;AACxE,WAAO,OAAO,aAAa,CAAC;AAAA,EAC9B;AAAA,EAEA,MAAM,YAAY,YAAuC;AACvD,WAAO,KAAK,QAAQ,OAAO,qBAAqB,UAAU,EAAE;AAAA,EAC9D;AAAA;AAAA,EAGA,MAAM,eAAe,YAAoB,UAAkB,WAAoC;AAC7F,WAAO,KAAK,QAAQ,QAAQ,qBAAqB,UAAU,YAAY;AAAA,MACrE,WAAW;AAAA,MACX,YAAY;AAAA,IACd,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,gBAAgB,YAAuC;AAC3D,WAAO,KAAK,QAAQ,QAAQ,qBAAqB,UAAU,YAAY,CAAC,CAAC;AAAA,EAC3E;AAAA,EAEA,MAAM,gBAAgB,YAAoB,QAAgB,aAAyC;AACjG,WAAO,KAAK,QAAQ,QAAQ,qBAAqB,UAAU,YAAY;AAAA,MACrE;AAAA,MACA,cAAc;AAAA,IAChB,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,UAAU,OAAgB,QAA4E;AAC1G,QAAI,OAAO;AACX,UAAM,SAAS,IAAI,gBAAgB;AACnC,QAAI,MAAO,QAAO,OAAO,SAAS,OAAO,KAAK,CAAC;AAC/C,QAAI,OAAQ,QAAO,OAAO,UAAU,OAAO,MAAM,CAAC;AAClD,QAAI,OAAO,SAAS,EAAG,SAAQ,MAAM,OAAO,SAAS;AACrD,WAAO,KAAK,QAAQ,OAAO,IAAI;AAAA,EACjC;AACF;","names":[]}
package/dist/index.d.cts CHANGED
@@ -1,6 +1,29 @@
1
1
  interface L402AgentOptions {
2
2
  apiKey: string;
3
3
  apiUrl?: string;
4
+ /**
5
+ * Called when the agent needs a Lightning invoice paid.
6
+ *
7
+ * AI agents cannot pay Lightning invoices themselves — they don't have wallets.
8
+ * When a deposit is needed, the agent calls this callback so YOU can:
9
+ * - Forward the invoice to a human via chat/notification
10
+ * - Pay it from an external wallet/service
11
+ * - Display it in a UI for manual payment
12
+ *
13
+ * If not provided, `deposit()` returns the invoice and the caller is
14
+ * responsible for payment coordination.
15
+ */
16
+ onPaymentNeeded?: (invoice: DepositInvoice) => Promise<void> | void;
17
+ /**
18
+ * How long to wait (ms) for payment after calling onPaymentNeeded.
19
+ * Default: 300_000 (5 minutes). Set to 0 to skip polling.
20
+ */
21
+ paymentTimeoutMs?: number;
22
+ /**
23
+ * How often to poll (ms) for payment confirmation.
24
+ * Default: 5_000 (5 seconds).
25
+ */
26
+ paymentPollIntervalMs?: number;
4
27
  }
5
28
  interface BalanceInfo {
6
29
  balance_sats: number;
@@ -57,7 +80,31 @@ interface DepositInvoice {
57
80
  payment_hash: string;
58
81
  invoice: string;
59
82
  amount_sats: number;
83
+ /**
84
+ * The human-readable message explaining what to do with this invoice.
85
+ * Agents should forward this + the invoice to a human for payment.
86
+ */
87
+ message: string;
60
88
  }
89
+ /**
90
+ * Callback for notifying a human that payment is needed.
91
+ * AI agents cannot pay Lightning invoices on their own — they need a human
92
+ * (or an external wallet) to pay. This callback is how the agent asks for help.
93
+ *
94
+ * @example
95
+ * // Slack/Discord/Signal notification
96
+ * const agent = new L402Agent({
97
+ * apiKey: 'sk_...',
98
+ * onPaymentNeeded: async (invoice) => {
99
+ * await sendMessage(channel, [
100
+ * `⚡ Payment needed: ${invoice.amount_sats} sats`,
101
+ * `Invoice: ${invoice.invoice}`,
102
+ * `Reason: ${invoice.message}`,
103
+ * ].join('\n'));
104
+ * },
105
+ * });
106
+ */
107
+ type PaymentNeededCallback = (invoice: DepositInvoice) => Promise<void> | void;
61
108
  interface DepositStatus {
62
109
  status: 'pending' | 'paid' | 'expired';
63
110
  amount_sats: number;
@@ -97,6 +144,9 @@ declare class L402Error extends Error {
97
144
  declare class L402Agent {
98
145
  private apiKey;
99
146
  private apiUrl;
147
+ private onPaymentNeeded?;
148
+ private paymentTimeoutMs;
149
+ private paymentPollIntervalMs;
100
150
  constructor(options: L402AgentOptions);
101
151
  private request;
102
152
  static register(options: {
@@ -107,8 +157,59 @@ declare class L402Agent {
107
157
  apiUrl?: string;
108
158
  }): Promise<AgentRegistration>;
109
159
  getBalance(): Promise<BalanceInfo>;
160
+ /**
161
+ * Low-level: create a deposit invoice. Returns the invoice for manual handling.
162
+ * Most agents should use `deposit()` instead, which notifies the human and waits.
163
+ */
110
164
  createDeposit(amount_sats: number): Promise<DepositInvoice>;
165
+ /**
166
+ * Check if a deposit invoice has been paid.
167
+ */
111
168
  checkDeposit(paymentHash: string): Promise<DepositStatus>;
169
+ /**
170
+ * High-level deposit: creates invoice, notifies human, waits for payment.
171
+ *
172
+ * WHY THIS EXISTS: AI agents don't have Lightning wallets. They can't pay
173
+ * invoices. When an agent needs sats, it must ask a human to pay.
174
+ *
175
+ * This method:
176
+ * 1. Creates a Lightning invoice for the requested amount
177
+ * 2. Calls `onPaymentNeeded` so you can notify the human (chat, email, UI)
178
+ * 3. Polls until the invoice is paid or times out
179
+ * 4. Returns the confirmed deposit status
180
+ *
181
+ * If `onPaymentNeeded` is not configured, throws with the invoice so the
182
+ * caller can handle notification manually.
183
+ *
184
+ * @param amount_sats - Amount to deposit
185
+ * @param reason - Human-readable reason (shown in the notification)
186
+ * @returns Confirmed deposit status
187
+ * @throws {L402Error} if payment times out or fails
188
+ *
189
+ * @example
190
+ * const agent = new L402Agent({
191
+ * apiKey: 'sk_...',
192
+ * onPaymentNeeded: async (invoice) => {
193
+ * // Send to Slack, Discord, Signal, email, etc.
194
+ * await notify(`Pay ${invoice.amount_sats} sats: ${invoice.invoice}`);
195
+ * },
196
+ * });
197
+ *
198
+ * // Agent requests funding and waits
199
+ * const deposit = await agent.deposit(1000, 'Need funds to accept code-review offer');
200
+ * console.log(`Funded! Balance: ${deposit.amount_sats} sats`);
201
+ */
202
+ deposit(amount_sats: number, reason?: string): Promise<DepositStatus>;
203
+ /**
204
+ * Ensure the agent has at least `minBalance` sats. If not, request a deposit.
205
+ * Convenience method that checks balance first, then deposits the difference.
206
+ *
207
+ * @example
208
+ * // Before accepting an offer, make sure you can pay
209
+ * await agent.ensureBalance(500, 'Need funds for code-review contract');
210
+ * await agent.fundContract(contractId);
211
+ */
212
+ ensureBalance(minBalance: number, reason?: string): Promise<BalanceInfo>;
112
213
  withdraw(amount_sats?: number): Promise<WithdrawResult>;
113
214
  createOffer(params: CreateOfferParams): Promise<Offer>;
114
215
  listOffers(): Promise<Offer[]>;
@@ -130,4 +231,4 @@ declare class L402Agent {
130
231
  }>;
131
232
  }
132
233
 
133
- export { type AgentRegistration, type BalanceInfo, type Contract, type CreateOfferParams, type DepositInvoice, type DepositStatus, type FundResult, L402Agent, type L402AgentOptions, L402Error, type LedgerEntry, type Offer, type WithdrawResult };
234
+ export { type AgentRegistration, type BalanceInfo, type Contract, type CreateOfferParams, type DepositInvoice, type DepositStatus, type FundResult, L402Agent, type L402AgentOptions, L402Error, type LedgerEntry, type Offer, type PaymentNeededCallback, type WithdrawResult };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,29 @@
1
1
  interface L402AgentOptions {
2
2
  apiKey: string;
3
3
  apiUrl?: string;
4
+ /**
5
+ * Called when the agent needs a Lightning invoice paid.
6
+ *
7
+ * AI agents cannot pay Lightning invoices themselves — they don't have wallets.
8
+ * When a deposit is needed, the agent calls this callback so YOU can:
9
+ * - Forward the invoice to a human via chat/notification
10
+ * - Pay it from an external wallet/service
11
+ * - Display it in a UI for manual payment
12
+ *
13
+ * If not provided, `deposit()` returns the invoice and the caller is
14
+ * responsible for payment coordination.
15
+ */
16
+ onPaymentNeeded?: (invoice: DepositInvoice) => Promise<void> | void;
17
+ /**
18
+ * How long to wait (ms) for payment after calling onPaymentNeeded.
19
+ * Default: 300_000 (5 minutes). Set to 0 to skip polling.
20
+ */
21
+ paymentTimeoutMs?: number;
22
+ /**
23
+ * How often to poll (ms) for payment confirmation.
24
+ * Default: 5_000 (5 seconds).
25
+ */
26
+ paymentPollIntervalMs?: number;
4
27
  }
5
28
  interface BalanceInfo {
6
29
  balance_sats: number;
@@ -57,7 +80,31 @@ interface DepositInvoice {
57
80
  payment_hash: string;
58
81
  invoice: string;
59
82
  amount_sats: number;
83
+ /**
84
+ * The human-readable message explaining what to do with this invoice.
85
+ * Agents should forward this + the invoice to a human for payment.
86
+ */
87
+ message: string;
60
88
  }
89
+ /**
90
+ * Callback for notifying a human that payment is needed.
91
+ * AI agents cannot pay Lightning invoices on their own — they need a human
92
+ * (or an external wallet) to pay. This callback is how the agent asks for help.
93
+ *
94
+ * @example
95
+ * // Slack/Discord/Signal notification
96
+ * const agent = new L402Agent({
97
+ * apiKey: 'sk_...',
98
+ * onPaymentNeeded: async (invoice) => {
99
+ * await sendMessage(channel, [
100
+ * `⚡ Payment needed: ${invoice.amount_sats} sats`,
101
+ * `Invoice: ${invoice.invoice}`,
102
+ * `Reason: ${invoice.message}`,
103
+ * ].join('\n'));
104
+ * },
105
+ * });
106
+ */
107
+ type PaymentNeededCallback = (invoice: DepositInvoice) => Promise<void> | void;
61
108
  interface DepositStatus {
62
109
  status: 'pending' | 'paid' | 'expired';
63
110
  amount_sats: number;
@@ -97,6 +144,9 @@ declare class L402Error extends Error {
97
144
  declare class L402Agent {
98
145
  private apiKey;
99
146
  private apiUrl;
147
+ private onPaymentNeeded?;
148
+ private paymentTimeoutMs;
149
+ private paymentPollIntervalMs;
100
150
  constructor(options: L402AgentOptions);
101
151
  private request;
102
152
  static register(options: {
@@ -107,8 +157,59 @@ declare class L402Agent {
107
157
  apiUrl?: string;
108
158
  }): Promise<AgentRegistration>;
109
159
  getBalance(): Promise<BalanceInfo>;
160
+ /**
161
+ * Low-level: create a deposit invoice. Returns the invoice for manual handling.
162
+ * Most agents should use `deposit()` instead, which notifies the human and waits.
163
+ */
110
164
  createDeposit(amount_sats: number): Promise<DepositInvoice>;
165
+ /**
166
+ * Check if a deposit invoice has been paid.
167
+ */
111
168
  checkDeposit(paymentHash: string): Promise<DepositStatus>;
169
+ /**
170
+ * High-level deposit: creates invoice, notifies human, waits for payment.
171
+ *
172
+ * WHY THIS EXISTS: AI agents don't have Lightning wallets. They can't pay
173
+ * invoices. When an agent needs sats, it must ask a human to pay.
174
+ *
175
+ * This method:
176
+ * 1. Creates a Lightning invoice for the requested amount
177
+ * 2. Calls `onPaymentNeeded` so you can notify the human (chat, email, UI)
178
+ * 3. Polls until the invoice is paid or times out
179
+ * 4. Returns the confirmed deposit status
180
+ *
181
+ * If `onPaymentNeeded` is not configured, throws with the invoice so the
182
+ * caller can handle notification manually.
183
+ *
184
+ * @param amount_sats - Amount to deposit
185
+ * @param reason - Human-readable reason (shown in the notification)
186
+ * @returns Confirmed deposit status
187
+ * @throws {L402Error} if payment times out or fails
188
+ *
189
+ * @example
190
+ * const agent = new L402Agent({
191
+ * apiKey: 'sk_...',
192
+ * onPaymentNeeded: async (invoice) => {
193
+ * // Send to Slack, Discord, Signal, email, etc.
194
+ * await notify(`Pay ${invoice.amount_sats} sats: ${invoice.invoice}`);
195
+ * },
196
+ * });
197
+ *
198
+ * // Agent requests funding and waits
199
+ * const deposit = await agent.deposit(1000, 'Need funds to accept code-review offer');
200
+ * console.log(`Funded! Balance: ${deposit.amount_sats} sats`);
201
+ */
202
+ deposit(amount_sats: number, reason?: string): Promise<DepositStatus>;
203
+ /**
204
+ * Ensure the agent has at least `minBalance` sats. If not, request a deposit.
205
+ * Convenience method that checks balance first, then deposits the difference.
206
+ *
207
+ * @example
208
+ * // Before accepting an offer, make sure you can pay
209
+ * await agent.ensureBalance(500, 'Need funds for code-review contract');
210
+ * await agent.fundContract(contractId);
211
+ */
212
+ ensureBalance(minBalance: number, reason?: string): Promise<BalanceInfo>;
112
213
  withdraw(amount_sats?: number): Promise<WithdrawResult>;
113
214
  createOffer(params: CreateOfferParams): Promise<Offer>;
114
215
  listOffers(): Promise<Offer[]>;
@@ -130,4 +231,4 @@ declare class L402Agent {
130
231
  }>;
131
232
  }
132
233
 
133
- export { type AgentRegistration, type BalanceInfo, type Contract, type CreateOfferParams, type DepositInvoice, type DepositStatus, type FundResult, L402Agent, type L402AgentOptions, L402Error, type LedgerEntry, type Offer, type WithdrawResult };
234
+ export { type AgentRegistration, type BalanceInfo, type Contract, type CreateOfferParams, type DepositInvoice, type DepositStatus, type FundResult, L402Agent, type L402AgentOptions, L402Error, type LedgerEntry, type Offer, type PaymentNeededCallback, type WithdrawResult };
package/dist/index.js CHANGED
@@ -14,6 +14,9 @@ var L402Agent = class {
14
14
  }
15
15
  this.apiKey = options.apiKey;
16
16
  this.apiUrl = options.apiUrl ?? "https://l402gw.nosaltres2.info";
17
+ this.onPaymentNeeded = options.onPaymentNeeded;
18
+ this.paymentTimeoutMs = options.paymentTimeoutMs ?? 3e5;
19
+ this.paymentPollIntervalMs = options.paymentPollIntervalMs ?? 5e3;
17
20
  }
18
21
  async request(method, path, body) {
19
22
  const url = `${this.apiUrl}${path}`;
@@ -70,12 +73,100 @@ var L402Agent = class {
70
73
  async getBalance() {
71
74
  return this.request("GET", "/api/v1/wallet/balance");
72
75
  }
76
+ /**
77
+ * Low-level: create a deposit invoice. Returns the invoice for manual handling.
78
+ * Most agents should use `deposit()` instead, which notifies the human and waits.
79
+ */
73
80
  async createDeposit(amount_sats) {
74
81
  return this.request("POST", "/api/v1/wallet/deposit", { amount_sats });
75
82
  }
83
+ /**
84
+ * Check if a deposit invoice has been paid.
85
+ */
76
86
  async checkDeposit(paymentHash) {
77
87
  return this.request("GET", `/api/v1/wallet/deposit/${paymentHash}`);
78
88
  }
89
+ /**
90
+ * High-level deposit: creates invoice, notifies human, waits for payment.
91
+ *
92
+ * WHY THIS EXISTS: AI agents don't have Lightning wallets. They can't pay
93
+ * invoices. When an agent needs sats, it must ask a human to pay.
94
+ *
95
+ * This method:
96
+ * 1. Creates a Lightning invoice for the requested amount
97
+ * 2. Calls `onPaymentNeeded` so you can notify the human (chat, email, UI)
98
+ * 3. Polls until the invoice is paid or times out
99
+ * 4. Returns the confirmed deposit status
100
+ *
101
+ * If `onPaymentNeeded` is not configured, throws with the invoice so the
102
+ * caller can handle notification manually.
103
+ *
104
+ * @param amount_sats - Amount to deposit
105
+ * @param reason - Human-readable reason (shown in the notification)
106
+ * @returns Confirmed deposit status
107
+ * @throws {L402Error} if payment times out or fails
108
+ *
109
+ * @example
110
+ * const agent = new L402Agent({
111
+ * apiKey: 'sk_...',
112
+ * onPaymentNeeded: async (invoice) => {
113
+ * // Send to Slack, Discord, Signal, email, etc.
114
+ * await notify(`Pay ${invoice.amount_sats} sats: ${invoice.invoice}`);
115
+ * },
116
+ * });
117
+ *
118
+ * // Agent requests funding and waits
119
+ * const deposit = await agent.deposit(1000, 'Need funds to accept code-review offer');
120
+ * console.log(`Funded! Balance: ${deposit.amount_sats} sats`);
121
+ */
122
+ async deposit(amount_sats, reason) {
123
+ const invoice = await this.createDeposit(amount_sats);
124
+ if (!this.onPaymentNeeded) {
125
+ throw new L402Error(
126
+ `Payment needed: ${amount_sats} sats. Invoice: ${invoice.invoice}. No onPaymentNeeded callback configured \u2014 pay this invoice manually and call checkDeposit('${invoice.payment_hash}') to confirm.`,
127
+ 402,
128
+ "PAYMENT_NEEDED"
129
+ );
130
+ }
131
+ const enrichedInvoice = {
132
+ ...invoice,
133
+ message: reason ? `\u26A1 Agent needs ${amount_sats} sats: ${reason}` : `\u26A1 Agent needs ${amount_sats} sats deposited`
134
+ };
135
+ await this.onPaymentNeeded(enrichedInvoice);
136
+ if (this.paymentTimeoutMs === 0) {
137
+ return { status: "pending", amount_sats, paid_at: null };
138
+ }
139
+ const deadline = Date.now() + this.paymentTimeoutMs;
140
+ while (Date.now() < deadline) {
141
+ await new Promise((r) => setTimeout(r, this.paymentPollIntervalMs));
142
+ const status = await this.checkDeposit(invoice.payment_hash);
143
+ if (status.status === "paid") return status;
144
+ if (status.status === "expired") {
145
+ throw new L402Error("Deposit invoice expired before payment", 408, "PAYMENT_EXPIRED");
146
+ }
147
+ }
148
+ throw new L402Error(
149
+ `Payment not received within ${this.paymentTimeoutMs / 1e3}s. Invoice may still be valid \u2014 check with checkDeposit('${invoice.payment_hash}')`,
150
+ 408,
151
+ "PAYMENT_TIMEOUT"
152
+ );
153
+ }
154
+ /**
155
+ * Ensure the agent has at least `minBalance` sats. If not, request a deposit.
156
+ * Convenience method that checks balance first, then deposits the difference.
157
+ *
158
+ * @example
159
+ * // Before accepting an offer, make sure you can pay
160
+ * await agent.ensureBalance(500, 'Need funds for code-review contract');
161
+ * await agent.fundContract(contractId);
162
+ */
163
+ async ensureBalance(minBalance, reason) {
164
+ const current = await this.getBalance();
165
+ if (current.balance_sats >= minBalance) return current;
166
+ const needed = minBalance - current.balance_sats;
167
+ await this.deposit(needed, reason ?? `Need ${needed} more sats (have ${current.balance_sats}, need ${minBalance})`);
168
+ return this.getBalance();
169
+ }
79
170
  async withdraw(amount_sats) {
80
171
  return this.request("POST", "/api/v1/wallet/withdraw", amount_sats ? { amount_sats } : {});
81
172
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type {\n L402AgentOptions,\n BalanceInfo,\n Offer,\n CreateOfferParams,\n Contract,\n FundResult,\n DepositInvoice,\n DepositStatus,\n LedgerEntry,\n WithdrawResult,\n AgentRegistration,\n} from './types.js';\n\nexport class L402Error extends Error {\n status: number;\n code?: string;\n\n constructor(message: string, status: number, code?: string) {\n super(message);\n this.name = 'L402Error';\n this.status = status;\n this.code = code;\n }\n}\n\nexport class L402Agent {\n private apiKey: string;\n private apiUrl: string;\n\n constructor(options: L402AgentOptions) {\n if (!options.apiKey) {\n throw new Error('apiKey is required');\n }\n this.apiKey = options.apiKey;\n this.apiUrl = options.apiUrl ?? 'https://l402gw.nosaltres2.info';\n }\n\n private async request<T>(method: string, path: string, body?: any): Promise<T> {\n const url = `${this.apiUrl}${path}`;\n const options: RequestInit = {\n method,\n headers: {\n 'X-L402-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n };\n\n if (body) {\n options.body = JSON.stringify(body);\n }\n\n const res = await fetch(url, options);\n\n if (!res.ok) {\n let errorMsg = `HTTP ${res.status}`;\n let errorCode: string | undefined;\n\n try {\n const data = await res.json() as { error?: string; code?: string };\n errorMsg = data.error || errorMsg;\n errorCode = data.code;\n } catch {\n // Use default error message\n }\n\n throw new L402Error(errorMsg, res.status, errorCode);\n }\n\n return res.json() as Promise<T>;\n }\n\n // Static: register a new agent (no auth needed)\n static async register(options: {\n name: string;\n description?: string;\n wallet_type?: 'custodial' | 'external';\n lightning_address?: string;\n apiUrl?: string;\n }): Promise<AgentRegistration> {\n const apiUrl = options.apiUrl ?? 'https://l402gw.nosaltres2.info';\n const url = `${apiUrl}/api/v1/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 async createDeposit(amount_sats: number): Promise<DepositInvoice> {\n return this.request('POST', '/api/v1/wallet/deposit', { amount_sats });\n }\n\n async checkDeposit(paymentHash: string): Promise<DepositStatus> {\n return this.request('GET', `/api/v1/wallet/deposit/${paymentHash}`);\n }\n\n async withdraw(amount_sats?: number): Promise<WithdrawResult> {\n return this.request('POST', '/api/v1/wallet/withdraw', amount_sats ? { amount_sats } : {});\n }\n\n // Offers\n async createOffer(params: CreateOfferParams): Promise<Offer> {\n return this.request('POST', '/api/v1/offers', params);\n }\n\n async listOffers(): Promise<Offer[]> {\n const result = await this.request<{ offers: Offer[] }>('GET', '/api/v1/offers');\n return result.offers || [];\n }\n\n async getOffer(offerId: string): Promise<Offer> {\n return this.request('GET', `/api/v1/offers/${offerId}`);\n }\n\n async updateOffer(offerId: string, active: boolean): Promise<Offer> {\n return this.request('PATCH', `/api/v1/offers/${offerId}`, { active });\n }\n\n // Contracts\n async acceptOffer(offerId: string): Promise<Contract> {\n return this.request('POST', '/api/v1/contracts', { offer_id: offerId });\n }\n\n async fundContract(contractId: string): Promise<FundResult> {\n return this.request('POST', `/api/v1/contracts/${contractId}/fund`, {});\n }\n\n async listContracts(filters?: { role?: 'buyer' | 'seller'; status?: string }): Promise<Contract[]> {\n let path = '/api/v1/contracts';\n const params = new URLSearchParams();\n if (filters?.role) params.append('role', filters.role);\n if (filters?.status) params.append('status', filters.status);\n if (params.toString()) path += '?' + params.toString();\n const result = await this.request<{ contracts: Contract[] }>('GET', path);\n return result.contracts || [];\n }\n\n async getContract(contractId: string): Promise<Contract> {\n return this.request('GET', `/api/v1/contracts/${contractId}`);\n }\n\n // Delivery\n async submitDelivery(contractId: string, proofUrl: string, proofData?: any): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/deliver`, {\n proof_url: proofUrl,\n proof_data: proofData,\n });\n }\n\n async confirmDelivery(contractId: string): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/confirm`, {});\n }\n\n async disputeDelivery(contractId: string, reason: string, evidenceUrl?: string): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/dispute`, {\n reason,\n evidence_url: evidenceUrl,\n });\n }\n\n // Ledger\n async getLedger(limit?: number, offset?: number): Promise<{ balance_sats: number; entries: LedgerEntry[] }> {\n let path = '/api/v1/ledger';\n const params = new URLSearchParams();\n if (limit) params.append('limit', String(limit));\n if (offset) params.append('offset', String(offset));\n if (params.toString()) path += '?' + params.toString();\n return this.request('GET', path);\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type {\n L402AgentOptions,\n BalanceInfo,\n Offer,\n CreateOfferParams,\n Contract,\n FundResult,\n DepositInvoice,\n DepositStatus,\n LedgerEntry,\n WithdrawResult,\n AgentRegistration,\n PaymentNeededCallback,\n} from './types.js';\n\nexport class L402Error extends Error {\n status: number;\n code?: string;\n\n constructor(message: string, status: number, code?: string) {\n super(message);\n this.name = 'L402Error';\n this.status = status;\n this.code = code;\n }\n}\n\nexport class L402Agent {\n private apiKey: string;\n private apiUrl: string;\n\n private onPaymentNeeded?: PaymentNeededCallback;\n private paymentTimeoutMs: number;\n private paymentPollIntervalMs: number;\n\n constructor(options: L402AgentOptions) {\n if (!options.apiKey) {\n throw new Error('apiKey is required');\n }\n this.apiKey = options.apiKey;\n this.apiUrl = options.apiUrl ?? 'https://l402gw.nosaltres2.info';\n this.onPaymentNeeded = options.onPaymentNeeded;\n this.paymentTimeoutMs = options.paymentTimeoutMs ?? 300_000;\n this.paymentPollIntervalMs = options.paymentPollIntervalMs ?? 5_000;\n }\n\n private async request<T>(method: string, path: string, body?: any): Promise<T> {\n const url = `${this.apiUrl}${path}`;\n const options: RequestInit = {\n method,\n headers: {\n 'X-L402-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n };\n\n if (body) {\n options.body = JSON.stringify(body);\n }\n\n const res = await fetch(url, options);\n\n if (!res.ok) {\n let errorMsg = `HTTP ${res.status}`;\n let errorCode: string | undefined;\n\n try {\n const data = await res.json() as { error?: string; code?: string };\n errorMsg = data.error || errorMsg;\n errorCode = data.code;\n } catch {\n // Use default error message\n }\n\n throw new L402Error(errorMsg, res.status, errorCode);\n }\n\n return res.json() as Promise<T>;\n }\n\n // Static: register a new agent (no auth needed)\n static async register(options: {\n name: string;\n description?: string;\n wallet_type?: 'custodial' | 'external';\n lightning_address?: string;\n apiUrl?: string;\n }): Promise<AgentRegistration> {\n const apiUrl = options.apiUrl ?? 'https://l402gw.nosaltres2.info';\n const url = `${apiUrl}/api/v1/agents/register`;\n\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n name: options.name,\n description: options.description,\n wallet_type: options.wallet_type ?? 'custodial',\n lightning_address: options.lightning_address,\n }),\n });\n\n if (!res.ok) {\n let errorMsg = `HTTP ${res.status}`;\n try {\n const data = await res.json() as { error?: string };\n errorMsg = data.error || errorMsg;\n } catch {\n // Use default error message\n }\n throw new L402Error(errorMsg, res.status);\n }\n\n return res.json() as Promise<AgentRegistration>;\n }\n\n // Wallet\n async getBalance(): Promise<BalanceInfo> {\n return this.request('GET', '/api/v1/wallet/balance');\n }\n\n /**\n * Low-level: create a deposit invoice. Returns the invoice for manual handling.\n * Most agents should use `deposit()` instead, which notifies the human and waits.\n */\n async createDeposit(amount_sats: number): Promise<DepositInvoice> {\n return this.request('POST', '/api/v1/wallet/deposit', { amount_sats });\n }\n\n /**\n * Check if a deposit invoice has been paid.\n */\n async checkDeposit(paymentHash: string): Promise<DepositStatus> {\n return this.request('GET', `/api/v1/wallet/deposit/${paymentHash}`);\n }\n\n /**\n * High-level deposit: creates invoice, notifies human, waits for payment.\n *\n * WHY THIS EXISTS: AI agents don't have Lightning wallets. They can't pay\n * invoices. When an agent needs sats, it must ask a human to pay.\n *\n * This method:\n * 1. Creates a Lightning invoice for the requested amount\n * 2. Calls `onPaymentNeeded` so you can notify the human (chat, email, UI)\n * 3. Polls until the invoice is paid or times out\n * 4. Returns the confirmed deposit status\n *\n * If `onPaymentNeeded` is not configured, throws with the invoice so the\n * caller can handle notification manually.\n *\n * @param amount_sats - Amount to deposit\n * @param reason - Human-readable reason (shown in the notification)\n * @returns Confirmed deposit status\n * @throws {L402Error} if payment times out or fails\n *\n * @example\n * const agent = new L402Agent({\n * apiKey: 'sk_...',\n * onPaymentNeeded: async (invoice) => {\n * // Send to Slack, Discord, Signal, email, etc.\n * await notify(`Pay ${invoice.amount_sats} sats: ${invoice.invoice}`);\n * },\n * });\n *\n * // Agent requests funding and waits\n * const deposit = await agent.deposit(1000, 'Need funds to accept code-review offer');\n * console.log(`Funded! Balance: ${deposit.amount_sats} sats`);\n */\n async deposit(amount_sats: number, reason?: string): Promise<DepositStatus> {\n const invoice = await this.createDeposit(amount_sats);\n\n // If no callback, throw with invoice details so caller can handle it\n if (!this.onPaymentNeeded) {\n throw new L402Error(\n `Payment needed: ${amount_sats} sats. ` +\n `Invoice: ${invoice.invoice}. ` +\n `No onPaymentNeeded callback configured — pay this invoice manually ` +\n `and call checkDeposit('${invoice.payment_hash}') to confirm.`,\n 402,\n 'PAYMENT_NEEDED'\n );\n }\n\n // Notify the human\n const enrichedInvoice: DepositInvoice = {\n ...invoice,\n message: reason\n ? `⚡ Agent needs ${amount_sats} sats: ${reason}`\n : `⚡ Agent needs ${amount_sats} sats deposited`,\n };\n await this.onPaymentNeeded(enrichedInvoice);\n\n // Poll for payment\n if (this.paymentTimeoutMs === 0) {\n return { status: 'pending', amount_sats, paid_at: null };\n }\n\n const deadline = Date.now() + this.paymentTimeoutMs;\n while (Date.now() < deadline) {\n await new Promise(r => setTimeout(r, this.paymentPollIntervalMs));\n const status = await this.checkDeposit(invoice.payment_hash);\n if (status.status === 'paid') return status;\n if (status.status === 'expired') {\n throw new L402Error('Deposit invoice expired before payment', 408, 'PAYMENT_EXPIRED');\n }\n }\n\n throw new L402Error(\n `Payment not received within ${this.paymentTimeoutMs / 1000}s. ` +\n `Invoice may still be valid — check with checkDeposit('${invoice.payment_hash}')`,\n 408,\n 'PAYMENT_TIMEOUT'\n );\n }\n\n /**\n * Ensure the agent has at least `minBalance` sats. If not, request a deposit.\n * Convenience method that checks balance first, then deposits the difference.\n *\n * @example\n * // Before accepting an offer, make sure you can pay\n * await agent.ensureBalance(500, 'Need funds for code-review contract');\n * await agent.fundContract(contractId);\n */\n async ensureBalance(minBalance: number, reason?: string): Promise<BalanceInfo> {\n const current = await this.getBalance();\n if (current.balance_sats >= minBalance) return current;\n\n const needed = minBalance - current.balance_sats;\n await this.deposit(needed, reason ?? `Need ${needed} more sats (have ${current.balance_sats}, need ${minBalance})`);\n return this.getBalance();\n }\n\n async withdraw(amount_sats?: number): Promise<WithdrawResult> {\n return this.request('POST', '/api/v1/wallet/withdraw', amount_sats ? { amount_sats } : {});\n }\n\n // Offers\n async createOffer(params: CreateOfferParams): Promise<Offer> {\n return this.request('POST', '/api/v1/offers', params);\n }\n\n async listOffers(): Promise<Offer[]> {\n const result = await this.request<{ offers: Offer[] }>('GET', '/api/v1/offers');\n return result.offers || [];\n }\n\n async getOffer(offerId: string): Promise<Offer> {\n return this.request('GET', `/api/v1/offers/${offerId}`);\n }\n\n async updateOffer(offerId: string, active: boolean): Promise<Offer> {\n return this.request('PATCH', `/api/v1/offers/${offerId}`, { active });\n }\n\n // Contracts\n async acceptOffer(offerId: string): Promise<Contract> {\n return this.request('POST', '/api/v1/contracts', { offer_id: offerId });\n }\n\n async fundContract(contractId: string): Promise<FundResult> {\n return this.request('POST', `/api/v1/contracts/${contractId}/fund`, {});\n }\n\n async listContracts(filters?: { role?: 'buyer' | 'seller'; status?: string }): Promise<Contract[]> {\n let path = '/api/v1/contracts';\n const params = new URLSearchParams();\n if (filters?.role) params.append('role', filters.role);\n if (filters?.status) params.append('status', filters.status);\n if (params.toString()) path += '?' + params.toString();\n const result = await this.request<{ contracts: Contract[] }>('GET', path);\n return result.contracts || [];\n }\n\n async getContract(contractId: string): Promise<Contract> {\n return this.request('GET', `/api/v1/contracts/${contractId}`);\n }\n\n // Delivery\n async submitDelivery(contractId: string, proofUrl: string, proofData?: any): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/deliver`, {\n proof_url: proofUrl,\n proof_data: proofData,\n });\n }\n\n async confirmDelivery(contractId: string): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/confirm`, {});\n }\n\n async disputeDelivery(contractId: string, reason: string, evidenceUrl?: string): Promise<Contract> {\n return this.request('POST', `/api/v1/contracts/${contractId}/dispute`, {\n reason,\n evidence_url: evidenceUrl,\n });\n }\n\n // Ledger\n async getLedger(limit?: number, offset?: number): Promise<{ balance_sats: number; entries: LedgerEntry[] }> {\n let path = '/api/v1/ledger';\n const params = new URLSearchParams();\n if (limit) params.append('limit', String(limit));\n if (offset) params.append('offset', String(offset));\n if (params.toString()) path += '?' + params.toString();\n return this.request('GET', path);\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "satonomous",
3
- "version": "0.1.1",
3
+ "version": "0.2.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",