moltspay 0.5.4 → 0.7.1
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 +0 -124
- package/dist/cdp/index.d.mts +111 -0
- package/dist/cdp/index.d.ts +111 -0
- package/dist/cdp/index.js +30655 -0
- package/dist/cdp/index.js.map +1 -0
- package/dist/cdp/index.mjs +30631 -0
- package/dist/cdp/index.mjs.map +1 -0
- package/dist/chains/index.d.mts +1 -1
- package/dist/chains/index.d.ts +1 -1
- package/dist/cli/index.js +990 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/index.mjs +967 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/client/index.d.mts +134 -0
- package/dist/client/index.d.ts +134 -0
- package/dist/client/index.js +331 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/index.mjs +296 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/createWallet-D53qu7ie.d.mts +77 -0
- package/dist/createWallet-D53qu7ie.d.ts +77 -0
- package/dist/index-Dg8n6wdW.d.mts +32 -0
- package/dist/index-Dg8n6wdW.d.ts +32 -0
- package/dist/index.d.mts +6 -1483
- package/dist/index.d.ts +6 -1483
- package/dist/index.js +31039 -4254
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +31042 -4203
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.d.mts +120 -0
- package/dist/server/index.d.ts +120 -0
- package/dist/server/index.js +418 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/index.mjs +393 -0
- package/dist/server/index.mjs.map +1 -0
- package/dist/wallet/index.d.mts +3 -451
- package/dist/wallet/index.d.ts +3 -451
- package/dist/wallet/index.js +5 -1021
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/index.mjs +16 -1015
- package/dist/wallet/index.mjs.map +1 -1
- package/package.json +19 -19
- package/dist/cli.js +0 -1984
- package/dist/cli.js.map +0 -1
- package/dist/cli.mjs +0 -1969
- package/dist/cli.mjs.map +0 -1
- package/dist/guide/index.d.mts +0 -39
- package/dist/guide/index.d.ts +0 -39
- package/dist/guide/index.js +0 -181
- package/dist/guide/index.js.map +0 -1
- package/dist/guide/index.mjs +0 -152
- package/dist/guide/index.mjs.map +0 -1
- package/dist/index-CyFg9s2m.d.mts +0 -161
- package/dist/index-CyFg9s2m.d.ts +0 -161
- package/dist/orders/index.d.mts +0 -97
- package/dist/orders/index.d.ts +0 -97
- package/dist/orders/index.js +0 -162
- package/dist/orders/index.js.map +0 -1
- package/dist/orders/index.mjs +0 -136
- package/dist/orders/index.mjs.map +0 -1
- package/dist/permit/index.d.mts +0 -49
- package/dist/permit/index.d.ts +0 -49
- package/dist/permit/index.js +0 -273
- package/dist/permit/index.js.map +0 -1
- package/dist/permit/index.mjs +0 -246
- package/dist/permit/index.mjs.map +0 -1
- /package/dist/{cli.d.mts → cli/index.d.mts} +0 -0
- /package/dist/{cli.d.ts → cli/index.d.ts} +0 -0
package/dist/wallet/index.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
6
|
var __export = (target, all) => {
|
|
9
7
|
for (var name in all)
|
|
@@ -17,30 +15,15 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
15
|
}
|
|
18
16
|
return to;
|
|
19
17
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
19
|
|
|
30
20
|
// src/wallet/index.ts
|
|
31
21
|
var wallet_exports = {};
|
|
32
22
|
__export(wallet_exports, {
|
|
33
|
-
AllowanceWallet: () => AllowanceWallet,
|
|
34
|
-
PermitSigner: () => PermitSigner,
|
|
35
|
-
PermitWallet: () => PermitWallet,
|
|
36
|
-
SecureWallet: () => SecureWallet,
|
|
37
23
|
Wallet: () => Wallet,
|
|
38
24
|
createWallet: () => createWallet,
|
|
39
|
-
formatPermitRequest: () => formatPermitRequest,
|
|
40
|
-
generatePermitInstructions: () => generatePermitInstructions,
|
|
41
25
|
getWalletAddress: () => getWalletAddress,
|
|
42
26
|
loadWallet: () => loadWallet,
|
|
43
|
-
signPermit: () => signPermit,
|
|
44
27
|
walletExists: () => walletExists
|
|
45
28
|
});
|
|
46
29
|
module.exports = __toCommonJS(wallet_exports);
|
|
@@ -216,407 +199,6 @@ var Wallet = class {
|
|
|
216
199
|
}
|
|
217
200
|
};
|
|
218
201
|
|
|
219
|
-
// src/audit/AuditLog.ts
|
|
220
|
-
var fs = __toESM(require("fs"));
|
|
221
|
-
var path = __toESM(require("path"));
|
|
222
|
-
var crypto = __toESM(require("crypto"));
|
|
223
|
-
var AuditLog = class {
|
|
224
|
-
basePath;
|
|
225
|
-
lastHash = "0000000000000000";
|
|
226
|
-
constructor(basePath) {
|
|
227
|
-
this.basePath = basePath || path.join(process.cwd(), "data", "audit");
|
|
228
|
-
this.ensureDir();
|
|
229
|
-
this.loadLastHash();
|
|
230
|
-
}
|
|
231
|
-
/**
|
|
232
|
-
* Record audit log
|
|
233
|
-
*/
|
|
234
|
-
async log(params) {
|
|
235
|
-
const now = /* @__PURE__ */ new Date();
|
|
236
|
-
const entry = {
|
|
237
|
-
timestamp: now.getTime() / 1e3,
|
|
238
|
-
datetime: now.toISOString(),
|
|
239
|
-
action: params.action,
|
|
240
|
-
request_id: params.request_id,
|
|
241
|
-
from: params.from,
|
|
242
|
-
to: params.to,
|
|
243
|
-
amount: params.amount,
|
|
244
|
-
tx_hash: params.tx_hash,
|
|
245
|
-
reason: params.reason,
|
|
246
|
-
requester: params.requester,
|
|
247
|
-
prev_hash: this.lastHash,
|
|
248
|
-
hash: "",
|
|
249
|
-
// Filled after calculation
|
|
250
|
-
metadata: params.metadata
|
|
251
|
-
};
|
|
252
|
-
entry.hash = this.calculateHash(entry);
|
|
253
|
-
this.lastHash = entry.hash;
|
|
254
|
-
const filePath = this.getFilePath(now);
|
|
255
|
-
const line = JSON.stringify(entry) + "\n";
|
|
256
|
-
fs.appendFileSync(filePath, line, "utf-8");
|
|
257
|
-
return entry;
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Read logs for specified date
|
|
261
|
-
*/
|
|
262
|
-
read(date) {
|
|
263
|
-
const filePath = this.getFilePath(date || /* @__PURE__ */ new Date());
|
|
264
|
-
if (!fs.existsSync(filePath)) {
|
|
265
|
-
return [];
|
|
266
|
-
}
|
|
267
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
268
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
269
|
-
return lines.map((line) => JSON.parse(line));
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Verify log integrity
|
|
273
|
-
*/
|
|
274
|
-
verify(date) {
|
|
275
|
-
const entries = this.read(date);
|
|
276
|
-
const errors = [];
|
|
277
|
-
for (let i = 0; i < entries.length; i++) {
|
|
278
|
-
const entry = entries[i];
|
|
279
|
-
const expectedHash = this.calculateHash(entry);
|
|
280
|
-
if (entry.hash !== expectedHash) {
|
|
281
|
-
errors.push(`Entry ${i}: hash mismatch (expected ${expectedHash}, got ${entry.hash})`);
|
|
282
|
-
}
|
|
283
|
-
if (i > 0 && entry.prev_hash !== entries[i - 1].hash) {
|
|
284
|
-
errors.push(`Entry ${i}: prev_hash mismatch`);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
return { valid: errors.length === 0, errors };
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* Search logs
|
|
291
|
-
*/
|
|
292
|
-
search(filter) {
|
|
293
|
-
const results = [];
|
|
294
|
-
const startDate = filter.startDate || new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3);
|
|
295
|
-
const endDate = filter.endDate || /* @__PURE__ */ new Date();
|
|
296
|
-
const current = new Date(startDate);
|
|
297
|
-
while (current <= endDate) {
|
|
298
|
-
const entries = this.read(current);
|
|
299
|
-
for (const entry of entries) {
|
|
300
|
-
let match = true;
|
|
301
|
-
if (filter.action && entry.action !== filter.action) match = false;
|
|
302
|
-
if (filter.request_id && entry.request_id !== filter.request_id) match = false;
|
|
303
|
-
if (filter.from && entry.from?.toLowerCase() !== filter.from.toLowerCase()) match = false;
|
|
304
|
-
if (filter.to && entry.to?.toLowerCase() !== filter.to.toLowerCase()) match = false;
|
|
305
|
-
if (match) {
|
|
306
|
-
results.push(entry);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
current.setDate(current.getDate() + 1);
|
|
310
|
-
}
|
|
311
|
-
return results;
|
|
312
|
-
}
|
|
313
|
-
/**
|
|
314
|
-
* Get log file path
|
|
315
|
-
*/
|
|
316
|
-
getFilePath(date) {
|
|
317
|
-
const dateStr = date.toISOString().slice(0, 10);
|
|
318
|
-
return path.join(this.basePath, `audit_${dateStr}.jsonl`);
|
|
319
|
-
}
|
|
320
|
-
/**
|
|
321
|
-
* Calculate entry hash
|
|
322
|
-
*/
|
|
323
|
-
calculateHash(entry) {
|
|
324
|
-
const data = {
|
|
325
|
-
timestamp: entry.timestamp,
|
|
326
|
-
action: entry.action,
|
|
327
|
-
request_id: entry.request_id,
|
|
328
|
-
from: entry.from,
|
|
329
|
-
to: entry.to,
|
|
330
|
-
amount: entry.amount,
|
|
331
|
-
tx_hash: entry.tx_hash,
|
|
332
|
-
prev_hash: entry.prev_hash
|
|
333
|
-
};
|
|
334
|
-
const str = JSON.stringify(data);
|
|
335
|
-
return crypto.createHash("sha256").update(str).digest("hex").slice(0, 16);
|
|
336
|
-
}
|
|
337
|
-
/**
|
|
338
|
-
* Load last log entry hash
|
|
339
|
-
*/
|
|
340
|
-
loadLastHash() {
|
|
341
|
-
const today = /* @__PURE__ */ new Date();
|
|
342
|
-
for (let i = 0; i < 2; i++) {
|
|
343
|
-
const date = new Date(today);
|
|
344
|
-
date.setDate(date.getDate() - i);
|
|
345
|
-
const entries = this.read(date);
|
|
346
|
-
if (entries.length > 0) {
|
|
347
|
-
this.lastHash = entries[entries.length - 1].hash;
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* Ensure directory exists
|
|
354
|
-
*/
|
|
355
|
-
ensureDir() {
|
|
356
|
-
if (!fs.existsSync(this.basePath)) {
|
|
357
|
-
fs.mkdirSync(this.basePath, { recursive: true });
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
// src/wallet/SecureWallet.ts
|
|
363
|
-
var DEFAULT_LIMITS = {
|
|
364
|
-
singleMax: 100,
|
|
365
|
-
// Single max $100
|
|
366
|
-
dailyMax: 1e3,
|
|
367
|
-
// Daily max $1000
|
|
368
|
-
requireWhitelist: true
|
|
369
|
-
};
|
|
370
|
-
var SecureWallet = class {
|
|
371
|
-
wallet;
|
|
372
|
-
limits;
|
|
373
|
-
whitelist;
|
|
374
|
-
auditLog;
|
|
375
|
-
dailyTotal = 0;
|
|
376
|
-
dailyDate = "";
|
|
377
|
-
pendingTransfers = /* @__PURE__ */ new Map();
|
|
378
|
-
constructor(config = {}) {
|
|
379
|
-
this.wallet = new Wallet({
|
|
380
|
-
chain: config.chain,
|
|
381
|
-
privateKey: config.privateKey
|
|
382
|
-
});
|
|
383
|
-
this.limits = { ...DEFAULT_LIMITS, ...config.limits };
|
|
384
|
-
this.whitelist = new Set((config.whitelist || []).map((a) => a.toLowerCase()));
|
|
385
|
-
this.auditLog = new AuditLog(config.auditPath);
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* Get wallet address
|
|
389
|
-
*/
|
|
390
|
-
get address() {
|
|
391
|
-
return this.wallet.address;
|
|
392
|
-
}
|
|
393
|
-
/**
|
|
394
|
-
* Get balance
|
|
395
|
-
*/
|
|
396
|
-
async getBalance() {
|
|
397
|
-
return this.wallet.getBalance();
|
|
398
|
-
}
|
|
399
|
-
/**
|
|
400
|
-
* Secure transfer (with limit and whitelist checks)
|
|
401
|
-
*
|
|
402
|
-
* Supports two calling methods:
|
|
403
|
-
* - transfer({ to, amount, reason?, requester? })
|
|
404
|
-
* - transfer(to, amount)
|
|
405
|
-
*/
|
|
406
|
-
async transfer(paramsOrTo, amountArg) {
|
|
407
|
-
let params;
|
|
408
|
-
if (typeof paramsOrTo === "string") {
|
|
409
|
-
params = {
|
|
410
|
-
to: paramsOrTo,
|
|
411
|
-
amount: typeof amountArg === "string" ? parseFloat(amountArg) : amountArg || 0
|
|
412
|
-
};
|
|
413
|
-
} else {
|
|
414
|
-
params = paramsOrTo;
|
|
415
|
-
}
|
|
416
|
-
const { to, amount, reason, requester } = params;
|
|
417
|
-
const toAddress = to.toLowerCase();
|
|
418
|
-
const requestId = `tr_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
419
|
-
await this.auditLog.log({
|
|
420
|
-
action: "transfer_request",
|
|
421
|
-
request_id: requestId,
|
|
422
|
-
from: this.wallet.address,
|
|
423
|
-
to,
|
|
424
|
-
amount,
|
|
425
|
-
reason,
|
|
426
|
-
requester
|
|
427
|
-
});
|
|
428
|
-
if (this.limits.requireWhitelist && !this.whitelist.has(toAddress)) {
|
|
429
|
-
await this.auditLog.log({
|
|
430
|
-
action: "transfer_failed",
|
|
431
|
-
request_id: requestId,
|
|
432
|
-
metadata: { error: "Address not in whitelist" }
|
|
433
|
-
});
|
|
434
|
-
return { success: false, error: `Address not in whitelist: ${to}` };
|
|
435
|
-
}
|
|
436
|
-
if (amount > this.limits.singleMax) {
|
|
437
|
-
const pending = {
|
|
438
|
-
id: requestId,
|
|
439
|
-
to,
|
|
440
|
-
amount,
|
|
441
|
-
reason,
|
|
442
|
-
requester,
|
|
443
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
444
|
-
status: "pending"
|
|
445
|
-
};
|
|
446
|
-
this.pendingTransfers.set(requestId, pending);
|
|
447
|
-
await this.auditLog.log({
|
|
448
|
-
action: "transfer_request",
|
|
449
|
-
request_id: requestId,
|
|
450
|
-
metadata: { pending: true, reason: "Exceeds single limit" }
|
|
451
|
-
});
|
|
452
|
-
return {
|
|
453
|
-
success: false,
|
|
454
|
-
error: `Amount ${amount} exceeds single limit ${this.limits.singleMax}. Pending approval: ${requestId}`
|
|
455
|
-
};
|
|
456
|
-
}
|
|
457
|
-
this.updateDailyTotal();
|
|
458
|
-
if (this.dailyTotal + amount > this.limits.dailyMax) {
|
|
459
|
-
const pending = {
|
|
460
|
-
id: requestId,
|
|
461
|
-
to,
|
|
462
|
-
amount,
|
|
463
|
-
reason,
|
|
464
|
-
requester,
|
|
465
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
466
|
-
status: "pending"
|
|
467
|
-
};
|
|
468
|
-
this.pendingTransfers.set(requestId, pending);
|
|
469
|
-
await this.auditLog.log({
|
|
470
|
-
action: "transfer_request",
|
|
471
|
-
request_id: requestId,
|
|
472
|
-
metadata: { pending: true, reason: "Exceeds daily limit" }
|
|
473
|
-
});
|
|
474
|
-
return {
|
|
475
|
-
success: false,
|
|
476
|
-
error: `Daily limit would be exceeded (${this.dailyTotal} + ${amount} > ${this.limits.dailyMax}). Pending approval: ${requestId}`
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
const result = await this.wallet.transfer(to, amount);
|
|
480
|
-
if (result.success) {
|
|
481
|
-
this.dailyTotal += amount;
|
|
482
|
-
await this.auditLog.log({
|
|
483
|
-
action: "transfer_executed",
|
|
484
|
-
request_id: requestId,
|
|
485
|
-
from: this.wallet.address,
|
|
486
|
-
to,
|
|
487
|
-
amount,
|
|
488
|
-
tx_hash: result.tx_hash,
|
|
489
|
-
reason,
|
|
490
|
-
requester
|
|
491
|
-
});
|
|
492
|
-
} else {
|
|
493
|
-
await this.auditLog.log({
|
|
494
|
-
action: "transfer_failed",
|
|
495
|
-
request_id: requestId,
|
|
496
|
-
metadata: { error: result.error }
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
return result;
|
|
500
|
-
}
|
|
501
|
-
/**
|
|
502
|
-
* Approve pending transfer
|
|
503
|
-
*/
|
|
504
|
-
async approve(requestId, approver) {
|
|
505
|
-
const pending = this.pendingTransfers.get(requestId);
|
|
506
|
-
if (!pending) {
|
|
507
|
-
return { success: false, error: `Pending transfer not found: ${requestId}` };
|
|
508
|
-
}
|
|
509
|
-
if (pending.status !== "pending") {
|
|
510
|
-
return { success: false, error: `Transfer already ${pending.status}` };
|
|
511
|
-
}
|
|
512
|
-
await this.auditLog.log({
|
|
513
|
-
action: "transfer_approved",
|
|
514
|
-
request_id: requestId,
|
|
515
|
-
metadata: { approver }
|
|
516
|
-
});
|
|
517
|
-
pending.status = "approved";
|
|
518
|
-
const result = await this.wallet.transfer(pending.to, pending.amount);
|
|
519
|
-
if (result.success) {
|
|
520
|
-
pending.status = "executed";
|
|
521
|
-
this.dailyTotal += pending.amount;
|
|
522
|
-
await this.auditLog.log({
|
|
523
|
-
action: "transfer_executed",
|
|
524
|
-
request_id: requestId,
|
|
525
|
-
from: this.wallet.address,
|
|
526
|
-
to: pending.to,
|
|
527
|
-
amount: pending.amount,
|
|
528
|
-
tx_hash: result.tx_hash,
|
|
529
|
-
reason: pending.reason,
|
|
530
|
-
requester: pending.requester,
|
|
531
|
-
metadata: { approved_by: approver }
|
|
532
|
-
});
|
|
533
|
-
} else {
|
|
534
|
-
await this.auditLog.log({
|
|
535
|
-
action: "transfer_failed",
|
|
536
|
-
request_id: requestId,
|
|
537
|
-
metadata: { error: result.error }
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
return result;
|
|
541
|
-
}
|
|
542
|
-
/**
|
|
543
|
-
* Reject pending transfer
|
|
544
|
-
*/
|
|
545
|
-
async reject(requestId, rejecter, reason) {
|
|
546
|
-
const pending = this.pendingTransfers.get(requestId);
|
|
547
|
-
if (!pending) {
|
|
548
|
-
throw new Error(`Pending transfer not found: ${requestId}`);
|
|
549
|
-
}
|
|
550
|
-
pending.status = "rejected";
|
|
551
|
-
await this.auditLog.log({
|
|
552
|
-
action: "transfer_rejected",
|
|
553
|
-
request_id: requestId,
|
|
554
|
-
metadata: { rejected_by: rejecter, reason }
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
/**
|
|
558
|
-
* Add to whitelist
|
|
559
|
-
*/
|
|
560
|
-
async addToWhitelist(address, addedBy) {
|
|
561
|
-
const addr = address.toLowerCase();
|
|
562
|
-
this.whitelist.add(addr);
|
|
563
|
-
await this.auditLog.log({
|
|
564
|
-
action: "whitelist_add",
|
|
565
|
-
request_id: `wl_${Date.now()}`,
|
|
566
|
-
to: address,
|
|
567
|
-
metadata: { added_by: addedBy }
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
/**
|
|
571
|
-
* Remove from whitelist
|
|
572
|
-
*/
|
|
573
|
-
async removeFromWhitelist(address, removedBy) {
|
|
574
|
-
const addr = address.toLowerCase();
|
|
575
|
-
this.whitelist.delete(addr);
|
|
576
|
-
await this.auditLog.log({
|
|
577
|
-
action: "whitelist_remove",
|
|
578
|
-
request_id: `wl_${Date.now()}`,
|
|
579
|
-
to: address,
|
|
580
|
-
metadata: { removed_by: removedBy }
|
|
581
|
-
});
|
|
582
|
-
}
|
|
583
|
-
/**
|
|
584
|
-
* Check if address is whitelisted
|
|
585
|
-
*/
|
|
586
|
-
isWhitelisted(address) {
|
|
587
|
-
return this.whitelist.has(address.toLowerCase());
|
|
588
|
-
}
|
|
589
|
-
/**
|
|
590
|
-
* Get pending transfers list
|
|
591
|
-
*/
|
|
592
|
-
getPendingTransfers() {
|
|
593
|
-
return Array.from(this.pendingTransfers.values()).filter((p) => p.status === "pending");
|
|
594
|
-
}
|
|
595
|
-
/**
|
|
596
|
-
* Get current limit config
|
|
597
|
-
*/
|
|
598
|
-
getLimits() {
|
|
599
|
-
return { ...this.limits };
|
|
600
|
-
}
|
|
601
|
-
/**
|
|
602
|
-
* Get daily used amount
|
|
603
|
-
*/
|
|
604
|
-
getDailyUsed() {
|
|
605
|
-
this.updateDailyTotal();
|
|
606
|
-
return this.dailyTotal;
|
|
607
|
-
}
|
|
608
|
-
/**
|
|
609
|
-
* Update daily limit counter
|
|
610
|
-
*/
|
|
611
|
-
updateDailyTotal() {
|
|
612
|
-
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
613
|
-
if (this.dailyDate !== today) {
|
|
614
|
-
this.dailyDate = today;
|
|
615
|
-
this.dailyTotal = 0;
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
};
|
|
619
|
-
|
|
620
202
|
// src/wallet/createWallet.ts
|
|
621
203
|
var import_ethers2 = require("ethers");
|
|
622
204
|
var import_fs = require("fs");
|
|
@@ -718,625 +300,27 @@ function loadWallet(options = {}) {
|
|
|
718
300
|
}
|
|
719
301
|
}
|
|
720
302
|
function getWalletAddress(storagePath) {
|
|
721
|
-
const
|
|
722
|
-
if (!(0, import_fs.existsSync)(
|
|
303
|
+
const path = storagePath || (0, import_path.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
|
|
304
|
+
if (!(0, import_fs.existsSync)(path)) {
|
|
723
305
|
return null;
|
|
724
306
|
}
|
|
725
307
|
try {
|
|
726
|
-
const data = JSON.parse((0, import_fs.readFileSync)(
|
|
308
|
+
const data = JSON.parse((0, import_fs.readFileSync)(path, "utf8"));
|
|
727
309
|
return data.address;
|
|
728
310
|
} catch {
|
|
729
311
|
return null;
|
|
730
312
|
}
|
|
731
313
|
}
|
|
732
314
|
function walletExists(storagePath) {
|
|
733
|
-
const
|
|
734
|
-
return (0, import_fs.existsSync)(
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
// src/wallet/PermitWallet.ts
|
|
738
|
-
var import_ethers3 = require("ethers");
|
|
739
|
-
var PERMIT_ABI = [
|
|
740
|
-
...ERC20_ABI,
|
|
741
|
-
"function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
|
|
742
|
-
"function transferFrom(address from, address to, uint256 amount) returns (bool)",
|
|
743
|
-
"function allowance(address owner, address spender) view returns (uint256)"
|
|
744
|
-
];
|
|
745
|
-
var PermitWallet = class {
|
|
746
|
-
chain;
|
|
747
|
-
chainConfig;
|
|
748
|
-
address;
|
|
749
|
-
wallet;
|
|
750
|
-
provider;
|
|
751
|
-
usdcContract;
|
|
752
|
-
constructor(config = {}) {
|
|
753
|
-
this.chain = config.chain || "base_sepolia";
|
|
754
|
-
this.chainConfig = getChain(this.chain);
|
|
755
|
-
let privateKey = config.privateKey || process.env.PAYMENT_AGENT_PRIVATE_KEY;
|
|
756
|
-
if (!privateKey && config.walletPath) {
|
|
757
|
-
const loaded = loadWallet({
|
|
758
|
-
storagePath: config.walletPath,
|
|
759
|
-
password: config.walletPassword
|
|
760
|
-
});
|
|
761
|
-
if (!loaded.success || !loaded.privateKey) {
|
|
762
|
-
throw new Error(loaded.error || "Failed to load wallet");
|
|
763
|
-
}
|
|
764
|
-
privateKey = loaded.privateKey;
|
|
765
|
-
}
|
|
766
|
-
if (!privateKey) {
|
|
767
|
-
throw new Error("privateKey is required. Set via config, env var, or walletPath.");
|
|
768
|
-
}
|
|
769
|
-
const rpcUrl = config.rpcUrl || this.chainConfig.rpc;
|
|
770
|
-
this.provider = new import_ethers3.ethers.JsonRpcProvider(rpcUrl);
|
|
771
|
-
this.wallet = new import_ethers3.ethers.Wallet(privateKey, this.provider);
|
|
772
|
-
this.address = this.wallet.address;
|
|
773
|
-
this.usdcContract = new import_ethers3.ethers.Contract(
|
|
774
|
-
this.chainConfig.usdc,
|
|
775
|
-
PERMIT_ABI,
|
|
776
|
-
this.wallet
|
|
777
|
-
);
|
|
778
|
-
}
|
|
779
|
-
/**
|
|
780
|
-
* Check if Permit is valid (current allowance)
|
|
781
|
-
*/
|
|
782
|
-
async checkPermitAllowance(owner) {
|
|
783
|
-
const allowance = await this.usdcContract.allowance(owner, this.address);
|
|
784
|
-
return (Number(allowance) / 1e6).toFixed(2);
|
|
785
|
-
}
|
|
786
|
-
/**
|
|
787
|
-
* Pay using Permit authorization
|
|
788
|
-
*
|
|
789
|
-
* Flow:
|
|
790
|
-
* 1. Call permit() to record Boss's authorization in the contract
|
|
791
|
-
* 2. Call transferFrom() to transfer from Boss's wallet to recipient
|
|
792
|
-
*
|
|
793
|
-
* @example
|
|
794
|
-
* ```typescript
|
|
795
|
-
* const wallet = new PermitWallet({ chain: 'base' });
|
|
796
|
-
*
|
|
797
|
-
* // Boss-signed permit data
|
|
798
|
-
* const permit = {
|
|
799
|
-
* owner: '0xBOSS...',
|
|
800
|
-
* spender: wallet.address,
|
|
801
|
-
* value: '10000000', // 10 USDC
|
|
802
|
-
* deadline: 1234567890,
|
|
803
|
-
* v: 27,
|
|
804
|
-
* r: '0x...',
|
|
805
|
-
* s: '0x...'
|
|
806
|
-
* };
|
|
807
|
-
*
|
|
808
|
-
* const result = await wallet.transferWithPermit({
|
|
809
|
-
* to: '0xSELLER...',
|
|
810
|
-
* amount: 3.99,
|
|
811
|
-
* permit
|
|
812
|
-
* });
|
|
813
|
-
* ```
|
|
814
|
-
*/
|
|
815
|
-
async transferWithPermit(params) {
|
|
816
|
-
const { to, amount, permit } = params;
|
|
817
|
-
try {
|
|
818
|
-
const toAddress = import_ethers3.ethers.getAddress(to);
|
|
819
|
-
const ownerAddress = import_ethers3.ethers.getAddress(permit.owner);
|
|
820
|
-
if (import_ethers3.ethers.getAddress(permit.spender).toLowerCase() !== this.address.toLowerCase()) {
|
|
821
|
-
return {
|
|
822
|
-
success: false,
|
|
823
|
-
error: `Permit spender (${permit.spender}) doesn't match wallet address (${this.address})`
|
|
824
|
-
};
|
|
825
|
-
}
|
|
826
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
827
|
-
if (permit.deadline < now) {
|
|
828
|
-
return {
|
|
829
|
-
success: false,
|
|
830
|
-
error: `Permit expired at ${new Date(permit.deadline * 1e3).toISOString()}`
|
|
831
|
-
};
|
|
832
|
-
}
|
|
833
|
-
const amountWei = BigInt(Math.floor(amount * 1e6));
|
|
834
|
-
const permitValue = BigInt(permit.value);
|
|
835
|
-
if (amountWei > permitValue) {
|
|
836
|
-
return {
|
|
837
|
-
success: false,
|
|
838
|
-
error: `Permit value (${Number(permitValue) / 1e6} USDC) < transfer amount (${amount} USDC)`
|
|
839
|
-
};
|
|
840
|
-
}
|
|
841
|
-
const currentAllowance = await this.usdcContract.allowance(ownerAddress, this.address);
|
|
842
|
-
let permitTxHash;
|
|
843
|
-
if (BigInt(currentAllowance) < amountWei) {
|
|
844
|
-
console.log("Executing permit...");
|
|
845
|
-
const permitTx = await this.usdcContract.permit(
|
|
846
|
-
ownerAddress,
|
|
847
|
-
this.address,
|
|
848
|
-
permitValue,
|
|
849
|
-
permit.deadline,
|
|
850
|
-
permit.v,
|
|
851
|
-
permit.r,
|
|
852
|
-
permit.s
|
|
853
|
-
);
|
|
854
|
-
const permitReceipt = await permitTx.wait();
|
|
855
|
-
if (permitReceipt.status !== 1) {
|
|
856
|
-
return {
|
|
857
|
-
success: false,
|
|
858
|
-
error: "Permit transaction failed",
|
|
859
|
-
permitTxHash: permitTx.hash
|
|
860
|
-
};
|
|
861
|
-
}
|
|
862
|
-
permitTxHash = permitTx.hash;
|
|
863
|
-
console.log("Permit executed:", permitTxHash);
|
|
864
|
-
}
|
|
865
|
-
console.log("Executing transferFrom...");
|
|
866
|
-
const transferTx = await this.usdcContract.transferFrom(
|
|
867
|
-
ownerAddress,
|
|
868
|
-
toAddress,
|
|
869
|
-
amountWei
|
|
870
|
-
);
|
|
871
|
-
const transferReceipt = await transferTx.wait();
|
|
872
|
-
if (transferReceipt.status === 1) {
|
|
873
|
-
return {
|
|
874
|
-
success: true,
|
|
875
|
-
tx_hash: transferTx.hash,
|
|
876
|
-
permitTxHash,
|
|
877
|
-
transferTxHash: transferTx.hash,
|
|
878
|
-
from: ownerAddress,
|
|
879
|
-
to: toAddress,
|
|
880
|
-
amount,
|
|
881
|
-
gas_used: Number(transferReceipt.gasUsed),
|
|
882
|
-
block_number: transferReceipt.blockNumber,
|
|
883
|
-
explorer_url: `${this.chainConfig.explorerTx}${transferTx.hash}`
|
|
884
|
-
};
|
|
885
|
-
} else {
|
|
886
|
-
return {
|
|
887
|
-
success: false,
|
|
888
|
-
error: "TransferFrom transaction failed",
|
|
889
|
-
tx_hash: transferTx.hash,
|
|
890
|
-
permitTxHash
|
|
891
|
-
};
|
|
892
|
-
}
|
|
893
|
-
} catch (error) {
|
|
894
|
-
const message = error.message;
|
|
895
|
-
if (message.includes("ERC20InsufficientAllowance")) {
|
|
896
|
-
return {
|
|
897
|
-
success: false,
|
|
898
|
-
error: "Insufficient allowance. Permit may have been used or expired."
|
|
899
|
-
};
|
|
900
|
-
}
|
|
901
|
-
if (message.includes("ERC20InsufficientBalance")) {
|
|
902
|
-
return {
|
|
903
|
-
success: false,
|
|
904
|
-
error: "Boss wallet has insufficient USDC balance."
|
|
905
|
-
};
|
|
906
|
-
}
|
|
907
|
-
if (message.includes("InvalidSignature") || message.includes("invalid signature")) {
|
|
908
|
-
return {
|
|
909
|
-
success: false,
|
|
910
|
-
error: "Invalid permit signature. Ask Boss to re-sign."
|
|
911
|
-
};
|
|
912
|
-
}
|
|
913
|
-
return {
|
|
914
|
-
success: false,
|
|
915
|
-
error: message
|
|
916
|
-
};
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
/**
|
|
920
|
-
* Get ETH balance (for gas)
|
|
921
|
-
*/
|
|
922
|
-
async getGasBalance() {
|
|
923
|
-
const balance = await this.provider.getBalance(this.address);
|
|
924
|
-
return import_ethers3.ethers.formatEther(balance);
|
|
925
|
-
}
|
|
926
|
-
/**
|
|
927
|
-
* Check if there's enough gas
|
|
928
|
-
*/
|
|
929
|
-
async hasEnoughGas(minEth = 1e-3) {
|
|
930
|
-
const balance = await this.getGasBalance();
|
|
931
|
-
return parseFloat(balance) >= minEth;
|
|
932
|
-
}
|
|
933
|
-
};
|
|
934
|
-
function formatPermitRequest(params) {
|
|
935
|
-
const { agentAddress, amount, deadlineHours = 24, chain = "base", reason } = params;
|
|
936
|
-
const chainConfig = getChain(chain);
|
|
937
|
-
const deadline = Math.floor(Date.now() / 1e3) + deadlineHours * 3600;
|
|
938
|
-
const value = BigInt(Math.floor(amount * 1e6)).toString();
|
|
939
|
-
return `\u{1F510} **USDC Spending Allowance Request**
|
|
940
|
-
|
|
941
|
-
${reason ? `**Purpose:** ${reason}
|
|
942
|
-
` : ""}
|
|
943
|
-
**Authorization Details:**
|
|
944
|
-
- Authorized address (Agent): \`${agentAddress}\`
|
|
945
|
-
- Amount: ${amount} USDC
|
|
946
|
-
- Valid for: ${deadlineHours} hours
|
|
947
|
-
- Chain: ${chainConfig.name}
|
|
948
|
-
|
|
949
|
-
**Please sign the following EIP-2612 Permit with your wallet:**
|
|
950
|
-
|
|
951
|
-
\`\`\`json
|
|
952
|
-
{
|
|
953
|
-
"types": {
|
|
954
|
-
"Permit": [
|
|
955
|
-
{ "name": "owner", "type": "address" },
|
|
956
|
-
{ "name": "spender", "type": "address" },
|
|
957
|
-
{ "name": "value", "type": "uint256" },
|
|
958
|
-
{ "name": "nonce", "type": "uint256" },
|
|
959
|
-
{ "name": "deadline", "type": "uint256" }
|
|
960
|
-
]
|
|
961
|
-
},
|
|
962
|
-
"primaryType": "Permit",
|
|
963
|
-
"domain": {
|
|
964
|
-
"name": "USD Coin",
|
|
965
|
-
"version": "2",
|
|
966
|
-
"chainId": ${chainConfig.chainId},
|
|
967
|
-
"verifyingContract": "${chainConfig.usdc}"
|
|
968
|
-
},
|
|
969
|
-
"message": {
|
|
970
|
-
"owner": "<YOUR_WALLET_ADDRESS>",
|
|
971
|
-
"spender": "${agentAddress}",
|
|
972
|
-
"value": "${value}",
|
|
973
|
-
"nonce": "<GET_FROM_CONTRACT>",
|
|
974
|
-
"deadline": ${deadline}
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
\`\`\`
|
|
978
|
-
|
|
979
|
-
After signing, send { v, r, s, deadline } to the Agent.
|
|
980
|
-
|
|
981
|
-
\u26A0\uFE0F Note: This authorization only allows the Agent to spend up to ${amount} USDC from your wallet. Your private key is never exposed.`;
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
// src/wallet/signPermit.ts
|
|
985
|
-
var import_ethers4 = require("ethers");
|
|
986
|
-
async function signPermit(config, params) {
|
|
987
|
-
const chain = config.chain || "base";
|
|
988
|
-
const chainConfig = getChain(chain);
|
|
989
|
-
const privateKey = config.privateKey || process.env.PAYMENT_AGENT_PRIVATE_KEY;
|
|
990
|
-
if (!privateKey) {
|
|
991
|
-
throw new Error("privateKey is required");
|
|
992
|
-
}
|
|
993
|
-
const rpcUrl = config.rpcUrl || chainConfig.rpc;
|
|
994
|
-
const provider = new import_ethers4.ethers.JsonRpcProvider(rpcUrl);
|
|
995
|
-
const wallet = new import_ethers4.ethers.Wallet(privateKey, provider);
|
|
996
|
-
const usdcContract = new import_ethers4.ethers.Contract(chainConfig.usdc, ERC20_ABI, provider);
|
|
997
|
-
const nonce = Number(await usdcContract.nonces(wallet.address));
|
|
998
|
-
let deadline;
|
|
999
|
-
if (!params.deadline) {
|
|
1000
|
-
deadline = Math.floor(Date.now() / 1e3) + 30 * 60;
|
|
1001
|
-
} else if (params.deadline < 1e6) {
|
|
1002
|
-
deadline = Math.floor(Date.now() / 1e3) + params.deadline * 60;
|
|
1003
|
-
} else {
|
|
1004
|
-
deadline = params.deadline;
|
|
1005
|
-
}
|
|
1006
|
-
const value = BigInt(Math.floor(params.amount * 1e6)).toString();
|
|
1007
|
-
const domain = {
|
|
1008
|
-
name: "USD Coin",
|
|
1009
|
-
version: "2",
|
|
1010
|
-
chainId: chainConfig.chainId,
|
|
1011
|
-
verifyingContract: chainConfig.usdc
|
|
1012
|
-
};
|
|
1013
|
-
const types = {
|
|
1014
|
-
Permit: [
|
|
1015
|
-
{ name: "owner", type: "address" },
|
|
1016
|
-
{ name: "spender", type: "address" },
|
|
1017
|
-
{ name: "value", type: "uint256" },
|
|
1018
|
-
{ name: "nonce", type: "uint256" },
|
|
1019
|
-
{ name: "deadline", type: "uint256" }
|
|
1020
|
-
]
|
|
1021
|
-
};
|
|
1022
|
-
const message = {
|
|
1023
|
-
owner: wallet.address,
|
|
1024
|
-
spender: params.spender,
|
|
1025
|
-
value,
|
|
1026
|
-
nonce,
|
|
1027
|
-
deadline
|
|
1028
|
-
};
|
|
1029
|
-
const signature = await wallet.signTypedData(domain, types, message);
|
|
1030
|
-
const sig = import_ethers4.ethers.Signature.from(signature);
|
|
1031
|
-
return {
|
|
1032
|
-
owner: wallet.address,
|
|
1033
|
-
spender: params.spender,
|
|
1034
|
-
value,
|
|
1035
|
-
deadline,
|
|
1036
|
-
nonce,
|
|
1037
|
-
v: sig.v,
|
|
1038
|
-
r: sig.r,
|
|
1039
|
-
s: sig.s
|
|
1040
|
-
};
|
|
1041
|
-
}
|
|
1042
|
-
var PermitSigner = class {
|
|
1043
|
-
config;
|
|
1044
|
-
constructor(config) {
|
|
1045
|
-
this.config = config;
|
|
1046
|
-
}
|
|
1047
|
-
async sign(params) {
|
|
1048
|
-
return signPermit(this.config, params);
|
|
1049
|
-
}
|
|
1050
|
-
};
|
|
1051
|
-
|
|
1052
|
-
// src/wallet/AllowanceWallet.ts
|
|
1053
|
-
var import_ethers5 = require("ethers");
|
|
1054
|
-
var PERMIT_ABI2 = [
|
|
1055
|
-
...ERC20_ABI,
|
|
1056
|
-
"function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
|
|
1057
|
-
"function transferFrom(address from, address to, uint256 amount) returns (bool)",
|
|
1058
|
-
"function allowance(address owner, address spender) view returns (uint256)",
|
|
1059
|
-
"function nonces(address owner) view returns (uint256)"
|
|
1060
|
-
];
|
|
1061
|
-
var AllowanceWallet = class {
|
|
1062
|
-
chain;
|
|
1063
|
-
chainConfig;
|
|
1064
|
-
address;
|
|
1065
|
-
// Agent's address
|
|
1066
|
-
wallet;
|
|
1067
|
-
provider;
|
|
1068
|
-
usdcContract;
|
|
1069
|
-
/** Stored permits from owners */
|
|
1070
|
-
permits = /* @__PURE__ */ new Map();
|
|
1071
|
-
constructor(config) {
|
|
1072
|
-
this.chain = config.chain || "base_sepolia";
|
|
1073
|
-
this.chainConfig = getChain(this.chain);
|
|
1074
|
-
const rpcUrl = config.rpcUrl || this.chainConfig.rpc;
|
|
1075
|
-
this.provider = new import_ethers5.ethers.JsonRpcProvider(rpcUrl);
|
|
1076
|
-
this.wallet = new import_ethers5.ethers.Wallet(config.privateKey, this.provider);
|
|
1077
|
-
this.address = this.wallet.address;
|
|
1078
|
-
this.usdcContract = new import_ethers5.ethers.Contract(
|
|
1079
|
-
this.chainConfig.usdc,
|
|
1080
|
-
PERMIT_ABI2,
|
|
1081
|
-
this.wallet
|
|
1082
|
-
);
|
|
1083
|
-
}
|
|
1084
|
-
/**
|
|
1085
|
-
* Store a Permit received from Owner
|
|
1086
|
-
* Call this when Owner sends you a signed Permit
|
|
1087
|
-
*/
|
|
1088
|
-
storePermit(permit) {
|
|
1089
|
-
const ownerLower = permit.owner.toLowerCase();
|
|
1090
|
-
this.permits.set(ownerLower, permit);
|
|
1091
|
-
}
|
|
1092
|
-
/**
|
|
1093
|
-
* Get stored Permit for an owner
|
|
1094
|
-
*/
|
|
1095
|
-
getPermit(owner) {
|
|
1096
|
-
return this.permits.get(owner.toLowerCase());
|
|
1097
|
-
}
|
|
1098
|
-
/**
|
|
1099
|
-
* Check allowance status with an owner
|
|
1100
|
-
*/
|
|
1101
|
-
async checkAllowance(owner) {
|
|
1102
|
-
const ownerAddress = import_ethers5.ethers.getAddress(owner);
|
|
1103
|
-
const [allowance, ownerBalance, agentGasBalance] = await Promise.all([
|
|
1104
|
-
this.usdcContract.allowance(ownerAddress, this.address),
|
|
1105
|
-
this.usdcContract.balanceOf(ownerAddress),
|
|
1106
|
-
this.provider.getBalance(this.address)
|
|
1107
|
-
]);
|
|
1108
|
-
const allowanceNum = Number(allowance) / 1e6;
|
|
1109
|
-
const hasGas = Number(import_ethers5.ethers.formatEther(agentGasBalance)) >= 1e-4;
|
|
1110
|
-
return {
|
|
1111
|
-
owner: ownerAddress,
|
|
1112
|
-
agent: this.address,
|
|
1113
|
-
allowance: allowanceNum.toFixed(2),
|
|
1114
|
-
ownerBalance: (Number(ownerBalance) / 1e6).toFixed(2),
|
|
1115
|
-
agentGasBalance: import_ethers5.ethers.formatEther(agentGasBalance),
|
|
1116
|
-
canSpend: allowanceNum > 0 && hasGas,
|
|
1117
|
-
chain: this.chainConfig.name
|
|
1118
|
-
};
|
|
1119
|
-
}
|
|
1120
|
-
/**
|
|
1121
|
-
* Spend from Owner's wallet using Permit allowance
|
|
1122
|
-
*
|
|
1123
|
-
* @example
|
|
1124
|
-
* ```typescript
|
|
1125
|
-
* const agent = new AllowanceWallet({
|
|
1126
|
-
* chain: 'base',
|
|
1127
|
-
* privateKey: process.env.AGENT_KEY // Only needs gas
|
|
1128
|
-
* });
|
|
1129
|
-
*
|
|
1130
|
-
* // Owner gave us a Permit
|
|
1131
|
-
* agent.storePermit(ownerPermit);
|
|
1132
|
-
*
|
|
1133
|
-
* // Spend to pay for a service
|
|
1134
|
-
* const result = await agent.spend({
|
|
1135
|
-
* to: '0xServiceProvider...',
|
|
1136
|
-
* amount: 2.99,
|
|
1137
|
-
* });
|
|
1138
|
-
* ```
|
|
1139
|
-
*/
|
|
1140
|
-
async spend(params) {
|
|
1141
|
-
const { to, amount, permit } = params;
|
|
1142
|
-
try {
|
|
1143
|
-
const toAddress = import_ethers5.ethers.getAddress(to);
|
|
1144
|
-
const amountWei = BigInt(Math.floor(amount * 1e6));
|
|
1145
|
-
let ownerPermit = permit;
|
|
1146
|
-
let ownerAddress;
|
|
1147
|
-
if (ownerPermit) {
|
|
1148
|
-
ownerAddress = import_ethers5.ethers.getAddress(ownerPermit.owner);
|
|
1149
|
-
this.storePermit(ownerPermit);
|
|
1150
|
-
} else {
|
|
1151
|
-
for (const [owner, p] of this.permits) {
|
|
1152
|
-
const allowance = await this.usdcContract.allowance(owner, this.address);
|
|
1153
|
-
if (BigInt(allowance) >= amountWei) {
|
|
1154
|
-
ownerAddress = import_ethers5.ethers.getAddress(owner);
|
|
1155
|
-
ownerPermit = p;
|
|
1156
|
-
break;
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
if (!ownerPermit) {
|
|
1160
|
-
return {
|
|
1161
|
-
success: false,
|
|
1162
|
-
error: "No valid permit found. Ask Owner to sign a Permit first.",
|
|
1163
|
-
from: "",
|
|
1164
|
-
to: toAddress,
|
|
1165
|
-
amount
|
|
1166
|
-
};
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
const currentAllowance = await this.usdcContract.allowance(ownerAddress, this.address);
|
|
1170
|
-
if (BigInt(currentAllowance) < amountWei) {
|
|
1171
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
1172
|
-
if (ownerPermit.deadline < now) {
|
|
1173
|
-
return {
|
|
1174
|
-
success: false,
|
|
1175
|
-
error: `Permit expired at ${new Date(ownerPermit.deadline * 1e3).toISOString()}. Ask Owner for a new Permit.`,
|
|
1176
|
-
from: ownerAddress,
|
|
1177
|
-
to: toAddress,
|
|
1178
|
-
amount
|
|
1179
|
-
};
|
|
1180
|
-
}
|
|
1181
|
-
const currentNonce = await this.usdcContract.nonces(ownerAddress);
|
|
1182
|
-
if (Number(currentNonce) !== ownerPermit.nonce) {
|
|
1183
|
-
return {
|
|
1184
|
-
success: false,
|
|
1185
|
-
error: `Permit nonce mismatch (expected ${ownerPermit.nonce}, got ${currentNonce}). Owner may have used this permit or signed a new one.`,
|
|
1186
|
-
from: ownerAddress,
|
|
1187
|
-
to: toAddress,
|
|
1188
|
-
amount
|
|
1189
|
-
};
|
|
1190
|
-
}
|
|
1191
|
-
console.log("[AllowanceWallet] Submitting permit on-chain...");
|
|
1192
|
-
const permitTx = await this.usdcContract.permit(
|
|
1193
|
-
ownerAddress,
|
|
1194
|
-
this.address,
|
|
1195
|
-
ownerPermit.value,
|
|
1196
|
-
ownerPermit.deadline,
|
|
1197
|
-
ownerPermit.v,
|
|
1198
|
-
ownerPermit.r,
|
|
1199
|
-
ownerPermit.s
|
|
1200
|
-
);
|
|
1201
|
-
await permitTx.wait();
|
|
1202
|
-
console.log("[AllowanceWallet] Permit submitted:", permitTx.hash);
|
|
1203
|
-
}
|
|
1204
|
-
console.log("[AllowanceWallet] Executing transferFrom...");
|
|
1205
|
-
const tx = await this.usdcContract.transferFrom(
|
|
1206
|
-
ownerAddress,
|
|
1207
|
-
toAddress,
|
|
1208
|
-
amountWei
|
|
1209
|
-
);
|
|
1210
|
-
const receipt = await tx.wait();
|
|
1211
|
-
const newAllowance = await this.usdcContract.allowance(ownerAddress, this.address);
|
|
1212
|
-
return {
|
|
1213
|
-
success: true,
|
|
1214
|
-
txHash: tx.hash,
|
|
1215
|
-
from: ownerAddress,
|
|
1216
|
-
to: toAddress,
|
|
1217
|
-
amount,
|
|
1218
|
-
remainingAllowance: (Number(newAllowance) / 1e6).toFixed(2),
|
|
1219
|
-
explorerUrl: `${this.chainConfig.explorerTx}${tx.hash}`
|
|
1220
|
-
};
|
|
1221
|
-
} catch (error) {
|
|
1222
|
-
const message = error.message;
|
|
1223
|
-
if (message.includes("ERC20InsufficientAllowance")) {
|
|
1224
|
-
return {
|
|
1225
|
-
success: false,
|
|
1226
|
-
error: "Insufficient allowance. Ask Owner to sign a new Permit with higher amount.",
|
|
1227
|
-
from: "",
|
|
1228
|
-
to,
|
|
1229
|
-
amount
|
|
1230
|
-
};
|
|
1231
|
-
}
|
|
1232
|
-
if (message.includes("ERC20InsufficientBalance")) {
|
|
1233
|
-
return {
|
|
1234
|
-
success: false,
|
|
1235
|
-
error: "Owner's wallet has insufficient USDC balance.",
|
|
1236
|
-
from: "",
|
|
1237
|
-
to,
|
|
1238
|
-
amount
|
|
1239
|
-
};
|
|
1240
|
-
}
|
|
1241
|
-
return {
|
|
1242
|
-
success: false,
|
|
1243
|
-
error: message,
|
|
1244
|
-
from: "",
|
|
1245
|
-
to,
|
|
1246
|
-
amount
|
|
1247
|
-
};
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
/**
|
|
1251
|
-
* Get Agent's gas balance (ETH)
|
|
1252
|
-
*/
|
|
1253
|
-
async getGasBalance() {
|
|
1254
|
-
const balance = await this.provider.getBalance(this.address);
|
|
1255
|
-
return import_ethers5.ethers.formatEther(balance);
|
|
1256
|
-
}
|
|
1257
|
-
};
|
|
1258
|
-
function generatePermitInstructions(params) {
|
|
1259
|
-
const { ownerAddress, agentAddress, amount, deadlineHours = 24, chain = "base" } = params;
|
|
1260
|
-
const chainConfig = getChain(chain);
|
|
1261
|
-
const deadline = Math.floor(Date.now() / 1e3) + deadlineHours * 3600;
|
|
1262
|
-
const value = BigInt(Math.floor(amount * 1e6)).toString();
|
|
1263
|
-
const eip712Domain = {
|
|
1264
|
-
name: "USD Coin",
|
|
1265
|
-
version: "2",
|
|
1266
|
-
chainId: chainConfig.chainId,
|
|
1267
|
-
verifyingContract: chainConfig.usdc
|
|
1268
|
-
};
|
|
1269
|
-
const typedData = {
|
|
1270
|
-
types: {
|
|
1271
|
-
EIP712Domain: [
|
|
1272
|
-
{ name: "name", type: "string" },
|
|
1273
|
-
{ name: "version", type: "string" },
|
|
1274
|
-
{ name: "chainId", type: "uint256" },
|
|
1275
|
-
{ name: "verifyingContract", type: "address" }
|
|
1276
|
-
],
|
|
1277
|
-
Permit: [
|
|
1278
|
-
{ name: "owner", type: "address" },
|
|
1279
|
-
{ name: "spender", type: "address" },
|
|
1280
|
-
{ name: "value", type: "uint256" },
|
|
1281
|
-
{ name: "nonce", type: "uint256" },
|
|
1282
|
-
{ name: "deadline", type: "uint256" }
|
|
1283
|
-
]
|
|
1284
|
-
},
|
|
1285
|
-
primaryType: "Permit",
|
|
1286
|
-
domain: eip712Domain,
|
|
1287
|
-
message: {
|
|
1288
|
-
owner: ownerAddress,
|
|
1289
|
-
spender: agentAddress,
|
|
1290
|
-
value,
|
|
1291
|
-
nonce: "<GET_FROM_USDC_CONTRACT>",
|
|
1292
|
-
// Owner needs to query this
|
|
1293
|
-
deadline
|
|
1294
|
-
}
|
|
1295
|
-
};
|
|
1296
|
-
const instructions = `
|
|
1297
|
-
\u{1F510} **Grant USDC Spending Allowance to Your Agent**
|
|
1298
|
-
|
|
1299
|
-
Your Agent (${agentAddress}) is requesting permission to spend up to ${amount} USDC from your wallet.
|
|
1300
|
-
|
|
1301
|
-
**What this does:**
|
|
1302
|
-
- Allows your Agent to pay for services on your behalf
|
|
1303
|
-
- Your USDC stays in YOUR wallet until spent
|
|
1304
|
-
- Agent can only spend up to the authorized amount
|
|
1305
|
-
- Expires in ${deadlineHours} hours
|
|
1306
|
-
- You can revoke anytime by moving your USDC
|
|
1307
|
-
|
|
1308
|
-
**How to sign (MetaMask / any web3 wallet):**
|
|
1309
|
-
|
|
1310
|
-
1. Go to https://etherscan.io/address/${chainConfig.usdc}#readContract
|
|
1311
|
-
2. Query \`nonces(${ownerAddress})\` to get your current nonce
|
|
1312
|
-
3. Use eth_signTypedData_v4 with the data below (replace nonce)
|
|
1313
|
-
4. Send the signature {v, r, s, deadline, nonce} to your Agent
|
|
1314
|
-
|
|
1315
|
-
**Chain:** ${chainConfig.name}
|
|
1316
|
-
**USDC Contract:** ${chainConfig.usdc}
|
|
1317
|
-
|
|
1318
|
-
**EIP-712 Typed Data:**
|
|
1319
|
-
\`\`\`json
|
|
1320
|
-
${JSON.stringify(typedData, null, 2)}
|
|
1321
|
-
\`\`\`
|
|
1322
|
-
|
|
1323
|
-
\u26A0\uFE0F Never share your private key. This signature only authorizes spending, not wallet access.
|
|
1324
|
-
`;
|
|
1325
|
-
return { instructions, typedData, eip712Domain };
|
|
315
|
+
const path = storagePath || (0, import_path.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
|
|
316
|
+
return (0, import_fs.existsSync)(path);
|
|
1326
317
|
}
|
|
1327
318
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1328
319
|
0 && (module.exports = {
|
|
1329
|
-
AllowanceWallet,
|
|
1330
|
-
PermitSigner,
|
|
1331
|
-
PermitWallet,
|
|
1332
|
-
SecureWallet,
|
|
1333
320
|
Wallet,
|
|
1334
321
|
createWallet,
|
|
1335
|
-
formatPermitRequest,
|
|
1336
|
-
generatePermitInstructions,
|
|
1337
322
|
getWalletAddress,
|
|
1338
323
|
loadWallet,
|
|
1339
|
-
signPermit,
|
|
1340
324
|
walletExists
|
|
1341
325
|
});
|
|
1342
326
|
//# sourceMappingURL=index.js.map
|