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,585 @@
1
+ /**
2
+ * Transaction Approval System
3
+ *
4
+ * Multi-step approval workflow for large or sensitive transactions.
5
+ * Integrates with Clawdbot for conversational approvals.
6
+ */
7
+
8
+ import crypto from 'crypto';
9
+ import fs from 'fs/promises';
10
+ import path from 'path';
11
+
12
+ export interface ApprovalPolicy {
13
+ id: string;
14
+ name: string;
15
+ enabled: boolean;
16
+
17
+ // Conditions that trigger approval requirement
18
+ conditions: {
19
+ minAmount?: string; // Require approval above this amount
20
+ maxDailyLimit?: string; // Require approval if daily limit exceeded
21
+ addresses?: string[]; // Require approval for these addresses
22
+ chains?: string[]; // Require approval for these chains
23
+ always?: boolean; // Always require approval
24
+ };
25
+
26
+ // Approval requirements
27
+ approvers: string[]; // List of approver IDs (Clawdbot sessions, emails, etc.)
28
+ requiredApprovals: number; // Number of approvals needed
29
+ timeout: number; // Timeout in seconds (default 24h)
30
+
31
+ // Actions on timeout
32
+ timeoutAction: 'cancel' | 'auto-approve';
33
+
34
+ createdAt: string;
35
+ updatedAt: string;
36
+ }
37
+
38
+ export interface PendingTransaction {
39
+ id: string;
40
+ policyId: string;
41
+
42
+ // Transaction details
43
+ type: 'send' | 'bridge' | 'recurring';
44
+ fromWalletId: string;
45
+ toAddress: string;
46
+ toName?: string;
47
+ amount: string;
48
+ chain: string;
49
+ targetChain?: string; // For bridges
50
+ memo?: string;
51
+
52
+ // Approval status
53
+ status: 'pending' | 'approved' | 'rejected' | 'cancelled' | 'expired' | 'executed';
54
+ approvals: {
55
+ approverId: string;
56
+ decision: 'approve' | 'reject';
57
+ timestamp: string;
58
+ note?: string;
59
+ }[];
60
+ rejectionReason?: string;
61
+
62
+ // Execution
63
+ txHash?: string;
64
+ executedAt?: string;
65
+
66
+ // Metadata
67
+ requestedBy: string;
68
+ createdAt: string;
69
+ expiresAt: string;
70
+ }
71
+
72
+ export interface DailySpending {
73
+ date: string;
74
+ walletId: string;
75
+ total: string;
76
+ transactions: { amount: string; txHash: string; timestamp: string }[];
77
+ }
78
+
79
+ const DATA_DIR = process.env.USDC_DATA_DIR || './data';
80
+
81
+ /**
82
+ * Approval Manager
83
+ */
84
+ export class ApprovalManager {
85
+ private policiesPath: string;
86
+ private pendingPath: string;
87
+ private spendingPath: string;
88
+
89
+ constructor(dataDir = DATA_DIR) {
90
+ this.policiesPath = path.join(dataDir, 'approval-policies.json');
91
+ this.pendingPath = path.join(dataDir, 'pending-transactions.json');
92
+ this.spendingPath = path.join(dataDir, 'daily-spending.json');
93
+ }
94
+
95
+ private async loadPolicies(): Promise<ApprovalPolicy[]> {
96
+ try {
97
+ const data = await fs.readFile(this.policiesPath, 'utf-8');
98
+ return JSON.parse(data);
99
+ } catch {
100
+ return [];
101
+ }
102
+ }
103
+
104
+ private async savePolicies(policies: ApprovalPolicy[]): Promise<void> {
105
+ await fs.mkdir(path.dirname(this.policiesPath), { recursive: true });
106
+ await fs.writeFile(this.policiesPath, JSON.stringify(policies, null, 2));
107
+ }
108
+
109
+ private async loadPending(): Promise<PendingTransaction[]> {
110
+ try {
111
+ const data = await fs.readFile(this.pendingPath, 'utf-8');
112
+ return JSON.parse(data);
113
+ } catch {
114
+ return [];
115
+ }
116
+ }
117
+
118
+ private async savePending(pending: PendingTransaction[]): Promise<void> {
119
+ await fs.mkdir(path.dirname(this.pendingPath), { recursive: true });
120
+ await fs.writeFile(this.pendingPath, JSON.stringify(pending, null, 2));
121
+ }
122
+
123
+ private async loadSpending(): Promise<DailySpending[]> {
124
+ try {
125
+ const data = await fs.readFile(this.spendingPath, 'utf-8');
126
+ return JSON.parse(data);
127
+ } catch {
128
+ return [];
129
+ }
130
+ }
131
+
132
+ private async saveSpending(spending: DailySpending[]): Promise<void> {
133
+ await fs.mkdir(path.dirname(this.spendingPath), { recursive: true });
134
+ await fs.writeFile(this.spendingPath, JSON.stringify(spending, null, 2));
135
+ }
136
+
137
+ // ============ Policy Management ============
138
+
139
+ /**
140
+ * Create approval policy
141
+ */
142
+ async createPolicy(params: {
143
+ name: string;
144
+ conditions: ApprovalPolicy['conditions'];
145
+ approvers: string[];
146
+ requiredApprovals?: number;
147
+ timeout?: number;
148
+ timeoutAction?: ApprovalPolicy['timeoutAction'];
149
+ }): Promise<ApprovalPolicy> {
150
+ const policies = await this.loadPolicies();
151
+
152
+ const policy: ApprovalPolicy = {
153
+ id: crypto.randomUUID(),
154
+ name: params.name,
155
+ enabled: true,
156
+ conditions: params.conditions,
157
+ approvers: params.approvers,
158
+ requiredApprovals: params.requiredApprovals || 1,
159
+ timeout: params.timeout || 86400, // 24 hours default
160
+ timeoutAction: params.timeoutAction || 'cancel',
161
+ createdAt: new Date().toISOString(),
162
+ updatedAt: new Date().toISOString(),
163
+ };
164
+
165
+ policies.push(policy);
166
+ await this.savePolicies(policies);
167
+
168
+ return policy;
169
+ }
170
+
171
+ /**
172
+ * Get active policies for a transaction
173
+ */
174
+ async getMatchingPolicies(tx: {
175
+ amount: string;
176
+ toAddress: string;
177
+ chain: string;
178
+ walletId: string;
179
+ }): Promise<ApprovalPolicy[]> {
180
+ const policies = await this.loadPolicies();
181
+ const spending = await this.getDailySpending(tx.walletId);
182
+ const matchingPolicies: ApprovalPolicy[] = [];
183
+
184
+ for (const policy of policies) {
185
+ if (!policy.enabled) continue;
186
+
187
+ const { conditions } = policy;
188
+ let matches = false;
189
+
190
+ // Check amount threshold
191
+ if (conditions.minAmount && parseFloat(tx.amount) >= parseFloat(conditions.minAmount)) {
192
+ matches = true;
193
+ }
194
+
195
+ // Check daily limit
196
+ if (conditions.maxDailyLimit) {
197
+ const todayTotal = parseFloat(spending?.total || '0') + parseFloat(tx.amount);
198
+ if (todayTotal > parseFloat(conditions.maxDailyLimit)) {
199
+ matches = true;
200
+ }
201
+ }
202
+
203
+ // Check specific addresses
204
+ if (conditions.addresses && conditions.addresses.length > 0) {
205
+ if (conditions.addresses.some(a => a.toLowerCase() === tx.toAddress.toLowerCase())) {
206
+ matches = true;
207
+ }
208
+ }
209
+
210
+ // Check chains
211
+ if (conditions.chains && conditions.chains.length > 0) {
212
+ if (conditions.chains.includes(tx.chain)) {
213
+ matches = true;
214
+ }
215
+ }
216
+
217
+ // Always require approval
218
+ if (conditions.always) {
219
+ matches = true;
220
+ }
221
+
222
+ if (matches) {
223
+ matchingPolicies.push(policy);
224
+ }
225
+ }
226
+
227
+ return matchingPolicies;
228
+ }
229
+
230
+ /**
231
+ * List all policies
232
+ */
233
+ async listPolicies(): Promise<ApprovalPolicy[]> {
234
+ return this.loadPolicies();
235
+ }
236
+
237
+ /**
238
+ * Toggle policy enabled state
239
+ */
240
+ async togglePolicy(id: string, enabled: boolean): Promise<ApprovalPolicy | null> {
241
+ const policies = await this.loadPolicies();
242
+ const policy = policies.find(p => p.id === id);
243
+
244
+ if (policy) {
245
+ policy.enabled = enabled;
246
+ policy.updatedAt = new Date().toISOString();
247
+ await this.savePolicies(policies);
248
+ }
249
+
250
+ return policy || null;
251
+ }
252
+
253
+ /**
254
+ * Delete policy
255
+ */
256
+ async deletePolicy(id: string): Promise<boolean> {
257
+ const policies = await this.loadPolicies();
258
+ const index = policies.findIndex(p => p.id === id);
259
+
260
+ if (index >= 0) {
261
+ policies.splice(index, 1);
262
+ await this.savePolicies(policies);
263
+ return true;
264
+ }
265
+
266
+ return false;
267
+ }
268
+
269
+ // ============ Pending Transactions ============
270
+
271
+ /**
272
+ * Submit transaction for approval
273
+ */
274
+ async submitForApproval(tx: {
275
+ type: PendingTransaction['type'];
276
+ fromWalletId: string;
277
+ toAddress: string;
278
+ toName?: string;
279
+ amount: string;
280
+ chain: string;
281
+ targetChain?: string;
282
+ memo?: string;
283
+ requestedBy: string;
284
+ }): Promise<PendingTransaction | { approved: true }> {
285
+ // Check if approval is required
286
+ const policies = await this.getMatchingPolicies({
287
+ amount: tx.amount,
288
+ toAddress: tx.toAddress,
289
+ chain: tx.chain,
290
+ walletId: tx.fromWalletId,
291
+ });
292
+
293
+ // No policies match - auto-approve
294
+ if (policies.length === 0) {
295
+ return { approved: true };
296
+ }
297
+
298
+ // Use the strictest policy (most approvals required)
299
+ const policy = policies.reduce((strictest, p) =>
300
+ p.requiredApprovals > strictest.requiredApprovals ? p : strictest
301
+ );
302
+
303
+ const pending = await this.loadPending();
304
+
305
+ const transaction: PendingTransaction = {
306
+ id: crypto.randomUUID(),
307
+ policyId: policy.id,
308
+ type: tx.type,
309
+ fromWalletId: tx.fromWalletId,
310
+ toAddress: tx.toAddress,
311
+ toName: tx.toName,
312
+ amount: tx.amount,
313
+ chain: tx.chain,
314
+ targetChain: tx.targetChain,
315
+ memo: tx.memo,
316
+ status: 'pending',
317
+ approvals: [],
318
+ requestedBy: tx.requestedBy,
319
+ createdAt: new Date().toISOString(),
320
+ expiresAt: new Date(Date.now() + policy.timeout * 1000).toISOString(),
321
+ };
322
+
323
+ pending.push(transaction);
324
+ await this.savePending(pending);
325
+
326
+ return transaction;
327
+ }
328
+
329
+ /**
330
+ * Approve or reject a pending transaction
331
+ */
332
+ async decide(
333
+ txId: string,
334
+ approverId: string,
335
+ decision: 'approve' | 'reject',
336
+ note?: string
337
+ ): Promise<PendingTransaction | null> {
338
+ const pending = await this.loadPending();
339
+ const tx = pending.find(t => t.id === txId);
340
+
341
+ if (!tx || tx.status !== 'pending') {
342
+ return null;
343
+ }
344
+
345
+ // Check if already decided by this approver
346
+ if (tx.approvals.some(a => a.approverId === approverId)) {
347
+ throw new Error('Already submitted decision');
348
+ }
349
+
350
+ // Check if approver is authorized
351
+ const policies = await this.loadPolicies();
352
+ const policy = policies.find(p => p.id === tx.policyId);
353
+
354
+ if (!policy || !policy.approvers.includes(approverId)) {
355
+ throw new Error('Not authorized to approve this transaction');
356
+ }
357
+
358
+ // Record decision
359
+ tx.approvals.push({
360
+ approverId,
361
+ decision,
362
+ timestamp: new Date().toISOString(),
363
+ note,
364
+ });
365
+
366
+ // Check if rejected
367
+ if (decision === 'reject') {
368
+ tx.status = 'rejected';
369
+ tx.rejectionReason = note || 'Rejected by approver';
370
+ }
371
+ // Check if enough approvals
372
+ else {
373
+ const approvalCount = tx.approvals.filter(a => a.decision === 'approve').length;
374
+ if (approvalCount >= policy.requiredApprovals) {
375
+ tx.status = 'approved';
376
+ }
377
+ }
378
+
379
+ await this.savePending(pending);
380
+ return tx;
381
+ }
382
+
383
+ /**
384
+ * Mark transaction as executed
385
+ */
386
+ async markExecuted(txId: string, txHash: string): Promise<PendingTransaction | null> {
387
+ const pending = await this.loadPending();
388
+ const tx = pending.find(t => t.id === txId);
389
+
390
+ if (tx && tx.status === 'approved') {
391
+ tx.status = 'executed';
392
+ tx.txHash = txHash;
393
+ tx.executedAt = new Date().toISOString();
394
+
395
+ // Record spending
396
+ await this.recordSpending(tx.fromWalletId, tx.amount, txHash);
397
+
398
+ await this.savePending(pending);
399
+ }
400
+
401
+ return tx || null;
402
+ }
403
+
404
+ /**
405
+ * Cancel a pending transaction
406
+ */
407
+ async cancel(txId: string, requesterId: string): Promise<PendingTransaction | null> {
408
+ const pending = await this.loadPending();
409
+ const tx = pending.find(t => t.id === txId);
410
+
411
+ if (tx && tx.status === 'pending' && tx.requestedBy === requesterId) {
412
+ tx.status = 'cancelled';
413
+ await this.savePending(pending);
414
+ }
415
+
416
+ return tx || null;
417
+ }
418
+
419
+ /**
420
+ * Get pending transactions
421
+ */
422
+ async getPending(options?: {
423
+ status?: PendingTransaction['status'];
424
+ approverId?: string;
425
+ }): Promise<PendingTransaction[]> {
426
+ let pending = await this.loadPending();
427
+
428
+ if (options?.status) {
429
+ pending = pending.filter(t => t.status === options.status);
430
+ }
431
+
432
+ if (options?.approverId) {
433
+ const policies = await this.loadPolicies();
434
+ const approverPolicies = policies.filter(p =>
435
+ p.approvers.includes(options.approverId!)
436
+ ).map(p => p.id);
437
+
438
+ pending = pending.filter(t => approverPolicies.includes(t.policyId));
439
+ }
440
+
441
+ return pending.sort((a, b) =>
442
+ new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
443
+ );
444
+ }
445
+
446
+ /**
447
+ * Check and expire old transactions
448
+ */
449
+ async processExpired(): Promise<PendingTransaction[]> {
450
+ const pending = await this.loadPending();
451
+ const policies = await this.loadPolicies();
452
+ const now = new Date();
453
+ const expired: PendingTransaction[] = [];
454
+
455
+ for (const tx of pending) {
456
+ if (tx.status !== 'pending') continue;
457
+ if (new Date(tx.expiresAt) > now) continue;
458
+
459
+ const policy = policies.find(p => p.id === tx.policyId);
460
+
461
+ if (policy?.timeoutAction === 'auto-approve') {
462
+ tx.status = 'approved';
463
+ } else {
464
+ tx.status = 'expired';
465
+ }
466
+
467
+ expired.push(tx);
468
+ }
469
+
470
+ if (expired.length > 0) {
471
+ await this.savePending(pending);
472
+ }
473
+
474
+ return expired;
475
+ }
476
+
477
+ // ============ Daily Spending Tracking ============
478
+
479
+ /**
480
+ * Get daily spending for a wallet
481
+ */
482
+ async getDailySpending(walletId: string): Promise<DailySpending | null> {
483
+ const spending = await this.loadSpending();
484
+ const today = new Date().toISOString().split('T')[0];
485
+ return spending.find(s => s.date === today && s.walletId === walletId) || null;
486
+ }
487
+
488
+ /**
489
+ * Record spending
490
+ */
491
+ private async recordSpending(walletId: string, amount: string, txHash: string): Promise<void> {
492
+ const spending = await this.loadSpending();
493
+ const today = new Date().toISOString().split('T')[0];
494
+
495
+ let todaySpending = spending.find(s => s.date === today && s.walletId === walletId);
496
+
497
+ if (!todaySpending) {
498
+ todaySpending = {
499
+ date: today,
500
+ walletId,
501
+ total: '0',
502
+ transactions: [],
503
+ };
504
+ spending.push(todaySpending);
505
+ }
506
+
507
+ todaySpending.total = (parseFloat(todaySpending.total) + parseFloat(amount)).toString();
508
+ todaySpending.transactions.push({
509
+ amount,
510
+ txHash,
511
+ timestamp: new Date().toISOString(),
512
+ });
513
+
514
+ // Keep only last 30 days
515
+ const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
516
+ const filtered = spending.filter(s => s.date >= thirtyDaysAgo);
517
+
518
+ await this.saveSpending(filtered);
519
+ }
520
+
521
+ /**
522
+ * Get spending history
523
+ */
524
+ async getSpendingHistory(walletId: string, days = 30): Promise<DailySpending[]> {
525
+ const spending = await this.loadSpending();
526
+ const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
527
+
528
+ return spending
529
+ .filter(s => s.walletId === walletId && s.date >= cutoff)
530
+ .sort((a, b) => b.date.localeCompare(a.date));
531
+ }
532
+
533
+ // ============ Helpers ============
534
+
535
+ /**
536
+ * Format pending transaction for display
537
+ */
538
+ formatPendingTx(tx: PendingTransaction): string {
539
+ const typeEmoji = tx.type === 'send' ? '📤' : tx.type === 'bridge' ? '🌉' : '🔄';
540
+ const statusEmoji = {
541
+ pending: '⏳',
542
+ approved: '✅',
543
+ rejected: '❌',
544
+ cancelled: '🚫',
545
+ expired: '⌛',
546
+ executed: '✓',
547
+ }[tx.status];
548
+
549
+ let text = `${typeEmoji} **${tx.amount} USDC** → ${tx.toName || this.shortAddress(tx.toAddress)}\n`;
550
+ text += `Status: ${statusEmoji} ${tx.status.toUpperCase()}\n`;
551
+ text += `Chain: ${tx.chain}${tx.targetChain ? ` → ${tx.targetChain}` : ''}\n`;
552
+
553
+ if (tx.approvals.length > 0) {
554
+ text += `Approvals: ${tx.approvals.filter(a => a.decision === 'approve').length}/${tx.approvals.length}\n`;
555
+ }
556
+
557
+ text += `Expires: ${new Date(tx.expiresAt).toLocaleString()}\n`;
558
+
559
+ if (tx.memo) {
560
+ text += `Memo: ${tx.memo}\n`;
561
+ }
562
+
563
+ return text;
564
+ }
565
+
566
+ private shortAddress(address: string): string {
567
+ return `${address.slice(0, 6)}...${address.slice(-4)}`;
568
+ }
569
+
570
+ /**
571
+ * Create default approval policy for new users
572
+ */
573
+ async createDefaultPolicy(approverIds: string[]): Promise<ApprovalPolicy> {
574
+ return this.createPolicy({
575
+ name: 'Large Transactions (>500 USDC)',
576
+ conditions: { minAmount: '500' },
577
+ approvers: approverIds,
578
+ requiredApprovals: 1,
579
+ timeout: 86400, // 24 hours
580
+ timeoutAction: 'cancel',
581
+ });
582
+ }
583
+ }
584
+
585
+ export default ApprovalManager;