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,474 @@
1
+ /**
2
+ * Transaction Analytics & Reporting
3
+ *
4
+ * Insights, summaries, and reports on USDC activity.
5
+ */
6
+
7
+ import fs from 'fs/promises';
8
+ import path from 'path';
9
+
10
+ export interface TransactionRecord {
11
+ id: string;
12
+ type: 'send' | 'receive' | 'bridge';
13
+ amount: string;
14
+ fromAddress: string;
15
+ toAddress: string;
16
+ chain: string;
17
+ targetChain?: string;
18
+ txHash: string;
19
+ fee?: string;
20
+ timestamp: string;
21
+ contactName?: string;
22
+ category?: string;
23
+ memo?: string;
24
+ }
25
+
26
+ export interface DailySummary {
27
+ date: string;
28
+ sent: string;
29
+ received: string;
30
+ net: string;
31
+ txCount: number;
32
+ fees: string;
33
+ }
34
+
35
+ export interface CategorySummary {
36
+ category: string;
37
+ sent: string;
38
+ received: string;
39
+ txCount: number;
40
+ }
41
+
42
+ export interface ContactSummary {
43
+ name: string;
44
+ address: string;
45
+ sent: string;
46
+ received: string;
47
+ txCount: number;
48
+ lastTxDate: string;
49
+ }
50
+
51
+ const DATA_DIR = process.env.USDC_DATA_DIR || './data';
52
+
53
+ /**
54
+ * Analytics Engine
55
+ */
56
+ export class AnalyticsEngine {
57
+ private txPath: string;
58
+
59
+ constructor(dataDir = DATA_DIR) {
60
+ this.txPath = path.join(dataDir, 'transactions.json');
61
+ }
62
+
63
+ private async loadTransactions(): Promise<TransactionRecord[]> {
64
+ try {
65
+ const data = await fs.readFile(this.txPath, 'utf-8');
66
+ return JSON.parse(data);
67
+ } catch {
68
+ return [];
69
+ }
70
+ }
71
+
72
+ private async saveTransactions(txs: TransactionRecord[]): Promise<void> {
73
+ await fs.mkdir(path.dirname(this.txPath), { recursive: true });
74
+ await fs.writeFile(this.txPath, JSON.stringify(txs, null, 2));
75
+ }
76
+
77
+ /**
78
+ * Record a transaction for analytics
79
+ */
80
+ async recordTransaction(tx: Omit<TransactionRecord, 'id'>): Promise<TransactionRecord> {
81
+ const txs = await this.loadTransactions();
82
+
83
+ const record: TransactionRecord = {
84
+ id: `tx-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
85
+ ...tx,
86
+ };
87
+
88
+ txs.push(record);
89
+ await this.saveTransactions(txs);
90
+
91
+ return record;
92
+ }
93
+
94
+ /**
95
+ * Get transactions with filters
96
+ */
97
+ async getTransactions(filters?: {
98
+ type?: TransactionRecord['type'];
99
+ chain?: string;
100
+ fromDate?: string;
101
+ toDate?: string;
102
+ minAmount?: string;
103
+ maxAmount?: string;
104
+ contact?: string;
105
+ category?: string;
106
+ limit?: number;
107
+ }): Promise<TransactionRecord[]> {
108
+ let txs = await this.loadTransactions();
109
+
110
+ if (filters?.type) {
111
+ txs = txs.filter(tx => tx.type === filters.type);
112
+ }
113
+ if (filters?.chain) {
114
+ txs = txs.filter(tx => tx.chain === filters.chain);
115
+ }
116
+ if (filters?.fromDate) {
117
+ txs = txs.filter(tx => tx.timestamp >= filters.fromDate!);
118
+ }
119
+ if (filters?.toDate) {
120
+ txs = txs.filter(tx => tx.timestamp <= filters.toDate!);
121
+ }
122
+ if (filters?.minAmount) {
123
+ txs = txs.filter(tx => parseFloat(tx.amount) >= parseFloat(filters.minAmount!));
124
+ }
125
+ if (filters?.maxAmount) {
126
+ txs = txs.filter(tx => parseFloat(tx.amount) <= parseFloat(filters.maxAmount!));
127
+ }
128
+ if (filters?.contact) {
129
+ const contactLower = filters.contact.toLowerCase();
130
+ txs = txs.filter(tx => tx.contactName?.toLowerCase().includes(contactLower));
131
+ }
132
+ if (filters?.category) {
133
+ txs = txs.filter(tx => tx.category === filters.category);
134
+ }
135
+
136
+ txs.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
137
+
138
+ if (filters?.limit) {
139
+ txs = txs.slice(0, filters.limit);
140
+ }
141
+
142
+ return txs;
143
+ }
144
+
145
+ /**
146
+ * Get daily summaries for a date range
147
+ */
148
+ async getDailySummaries(fromDate: string, toDate: string): Promise<DailySummary[]> {
149
+ const txs = await this.getTransactions({ fromDate, toDate });
150
+ const dailyMap = new Map<string, DailySummary>();
151
+
152
+ for (const tx of txs) {
153
+ const date = tx.timestamp.split('T')[0];
154
+
155
+ if (!dailyMap.has(date)) {
156
+ dailyMap.set(date, {
157
+ date,
158
+ sent: '0',
159
+ received: '0',
160
+ net: '0',
161
+ txCount: 0,
162
+ fees: '0',
163
+ });
164
+ }
165
+
166
+ const summary = dailyMap.get(date)!;
167
+ const amount = parseFloat(tx.amount);
168
+
169
+ if (tx.type === 'send') {
170
+ summary.sent = (parseFloat(summary.sent) + amount).toString();
171
+ } else if (tx.type === 'receive') {
172
+ summary.received = (parseFloat(summary.received) + amount).toString();
173
+ }
174
+
175
+ summary.txCount++;
176
+
177
+ if (tx.fee) {
178
+ summary.fees = (parseFloat(summary.fees) + parseFloat(tx.fee)).toString();
179
+ }
180
+ }
181
+
182
+ // Calculate net
183
+ for (const summary of dailyMap.values()) {
184
+ summary.net = (parseFloat(summary.received) - parseFloat(summary.sent)).toString();
185
+ }
186
+
187
+ return Array.from(dailyMap.values()).sort((a, b) => b.date.localeCompare(a.date));
188
+ }
189
+
190
+ /**
191
+ * Get category breakdown
192
+ */
193
+ async getCategoryBreakdown(fromDate?: string, toDate?: string): Promise<CategorySummary[]> {
194
+ const txs = await this.getTransactions({ fromDate, toDate });
195
+ const categoryMap = new Map<string, CategorySummary>();
196
+
197
+ for (const tx of txs) {
198
+ const category = tx.category || 'Uncategorized';
199
+
200
+ if (!categoryMap.has(category)) {
201
+ categoryMap.set(category, {
202
+ category,
203
+ sent: '0',
204
+ received: '0',
205
+ txCount: 0,
206
+ });
207
+ }
208
+
209
+ const summary = categoryMap.get(category)!;
210
+ const amount = parseFloat(tx.amount);
211
+
212
+ if (tx.type === 'send') {
213
+ summary.sent = (parseFloat(summary.sent) + amount).toString();
214
+ } else if (tx.type === 'receive') {
215
+ summary.received = (parseFloat(summary.received) + amount).toString();
216
+ }
217
+
218
+ summary.txCount++;
219
+ }
220
+
221
+ return Array.from(categoryMap.values())
222
+ .sort((a, b) => parseFloat(b.sent) + parseFloat(b.received) - parseFloat(a.sent) - parseFloat(a.received));
223
+ }
224
+
225
+ /**
226
+ * Get top contacts by volume
227
+ */
228
+ async getTopContacts(limit = 10, fromDate?: string, toDate?: string): Promise<ContactSummary[]> {
229
+ const txs = await this.getTransactions({ fromDate, toDate });
230
+ const contactMap = new Map<string, ContactSummary>();
231
+
232
+ for (const tx of txs) {
233
+ // Use contact name or address
234
+ const key = tx.contactName || (tx.type === 'send' ? tx.toAddress : tx.fromAddress);
235
+ const address = tx.type === 'send' ? tx.toAddress : tx.fromAddress;
236
+
237
+ if (!contactMap.has(key)) {
238
+ contactMap.set(key, {
239
+ name: tx.contactName || this.shortAddress(address),
240
+ address,
241
+ sent: '0',
242
+ received: '0',
243
+ txCount: 0,
244
+ lastTxDate: tx.timestamp,
245
+ });
246
+ }
247
+
248
+ const summary = contactMap.get(key)!;
249
+ const amount = parseFloat(tx.amount);
250
+
251
+ if (tx.type === 'send') {
252
+ summary.sent = (parseFloat(summary.sent) + amount).toString();
253
+ } else if (tx.type === 'receive') {
254
+ summary.received = (parseFloat(summary.received) + amount).toString();
255
+ }
256
+
257
+ summary.txCount++;
258
+
259
+ if (tx.timestamp > summary.lastTxDate) {
260
+ summary.lastTxDate = tx.timestamp;
261
+ }
262
+ }
263
+
264
+ return Array.from(contactMap.values())
265
+ .sort((a, b) => {
266
+ const volumeA = parseFloat(a.sent) + parseFloat(a.received);
267
+ const volumeB = parseFloat(b.sent) + parseFloat(b.received);
268
+ return volumeB - volumeA;
269
+ })
270
+ .slice(0, limit);
271
+ }
272
+
273
+ /**
274
+ * Get total stats
275
+ */
276
+ async getTotalStats(fromDate?: string, toDate?: string): Promise<{
277
+ totalSent: string;
278
+ totalReceived: string;
279
+ totalBridged: string;
280
+ netFlow: string;
281
+ totalFees: string;
282
+ txCount: number;
283
+ avgTxSize: string;
284
+ largestTx: TransactionRecord | null;
285
+ mostFrequentContact: ContactSummary | null;
286
+ }> {
287
+ const txs = await this.getTransactions({ fromDate, toDate });
288
+
289
+ let totalSent = 0;
290
+ let totalReceived = 0;
291
+ let totalBridged = 0;
292
+ let totalFees = 0;
293
+ let largestTx: TransactionRecord | null = null;
294
+ let largestAmount = 0;
295
+
296
+ for (const tx of txs) {
297
+ const amount = parseFloat(tx.amount);
298
+
299
+ if (tx.type === 'send') {
300
+ totalSent += amount;
301
+ } else if (tx.type === 'receive') {
302
+ totalReceived += amount;
303
+ } else if (tx.type === 'bridge') {
304
+ totalBridged += amount;
305
+ }
306
+
307
+ if (tx.fee) {
308
+ totalFees += parseFloat(tx.fee);
309
+ }
310
+
311
+ if (amount > largestAmount) {
312
+ largestAmount = amount;
313
+ largestTx = tx;
314
+ }
315
+ }
316
+
317
+ const contacts = await this.getTopContacts(1, fromDate, toDate);
318
+
319
+ return {
320
+ totalSent: totalSent.toFixed(2),
321
+ totalReceived: totalReceived.toFixed(2),
322
+ totalBridged: totalBridged.toFixed(2),
323
+ netFlow: (totalReceived - totalSent).toFixed(2),
324
+ totalFees: totalFees.toFixed(6),
325
+ txCount: txs.length,
326
+ avgTxSize: txs.length > 0
327
+ ? ((totalSent + totalReceived) / txs.length).toFixed(2)
328
+ : '0',
329
+ largestTx,
330
+ mostFrequentContact: contacts[0] || null,
331
+ };
332
+ }
333
+
334
+ /**
335
+ * Get chain distribution
336
+ */
337
+ async getChainDistribution(fromDate?: string, toDate?: string): Promise<{
338
+ chain: string;
339
+ sent: string;
340
+ received: string;
341
+ txCount: number;
342
+ percentage: string;
343
+ }[]> {
344
+ const txs = await this.getTransactions({ fromDate, toDate });
345
+ const chainMap = new Map<string, { sent: number; received: number; count: number }>();
346
+ let totalVolume = 0;
347
+
348
+ for (const tx of txs) {
349
+ const chain = tx.chain;
350
+
351
+ if (!chainMap.has(chain)) {
352
+ chainMap.set(chain, { sent: 0, received: 0, count: 0 });
353
+ }
354
+
355
+ const stats = chainMap.get(chain)!;
356
+ const amount = parseFloat(tx.amount);
357
+
358
+ if (tx.type === 'send') {
359
+ stats.sent += amount;
360
+ } else if (tx.type === 'receive') {
361
+ stats.received += amount;
362
+ }
363
+
364
+ stats.count++;
365
+ totalVolume += amount;
366
+ }
367
+
368
+ return Array.from(chainMap.entries())
369
+ .map(([chain, stats]) => ({
370
+ chain,
371
+ sent: stats.sent.toFixed(2),
372
+ received: stats.received.toFixed(2),
373
+ txCount: stats.count,
374
+ percentage: totalVolume > 0
375
+ ? ((stats.sent + stats.received) / totalVolume * 100).toFixed(1)
376
+ : '0',
377
+ }))
378
+ .sort((a, b) => parseFloat(b.percentage) - parseFloat(a.percentage));
379
+ }
380
+
381
+ /**
382
+ * Generate summary report text
383
+ */
384
+ async generateReport(fromDate?: string, toDate?: string): Promise<string> {
385
+ const stats = await this.getTotalStats(fromDate, toDate);
386
+ const dailies = await this.getDailySummaries(
387
+ fromDate || new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(),
388
+ toDate || new Date().toISOString()
389
+ );
390
+ const chains = await this.getChainDistribution(fromDate, toDate);
391
+ const topContacts = await this.getTopContacts(5, fromDate, toDate);
392
+
393
+ let report = '# 📊 USDC Activity Report\n\n';
394
+
395
+ if (fromDate || toDate) {
396
+ report += `Period: ${fromDate || 'start'} to ${toDate || 'now'}\n\n`;
397
+ }
398
+
399
+ report += '## Summary\n';
400
+ report += `- Total Sent: **$${stats.totalSent} USDC**\n`;
401
+ report += `- Total Received: **$${stats.totalReceived} USDC**\n`;
402
+ report += `- Net Flow: **$${stats.netFlow} USDC**\n`;
403
+ report += `- Transactions: ${stats.txCount}\n`;
404
+ report += `- Avg Transaction: $${stats.avgTxSize} USDC\n`;
405
+ report += `- Total Fees: $${stats.totalFees}\n\n`;
406
+
407
+ if (stats.largestTx) {
408
+ report += '## Largest Transaction\n';
409
+ report += `$${stats.largestTx.amount} USDC (${stats.largestTx.type}) on ${new Date(stats.largestTx.timestamp).toLocaleDateString()}\n\n`;
410
+ }
411
+
412
+ if (chains.length > 0) {
413
+ report += '## Chain Distribution\n';
414
+ for (const chain of chains) {
415
+ report += `- ${chain.chain}: ${chain.percentage}% (${chain.txCount} txs)\n`;
416
+ }
417
+ report += '\n';
418
+ }
419
+
420
+ if (topContacts.length > 0) {
421
+ report += '## Top Contacts\n';
422
+ for (const contact of topContacts) {
423
+ const volume = (parseFloat(contact.sent) + parseFloat(contact.received)).toFixed(2);
424
+ report += `- ${contact.name}: $${volume} USDC (${contact.txCount} txs)\n`;
425
+ }
426
+ report += '\n';
427
+ }
428
+
429
+ if (dailies.length > 0 && dailies.length <= 14) {
430
+ report += '## Daily Activity (Last 14 Days)\n';
431
+ for (const day of dailies.slice(0, 14)) {
432
+ const netPrefix = parseFloat(day.net) >= 0 ? '+' : '';
433
+ report += `- ${day.date}: ${day.txCount} txs, net: ${netPrefix}$${day.net}\n`;
434
+ }
435
+ }
436
+
437
+ return report;
438
+ }
439
+
440
+ /**
441
+ * Categorize a transaction
442
+ */
443
+ async categorize(txId: string, category: string): Promise<TransactionRecord | null> {
444
+ const txs = await this.loadTransactions();
445
+ const tx = txs.find(t => t.id === txId);
446
+
447
+ if (tx) {
448
+ tx.category = category;
449
+ await this.saveTransactions(txs);
450
+ }
451
+
452
+ return tx || null;
453
+ }
454
+
455
+ /**
456
+ * Export transactions to CSV
457
+ */
458
+ async exportCSV(fromDate?: string, toDate?: string): Promise<string> {
459
+ const txs = await this.getTransactions({ fromDate, toDate });
460
+
461
+ const header = 'Date,Type,Amount,From,To,Chain,TxHash,Fee,Category,Memo\n';
462
+ const rows = txs.map(tx =>
463
+ `${tx.timestamp},${tx.type},${tx.amount},${tx.fromAddress},${tx.toAddress},${tx.chain},${tx.txHash},${tx.fee || ''},${tx.category || ''},"${tx.memo || ''}"`
464
+ );
465
+
466
+ return header + rows.join('\n');
467
+ }
468
+
469
+ private shortAddress(address: string): string {
470
+ return `${address.slice(0, 6)}...${address.slice(-4)}`;
471
+ }
472
+ }
473
+
474
+ export default AnalyticsEngine;