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.
- package/README.md +401 -0
- package/README.md.bak +401 -0
- package/dist/agent.d.ts +132 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +224 -0
- package/dist/agent.js.map +1 -0
- package/dist/analytics.d.ts +120 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +345 -0
- package/dist/analytics.js.map +1 -0
- package/dist/approvals.d.ts +168 -0
- package/dist/approvals.d.ts.map +1 -0
- package/dist/approvals.js +406 -0
- package/dist/approvals.js.map +1 -0
- package/dist/circle-client.d.ts +152 -0
- package/dist/circle-client.d.ts.map +1 -0
- package/dist/circle-client.js +266 -0
- package/dist/circle-client.js.map +1 -0
- package/dist/commission.d.ts +191 -0
- package/dist/commission.d.ts.map +1 -0
- package/dist/commission.js +475 -0
- package/dist/commission.js.map +1 -0
- package/dist/condition-builder.d.ts +98 -0
- package/dist/condition-builder.d.ts.map +1 -0
- package/dist/condition-builder.js +193 -0
- package/dist/condition-builder.js.map +1 -0
- package/dist/contacts.d.ts +179 -0
- package/dist/contacts.d.ts.map +1 -0
- package/dist/contacts.js +445 -0
- package/dist/contacts.js.map +1 -0
- package/dist/easy.d.ts +22 -0
- package/dist/easy.d.ts.map +1 -0
- package/dist/easy.js +40 -0
- package/dist/easy.js.map +1 -0
- package/dist/erc8004/constants.d.ts +152 -0
- package/dist/erc8004/constants.d.ts.map +1 -0
- package/dist/erc8004/constants.js +114 -0
- package/dist/erc8004/constants.js.map +1 -0
- package/dist/erc8004/discovery.d.ts +84 -0
- package/dist/erc8004/discovery.d.ts.map +1 -0
- package/dist/erc8004/discovery.js +217 -0
- package/dist/erc8004/discovery.js.map +1 -0
- package/dist/erc8004/identity.d.ts +91 -0
- package/dist/erc8004/identity.d.ts.map +1 -0
- package/dist/erc8004/identity.js +250 -0
- package/dist/erc8004/identity.js.map +1 -0
- package/dist/erc8004/index.d.ts +147 -0
- package/dist/erc8004/index.d.ts.map +1 -0
- package/dist/erc8004/index.js +225 -0
- package/dist/erc8004/index.js.map +1 -0
- package/dist/erc8004/reputation.d.ts +133 -0
- package/dist/erc8004/reputation.d.ts.map +1 -0
- package/dist/erc8004/reputation.js +277 -0
- package/dist/erc8004/reputation.js.map +1 -0
- package/dist/escrow-templates.d.ts +38 -0
- package/dist/escrow-templates.d.ts.map +1 -0
- package/dist/escrow-templates.js +419 -0
- package/dist/escrow-templates.js.map +1 -0
- package/dist/escrow.d.ts +320 -0
- package/dist/escrow.d.ts.map +1 -0
- package/dist/escrow.js +854 -0
- package/dist/escrow.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/invoices.d.ts +212 -0
- package/dist/invoices.d.ts.map +1 -0
- package/dist/invoices.js +393 -0
- package/dist/invoices.js.map +1 -0
- package/dist/notifications.d.ts +141 -0
- package/dist/notifications.d.ts.map +1 -0
- package/dist/notifications.js +350 -0
- package/dist/notifications.js.map +1 -0
- package/dist/tips.d.ts +171 -0
- package/dist/tips.d.ts.map +1 -0
- package/dist/tips.js +390 -0
- package/dist/tips.js.map +1 -0
- package/dist/types.d.ts +100 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/x402-client.d.ts +127 -0
- package/dist/x402-client.d.ts.map +1 -0
- package/dist/x402-client.js +350 -0
- package/dist/x402-client.js.map +1 -0
- package/dist/x402-server.d.ts +133 -0
- package/dist/x402-server.d.ts.map +1 -0
- package/dist/x402-server.js +330 -0
- package/dist/x402-server.js.map +1 -0
- package/lib/agent.ts +273 -0
- package/lib/analytics.ts +474 -0
- package/lib/analytics.ts.bak +474 -0
- package/lib/approvals.ts +585 -0
- package/lib/approvals.ts.bak +585 -0
- package/lib/circle-client.ts +376 -0
- package/lib/circle-client.ts.bak +376 -0
- package/lib/commission.ts +680 -0
- package/lib/commission.ts.bak +680 -0
- package/lib/condition-builder.ts +223 -0
- package/lib/condition-builder.ts.bak +223 -0
- package/lib/contacts.ts +615 -0
- package/lib/contacts.ts.bak +615 -0
- package/lib/easy.ts +46 -0
- package/lib/easy.ts.bak +352 -0
- package/lib/erc8004/constants.ts +175 -0
- package/lib/erc8004/discovery.ts +299 -0
- package/lib/erc8004/identity.ts +327 -0
- package/lib/erc8004/index.ts +285 -0
- package/lib/erc8004/reputation.ts +368 -0
- package/lib/escrow-templates.ts +462 -0
- package/lib/escrow.ts +1216 -0
- package/lib/index.ts +13 -0
- package/lib/invoices.ts +588 -0
- package/lib/notifications.ts +484 -0
- package/lib/tips.ts +570 -0
- package/lib/types.ts +108 -0
- package/lib/x402-client.ts +471 -0
- package/lib/x402-server.ts +462 -0
- package/package.json +58 -0
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payment Notifications & Webhooks
|
|
3
|
+
*
|
|
4
|
+
* Real-time notifications for USDC transactions and events.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import crypto from 'crypto';
|
|
8
|
+
import fs from 'fs/promises';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
|
|
11
|
+
export interface NotificationConfig {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
enabled: boolean;
|
|
15
|
+
|
|
16
|
+
// Trigger conditions
|
|
17
|
+
triggers: {
|
|
18
|
+
type: 'incoming' | 'outgoing' | 'recurring' | 'invoice' | 'threshold';
|
|
19
|
+
minAmount?: string; // Only trigger above this amount
|
|
20
|
+
addresses?: string[]; // Only for specific addresses
|
|
21
|
+
chains?: string[]; // Only for specific chains
|
|
22
|
+
}[];
|
|
23
|
+
|
|
24
|
+
// Delivery channels
|
|
25
|
+
channels: {
|
|
26
|
+
type: 'webhook' | 'telegram' | 'email' | 'clawdbot';
|
|
27
|
+
config: Record<string, string>;
|
|
28
|
+
}[];
|
|
29
|
+
|
|
30
|
+
// Rate limiting
|
|
31
|
+
cooldownMs?: number; // Minimum time between notifications
|
|
32
|
+
lastTriggeredAt?: string;
|
|
33
|
+
|
|
34
|
+
createdAt: string;
|
|
35
|
+
updatedAt: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface Notification {
|
|
39
|
+
id: string;
|
|
40
|
+
configId?: string;
|
|
41
|
+
type: 'payment_received' | 'payment_sent' | 'recurring_due' | 'invoice_paid' | 'threshold_alert' | 'bridge_complete';
|
|
42
|
+
title: string;
|
|
43
|
+
message: string;
|
|
44
|
+
data: Record<string, any>;
|
|
45
|
+
deliveries: {
|
|
46
|
+
channel: string;
|
|
47
|
+
status: 'pending' | 'sent' | 'failed';
|
|
48
|
+
sentAt?: string;
|
|
49
|
+
error?: string;
|
|
50
|
+
}[];
|
|
51
|
+
createdAt: string;
|
|
52
|
+
readAt?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const DATA_DIR = process.env.USDC_DATA_DIR || './data';
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Notification Manager
|
|
59
|
+
*/
|
|
60
|
+
export class NotificationManager {
|
|
61
|
+
private configPath: string;
|
|
62
|
+
private historyPath: string;
|
|
63
|
+
|
|
64
|
+
constructor(dataDir = DATA_DIR) {
|
|
65
|
+
this.configPath = path.join(dataDir, 'notification-configs.json');
|
|
66
|
+
this.historyPath = path.join(dataDir, 'notification-history.json');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private async loadConfigs(): Promise<NotificationConfig[]> {
|
|
70
|
+
try {
|
|
71
|
+
const data = await fs.readFile(this.configPath, 'utf-8');
|
|
72
|
+
return JSON.parse(data);
|
|
73
|
+
} catch {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private async saveConfigs(configs: NotificationConfig[]): Promise<void> {
|
|
79
|
+
await fs.mkdir(path.dirname(this.configPath), { recursive: true });
|
|
80
|
+
await fs.writeFile(this.configPath, JSON.stringify(configs, null, 2));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private async loadHistory(): Promise<Notification[]> {
|
|
84
|
+
try {
|
|
85
|
+
const data = await fs.readFile(this.historyPath, 'utf-8');
|
|
86
|
+
return JSON.parse(data);
|
|
87
|
+
} catch {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private async saveHistory(history: Notification[]): Promise<void> {
|
|
93
|
+
await fs.mkdir(path.dirname(this.historyPath), { recursive: true });
|
|
94
|
+
// Keep last 1000 notifications
|
|
95
|
+
const trimmed = history.slice(-1000);
|
|
96
|
+
await fs.writeFile(this.historyPath, JSON.stringify(trimmed, null, 2));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ============ Configuration Management ============
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Create notification configuration
|
|
103
|
+
*/
|
|
104
|
+
async createConfig(params: {
|
|
105
|
+
name: string;
|
|
106
|
+
triggers: NotificationConfig['triggers'];
|
|
107
|
+
channels: NotificationConfig['channels'];
|
|
108
|
+
cooldownMs?: number;
|
|
109
|
+
}): Promise<NotificationConfig> {
|
|
110
|
+
const configs = await this.loadConfigs();
|
|
111
|
+
|
|
112
|
+
const config: NotificationConfig = {
|
|
113
|
+
id: crypto.randomUUID(),
|
|
114
|
+
name: params.name,
|
|
115
|
+
enabled: true,
|
|
116
|
+
triggers: params.triggers,
|
|
117
|
+
channels: params.channels,
|
|
118
|
+
cooldownMs: params.cooldownMs,
|
|
119
|
+
createdAt: new Date().toISOString(),
|
|
120
|
+
updatedAt: new Date().toISOString(),
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
configs.push(config);
|
|
124
|
+
await this.saveConfigs(configs);
|
|
125
|
+
|
|
126
|
+
return config;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Enable/disable notification config
|
|
131
|
+
*/
|
|
132
|
+
async toggleConfig(id: string, enabled: boolean): Promise<NotificationConfig | null> {
|
|
133
|
+
const configs = await this.loadConfigs();
|
|
134
|
+
const config = configs.find(c => c.id === id);
|
|
135
|
+
|
|
136
|
+
if (config) {
|
|
137
|
+
config.enabled = enabled;
|
|
138
|
+
config.updatedAt = new Date().toISOString();
|
|
139
|
+
await this.saveConfigs(configs);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return config || null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* List notification configs
|
|
147
|
+
*/
|
|
148
|
+
async listConfigs(): Promise<NotificationConfig[]> {
|
|
149
|
+
return this.loadConfigs();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Delete notification config
|
|
154
|
+
*/
|
|
155
|
+
async deleteConfig(id: string): Promise<boolean> {
|
|
156
|
+
const configs = await this.loadConfigs();
|
|
157
|
+
const index = configs.findIndex(c => c.id === id);
|
|
158
|
+
|
|
159
|
+
if (index >= 0) {
|
|
160
|
+
configs.splice(index, 1);
|
|
161
|
+
await this.saveConfigs(configs);
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ============ Notification Triggering ============
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Process a transaction and trigger matching notifications
|
|
172
|
+
*/
|
|
173
|
+
async processTransaction(tx: {
|
|
174
|
+
type: 'incoming' | 'outgoing';
|
|
175
|
+
amount: string;
|
|
176
|
+
fromAddress: string;
|
|
177
|
+
toAddress: string;
|
|
178
|
+
chain: string;
|
|
179
|
+
txHash: string;
|
|
180
|
+
}): Promise<Notification[]> {
|
|
181
|
+
const configs = await this.loadConfigs();
|
|
182
|
+
const triggeredNotifications: Notification[] = [];
|
|
183
|
+
const now = new Date();
|
|
184
|
+
|
|
185
|
+
for (const config of configs) {
|
|
186
|
+
if (!config.enabled) continue;
|
|
187
|
+
|
|
188
|
+
// Check cooldown
|
|
189
|
+
if (config.cooldownMs && config.lastTriggeredAt) {
|
|
190
|
+
const timeSince = now.getTime() - new Date(config.lastTriggeredAt).getTime();
|
|
191
|
+
if (timeSince < config.cooldownMs) continue;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Check triggers
|
|
195
|
+
const matchingTrigger = config.triggers.find(trigger => {
|
|
196
|
+
if (trigger.type !== tx.type) return false;
|
|
197
|
+
|
|
198
|
+
if (trigger.minAmount && parseFloat(tx.amount) < parseFloat(trigger.minAmount)) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (trigger.addresses && trigger.addresses.length > 0) {
|
|
203
|
+
const relevantAddr = tx.type === 'incoming' ? tx.fromAddress : tx.toAddress;
|
|
204
|
+
if (!trigger.addresses.some(a => a.toLowerCase() === relevantAddr.toLowerCase())) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (trigger.chains && trigger.chains.length > 0) {
|
|
210
|
+
if (!trigger.chains.includes(tx.chain)) return false;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return true;
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
if (!matchingTrigger) continue;
|
|
217
|
+
|
|
218
|
+
// Create notification
|
|
219
|
+
const notification = await this.createNotification({
|
|
220
|
+
configId: config.id,
|
|
221
|
+
type: tx.type === 'incoming' ? 'payment_received' : 'payment_sent',
|
|
222
|
+
title: tx.type === 'incoming'
|
|
223
|
+
? `Received ${tx.amount} USDC`
|
|
224
|
+
: `Sent ${tx.amount} USDC`,
|
|
225
|
+
message: tx.type === 'incoming'
|
|
226
|
+
? `You received ${tx.amount} USDC from ${this.shortAddress(tx.fromAddress)} on ${tx.chain}`
|
|
227
|
+
: `You sent ${tx.amount} USDC to ${this.shortAddress(tx.toAddress)} on ${tx.chain}`,
|
|
228
|
+
data: tx,
|
|
229
|
+
channels: config.channels,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
triggeredNotifications.push(notification);
|
|
233
|
+
|
|
234
|
+
// Update last triggered
|
|
235
|
+
config.lastTriggeredAt = now.toISOString();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
await this.saveConfigs(configs);
|
|
239
|
+
return triggeredNotifications;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Create and deliver a notification
|
|
244
|
+
*/
|
|
245
|
+
async createNotification(params: {
|
|
246
|
+
configId?: string;
|
|
247
|
+
type: Notification['type'];
|
|
248
|
+
title: string;
|
|
249
|
+
message: string;
|
|
250
|
+
data: Record<string, any>;
|
|
251
|
+
channels: NotificationConfig['channels'];
|
|
252
|
+
}): Promise<Notification> {
|
|
253
|
+
const notification: Notification = {
|
|
254
|
+
id: crypto.randomUUID(),
|
|
255
|
+
configId: params.configId,
|
|
256
|
+
type: params.type,
|
|
257
|
+
title: params.title,
|
|
258
|
+
message: params.message,
|
|
259
|
+
data: params.data,
|
|
260
|
+
deliveries: params.channels.map(ch => ({
|
|
261
|
+
channel: ch.type,
|
|
262
|
+
status: 'pending' as const,
|
|
263
|
+
})),
|
|
264
|
+
createdAt: new Date().toISOString(),
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// Attempt delivery to each channel
|
|
268
|
+
for (let i = 0; i < params.channels.length; i++) {
|
|
269
|
+
const channel = params.channels[i];
|
|
270
|
+
try {
|
|
271
|
+
await this.deliver(channel, notification);
|
|
272
|
+
notification.deliveries[i].status = 'sent';
|
|
273
|
+
notification.deliveries[i].sentAt = new Date().toISOString();
|
|
274
|
+
} catch (err: any) {
|
|
275
|
+
notification.deliveries[i].status = 'failed';
|
|
276
|
+
notification.deliveries[i].error = err.message;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Save to history
|
|
281
|
+
const history = await this.loadHistory();
|
|
282
|
+
history.push(notification);
|
|
283
|
+
await this.saveHistory(history);
|
|
284
|
+
|
|
285
|
+
return notification;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Deliver notification to a channel
|
|
290
|
+
*/
|
|
291
|
+
private async deliver(
|
|
292
|
+
channel: NotificationConfig['channels'][0],
|
|
293
|
+
notification: Notification
|
|
294
|
+
): Promise<void> {
|
|
295
|
+
switch (channel.type) {
|
|
296
|
+
case 'webhook':
|
|
297
|
+
await this.deliverWebhook(channel.config, notification);
|
|
298
|
+
break;
|
|
299
|
+
case 'telegram':
|
|
300
|
+
await this.deliverTelegram(channel.config, notification);
|
|
301
|
+
break;
|
|
302
|
+
case 'clawdbot':
|
|
303
|
+
await this.deliverClawdbot(channel.config, notification);
|
|
304
|
+
break;
|
|
305
|
+
case 'email':
|
|
306
|
+
// Email delivery would require SMTP config
|
|
307
|
+
console.log('Email delivery not implemented');
|
|
308
|
+
break;
|
|
309
|
+
default:
|
|
310
|
+
throw new Error(`Unknown channel type: ${channel.type}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Deliver via webhook
|
|
316
|
+
*/
|
|
317
|
+
private async deliverWebhook(
|
|
318
|
+
config: Record<string, string>,
|
|
319
|
+
notification: Notification
|
|
320
|
+
): Promise<void> {
|
|
321
|
+
if (!config.url) {
|
|
322
|
+
throw new Error('Webhook URL not configured');
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const payload = {
|
|
326
|
+
id: notification.id,
|
|
327
|
+
type: notification.type,
|
|
328
|
+
title: notification.title,
|
|
329
|
+
message: notification.message,
|
|
330
|
+
data: notification.data,
|
|
331
|
+
timestamp: notification.createdAt,
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const headers: Record<string, string> = {
|
|
335
|
+
'Content-Type': 'application/json',
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// Add optional secret for verification
|
|
339
|
+
if (config.secret) {
|
|
340
|
+
const signature = crypto
|
|
341
|
+
.createHmac('sha256', config.secret)
|
|
342
|
+
.update(JSON.stringify(payload))
|
|
343
|
+
.digest('hex');
|
|
344
|
+
headers['X-Signature'] = signature;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const response = await fetch(config.url, {
|
|
348
|
+
method: 'POST',
|
|
349
|
+
headers,
|
|
350
|
+
body: JSON.stringify(payload),
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
if (!response.ok) {
|
|
354
|
+
throw new Error(`Webhook failed: ${response.status}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Deliver via Telegram (would integrate with Clawdbot's messaging)
|
|
360
|
+
*/
|
|
361
|
+
private async deliverTelegram(
|
|
362
|
+
config: Record<string, string>,
|
|
363
|
+
notification: Notification
|
|
364
|
+
): Promise<void> {
|
|
365
|
+
// This would use Clawdbot's message tool
|
|
366
|
+
console.log(`[Telegram] ${notification.title}: ${notification.message}`);
|
|
367
|
+
// In practice, this would call the message API
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Deliver via Clawdbot session message
|
|
372
|
+
*/
|
|
373
|
+
private async deliverClawdbot(
|
|
374
|
+
config: Record<string, string>,
|
|
375
|
+
notification: Notification
|
|
376
|
+
): Promise<void> {
|
|
377
|
+
// This would use sessions_send
|
|
378
|
+
console.log(`[Clawdbot] ${notification.title}: ${notification.message}`);
|
|
379
|
+
// In practice, this would call sessions_send
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// ============ History & Management ============
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Get notification history
|
|
386
|
+
*/
|
|
387
|
+
async getHistory(options?: {
|
|
388
|
+
type?: Notification['type'];
|
|
389
|
+
limit?: number;
|
|
390
|
+
unreadOnly?: boolean;
|
|
391
|
+
}): Promise<Notification[]> {
|
|
392
|
+
let history = await this.loadHistory();
|
|
393
|
+
|
|
394
|
+
if (options?.type) {
|
|
395
|
+
history = history.filter(n => n.type === options.type);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (options?.unreadOnly) {
|
|
399
|
+
history = history.filter(n => !n.readAt);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
history.sort((a, b) =>
|
|
403
|
+
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
if (options?.limit) {
|
|
407
|
+
history = history.slice(0, options.limit);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return history;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Mark notification as read
|
|
415
|
+
*/
|
|
416
|
+
async markRead(id: string): Promise<void> {
|
|
417
|
+
const history = await this.loadHistory();
|
|
418
|
+
const notification = history.find(n => n.id === id);
|
|
419
|
+
|
|
420
|
+
if (notification) {
|
|
421
|
+
notification.readAt = new Date().toISOString();
|
|
422
|
+
await this.saveHistory(history);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Mark all as read
|
|
428
|
+
*/
|
|
429
|
+
async markAllRead(): Promise<void> {
|
|
430
|
+
const history = await this.loadHistory();
|
|
431
|
+
const now = new Date().toISOString();
|
|
432
|
+
|
|
433
|
+
for (const notification of history) {
|
|
434
|
+
if (!notification.readAt) {
|
|
435
|
+
notification.readAt = now;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
await this.saveHistory(history);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Get unread count
|
|
444
|
+
*/
|
|
445
|
+
async getUnreadCount(): Promise<number> {
|
|
446
|
+
const history = await this.loadHistory();
|
|
447
|
+
return history.filter(n => !n.readAt).length;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// ============ Helpers ============
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Shorten address for display
|
|
454
|
+
*/
|
|
455
|
+
private shortAddress(address: string): string {
|
|
456
|
+
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Create default notification configs for a new user
|
|
461
|
+
*/
|
|
462
|
+
async createDefaultConfigs(): Promise<NotificationConfig[]> {
|
|
463
|
+
const configs: NotificationConfig[] = [];
|
|
464
|
+
|
|
465
|
+
// Incoming payments
|
|
466
|
+
configs.push(await this.createConfig({
|
|
467
|
+
name: 'All Incoming Payments',
|
|
468
|
+
triggers: [{ type: 'incoming' }],
|
|
469
|
+
channels: [{ type: 'clawdbot', config: {} }],
|
|
470
|
+
}));
|
|
471
|
+
|
|
472
|
+
// Large outgoing (security alert)
|
|
473
|
+
configs.push(await this.createConfig({
|
|
474
|
+
name: 'Large Outgoing (>1000 USDC)',
|
|
475
|
+
triggers: [{ type: 'outgoing', minAmount: '1000' }],
|
|
476
|
+
channels: [{ type: 'clawdbot', config: {} }],
|
|
477
|
+
cooldownMs: 60000, // 1 min cooldown
|
|
478
|
+
}));
|
|
479
|
+
|
|
480
|
+
return configs;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export default NotificationManager;
|