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