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,615 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contact & Address Book Management
|
|
3
|
+
*
|
|
4
|
+
* Secure storage for payment contacts with labels, validation, and search.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import crypto from 'crypto';
|
|
8
|
+
import fs from 'fs/promises';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
|
|
11
|
+
export interface Contact {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
alias?: string; // Short name for quick reference
|
|
15
|
+
email?: string;
|
|
16
|
+
phone?: string;
|
|
17
|
+
|
|
18
|
+
// Wallet addresses by chain
|
|
19
|
+
addresses: {
|
|
20
|
+
chain: string;
|
|
21
|
+
address: string;
|
|
22
|
+
label?: string;
|
|
23
|
+
verified: boolean;
|
|
24
|
+
addedAt: string;
|
|
25
|
+
}[];
|
|
26
|
+
|
|
27
|
+
// Payment preferences
|
|
28
|
+
defaultChain?: string;
|
|
29
|
+
defaultAddress?: string;
|
|
30
|
+
|
|
31
|
+
// Tags for organization
|
|
32
|
+
tags: string[];
|
|
33
|
+
|
|
34
|
+
// Transaction history summary
|
|
35
|
+
totalSent: string;
|
|
36
|
+
totalReceived: string;
|
|
37
|
+
lastTransactionAt?: string;
|
|
38
|
+
transactionCount: number;
|
|
39
|
+
|
|
40
|
+
// Notes
|
|
41
|
+
notes?: string;
|
|
42
|
+
|
|
43
|
+
// Metadata
|
|
44
|
+
createdAt: string;
|
|
45
|
+
updatedAt: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const DATA_DIR = process.env.USDC_DATA_DIR || './data';
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Contact Manager
|
|
52
|
+
*/
|
|
53
|
+
export class ContactManager {
|
|
54
|
+
private dataPath: string;
|
|
55
|
+
|
|
56
|
+
constructor(dataDir = DATA_DIR) {
|
|
57
|
+
this.dataPath = path.join(dataDir, 'contacts.json');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private async loadContacts(): Promise<Contact[]> {
|
|
61
|
+
try {
|
|
62
|
+
const data = await fs.readFile(this.dataPath, 'utf-8');
|
|
63
|
+
return JSON.parse(data);
|
|
64
|
+
} catch {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private async saveContacts(contacts: Contact[]): Promise<void> {
|
|
70
|
+
await fs.mkdir(path.dirname(this.dataPath), { recursive: true });
|
|
71
|
+
await fs.writeFile(this.dataPath, JSON.stringify(contacts, null, 2));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Add a new contact
|
|
76
|
+
*/
|
|
77
|
+
async add(params: {
|
|
78
|
+
name: string;
|
|
79
|
+
alias?: string;
|
|
80
|
+
email?: string;
|
|
81
|
+
phone?: string;
|
|
82
|
+
addresses?: { chain: string; address: string; label?: string }[];
|
|
83
|
+
tags?: string[];
|
|
84
|
+
notes?: string;
|
|
85
|
+
}): Promise<Contact> {
|
|
86
|
+
const contacts = await this.loadContacts();
|
|
87
|
+
|
|
88
|
+
// Check for duplicate alias
|
|
89
|
+
if (params.alias) {
|
|
90
|
+
const existing = contacts.find(c =>
|
|
91
|
+
c.alias?.toLowerCase() === params.alias!.toLowerCase()
|
|
92
|
+
);
|
|
93
|
+
if (existing) {
|
|
94
|
+
throw new Error(`Alias "${params.alias}" already exists`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const contact: Contact = {
|
|
99
|
+
id: crypto.randomUUID(),
|
|
100
|
+
name: params.name,
|
|
101
|
+
alias: params.alias,
|
|
102
|
+
email: params.email,
|
|
103
|
+
phone: params.phone,
|
|
104
|
+
addresses: (params.addresses || []).map(addr => ({
|
|
105
|
+
...addr,
|
|
106
|
+
verified: this.isValidAddress(addr.address),
|
|
107
|
+
addedAt: new Date().toISOString(),
|
|
108
|
+
})),
|
|
109
|
+
tags: params.tags || [],
|
|
110
|
+
totalSent: '0',
|
|
111
|
+
totalReceived: '0',
|
|
112
|
+
transactionCount: 0,
|
|
113
|
+
notes: params.notes,
|
|
114
|
+
createdAt: new Date().toISOString(),
|
|
115
|
+
updatedAt: new Date().toISOString(),
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Set default address if provided
|
|
119
|
+
if (contact.addresses.length > 0) {
|
|
120
|
+
contact.defaultChain = contact.addresses[0].chain;
|
|
121
|
+
contact.defaultAddress = contact.addresses[0].address;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
contacts.push(contact);
|
|
125
|
+
await this.saveContacts(contacts);
|
|
126
|
+
|
|
127
|
+
return contact;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Find contact by name, alias, or address
|
|
132
|
+
*/
|
|
133
|
+
async find(query: string): Promise<Contact | null> {
|
|
134
|
+
const contacts = await this.loadContacts();
|
|
135
|
+
const lowerQuery = query.toLowerCase();
|
|
136
|
+
|
|
137
|
+
return contacts.find(c =>
|
|
138
|
+
c.name.toLowerCase() === lowerQuery ||
|
|
139
|
+
c.alias?.toLowerCase() === lowerQuery ||
|
|
140
|
+
c.addresses.some(a => a.address.toLowerCase() === lowerQuery)
|
|
141
|
+
) || null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Resolve a recipient (name, alias, or address) to an address
|
|
146
|
+
*/
|
|
147
|
+
async resolveRecipient(recipient: string, chain?: string): Promise<{
|
|
148
|
+
contact?: Contact;
|
|
149
|
+
address: string;
|
|
150
|
+
chain: string;
|
|
151
|
+
} | null> {
|
|
152
|
+
// If it's already a valid address, return it
|
|
153
|
+
if (this.isValidAddress(recipient)) {
|
|
154
|
+
const contact = await this.findByAddress(recipient);
|
|
155
|
+
return {
|
|
156
|
+
contact: contact || undefined,
|
|
157
|
+
address: recipient,
|
|
158
|
+
chain: chain || 'ETH-SEPOLIA',
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Look up contact
|
|
163
|
+
const contact = await this.find(recipient);
|
|
164
|
+
if (!contact) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Find address for requested chain or default
|
|
169
|
+
const targetChain = chain || contact.defaultChain || 'ETH-SEPOLIA';
|
|
170
|
+
const addr = contact.addresses.find(a => a.chain === targetChain) ||
|
|
171
|
+
contact.addresses[0];
|
|
172
|
+
|
|
173
|
+
if (!addr) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
contact,
|
|
179
|
+
address: addr.address,
|
|
180
|
+
chain: addr.chain,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Search contacts
|
|
186
|
+
*/
|
|
187
|
+
async search(query: string): Promise<Contact[]> {
|
|
188
|
+
const contacts = await this.loadContacts();
|
|
189
|
+
const lowerQuery = query.toLowerCase();
|
|
190
|
+
|
|
191
|
+
return contacts.filter(c =>
|
|
192
|
+
c.name.toLowerCase().includes(lowerQuery) ||
|
|
193
|
+
c.alias?.toLowerCase().includes(lowerQuery) ||
|
|
194
|
+
c.email?.toLowerCase().includes(lowerQuery) ||
|
|
195
|
+
c.tags.some(t => t.toLowerCase().includes(lowerQuery)) ||
|
|
196
|
+
c.notes?.toLowerCase().includes(lowerQuery)
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* List all contacts
|
|
202
|
+
*/
|
|
203
|
+
async list(options?: {
|
|
204
|
+
tag?: string;
|
|
205
|
+
sortBy?: 'name' | 'recent' | 'frequent';
|
|
206
|
+
}): Promise<Contact[]> {
|
|
207
|
+
let contacts = await this.loadContacts();
|
|
208
|
+
|
|
209
|
+
if (options?.tag) {
|
|
210
|
+
contacts = contacts.filter(c =>
|
|
211
|
+
c.tags.some(t => t.toLowerCase() === options.tag!.toLowerCase())
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
switch (options?.sortBy) {
|
|
216
|
+
case 'recent':
|
|
217
|
+
contacts.sort((a, b) => {
|
|
218
|
+
const dateA = a.lastTransactionAt || a.createdAt;
|
|
219
|
+
const dateB = b.lastTransactionAt || b.createdAt;
|
|
220
|
+
return new Date(dateB).getTime() - new Date(dateA).getTime();
|
|
221
|
+
});
|
|
222
|
+
break;
|
|
223
|
+
case 'frequent':
|
|
224
|
+
contacts.sort((a, b) => b.transactionCount - a.transactionCount);
|
|
225
|
+
break;
|
|
226
|
+
case 'name':
|
|
227
|
+
default:
|
|
228
|
+
contacts.sort((a, b) => a.name.localeCompare(b.name));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return contacts;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Find contact by address
|
|
236
|
+
*/
|
|
237
|
+
async findByAddress(address: string): Promise<Contact | null> {
|
|
238
|
+
const contacts = await this.loadContacts();
|
|
239
|
+
return contacts.find(c =>
|
|
240
|
+
c.addresses.some(a => a.address.toLowerCase() === address.toLowerCase())
|
|
241
|
+
) || null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Update contact
|
|
246
|
+
*/
|
|
247
|
+
async update(id: string, updates: Partial<Omit<Contact, 'id' | 'createdAt'>>): Promise<Contact | null> {
|
|
248
|
+
const contacts = await this.loadContacts();
|
|
249
|
+
const contact = contacts.find(c => c.id === id);
|
|
250
|
+
|
|
251
|
+
if (contact) {
|
|
252
|
+
Object.assign(contact, updates, { updatedAt: new Date().toISOString() });
|
|
253
|
+
await this.saveContacts(contacts);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return contact || null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Add address to contact
|
|
261
|
+
*/
|
|
262
|
+
async addAddress(contactId: string, address: {
|
|
263
|
+
chain: string;
|
|
264
|
+
address: string;
|
|
265
|
+
label?: string;
|
|
266
|
+
}): Promise<Contact | null> {
|
|
267
|
+
const contacts = await this.loadContacts();
|
|
268
|
+
const contact = contacts.find(c => c.id === contactId);
|
|
269
|
+
|
|
270
|
+
if (contact) {
|
|
271
|
+
// Check if address already exists
|
|
272
|
+
if (contact.addresses.some(a =>
|
|
273
|
+
a.chain === address.chain &&
|
|
274
|
+
a.address.toLowerCase() === address.address.toLowerCase()
|
|
275
|
+
)) {
|
|
276
|
+
throw new Error('Address already exists for this chain');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
contact.addresses.push({
|
|
280
|
+
...address,
|
|
281
|
+
verified: this.isValidAddress(address.address),
|
|
282
|
+
addedAt: new Date().toISOString(),
|
|
283
|
+
});
|
|
284
|
+
contact.updatedAt = new Date().toISOString();
|
|
285
|
+
await this.saveContacts(contacts);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return contact || null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Remove address from contact
|
|
293
|
+
*/
|
|
294
|
+
async removeAddress(contactId: string, chain: string): Promise<Contact | null> {
|
|
295
|
+
const contacts = await this.loadContacts();
|
|
296
|
+
const contact = contacts.find(c => c.id === contactId);
|
|
297
|
+
|
|
298
|
+
if (contact) {
|
|
299
|
+
contact.addresses = contact.addresses.filter(a => a.chain !== chain);
|
|
300
|
+
contact.updatedAt = new Date().toISOString();
|
|
301
|
+
|
|
302
|
+
// Update default if removed
|
|
303
|
+
if (contact.defaultChain === chain && contact.addresses.length > 0) {
|
|
304
|
+
contact.defaultChain = contact.addresses[0].chain;
|
|
305
|
+
contact.defaultAddress = contact.addresses[0].address;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
await this.saveContacts(contacts);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return contact || null;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Set default address for contact
|
|
316
|
+
*/
|
|
317
|
+
async setDefault(contactId: string, chain: string): Promise<Contact | null> {
|
|
318
|
+
const contacts = await this.loadContacts();
|
|
319
|
+
const contact = contacts.find(c => c.id === contactId);
|
|
320
|
+
|
|
321
|
+
if (contact) {
|
|
322
|
+
const addr = contact.addresses.find(a => a.chain === chain);
|
|
323
|
+
if (addr) {
|
|
324
|
+
contact.defaultChain = addr.chain;
|
|
325
|
+
contact.defaultAddress = addr.address;
|
|
326
|
+
contact.updatedAt = new Date().toISOString();
|
|
327
|
+
await this.saveContacts(contacts);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return contact || null;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Add tags to contact
|
|
336
|
+
*/
|
|
337
|
+
async addTags(contactId: string, tags: string[]): Promise<Contact | null> {
|
|
338
|
+
const contacts = await this.loadContacts();
|
|
339
|
+
const contact = contacts.find(c => c.id === contactId);
|
|
340
|
+
|
|
341
|
+
if (contact) {
|
|
342
|
+
const newTags = tags.filter(t => !contact.tags.includes(t));
|
|
343
|
+
contact.tags.push(...newTags);
|
|
344
|
+
contact.updatedAt = new Date().toISOString();
|
|
345
|
+
await this.saveContacts(contacts);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return contact || null;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Record a transaction with contact
|
|
353
|
+
*/
|
|
354
|
+
async recordTransaction(address: string, type: 'sent' | 'received', amount: string): Promise<void> {
|
|
355
|
+
const contacts = await this.loadContacts();
|
|
356
|
+
const contact = contacts.find(c =>
|
|
357
|
+
c.addresses.some(a => a.address.toLowerCase() === address.toLowerCase())
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
if (contact) {
|
|
361
|
+
const amountNum = parseFloat(amount);
|
|
362
|
+
if (type === 'sent') {
|
|
363
|
+
contact.totalSent = (parseFloat(contact.totalSent) + amountNum).toString();
|
|
364
|
+
} else {
|
|
365
|
+
contact.totalReceived = (parseFloat(contact.totalReceived) + amountNum).toString();
|
|
366
|
+
}
|
|
367
|
+
contact.transactionCount++;
|
|
368
|
+
contact.lastTransactionAt = new Date().toISOString();
|
|
369
|
+
contact.updatedAt = new Date().toISOString();
|
|
370
|
+
await this.saveContacts(contacts);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Delete contact
|
|
376
|
+
*/
|
|
377
|
+
async delete(id: string): Promise<boolean> {
|
|
378
|
+
const contacts = await this.loadContacts();
|
|
379
|
+
const index = contacts.findIndex(c => c.id === id);
|
|
380
|
+
|
|
381
|
+
if (index >= 0) {
|
|
382
|
+
contacts.splice(index, 1);
|
|
383
|
+
await this.saveContacts(contacts);
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Export contacts to CSV
|
|
392
|
+
*/
|
|
393
|
+
async exportCSV(): Promise<string> {
|
|
394
|
+
const contacts = await this.loadContacts();
|
|
395
|
+
|
|
396
|
+
const header = 'Name,Alias,Email,Chain,Address,Tags,Total Sent,Total Received\n';
|
|
397
|
+
const rows = contacts.flatMap(c =>
|
|
398
|
+
c.addresses.map(a =>
|
|
399
|
+
`"${c.name}","${c.alias || ''}","${c.email || ''}","${a.chain}","${a.address}","${c.tags.join(';')}","${c.totalSent}","${c.totalReceived}"`
|
|
400
|
+
)
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
return header + rows.join('\n');
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Import contacts from CSV
|
|
408
|
+
*/
|
|
409
|
+
async importCSV(csvContent: string): Promise<{ imported: number; skipped: number }> {
|
|
410
|
+
const lines = csvContent.split('\n').slice(1); // Skip header
|
|
411
|
+
let imported = 0;
|
|
412
|
+
let skipped = 0;
|
|
413
|
+
|
|
414
|
+
for (const line of lines) {
|
|
415
|
+
if (!line.trim()) continue;
|
|
416
|
+
|
|
417
|
+
try {
|
|
418
|
+
// Simple CSV parsing (doesn't handle all edge cases)
|
|
419
|
+
const parts = line.match(/(?:"[^"]*"|[^,])+/g) || [];
|
|
420
|
+
const [name, alias, email, chain, address, tags] = parts.map(p =>
|
|
421
|
+
p.replace(/^"|"$/g, '')
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
if (!name || !address) {
|
|
425
|
+
skipped++;
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Check if contact exists
|
|
430
|
+
let contact = await this.find(name);
|
|
431
|
+
|
|
432
|
+
if (contact) {
|
|
433
|
+
// Add address to existing contact
|
|
434
|
+
await this.addAddress(contact.id, { chain: chain || 'ETH-SEPOLIA', address });
|
|
435
|
+
} else {
|
|
436
|
+
// Create new contact
|
|
437
|
+
await this.add({
|
|
438
|
+
name,
|
|
439
|
+
alias: alias || undefined,
|
|
440
|
+
email: email || undefined,
|
|
441
|
+
addresses: [{ chain: chain || 'ETH-SEPOLIA', address }],
|
|
442
|
+
tags: tags ? tags.split(';') : [],
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
imported++;
|
|
446
|
+
} catch (err) {
|
|
447
|
+
skipped++;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return { imported, skipped };
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Validate Ethereum address
|
|
456
|
+
*/
|
|
457
|
+
private isValidAddress(address: string): boolean {
|
|
458
|
+
return /^0x[a-fA-F0-9]{40}$/.test(address);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* x402 Premium Contact Features
|
|
464
|
+
*
|
|
465
|
+
* Premium verification and risk assessment services
|
|
466
|
+
*/
|
|
467
|
+
export interface PremiumContactFeatures {
|
|
468
|
+
onChainVerification?: boolean; // Verify address activity on-chain
|
|
469
|
+
riskAssessment?: boolean; // Calculate risk score
|
|
470
|
+
fraudDetection?: boolean; // Check for fraud patterns
|
|
471
|
+
transactionHistory?: boolean; // Full on-chain transaction history
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
export interface ContactVerificationResult {
|
|
475
|
+
contact: Contact;
|
|
476
|
+
onChainVerified: boolean;
|
|
477
|
+
riskScore?: {
|
|
478
|
+
score: number; // 0-100 (lower is better)
|
|
479
|
+
level: 'low' | 'medium' | 'high' | 'critical';
|
|
480
|
+
factors: string[];
|
|
481
|
+
};
|
|
482
|
+
fraudFlags?: string[];
|
|
483
|
+
transactionSummary?: {
|
|
484
|
+
totalVolume: string;
|
|
485
|
+
transactionCount: number;
|
|
486
|
+
firstSeen: string;
|
|
487
|
+
lastSeen: string;
|
|
488
|
+
interactedWith: number;
|
|
489
|
+
};
|
|
490
|
+
verified: boolean;
|
|
491
|
+
verifiedAt: string;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Premium verification pricing (in USDC)
|
|
495
|
+
export const PREMIUM_CONTACT_PRICING = {
|
|
496
|
+
basicVerification: '0.05', // Basic on-chain verification
|
|
497
|
+
fullVerification: '0.10', // Full verification + risk score
|
|
498
|
+
fraudCheck: '0.15', // Fraud detection analysis
|
|
499
|
+
fullReport: '0.25', // Complete verification report
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Generate x402 verification URL for contact
|
|
504
|
+
*/
|
|
505
|
+
export function generateX402VerificationUrl(
|
|
506
|
+
contactId: string,
|
|
507
|
+
verificationType: 'basic' | 'full' | 'fraud' | 'report' = 'basic',
|
|
508
|
+
baseUrl?: string
|
|
509
|
+
): string {
|
|
510
|
+
const base = baseUrl || process.env.X402_BASE_URL || 'https://api.lobster-pay.com';
|
|
511
|
+
return `${base}/contacts/${contactId}/verify/${verificationType}`;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Verify contact with premium features via x402
|
|
516
|
+
*/
|
|
517
|
+
export async function verifyContactPremium(
|
|
518
|
+
contact: Contact,
|
|
519
|
+
features: PremiumContactFeatures,
|
|
520
|
+
x402Fetch: (url: string) => Promise<Response>
|
|
521
|
+
): Promise<ContactVerificationResult> {
|
|
522
|
+
const result: ContactVerificationResult = {
|
|
523
|
+
contact,
|
|
524
|
+
onChainVerified: false,
|
|
525
|
+
verified: false,
|
|
526
|
+
verifiedAt: new Date().toISOString(),
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
try {
|
|
530
|
+
// Determine verification type based on features
|
|
531
|
+
let verificationType: 'basic' | 'full' | 'fraud' | 'report' = 'basic';
|
|
532
|
+
|
|
533
|
+
if (features.fraudDetection) {
|
|
534
|
+
verificationType = 'fraud';
|
|
535
|
+
} else if (features.onChainVerification && features.riskAssessment && features.transactionHistory) {
|
|
536
|
+
verificationType = 'report';
|
|
537
|
+
} else if (features.riskAssessment) {
|
|
538
|
+
verificationType = 'full';
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Call x402-protected verification endpoint
|
|
542
|
+
const url = generateX402VerificationUrl(contact.id, verificationType);
|
|
543
|
+
const response = await x402Fetch(url);
|
|
544
|
+
|
|
545
|
+
if (!response.ok) {
|
|
546
|
+
throw new Error(`Verification failed: ${response.statusText}`);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const data = await response.json();
|
|
550
|
+
|
|
551
|
+
// Parse verification results
|
|
552
|
+
result.onChainVerified = data.onChainVerified || false;
|
|
553
|
+
result.riskScore = data.riskScore;
|
|
554
|
+
result.fraudFlags = data.fraudFlags;
|
|
555
|
+
result.transactionSummary = data.transactionSummary;
|
|
556
|
+
result.verified = true;
|
|
557
|
+
|
|
558
|
+
} catch (error) {
|
|
559
|
+
console.error('Contact verification error:', error);
|
|
560
|
+
result.verified = false;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return result;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Mock verification for testing
|
|
568
|
+
* Simulates what a real x402-protected endpoint would return
|
|
569
|
+
*/
|
|
570
|
+
export function mockContactVerification(contact: Contact): ContactVerificationResult {
|
|
571
|
+
// Generate mock on-chain data
|
|
572
|
+
const addressCount = contact.addresses.length;
|
|
573
|
+
const hasMultipleChains = addressCount > 1;
|
|
574
|
+
|
|
575
|
+
// Calculate mock risk score
|
|
576
|
+
let riskScore = 20; // Base low risk
|
|
577
|
+
if (addressCount === 0) riskScore += 40; // No addresses = higher risk
|
|
578
|
+
if (!contact.email && !contact.phone) riskScore += 20; // No contact info
|
|
579
|
+
if (contact.transactionCount === 0) riskScore += 30; // No transaction history
|
|
580
|
+
|
|
581
|
+
riskScore = Math.min(100, riskScore);
|
|
582
|
+
|
|
583
|
+
const riskLevel =
|
|
584
|
+
riskScore < 25 ? 'low' :
|
|
585
|
+
riskScore < 50 ? 'medium' :
|
|
586
|
+
riskScore < 75 ? 'high' : 'critical';
|
|
587
|
+
|
|
588
|
+
const factors: string[] = [];
|
|
589
|
+
if (addressCount === 0) factors.push('No wallet addresses on file');
|
|
590
|
+
if (contact.transactionCount === 0) factors.push('No transaction history');
|
|
591
|
+
if (!contact.email && !contact.phone) factors.push('No contact information');
|
|
592
|
+
if (hasMultipleChains) factors.push('Active on multiple chains (positive)');
|
|
593
|
+
|
|
594
|
+
return {
|
|
595
|
+
contact,
|
|
596
|
+
onChainVerified: addressCount > 0,
|
|
597
|
+
riskScore: {
|
|
598
|
+
score: riskScore,
|
|
599
|
+
level: riskLevel,
|
|
600
|
+
factors,
|
|
601
|
+
},
|
|
602
|
+
fraudFlags: riskScore > 70 ? ['High risk score', 'Limited verification data'] : [],
|
|
603
|
+
transactionSummary: {
|
|
604
|
+
totalVolume: contact.totalSent || '0',
|
|
605
|
+
transactionCount: contact.transactionCount,
|
|
606
|
+
firstSeen: contact.createdAt,
|
|
607
|
+
lastSeen: contact.lastTransactionAt || contact.createdAt,
|
|
608
|
+
interactedWith: Math.floor(Math.random() * 50) + 5,
|
|
609
|
+
},
|
|
610
|
+
verified: true,
|
|
611
|
+
verifiedAt: new Date().toISOString(),
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
export default ContactManager;
|
package/lib/easy.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Easy Mode API - Simplified interface for Pay Lobster
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { LobsterAgent } from './agent';
|
|
6
|
+
import type { LobsterConfig } from './types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create a new Lobster agent with minimal configuration
|
|
10
|
+
*/
|
|
11
|
+
export function createLobsterAgent(config: LobsterConfig = {}): LobsterAgent {
|
|
12
|
+
return new LobsterAgent(config);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Quick start - create and initialize an agent in one call
|
|
17
|
+
*/
|
|
18
|
+
export async function quickStart(config: LobsterConfig = {}): Promise<LobsterAgent> {
|
|
19
|
+
const agent = createLobsterAgent(config);
|
|
20
|
+
await agent.initialize();
|
|
21
|
+
return agent;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* One-liner to send USDC
|
|
26
|
+
*/
|
|
27
|
+
export async function sendUSDC(
|
|
28
|
+
to: string,
|
|
29
|
+
amount: number,
|
|
30
|
+
config: LobsterConfig = {}
|
|
31
|
+
): Promise<string> {
|
|
32
|
+
const agent = await quickStart(config);
|
|
33
|
+
const tx = await agent.send(to, amount);
|
|
34
|
+
return tx.id;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* One-liner to check balance
|
|
39
|
+
*/
|
|
40
|
+
export async function checkBalance(
|
|
41
|
+
address: string,
|
|
42
|
+
config: LobsterConfig = {}
|
|
43
|
+
): Promise<string> {
|
|
44
|
+
const agent = createLobsterAgent({ ...config, walletId: address });
|
|
45
|
+
return agent.getBalance();
|
|
46
|
+
}
|