pay-lobster 1.0.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.
Files changed (120) hide show
  1. package/README.md +401 -0
  2. package/README.md.bak +401 -0
  3. package/dist/agent.d.ts +132 -0
  4. package/dist/agent.d.ts.map +1 -0
  5. package/dist/agent.js +224 -0
  6. package/dist/agent.js.map +1 -0
  7. package/dist/analytics.d.ts +120 -0
  8. package/dist/analytics.d.ts.map +1 -0
  9. package/dist/analytics.js +345 -0
  10. package/dist/analytics.js.map +1 -0
  11. package/dist/approvals.d.ts +168 -0
  12. package/dist/approvals.d.ts.map +1 -0
  13. package/dist/approvals.js +406 -0
  14. package/dist/approvals.js.map +1 -0
  15. package/dist/circle-client.d.ts +152 -0
  16. package/dist/circle-client.d.ts.map +1 -0
  17. package/dist/circle-client.js +266 -0
  18. package/dist/circle-client.js.map +1 -0
  19. package/dist/commission.d.ts +191 -0
  20. package/dist/commission.d.ts.map +1 -0
  21. package/dist/commission.js +475 -0
  22. package/dist/commission.js.map +1 -0
  23. package/dist/condition-builder.d.ts +98 -0
  24. package/dist/condition-builder.d.ts.map +1 -0
  25. package/dist/condition-builder.js +193 -0
  26. package/dist/condition-builder.js.map +1 -0
  27. package/dist/contacts.d.ts +179 -0
  28. package/dist/contacts.d.ts.map +1 -0
  29. package/dist/contacts.js +445 -0
  30. package/dist/contacts.js.map +1 -0
  31. package/dist/easy.d.ts +22 -0
  32. package/dist/easy.d.ts.map +1 -0
  33. package/dist/easy.js +40 -0
  34. package/dist/easy.js.map +1 -0
  35. package/dist/erc8004/constants.d.ts +152 -0
  36. package/dist/erc8004/constants.d.ts.map +1 -0
  37. package/dist/erc8004/constants.js +114 -0
  38. package/dist/erc8004/constants.js.map +1 -0
  39. package/dist/erc8004/discovery.d.ts +84 -0
  40. package/dist/erc8004/discovery.d.ts.map +1 -0
  41. package/dist/erc8004/discovery.js +217 -0
  42. package/dist/erc8004/discovery.js.map +1 -0
  43. package/dist/erc8004/identity.d.ts +91 -0
  44. package/dist/erc8004/identity.d.ts.map +1 -0
  45. package/dist/erc8004/identity.js +250 -0
  46. package/dist/erc8004/identity.js.map +1 -0
  47. package/dist/erc8004/index.d.ts +147 -0
  48. package/dist/erc8004/index.d.ts.map +1 -0
  49. package/dist/erc8004/index.js +225 -0
  50. package/dist/erc8004/index.js.map +1 -0
  51. package/dist/erc8004/reputation.d.ts +133 -0
  52. package/dist/erc8004/reputation.d.ts.map +1 -0
  53. package/dist/erc8004/reputation.js +277 -0
  54. package/dist/erc8004/reputation.js.map +1 -0
  55. package/dist/escrow-templates.d.ts +38 -0
  56. package/dist/escrow-templates.d.ts.map +1 -0
  57. package/dist/escrow-templates.js +419 -0
  58. package/dist/escrow-templates.js.map +1 -0
  59. package/dist/escrow.d.ts +320 -0
  60. package/dist/escrow.d.ts.map +1 -0
  61. package/dist/escrow.js +854 -0
  62. package/dist/escrow.js.map +1 -0
  63. package/dist/index.d.ts +11 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +33 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/invoices.d.ts +212 -0
  68. package/dist/invoices.d.ts.map +1 -0
  69. package/dist/invoices.js +393 -0
  70. package/dist/invoices.js.map +1 -0
  71. package/dist/notifications.d.ts +141 -0
  72. package/dist/notifications.d.ts.map +1 -0
  73. package/dist/notifications.js +350 -0
  74. package/dist/notifications.js.map +1 -0
  75. package/dist/tips.d.ts +171 -0
  76. package/dist/tips.d.ts.map +1 -0
  77. package/dist/tips.js +390 -0
  78. package/dist/tips.js.map +1 -0
  79. package/dist/types.d.ts +100 -0
  80. package/dist/types.d.ts.map +1 -0
  81. package/dist/types.js +6 -0
  82. package/dist/types.js.map +1 -0
  83. package/dist/x402-client.d.ts +127 -0
  84. package/dist/x402-client.d.ts.map +1 -0
  85. package/dist/x402-client.js +350 -0
  86. package/dist/x402-client.js.map +1 -0
  87. package/dist/x402-server.d.ts +133 -0
  88. package/dist/x402-server.d.ts.map +1 -0
  89. package/dist/x402-server.js +330 -0
  90. package/dist/x402-server.js.map +1 -0
  91. package/lib/agent.ts +273 -0
  92. package/lib/analytics.ts +474 -0
  93. package/lib/analytics.ts.bak +474 -0
  94. package/lib/approvals.ts +585 -0
  95. package/lib/approvals.ts.bak +585 -0
  96. package/lib/circle-client.ts +376 -0
  97. package/lib/circle-client.ts.bak +376 -0
  98. package/lib/commission.ts +680 -0
  99. package/lib/commission.ts.bak +680 -0
  100. package/lib/condition-builder.ts +223 -0
  101. package/lib/condition-builder.ts.bak +223 -0
  102. package/lib/contacts.ts +615 -0
  103. package/lib/contacts.ts.bak +615 -0
  104. package/lib/easy.ts +46 -0
  105. package/lib/easy.ts.bak +352 -0
  106. package/lib/erc8004/constants.ts +175 -0
  107. package/lib/erc8004/discovery.ts +299 -0
  108. package/lib/erc8004/identity.ts +327 -0
  109. package/lib/erc8004/index.ts +285 -0
  110. package/lib/erc8004/reputation.ts +368 -0
  111. package/lib/escrow-templates.ts +462 -0
  112. package/lib/escrow.ts +1216 -0
  113. package/lib/index.ts +13 -0
  114. package/lib/invoices.ts +588 -0
  115. package/lib/notifications.ts +484 -0
  116. package/lib/tips.ts +570 -0
  117. package/lib/types.ts +108 -0
  118. package/lib/x402-client.ts +471 -0
  119. package/lib/x402-server.ts +462 -0
  120. package/package.json +58 -0
