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
package/lib/tips.ts ADDED
@@ -0,0 +1,570 @@
1
+ /**
2
+ * Tip Jar / Creator Economy Module
3
+ *
4
+ * Enable Clawdbot operators and agents to receive USDC tips
5
+ * via simple commands. Built for the creator economy.
6
+ */
7
+
8
+ import crypto from 'crypto';
9
+ import fs from 'fs/promises';
10
+ import path from 'path';
11
+
12
+ export interface TipJar {
13
+ id: string;
14
+ name: string;
15
+ description?: string;
16
+
17
+ // Owner info
18
+ ownerId: string; // Clawdbot session/agent ID
19
+ ownerName: string; // Display name
20
+
21
+ // Wallet addresses by chain
22
+ addresses: {
23
+ chain: string;
24
+ address: string;
25
+ isDefault: boolean;
26
+ }[];
27
+
28
+ // Settings
29
+ settings: {
30
+ minTip: string; // Minimum tip amount
31
+ suggestedAmounts: string[]; // Quick-select amounts
32
+ thankYouMessage?: string; // Custom thank you message
33
+ allowAnonymous: boolean; // Allow anonymous tips
34
+ notifyOnTip: boolean; // Send notification on tip
35
+ };
36
+
37
+ // Stats
38
+ stats: {
39
+ totalReceived: string;
40
+ tipCount: number;
41
+ uniqueTippers: number;
42
+ largestTip: string;
43
+ lastTipAt?: string;
44
+ };
45
+
46
+ // Public link
47
+ publicSlug?: string; // e.g., "gustav" for tip.clawd.bot/gustav
48
+
49
+ createdAt: string;
50
+ updatedAt: string;
51
+ }
52
+
53
+ export interface Tip {
54
+ id: string;
55
+ tipJarId: string;
56
+
57
+ // Sender
58
+ fromAddress: string;
59
+ fromName?: string;
60
+ isAnonymous: boolean;
61
+
62
+ // Payment
63
+ amount: string;
64
+ chain: string;
65
+ txHash: string;
66
+
67
+ // Message
68
+ message?: string;
69
+
70
+ // Status
71
+ status: 'pending' | 'confirmed' | 'failed';
72
+ confirmedAt?: string;
73
+
74
+ createdAt: string;
75
+ }
76
+
77
+ export interface Leaderboard {
78
+ tipJarId: string;
79
+ period: 'all-time' | 'monthly' | 'weekly';
80
+ entries: {
81
+ rank: number;
82
+ name: string;
83
+ address: string;
84
+ totalTipped: string;
85
+ tipCount: number;
86
+ }[];
87
+ generatedAt: string;
88
+ }
89
+
90
+ const DATA_DIR = process.env.USDC_DATA_DIR || './data';
91
+
92
+ /**
93
+ * Tip Jar Manager
94
+ */
95
+ export class TipJarManager {
96
+ private jarsPath: string;
97
+ private tipsPath: string;
98
+
99
+ constructor(dataDir = DATA_DIR) {
100
+ this.jarsPath = path.join(dataDir, 'tip-jars.json');
101
+ this.tipsPath = path.join(dataDir, 'tips.json');
102
+ }
103
+
104
+ private async loadJars(): Promise<TipJar[]> {
105
+ try {
106
+ const data = await fs.readFile(this.jarsPath, 'utf-8');
107
+ return JSON.parse(data);
108
+ } catch {
109
+ return [];
110
+ }
111
+ }
112
+
113
+ private async saveJars(jars: TipJar[]): Promise<void> {
114
+ await fs.mkdir(path.dirname(this.jarsPath), { recursive: true });
115
+ await fs.writeFile(this.jarsPath, JSON.stringify(jars, null, 2));
116
+ }
117
+
118
+ private async loadTips(): Promise<Tip[]> {
119
+ try {
120
+ const data = await fs.readFile(this.tipsPath, 'utf-8');
121
+ return JSON.parse(data);
122
+ } catch {
123
+ return [];
124
+ }
125
+ }
126
+
127
+ private async saveTips(tips: Tip[]): Promise<void> {
128
+ await fs.mkdir(path.dirname(this.tipsPath), { recursive: true });
129
+ await fs.writeFile(this.tipsPath, JSON.stringify(tips, null, 2));
130
+ }
131
+
132
+ // ============ Tip Jar Management ============
133
+
134
+ /**
135
+ * Create a new tip jar
136
+ */
137
+ async createJar(params: {
138
+ ownerId: string;
139
+ ownerName: string;
140
+ name?: string;
141
+ description?: string;
142
+ addresses: { chain: string; address: string }[];
143
+ publicSlug?: string;
144
+ settings?: Partial<TipJar['settings']>;
145
+ }): Promise<TipJar> {
146
+ const jars = await this.loadJars();
147
+
148
+ // Check for duplicate slug
149
+ if (params.publicSlug) {
150
+ const existing = jars.find(j => j.publicSlug === params.publicSlug);
151
+ if (existing) {
152
+ throw new Error(`Slug "${params.publicSlug}" is already taken`);
153
+ }
154
+ }
155
+
156
+ const jar: TipJar = {
157
+ id: crypto.randomUUID(),
158
+ name: params.name || `${params.ownerName}'s Tip Jar`,
159
+ description: params.description,
160
+ ownerId: params.ownerId,
161
+ ownerName: params.ownerName,
162
+ addresses: params.addresses.map((a, i) => ({
163
+ ...a,
164
+ isDefault: i === 0,
165
+ })),
166
+ settings: {
167
+ minTip: params.settings?.minTip || '1',
168
+ suggestedAmounts: params.settings?.suggestedAmounts || ['5', '10', '25', '50'],
169
+ thankYouMessage: params.settings?.thankYouMessage || 'Thanks for the tip! 🙏',
170
+ allowAnonymous: params.settings?.allowAnonymous ?? true,
171
+ notifyOnTip: params.settings?.notifyOnTip ?? true,
172
+ },
173
+ stats: {
174
+ totalReceived: '0',
175
+ tipCount: 0,
176
+ uniqueTippers: 0,
177
+ largestTip: '0',
178
+ },
179
+ publicSlug: params.publicSlug,
180
+ createdAt: new Date().toISOString(),
181
+ updatedAt: new Date().toISOString(),
182
+ };
183
+
184
+ jars.push(jar);
185
+ await this.saveJars(jars);
186
+
187
+ return jar;
188
+ }
189
+
190
+ /**
191
+ * Get tip jar by ID, slug, or owner
192
+ */
193
+ async getJar(identifier: string): Promise<TipJar | null> {
194
+ const jars = await this.loadJars();
195
+ return jars.find(j =>
196
+ j.id === identifier ||
197
+ j.publicSlug === identifier ||
198
+ j.ownerId === identifier
199
+ ) || null;
200
+ }
201
+
202
+ /**
203
+ * Update tip jar settings
204
+ */
205
+ async updateJar(
206
+ id: string,
207
+ updates: Partial<Pick<TipJar, 'name' | 'description' | 'settings' | 'publicSlug'>>
208
+ ): Promise<TipJar | null> {
209
+ const jars = await this.loadJars();
210
+ const jar = jars.find(j => j.id === id);
211
+
212
+ if (jar) {
213
+ if (updates.name) jar.name = updates.name;
214
+ if (updates.description !== undefined) jar.description = updates.description;
215
+ if (updates.publicSlug !== undefined) {
216
+ // Check for duplicate
217
+ if (updates.publicSlug && jars.some(j => j.id !== id && j.publicSlug === updates.publicSlug)) {
218
+ throw new Error(`Slug "${updates.publicSlug}" is already taken`);
219
+ }
220
+ jar.publicSlug = updates.publicSlug;
221
+ }
222
+ if (updates.settings) {
223
+ jar.settings = { ...jar.settings, ...updates.settings };
224
+ }
225
+ jar.updatedAt = new Date().toISOString();
226
+ await this.saveJars(jars);
227
+ }
228
+
229
+ return jar || null;
230
+ }
231
+
232
+ /**
233
+ * Add address to tip jar
234
+ */
235
+ async addAddress(jarId: string, chain: string, address: string): Promise<TipJar | null> {
236
+ const jars = await this.loadJars();
237
+ const jar = jars.find(j => j.id === jarId);
238
+
239
+ if (jar) {
240
+ if (jar.addresses.some(a => a.chain === chain)) {
241
+ throw new Error(`Address for ${chain} already exists`);
242
+ }
243
+ jar.addresses.push({ chain, address, isDefault: false });
244
+ jar.updatedAt = new Date().toISOString();
245
+ await this.saveJars(jars);
246
+ }
247
+
248
+ return jar || null;
249
+ }
250
+
251
+ /**
252
+ * Set default address
253
+ */
254
+ async setDefaultAddress(jarId: string, chain: string): Promise<TipJar | null> {
255
+ const jars = await this.loadJars();
256
+ const jar = jars.find(j => j.id === jarId);
257
+
258
+ if (jar) {
259
+ jar.addresses.forEach(a => {
260
+ a.isDefault = a.chain === chain;
261
+ });
262
+ jar.updatedAt = new Date().toISOString();
263
+ await this.saveJars(jars);
264
+ }
265
+
266
+ return jar || null;
267
+ }
268
+
269
+ // ============ Tip Processing ============
270
+
271
+ /**
272
+ * Record a tip
273
+ */
274
+ async recordTip(params: {
275
+ tipJarId: string;
276
+ fromAddress: string;
277
+ fromName?: string;
278
+ isAnonymous?: boolean;
279
+ amount: string;
280
+ chain: string;
281
+ txHash: string;
282
+ message?: string;
283
+ }): Promise<Tip> {
284
+ const jars = await this.loadJars();
285
+ const jar = jars.find(j => j.id === params.tipJarId);
286
+
287
+ if (!jar) {
288
+ throw new Error('Tip jar not found');
289
+ }
290
+
291
+ const tips = await this.loadTips();
292
+
293
+ const tip: Tip = {
294
+ id: crypto.randomUUID(),
295
+ tipJarId: params.tipJarId,
296
+ fromAddress: params.fromAddress,
297
+ fromName: params.fromName,
298
+ isAnonymous: params.isAnonymous ?? false,
299
+ amount: params.amount,
300
+ chain: params.chain,
301
+ txHash: params.txHash,
302
+ message: params.message,
303
+ status: 'confirmed',
304
+ confirmedAt: new Date().toISOString(),
305
+ createdAt: new Date().toISOString(),
306
+ };
307
+
308
+ tips.push(tip);
309
+ await this.saveTips(tips);
310
+
311
+ // Update jar stats
312
+ const amount = parseFloat(params.amount);
313
+ jar.stats.totalReceived = (parseFloat(jar.stats.totalReceived) + amount).toString();
314
+ jar.stats.tipCount++;
315
+
316
+ // Check if new unique tipper
317
+ const previousTips = tips.filter(t =>
318
+ t.tipJarId === jar.id &&
319
+ t.fromAddress.toLowerCase() === params.fromAddress.toLowerCase() &&
320
+ t.id !== tip.id
321
+ );
322
+ if (previousTips.length === 0) {
323
+ jar.stats.uniqueTippers++;
324
+ }
325
+
326
+ // Update largest tip
327
+ if (amount > parseFloat(jar.stats.largestTip)) {
328
+ jar.stats.largestTip = params.amount;
329
+ }
330
+
331
+ jar.stats.lastTipAt = new Date().toISOString();
332
+ jar.updatedAt = new Date().toISOString();
333
+ await this.saveJars(jars);
334
+
335
+ return tip;
336
+ }
337
+
338
+ /**
339
+ * Get tips for a jar
340
+ */
341
+ async getTips(jarId: string, options?: {
342
+ limit?: number;
343
+ includeAnonymous?: boolean;
344
+ }): Promise<Tip[]> {
345
+ let tips = await this.loadTips();
346
+ tips = tips.filter(t => t.tipJarId === jarId);
347
+
348
+ if (options?.includeAnonymous === false) {
349
+ tips = tips.filter(t => !t.isAnonymous);
350
+ }
351
+
352
+ tips.sort((a, b) =>
353
+ new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
354
+ );
355
+
356
+ if (options?.limit) {
357
+ tips = tips.slice(0, options.limit);
358
+ }
359
+
360
+ return tips;
361
+ }
362
+
363
+ /**
364
+ * Get recent tips across all jars (for activity feed)
365
+ */
366
+ async getRecentTips(limit = 10): Promise<(Tip & { jarName: string })[]> {
367
+ const tips = await this.loadTips();
368
+ const jars = await this.loadJars();
369
+
370
+ const jarMap = new Map(jars.map(j => [j.id, j]));
371
+
372
+ return tips
373
+ .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
374
+ .slice(0, limit)
375
+ .map(tip => ({
376
+ ...tip,
377
+ jarName: jarMap.get(tip.tipJarId)?.name || 'Unknown',
378
+ }));
379
+ }
380
+
381
+ // ============ Leaderboards ============
382
+
383
+ /**
384
+ * Generate leaderboard for a tip jar
385
+ */
386
+ async getLeaderboard(jarId: string, period: Leaderboard['period'] = 'all-time'): Promise<Leaderboard> {
387
+ let tips = await this.loadTips();
388
+ tips = tips.filter(t => t.tipJarId === jarId && !t.isAnonymous);
389
+
390
+ // Filter by period
391
+ const now = new Date();
392
+ if (period === 'weekly') {
393
+ const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
394
+ tips = tips.filter(t => new Date(t.createdAt) >= weekAgo);
395
+ } else if (period === 'monthly') {
396
+ const monthAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
397
+ tips = tips.filter(t => new Date(t.createdAt) >= monthAgo);
398
+ }
399
+
400
+ // Aggregate by tipper
401
+ const tipperMap = new Map<string, { name: string; total: number; count: number }>();
402
+
403
+ for (const tip of tips) {
404
+ const key = tip.fromAddress.toLowerCase();
405
+ const existing = tipperMap.get(key) || {
406
+ name: tip.fromName || this.shortAddress(tip.fromAddress),
407
+ total: 0,
408
+ count: 0
409
+ };
410
+ existing.total += parseFloat(tip.amount);
411
+ existing.count++;
412
+ if (tip.fromName) existing.name = tip.fromName;
413
+ tipperMap.set(key, existing);
414
+ }
415
+
416
+ // Sort and rank
417
+ const entries = Array.from(tipperMap.entries())
418
+ .map(([address, data]) => ({
419
+ address,
420
+ name: data.name,
421
+ totalTipped: data.total.toFixed(2),
422
+ tipCount: data.count,
423
+ }))
424
+ .sort((a, b) => parseFloat(b.totalTipped) - parseFloat(a.totalTipped))
425
+ .map((entry, i) => ({ rank: i + 1, ...entry }));
426
+
427
+ return {
428
+ tipJarId: jarId,
429
+ period,
430
+ entries,
431
+ generatedAt: new Date().toISOString(),
432
+ };
433
+ }
434
+
435
+ // ============ Tip Commands (for Clawdbot) ============
436
+
437
+ /**
438
+ * Parse tip command and return tip info
439
+ * Supports: "tip @gustav 10", "tip 5 to gustav", etc.
440
+ */
441
+ parseTipCommand(command: string): {
442
+ recipient: string;
443
+ amount: string;
444
+ message?: string;
445
+ } | null {
446
+ // Patterns:
447
+ // "tip @gustav 10"
448
+ // "tip 10 to @gustav"
449
+ // "tip gustav 10 usdc thanks!"
450
+
451
+ const patterns = [
452
+ // tip @recipient amount [message]
453
+ /^tip\s+@?(\w+)\s+(\d+(?:\.\d+)?)\s*(?:usdc)?\s*(.*)$/i,
454
+ // tip amount to @recipient [message]
455
+ /^tip\s+(\d+(?:\.\d+)?)\s*(?:usdc)?\s+to\s+@?(\w+)\s*(.*)$/i,
456
+ ];
457
+
458
+ for (const pattern of patterns) {
459
+ const match = command.match(pattern);
460
+ if (match) {
461
+ if (pattern === patterns[0]) {
462
+ return {
463
+ recipient: match[1],
464
+ amount: match[2],
465
+ message: match[3]?.trim() || undefined,
466
+ };
467
+ } else {
468
+ return {
469
+ recipient: match[2],
470
+ amount: match[1],
471
+ message: match[3]?.trim() || undefined,
472
+ };
473
+ }
474
+ }
475
+ }
476
+
477
+ return null;
478
+ }
479
+
480
+ /**
481
+ * Generate tip jar card/embed for display
482
+ */
483
+ formatTipJarCard(jar: TipJar): string {
484
+ const defaultAddr = jar.addresses.find(a => a.isDefault) || jar.addresses[0];
485
+
486
+ let card = `💰 **${jar.name}**\n`;
487
+ if (jar.description) {
488
+ card += `${jar.description}\n`;
489
+ }
490
+ card += `\n`;
491
+ card += `Owner: ${jar.ownerName}\n`;
492
+ card += `Total Received: **$${parseFloat(jar.stats.totalReceived).toFixed(2)} USDC**\n`;
493
+ card += `Tips: ${jar.stats.tipCount} from ${jar.stats.uniqueTippers} tippers\n`;
494
+ card += `\n`;
495
+ card += `**Quick Tip:**\n`;
496
+ card += jar.settings.suggestedAmounts.map(a => `[$${a}]`).join(' ') + '\n';
497
+ card += `\n`;
498
+ card += `Send to: \`${defaultAddr.address}\`\n`;
499
+ card += `Chain: ${defaultAddr.chain}\n`;
500
+
501
+ if (jar.publicSlug) {
502
+ card += `\nLink: tip.clawd.bot/${jar.publicSlug}`;
503
+ }
504
+
505
+ return card;
506
+ }
507
+
508
+ /**
509
+ * Format tip notification
510
+ */
511
+ formatTipNotification(tip: Tip, jar: TipJar): string {
512
+ const senderName = tip.isAnonymous ? 'Anonymous' : (tip.fromName || this.shortAddress(tip.fromAddress));
513
+
514
+ let notif = `🎉 **New Tip!**\n\n`;
515
+ notif += `${senderName} tipped **$${tip.amount} USDC**`;
516
+
517
+ if (tip.message) {
518
+ notif += `\n\n"${tip.message}"`;
519
+ }
520
+
521
+ notif += `\n\n${jar.settings.thankYouMessage}`;
522
+
523
+ return notif;
524
+ }
525
+
526
+ private shortAddress(address: string): string {
527
+ return `${address.slice(0, 6)}...${address.slice(-4)}`;
528
+ }
529
+
530
+ // ============ Agent-to-Agent Tips ============
531
+
532
+ /**
533
+ * Send tip from one agent to another
534
+ * Returns the transaction to be executed
535
+ */
536
+ async prepareAgentTip(params: {
537
+ fromAgentId: string;
538
+ fromWalletId: string;
539
+ toAgentId: string;
540
+ amount: string;
541
+ message?: string;
542
+ }): Promise<{
543
+ toJar: TipJar;
544
+ toAddress: string;
545
+ chain: string;
546
+ amount: string;
547
+ message?: string;
548
+ } | null> {
549
+ // Find recipient's tip jar
550
+ const toJar = await this.getJar(params.toAgentId);
551
+ if (!toJar) {
552
+ return null;
553
+ }
554
+
555
+ const defaultAddr = toJar.addresses.find(a => a.isDefault) || toJar.addresses[0];
556
+ if (!defaultAddr) {
557
+ return null;
558
+ }
559
+
560
+ return {
561
+ toJar,
562
+ toAddress: defaultAddr.address,
563
+ chain: defaultAddr.chain,
564
+ amount: params.amount,
565
+ message: params.message,
566
+ };
567
+ }
568
+ }
569
+
570
+ export default TipJarManager;
package/lib/types.ts ADDED
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Pay Lobster Type Definitions
3
+ */
4
+
5
+ export interface LobsterConfig {
6
+ /** Circle API key for wallet operations */
7
+ circleApiKey?: string;
8
+ /** Ethereum private key for direct transactions */
9
+ privateKey?: string;
10
+ /** Network: 'base' | 'base-sepolia' | 'ethereum' */
11
+ network?: 'base' | 'base-sepolia' | 'ethereum';
12
+ /** Existing Circle wallet ID */
13
+ walletId?: string;
14
+ /** Custom RPC URL */
15
+ rpcUrl?: string;
16
+ /** Enable ERC-8004 trust verification */
17
+ enableTrust?: boolean;
18
+ }
19
+
20
+ export interface Wallet {
21
+ id: string;
22
+ address: string;
23
+ network: string;
24
+ balance?: string;
25
+ createdAt?: string;
26
+ }
27
+
28
+ export interface Transfer {
29
+ id: string;
30
+ hash?: string;
31
+ status: 'pending' | 'confirmed' | 'failed';
32
+ amount: string;
33
+ to: string;
34
+ from: string;
35
+ memo?: string;
36
+ createdAt: string;
37
+ }
38
+
39
+ export interface Escrow {
40
+ id: string;
41
+ amount: string;
42
+ buyer: string;
43
+ seller: string;
44
+ status: 'funded' | 'released' | 'disputed' | 'refunded';
45
+ conditions?: EscrowConditions;
46
+ createdAt: string;
47
+ }
48
+
49
+ export interface EscrowConditions {
50
+ type: 'milestone' | 'time' | 'approval';
51
+ description: string;
52
+ deadline?: string;
53
+ }
54
+
55
+ export interface TrustScore {
56
+ score: number;
57
+ level: 'new' | 'established' | 'trusted' | 'verified';
58
+ totalTransactions: number;
59
+ successRate: number;
60
+ ratings?: Rating[];
61
+ }
62
+
63
+ export interface Rating {
64
+ rating: number;
65
+ comment?: string;
66
+ from: string;
67
+ createdAt: string;
68
+ }
69
+
70
+ export interface Agent {
71
+ address: string;
72
+ name: string;
73
+ capabilities: string[];
74
+ pricing?: Record<string, string>;
75
+ trustScore?: TrustScore;
76
+ metadata?: Record<string, any>;
77
+ }
78
+
79
+ export interface TransferOptions {
80
+ to: string;
81
+ amount: string;
82
+ memo?: string;
83
+ idempotencyKey?: string;
84
+ }
85
+
86
+ export interface EscrowOptions {
87
+ amount: string;
88
+ recipient: string;
89
+ conditions?: EscrowConditions;
90
+ template?: 'freelance' | 'marketplace' | 'rental';
91
+ deadline?: string;
92
+ milestones?: { name: string; amount: string }[];
93
+ }
94
+
95
+ export interface DiscoverOptions {
96
+ capability?: string;
97
+ minTrustScore?: number;
98
+ maxPrice?: string;
99
+ limit?: number;
100
+ }
101
+
102
+ export interface AutonomousConfig {
103
+ enabled: boolean;
104
+ dailyLimit: string;
105
+ perTransactionLimit: string;
106
+ autoApproveBelow: string;
107
+ allowedCapabilities: string[];
108
+ }