@@ -0,0 +1,471 @@
1
+ /**
2
+ * x402 Payment-Enabled HTTP Client
3
+ *
4
+ * Wraps fetch() to automatically handle 402 Payment Required responses.
5
+ * Uses Circle Programmable Wallets to pay and retry with signature.
6
+ */
7
+
8
+ import { CircleClient } from './circle-client';
9
+ import crypto from 'crypto';
10
+ import fs from 'fs/promises';
11
+ import path from 'path';
12
+
13
+ // x402 Payment Challenge (from server)
14
+ export interface PaymentChallenge {
15
+ 'x-payment-required': {
16
+ version: '1';
17
+ network: string; // e.g., 'ETH-SEPOLIA'
18
+ receiver: string; // Destination wallet address
19
+ asset: string; // 'USDC'
20
+ amount: string; // Amount in USDC
21
+ description: string; // Human-readable description
22
+ expires: number; // Unix timestamp
23
+ nonce: string; // Unique challenge ID
24
+ };
25
+ }
26
+
27
+ // Payment Receipt (cached)
28
+ export interface PaymentReceipt {
29
+ url: string;
30
+ challenge: PaymentChallenge['x-payment-required'];
31
+ txHash: string;
32
+ signature: string;
33
+ paidAt: string;
34
+ expiresAt: number;
35
+ }
36
+
37
+ // Configuration
38
+ export interface X402ClientConfig {
39
+ wallet: CircleClient;
40
+ walletId?: string; // Specific wallet to use
41
+ maxAutoPayUSDC?: string; // Max amount to auto-pay (default: none)
42
+ requireConfirmation?: boolean; // Prompt before paying (default: false)
43
+ cacheReceipts?: boolean; // Cache payment receipts (default: true)
44
+ cacheDir?: string; // Receipt cache location
45
+
46
+ // Event hooks
47
+ onPayment?: (amount: string, url: string, txHash: string) => void;
48
+ onChallenge?: (challenge: PaymentChallenge) => void;
49
+ onVerified?: (receipt: PaymentReceipt) => void;
50
+ onError?: (error: Error, url: string) => void;
51
+ }
52
+
53
+ /**
54
+ * x402-enabled fetch function
55
+ */
56
+ export type X402Fetch = (
57
+ url: string | URL | Request,
58
+ init?: RequestInit
59
+ ) => Promise<Response>;
60
+
61
+ /**
62
+ * Create a payment-enabled fetch wrapper
63
+ */
64
+ export function createX402Fetch(config: X402ClientConfig): X402Fetch {
65
+ const client = new X402Client(config);
66
+
67
+ return async (url: string | URL | Request, init?: RequestInit) => {
68
+ return client.fetch(url, init);
69
+ };
70
+ }
71
+
72
+ /**
73
+ * X402 HTTP Client
74
+ */
75
+ export class X402Client {
76
+ private wallet: CircleClient;
77
+ private walletId?: string;
78
+ private maxAutoPayUSDC?: number;
79
+ private requireConfirmation: boolean;
80
+ private cacheReceipts: boolean;
81
+ private cacheDir: string;
82
+
83
+ private onPayment?: (amount: string, url: string, txHash: string) => void;
84
+ private onChallenge?: (challenge: PaymentChallenge) => void;
85
+ private onVerified?: (receipt: PaymentReceipt) => void;
86
+ private onError?: (error: Error, url: string) => void;
87
+
88
+ // In-memory receipt cache
89
+ private receiptCache: Map<string, PaymentReceipt> = new Map();
90
+
91
+ constructor(config: X402ClientConfig) {
92
+ this.wallet = config.wallet;
93
+ this.walletId = config.walletId;
94
+ this.maxAutoPayUSDC = config.maxAutoPayUSDC ? parseFloat(config.maxAutoPayUSDC) : undefined;
95
+ this.requireConfirmation = config.requireConfirmation ?? false;
96
+ this.cacheReceipts = config.cacheReceipts ?? true;
97
+ this.cacheDir = config.cacheDir || './data/x402-receipts';
98
+
99
+ this.onPayment = config.onPayment;
100
+ this.onChallenge = config.onChallenge;
101
+ this.onVerified = config.onVerified;
102
+ this.onError = config.onError;
103
+
104
+ // Load cached receipts on init
105
+ if (this.cacheReceipts) {
106
+ this.loadReceiptCache().catch(() => {
107
+ // Ignore cache load errors
108
+ });
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Main fetch method - handles 402 automatically
114
+ */
115
+ async fetch(url: string | URL | Request, init?: RequestInit): Promise<Response> {
116
+ const urlString = this.getUrlString(url);
117
+
118
+ try {
119
+ // Check for cached receipt
120
+ const cachedReceipt = this.getCachedReceipt(urlString);
121
+ if (cachedReceipt) {
122
+ // Use cached payment signature
123
+ return this.fetchWithPayment(url, init, cachedReceipt.signature);
124
+ }
125
+
126
+ // First attempt - no payment
127
+ const response = await fetch(url, init);
128
+
129
+ // Success or non-402 error - return as-is
130
+ if (response.status !== 402) {
131
+ return response;
132
+ }
133
+
134
+ // 402 Payment Required - handle payment
135
+ const paymentResponse = await this.handlePaymentRequired(response, urlString, url, init);
136
+ return paymentResponse;
137
+
138
+ } catch (error) {
139
+ const err = error as Error;
140
+ this.onError?.(err, urlString);
141
+ throw err;
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Handle 402 Payment Required response
147
+ */
148
+ private async handlePaymentRequired(
149
+ response: Response,
150
+ urlString: string,
151
+ originalUrl: string | URL | Request,
152
+ originalInit?: RequestInit
153
+ ): Promise<Response> {
154
+ // Parse payment challenge from response
155
+ const challenge = await this.parsePaymentChallenge(response);
156
+
157
+ if (!challenge) {
158
+ throw new Error('Invalid 402 response: missing x-payment-required');
159
+ }
160
+
161
+ // Trigger challenge hook
162
+ this.onChallenge?.(challenge);
163
+
164
+ // Check if challenge is expired
165
+ if (challenge['x-payment-required'].expires < Math.floor(Date.now() / 1000)) {
166
+ throw new Error('Payment challenge expired');
167
+ }
168
+
169
+ // Check max auto-pay limit
170
+ const amount = parseFloat(challenge['x-payment-required'].amount);
171
+ if (this.maxAutoPayUSDC !== undefined && amount > this.maxAutoPayUSDC) {
172
+ throw new Error(
173
+ `Payment amount ${amount} USDC exceeds max auto-pay limit ${this.maxAutoPayUSDC} USDC`
174
+ );
175
+ }
176
+
177
+ // Confirm payment if required
178
+ if (this.requireConfirmation) {
179
+ const confirmed = await this.confirmPayment(challenge);
180
+ if (!confirmed) {
181
+ throw new Error('Payment cancelled by user');
182
+ }
183
+ }
184
+
185
+ // Execute payment
186
+ const txHash = await this.executePayment(challenge);
187
+
188
+ // Trigger payment hook
189
+ this.onPayment?.(
190
+ challenge['x-payment-required'].amount,
191
+ urlString,
192
+ txHash
193
+ );
194
+
195
+ // Generate payment signature
196
+ const signature = this.generatePaymentSignature(challenge, txHash);
197
+
198
+ // Cache receipt
199
+ const receipt: PaymentReceipt = {
200
+ url: urlString,
201
+ challenge: challenge['x-payment-required'],
202
+ txHash,
203
+ signature,
204
+ paidAt: new Date().toISOString(),
205
+ expiresAt: challenge['x-payment-required'].expires,
206
+ };
207
+
208
+ await this.cacheReceipt(receipt);
209
+ this.onVerified?.(receipt);
210
+
211
+ // Retry request with payment signature
212
+ return this.fetchWithPayment(originalUrl, originalInit, signature);
213
+ }
214
+
215
+ /**
216
+ * Execute USDC payment via Circle
217
+ */
218
+ private async executePayment(challenge: PaymentChallenge): Promise<string> {
219
+ const { amount, receiver, network } = challenge['x-payment-required'];
220
+
221
+ // Determine wallet to use
222
+ let walletId = this.walletId;
223
+
224
+ if (!walletId) {
225
+ // Auto-select wallet for the network
226
+ const wallets = await this.wallet.listWallets();
227
+ const wallet = wallets.find(w => w.blockchain === network);
228
+
229
+ if (!wallet) {
230
+ throw new Error(`No wallet found for network ${network}`);
231
+ }
232
+
233
+ walletId = wallet.id;
234
+ }
235
+
236
+ // Send USDC
237
+ const tx = await this.wallet.sendUSDC({
238
+ fromWalletId: walletId,
239
+ toAddress: receiver,
240
+ amount,
241
+ });
242
+
243
+ // Wait for transaction to be confirmed
244
+ // In production, you might want to poll for confirmation
245
+ // For now, return transaction ID
246
+ return tx.txHash || tx.id;
247
+ }
248
+
249
+ /**
250
+ * Fetch with payment signature header
251
+ */
252
+ private async fetchWithPayment(
253
+ url: string | URL | Request,
254
+ init: RequestInit | undefined,
255
+ signature: string
256
+ ): Promise<Response> {
257
+ const headers = new Headers(init?.headers);
258
+ headers.set('x-payment-signature', signature);
259
+
260
+ return fetch(url, {
261
+ ...init,
262
+ headers,
263
+ });
264
+ }
265
+
266
+ /**
267
+ * Parse payment challenge from 402 response
268
+ */
269
+ private async parsePaymentChallenge(response: Response): Promise<PaymentChallenge | null> {
270
+ try {
271
+ const body = await response.json();
272
+
273
+ if (body['x-payment-required']) {
274
+ return body as PaymentChallenge;
275
+ }
276
+
277
+ return null;
278
+ } catch {
279
+ return null;
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Generate payment signature from challenge and tx hash
285
+ */
286
+ private generatePaymentSignature(challenge: PaymentChallenge, txHash: string): string {
287
+ // In production, this would involve the x402 facilitator
288
+ // For now, create a deterministic signature
289
+ const data = JSON.stringify({
290
+ nonce: challenge['x-payment-required'].nonce,
291
+ amount: challenge['x-payment-required'].amount,
292
+ receiver: challenge['x-payment-required'].receiver,
293
+ txHash,
294
+ });
295
+
296
+ // Simple hash-based signature (production would use proper signing)
297
+ return crypto.createHash('sha256').update(data).digest('hex');
298
+ }
299
+
300
+ /**
301
+ * Confirm payment with user (if required)
302
+ */
303
+ private async confirmPayment(challenge: PaymentChallenge): Promise<boolean> {
304
+ // In a real implementation, this would prompt the user
305
+ // For CLI/automated contexts, we can skip confirmation
306
+ // For UI contexts, show a dialog
307
+
308
+ const { amount, description } = challenge['x-payment-required'];
309
+
310
+ console.log(`\n💳 Payment Required`);
311
+ console.log(` Amount: ${amount} USDC`);
312
+ console.log(` Description: ${description}`);
313
+ console.log(` Confirm? (auto-approved)\n`);
314
+
315
+ // Auto-approve for now
316
+ return true;
317
+ }
318
+
319
+ /**
320
+ * Get cached receipt for URL
321
+ */
322
+ private getCachedReceipt(url: string): PaymentReceipt | null {
323
+ if (!this.cacheReceipts) return null;
324
+
325
+ const receipt = this.receiptCache.get(url);
326
+
327
+ // Check if receipt is still valid
328
+ if (receipt && receipt.expiresAt > Math.floor(Date.now() / 1000)) {
329
+ return receipt;
330
+ }
331
+
332
+ // Expired - remove from cache
333
+ if (receipt) {
334
+ this.receiptCache.delete(url);
335
+ }
336
+
337
+ return null;
338
+ }
339
+
340
+ /**
341
+ * Cache payment receipt
342
+ */
343
+ private async cacheReceipt(receipt: PaymentReceipt): Promise<void> {
344
+ if (!this.cacheReceipts) return;
345
+
346
+ // Add to in-memory cache
347
+ this.receiptCache.set(receipt.url, receipt);
348
+
349
+ // Persist to disk
350
+ try {
351
+ await fs.mkdir(this.cacheDir, { recursive: true });
352
+
353
+ const filename = crypto.createHash('md5').update(receipt.url).digest('hex') + '.json';
354
+ const filepath = path.join(this.cacheDir, filename);
355
+
356
+ await fs.writeFile(filepath, JSON.stringify(receipt, null, 2));
357
+ } catch (error) {
358
+ // Ignore cache write errors
359
+ console.error('Failed to cache receipt:', error);
360
+ }
361
+ }
362
+
363
+ /**
364
+ * Load cached receipts from disk
365
+ */
366
+ private async loadReceiptCache(): Promise<void> {
367
+ try {
368
+ await fs.mkdir(this.cacheDir, { recursive: true });
369
+ const files = await fs.readdir(this.cacheDir);
370
+
371
+ for (const file of files) {
372
+ if (!file.endsWith('.json')) continue;
373
+
374
+ try {
375
+ const filepath = path.join(this.cacheDir, file);
376
+ const data = await fs.readFile(filepath, 'utf-8');
377
+ const receipt: PaymentReceipt = JSON.parse(data);
378
+
379
+ // Only cache if not expired
380
+ if (receipt.expiresAt > Math.floor(Date.now() / 1000)) {
381
+ this.receiptCache.set(receipt.url, receipt);
382
+ } else {
383
+ // Delete expired receipt
384
+ await fs.unlink(filepath);
385
+ }
386
+ } catch {
387
+ // Ignore individual file errors
388
+ }
389
+ }
390
+ } catch {
391
+ // Ignore cache directory errors
392
+ }
393
+ }
394
+
395
+ /**
396
+ * Get receipt history
397
+ */
398
+ async getReceiptHistory(): Promise<PaymentReceipt[]> {
399
+ const receipts: PaymentReceipt[] = [];
400
+
401
+ try {
402
+ const files = await fs.readdir(this.cacheDir);
403
+
404
+ for (const file of files) {
405
+ if (!file.endsWith('.json')) continue;
406
+
407
+ try {
408
+ const filepath = path.join(this.cacheDir, file);
409
+ const data = await fs.readFile(filepath, 'utf-8');
410
+ const receipt: PaymentReceipt = JSON.parse(data);
411
+ receipts.push(receipt);
412
+ } catch {
413
+ // Ignore individual file errors
414
+ }
415
+ }
416
+ } catch {
417
+ // Ignore directory errors
418
+ }
419
+
420
+ // Sort by payment date (newest first)
421
+ return receipts.sort((a, b) =>
422
+ new Date(b.paidAt).getTime() - new Date(a.paidAt).getTime()
423
+ );
424
+ }
425
+
426
+ /**
427
+ * Clear receipt cache
428
+ */
429
+ async clearCache(): Promise<void> {
430
+ this.receiptCache.clear();
431
+
432
+ try {
433
+ const files = await fs.readdir(this.cacheDir);
434
+
435
+ for (const file of files) {
436
+ const filepath = path.join(this.cacheDir, file);
437
+ await fs.unlink(filepath);
438
+ }
439
+ } catch {
440
+ // Ignore errors
441
+ }
442
+ }
443
+
444
+ /**
445
+ * Extract URL string from various input types
446
+ */
447
+ private getUrlString(url: string | URL | Request): string {
448
+ if (typeof url === 'string') {
449
+ return url;
450
+ } else if (url instanceof URL) {
451
+ return url.toString();
452
+ } else {
453
+ return url.url;
454
+ }
455
+ }
456
+ }
457
+
458
+ /**
459
+ * Helper: Create simple x402 client with minimal config
460
+ */
461
+ export function simpleX402Fetch(wallet: CircleClient, maxAutoPayUSDC?: string): X402Fetch {
462
+ return createX402Fetch({
463
+ wallet,
464
+ maxAutoPayUSDC,
465
+ onPayment: (amount, url) => {
466
+ console.log(`💸 Paid ${amount} USDC for ${url}`);
467
+ },
468
+ });
469
+ }
470
+
471
+ export default { createX402Fetch, simpleX402Fetch, X402Client };