moltspay 0.2.3 → 0.2.5

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/dist/index.mjs CHANGED
@@ -280,8 +280,317 @@ After payment, reply with your tx hash:
280
280
  }
281
281
  };
282
282
 
283
- // src/wallet/Wallet.ts
283
+ // src/agent/AgentWallet.ts
284
284
  import { ethers as ethers2 } from "ethers";
285
+ import * as fs from "fs";
286
+ import * as path from "path";
287
+ var PERMIT_ABI = [
288
+ ...ERC20_ABI,
289
+ "function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
290
+ "function transferFrom(address from, address to, uint256 amount) returns (bool)",
291
+ "function allowance(address owner, address spender) view returns (uint256)",
292
+ "function nonces(address owner) view returns (uint256)"
293
+ ];
294
+ var AgentWallet = class {
295
+ chain;
296
+ chainConfig;
297
+ storageDir;
298
+ _address = null;
299
+ _privateKey = null;
300
+ _wallet = null;
301
+ _provider = null;
302
+ _permits = /* @__PURE__ */ new Map();
303
+ constructor(config = {}) {
304
+ this.chain = config.chain || "base";
305
+ this.chainConfig = getChain(this.chain);
306
+ this.storageDir = config.storageDir || this.getDefaultStorageDir();
307
+ this.ensureInitialized();
308
+ }
309
+ getDefaultStorageDir() {
310
+ const home = process.env.HOME || process.env.USERPROFILE || ".";
311
+ return path.join(home, ".moltspay");
312
+ }
313
+ getWalletPath() {
314
+ return path.join(this.storageDir, "wallet.json");
315
+ }
316
+ getPermitsPath() {
317
+ return path.join(this.storageDir, "permits.json");
318
+ }
319
+ /**
320
+ * Auto-initialize: create wallet if not exists
321
+ * This is called automatically in constructor
322
+ */
323
+ ensureInitialized() {
324
+ if (!fs.existsSync(this.storageDir)) {
325
+ fs.mkdirSync(this.storageDir, { recursive: true });
326
+ }
327
+ const walletPath = this.getWalletPath();
328
+ if (fs.existsSync(walletPath)) {
329
+ const data = JSON.parse(fs.readFileSync(walletPath, "utf-8"));
330
+ this._address = data.address;
331
+ this._privateKey = data.privateKey;
332
+ } else {
333
+ const wallet = ethers2.Wallet.createRandom();
334
+ this._address = wallet.address;
335
+ this._privateKey = wallet.privateKey;
336
+ fs.writeFileSync(walletPath, JSON.stringify({
337
+ address: this._address,
338
+ privateKey: this._privateKey,
339
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
340
+ chain: this.chain
341
+ }, null, 2), { mode: 384 });
342
+ }
343
+ const permitsPath = this.getPermitsPath();
344
+ if (fs.existsSync(permitsPath)) {
345
+ const permits = JSON.parse(fs.readFileSync(permitsPath, "utf-8"));
346
+ for (const [owner, permit] of Object.entries(permits)) {
347
+ this._permits.set(owner.toLowerCase(), permit);
348
+ }
349
+ }
350
+ }
351
+ /** Agent's address (auto-generated on first use) */
352
+ get address() {
353
+ return this._address;
354
+ }
355
+ get wallet() {
356
+ if (!this._wallet) {
357
+ this._wallet = new ethers2.Wallet(this._privateKey, this.provider);
358
+ }
359
+ return this._wallet;
360
+ }
361
+ get provider() {
362
+ if (!this._provider) {
363
+ this._provider = new ethers2.JsonRpcProvider(this.chainConfig.rpc);
364
+ }
365
+ return this._provider;
366
+ }
367
+ /**
368
+ * Store a Permit from Owner
369
+ */
370
+ storePermit(permit) {
371
+ const ownerLower = permit.owner.toLowerCase();
372
+ this._permits.set(ownerLower, permit);
373
+ const permitsPath = this.getPermitsPath();
374
+ const permits = {};
375
+ for (const [owner, p] of this._permits) {
376
+ permits[owner] = p;
377
+ }
378
+ fs.writeFileSync(permitsPath, JSON.stringify(permits, null, 2));
379
+ }
380
+ /**
381
+ * Get stored permit for an owner
382
+ */
383
+ getPermit(owner) {
384
+ return this._permits.get(owner.toLowerCase());
385
+ }
386
+ /**
387
+ * Check allowance from an owner
388
+ */
389
+ async checkAllowance(owner) {
390
+ const usdcContract = new ethers2.Contract(
391
+ this.chainConfig.usdc,
392
+ PERMIT_ABI,
393
+ this.provider
394
+ );
395
+ const ownerAddress = ethers2.getAddress(owner);
396
+ const [allowance, balance] = await Promise.all([
397
+ usdcContract.allowance(ownerAddress, this.address),
398
+ usdcContract.balanceOf(ownerAddress)
399
+ ]);
400
+ return {
401
+ allowance: (Number(allowance) / 1e6).toFixed(2),
402
+ ownerBalance: (Number(balance) / 1e6).toFixed(2),
403
+ canSpend: Number(allowance) > 0
404
+ };
405
+ }
406
+ /**
407
+ * Spend USDC from Owner's wallet
408
+ *
409
+ * @param to - Recipient (service provider)
410
+ * @param amount - Amount in USDC
411
+ * @param permit - Optional, uses stored permit if not provided
412
+ */
413
+ async spend(to, amount, permit) {
414
+ const toAddress = ethers2.getAddress(to);
415
+ const amountWei = BigInt(Math.floor(amount * 1e6));
416
+ let usePermit = permit;
417
+ let ownerAddress;
418
+ if (usePermit) {
419
+ ownerAddress = ethers2.getAddress(usePermit.owner);
420
+ this.storePermit(usePermit);
421
+ } else {
422
+ const usdcContract = new ethers2.Contract(
423
+ this.chainConfig.usdc,
424
+ PERMIT_ABI,
425
+ this.provider
426
+ );
427
+ for (const [owner, p] of this._permits) {
428
+ const allowance = await usdcContract.allowance(owner, this.address);
429
+ if (BigInt(allowance) >= amountWei) {
430
+ ownerAddress = ethers2.getAddress(owner);
431
+ usePermit = p;
432
+ break;
433
+ }
434
+ }
435
+ if (!usePermit) {
436
+ return {
437
+ success: false,
438
+ error: "No valid permit. Ask Owner to authorize spending first.",
439
+ from: "",
440
+ to: toAddress,
441
+ amount
442
+ };
443
+ }
444
+ }
445
+ try {
446
+ const usdcContract = new ethers2.Contract(
447
+ this.chainConfig.usdc,
448
+ PERMIT_ABI,
449
+ this.wallet
450
+ );
451
+ const currentAllowance = await usdcContract.allowance(ownerAddress, this.address);
452
+ if (BigInt(currentAllowance) < amountWei) {
453
+ const now = Math.floor(Date.now() / 1e3);
454
+ if (usePermit.deadline < now) {
455
+ return {
456
+ success: false,
457
+ error: "Permit expired. Ask Owner for a new authorization.",
458
+ from: ownerAddress,
459
+ to: toAddress,
460
+ amount
461
+ };
462
+ }
463
+ const permitTx = await usdcContract.permit(
464
+ ownerAddress,
465
+ this.address,
466
+ usePermit.value,
467
+ usePermit.deadline,
468
+ usePermit.v,
469
+ usePermit.r,
470
+ usePermit.s
471
+ );
472
+ await permitTx.wait();
473
+ }
474
+ const tx = await usdcContract.transferFrom(ownerAddress, toAddress, amountWei);
475
+ await tx.wait();
476
+ const newAllowance = await usdcContract.allowance(ownerAddress, this.address);
477
+ return {
478
+ success: true,
479
+ txHash: tx.hash,
480
+ from: ownerAddress,
481
+ to: toAddress,
482
+ amount,
483
+ remainingAllowance: (Number(newAllowance) / 1e6).toFixed(2),
484
+ explorerUrl: `${this.chainConfig.explorerTx}${tx.hash}`
485
+ };
486
+ } catch (error) {
487
+ return {
488
+ success: false,
489
+ error: error.message,
490
+ from: ownerAddress,
491
+ to: toAddress,
492
+ amount
493
+ };
494
+ }
495
+ }
496
+ /**
497
+ * Get gas balance (ETH needed for transactions)
498
+ */
499
+ async getGasBalance() {
500
+ const balance = await this.provider.getBalance(this.address);
501
+ return ethers2.formatEther(balance);
502
+ }
503
+ /**
504
+ * Check if agent has enough gas
505
+ */
506
+ async hasGas(minEth = 5e-4) {
507
+ const balance = await this.getGasBalance();
508
+ return parseFloat(balance) >= minEth;
509
+ }
510
+ /**
511
+ * Generate authorization request for Owner
512
+ * Owner can sign this with CLI (ethers) or MetaMask
513
+ */
514
+ async generateAuthRequest(params) {
515
+ const { ownerAddress, amount, expiresInHours = 168 } = params;
516
+ const deadline = Math.floor(Date.now() / 1e3) + expiresInHours * 3600;
517
+ const value = BigInt(Math.floor(amount * 1e6)).toString();
518
+ const usdcContract = new ethers2.Contract(
519
+ this.chainConfig.usdc,
520
+ PERMIT_ABI,
521
+ this.provider
522
+ );
523
+ const nonce = Number(await usdcContract.nonces(ownerAddress));
524
+ const domain = {
525
+ name: "USD Coin",
526
+ version: "2",
527
+ chainId: this.chainConfig.chainId,
528
+ verifyingContract: this.chainConfig.usdc
529
+ };
530
+ const types = {
531
+ Permit: [
532
+ { name: "owner", type: "address" },
533
+ { name: "spender", type: "address" },
534
+ { name: "value", type: "uint256" },
535
+ { name: "nonce", type: "uint256" },
536
+ { name: "deadline", type: "uint256" }
537
+ ]
538
+ };
539
+ const permitMessage = {
540
+ owner: ownerAddress,
541
+ spender: this.address,
542
+ value,
543
+ nonce,
544
+ deadline
545
+ };
546
+ const typedData = {
547
+ types: { ...types, EIP712Domain: [
548
+ { name: "name", type: "string" },
549
+ { name: "version", type: "string" },
550
+ { name: "chainId", type: "uint256" },
551
+ { name: "verifyingContract", type: "address" }
552
+ ] },
553
+ primaryType: "Permit",
554
+ domain,
555
+ message: permitMessage
556
+ };
557
+ const cliCommand = `npx moltspay sign-permit \\
558
+ --owner ${ownerAddress} \\
559
+ --spender ${this.address} \\
560
+ --amount ${amount} \\
561
+ --deadline ${deadline} \\
562
+ --nonce ${nonce} \\
563
+ --chain ${this.chain}`;
564
+ const message = `\u{1F510} Authorization Request
565
+
566
+ I need permission to spend up to ${amount} USDC from your wallet.
567
+
568
+ **Details:**
569
+ - Your wallet: ${ownerAddress}
570
+ - My address: ${this.address}
571
+ - Amount: ${amount} USDC
572
+ - Expires: ${new Date(deadline * 1e3).toISOString()}
573
+ - Chain: ${this.chainConfig.name}
574
+
575
+ **Option 1: Sign with CLI** (if you have the private key)
576
+ \`\`\`
577
+ ${cliCommand}
578
+ \`\`\`
579
+
580
+ **Option 2: Sign with MetaMask**
581
+ Visit: https://moltspay.vercel.app/permit?data=${encodeURIComponent(JSON.stringify(typedData))}
582
+
583
+ After signing, send me the signature (v, r, s).`;
584
+ return { message, typedData, cliCommand };
585
+ }
586
+ };
587
+ function getAgentAddress(config) {
588
+ const wallet = new AgentWallet(config);
589
+ return wallet.address;
590
+ }
591
+
592
+ // src/wallet/Wallet.ts
593
+ import { ethers as ethers3 } from "ethers";
285
594
  var Wallet = class {
286
595
  chain;
287
596
  chainConfig;
@@ -297,10 +606,10 @@ var Wallet = class {
297
606
  throw new Error("privateKey is required. Set via config or PAYMENT_AGENT_PRIVATE_KEY env var.");
298
607
  }
299
608
  const rpcUrl = config.rpcUrl || this.chainConfig.rpc;
300
- this.provider = new ethers2.JsonRpcProvider(rpcUrl);
301
- this.wallet = new ethers2.Wallet(privateKey, this.provider);
609
+ this.provider = new ethers3.JsonRpcProvider(rpcUrl);
610
+ this.wallet = new ethers3.Wallet(privateKey, this.provider);
302
611
  this.address = this.wallet.address;
303
- this.usdcContract = new ethers2.Contract(
612
+ this.usdcContract = new ethers3.Contract(
304
613
  this.chainConfig.usdc,
305
614
  ERC20_ABI,
306
615
  this.wallet
@@ -316,7 +625,7 @@ var Wallet = class {
316
625
  ]);
317
626
  return {
318
627
  address: this.address,
319
- eth: ethers2.formatEther(ethBalance),
628
+ eth: ethers3.formatEther(ethBalance),
320
629
  usdc: (Number(usdcBalance) / 1e6).toFixed(2),
321
630
  chain: this.chain
322
631
  };
@@ -326,7 +635,7 @@ var Wallet = class {
326
635
  */
327
636
  async transfer(to, amount) {
328
637
  try {
329
- to = ethers2.getAddress(to);
638
+ to = ethers3.getAddress(to);
330
639
  const amountWei = BigInt(Math.floor(amount * 1e6));
331
640
  const balance = await this.usdcContract.balanceOf(this.address);
332
641
  if (BigInt(balance) < amountWei) {
@@ -367,7 +676,7 @@ var Wallet = class {
367
676
  */
368
677
  async getEthBalance() {
369
678
  const balance = await this.provider.getBalance(this.address);
370
- return ethers2.formatEther(balance);
679
+ return ethers3.formatEther(balance);
371
680
  }
372
681
  /**
373
682
  * Get USDC balance
@@ -379,14 +688,14 @@ var Wallet = class {
379
688
  };
380
689
 
381
690
  // src/audit/AuditLog.ts
382
- import * as fs from "fs";
383
- import * as path from "path";
691
+ import * as fs2 from "fs";
692
+ import * as path2 from "path";
384
693
  import * as crypto from "crypto";
385
694
  var AuditLog = class {
386
695
  basePath;
387
696
  lastHash = "0000000000000000";
388
697
  constructor(basePath) {
389
- this.basePath = basePath || path.join(process.cwd(), "data", "audit");
698
+ this.basePath = basePath || path2.join(process.cwd(), "data", "audit");
390
699
  this.ensureDir();
391
700
  this.loadLastHash();
392
701
  }
@@ -415,7 +724,7 @@ var AuditLog = class {
415
724
  this.lastHash = entry.hash;
416
725
  const filePath = this.getFilePath(now);
417
726
  const line = JSON.stringify(entry) + "\n";
418
- fs.appendFileSync(filePath, line, "utf-8");
727
+ fs2.appendFileSync(filePath, line, "utf-8");
419
728
  return entry;
420
729
  }
421
730
  /**
@@ -423,10 +732,10 @@ var AuditLog = class {
423
732
  */
424
733
  read(date) {
425
734
  const filePath = this.getFilePath(date || /* @__PURE__ */ new Date());
426
- if (!fs.existsSync(filePath)) {
735
+ if (!fs2.existsSync(filePath)) {
427
736
  return [];
428
737
  }
429
- const content = fs.readFileSync(filePath, "utf-8");
738
+ const content = fs2.readFileSync(filePath, "utf-8");
430
739
  const lines = content.trim().split("\n").filter(Boolean);
431
740
  return lines.map((line) => JSON.parse(line));
432
741
  }
@@ -477,7 +786,7 @@ var AuditLog = class {
477
786
  */
478
787
  getFilePath(date) {
479
788
  const dateStr = date.toISOString().slice(0, 10);
480
- return path.join(this.basePath, `audit_${dateStr}.jsonl`);
789
+ return path2.join(this.basePath, `audit_${dateStr}.jsonl`);
481
790
  }
482
791
  /**
483
792
  * Calculate entry hash
@@ -515,8 +824,8 @@ var AuditLog = class {
515
824
  * Ensure directory exists
516
825
  */
517
826
  ensureDir() {
518
- if (!fs.existsSync(this.basePath)) {
519
- fs.mkdirSync(this.basePath, { recursive: true });
827
+ if (!fs2.existsSync(this.basePath)) {
828
+ fs2.mkdirSync(this.basePath, { recursive: true });
520
829
  }
521
830
  }
522
831
  };
@@ -780,11 +1089,11 @@ var SecureWallet = class {
780
1089
  };
781
1090
 
782
1091
  // src/wallet/createWallet.ts
783
- import { ethers as ethers3 } from "ethers";
784
- import { writeFileSync, readFileSync as readFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
785
- import { join as join2, dirname } from "path";
1092
+ import { ethers as ethers4 } from "ethers";
1093
+ import { writeFileSync as writeFileSync2, readFileSync as readFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync3 } from "fs";
1094
+ import { join as join3, dirname } from "path";
786
1095
  import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
787
- var DEFAULT_STORAGE_DIR = join2(process.env.HOME || "~", ".moltspay");
1096
+ var DEFAULT_STORAGE_DIR = join3(process.env.HOME || "~", ".moltspay");
788
1097
  var DEFAULT_STORAGE_FILE = "wallet.json";
789
1098
  function encryptPrivateKey(privateKey, password) {
790
1099
  const salt = randomBytes(16);
@@ -807,10 +1116,10 @@ function decryptPrivateKey(encrypted, password, iv, salt) {
807
1116
  return decrypted;
808
1117
  }
809
1118
  function createWallet(options = {}) {
810
- const storagePath = options.storagePath || join2(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
811
- if (existsSync2(storagePath) && !options.overwrite) {
1119
+ const storagePath = options.storagePath || join3(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
1120
+ if (existsSync3(storagePath) && !options.overwrite) {
812
1121
  try {
813
- const existing = JSON.parse(readFileSync2(storagePath, "utf8"));
1122
+ const existing = JSON.parse(readFileSync3(storagePath, "utf8"));
814
1123
  return {
815
1124
  success: true,
816
1125
  address: existing.address,
@@ -825,7 +1134,7 @@ function createWallet(options = {}) {
825
1134
  }
826
1135
  }
827
1136
  try {
828
- const wallet = ethers3.Wallet.createRandom();
1137
+ const wallet = ethers4.Wallet.createRandom();
829
1138
  const walletData = {
830
1139
  address: wallet.address,
831
1140
  label: options.label,
@@ -842,10 +1151,10 @@ function createWallet(options = {}) {
842
1151
  walletData.privateKey = wallet.privateKey;
843
1152
  }
844
1153
  const dir = dirname(storagePath);
845
- if (!existsSync2(dir)) {
846
- mkdirSync2(dir, { recursive: true });
1154
+ if (!existsSync3(dir)) {
1155
+ mkdirSync3(dir, { recursive: true });
847
1156
  }
848
- writeFileSync(storagePath, JSON.stringify(walletData, null, 2), { mode: 384 });
1157
+ writeFileSync2(storagePath, JSON.stringify(walletData, null, 2), { mode: 384 });
849
1158
  return {
850
1159
  success: true,
851
1160
  address: wallet.address,
@@ -860,12 +1169,12 @@ function createWallet(options = {}) {
860
1169
  }
861
1170
  }
862
1171
  function loadWallet(options = {}) {
863
- const storagePath = options.storagePath || join2(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
864
- if (!existsSync2(storagePath)) {
1172
+ const storagePath = options.storagePath || join3(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
1173
+ if (!existsSync3(storagePath)) {
865
1174
  return { success: false, error: "Wallet not found. Run createWallet() first." };
866
1175
  }
867
1176
  try {
868
- const data = JSON.parse(readFileSync2(storagePath, "utf8"));
1177
+ const data = JSON.parse(readFileSync3(storagePath, "utf8"));
869
1178
  if (data.encrypted) {
870
1179
  if (!options.password) {
871
1180
  return { success: false, error: "Wallet is encrypted. Password required." };
@@ -880,25 +1189,25 @@ function loadWallet(options = {}) {
880
1189
  }
881
1190
  }
882
1191
  function getWalletAddress(storagePath) {
883
- const path2 = storagePath || join2(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
884
- if (!existsSync2(path2)) {
1192
+ const path3 = storagePath || join3(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
1193
+ if (!existsSync3(path3)) {
885
1194
  return null;
886
1195
  }
887
1196
  try {
888
- const data = JSON.parse(readFileSync2(path2, "utf8"));
1197
+ const data = JSON.parse(readFileSync3(path3, "utf8"));
889
1198
  return data.address;
890
1199
  } catch {
891
1200
  return null;
892
1201
  }
893
1202
  }
894
1203
  function walletExists(storagePath) {
895
- const path2 = storagePath || join2(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
896
- return existsSync2(path2);
1204
+ const path3 = storagePath || join3(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
1205
+ return existsSync3(path3);
897
1206
  }
898
1207
 
899
1208
  // src/wallet/PermitWallet.ts
900
- import { ethers as ethers4 } from "ethers";
901
- var PERMIT_ABI = [
1209
+ import { ethers as ethers5 } from "ethers";
1210
+ var PERMIT_ABI2 = [
902
1211
  ...ERC20_ABI,
903
1212
  "function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
904
1213
  "function transferFrom(address from, address to, uint256 amount) returns (bool)",
@@ -929,12 +1238,12 @@ var PermitWallet = class {
929
1238
  throw new Error("privateKey is required. Set via config, env var, or walletPath.");
930
1239
  }
931
1240
  const rpcUrl = config.rpcUrl || this.chainConfig.rpc;
932
- this.provider = new ethers4.JsonRpcProvider(rpcUrl);
933
- this.wallet = new ethers4.Wallet(privateKey, this.provider);
1241
+ this.provider = new ethers5.JsonRpcProvider(rpcUrl);
1242
+ this.wallet = new ethers5.Wallet(privateKey, this.provider);
934
1243
  this.address = this.wallet.address;
935
- this.usdcContract = new ethers4.Contract(
1244
+ this.usdcContract = new ethers5.Contract(
936
1245
  this.chainConfig.usdc,
937
- PERMIT_ABI,
1246
+ PERMIT_ABI2,
938
1247
  this.wallet
939
1248
  );
940
1249
  }
@@ -977,9 +1286,9 @@ var PermitWallet = class {
977
1286
  async transferWithPermit(params) {
978
1287
  const { to, amount, permit } = params;
979
1288
  try {
980
- const toAddress = ethers4.getAddress(to);
981
- const ownerAddress = ethers4.getAddress(permit.owner);
982
- if (ethers4.getAddress(permit.spender).toLowerCase() !== this.address.toLowerCase()) {
1289
+ const toAddress = ethers5.getAddress(to);
1290
+ const ownerAddress = ethers5.getAddress(permit.owner);
1291
+ if (ethers5.getAddress(permit.spender).toLowerCase() !== this.address.toLowerCase()) {
983
1292
  return {
984
1293
  success: false,
985
1294
  error: `Permit spender (${permit.spender}) doesn't match wallet address (${this.address})`
@@ -1083,7 +1392,7 @@ var PermitWallet = class {
1083
1392
  */
1084
1393
  async getGasBalance() {
1085
1394
  const balance = await this.provider.getBalance(this.address);
1086
- return ethers4.formatEther(balance);
1395
+ return ethers5.formatEther(balance);
1087
1396
  }
1088
1397
  /**
1089
1398
  * Check if there's enough gas
@@ -1144,7 +1453,7 @@ After signing, send { v, r, s, deadline } to the Agent.
1144
1453
  }
1145
1454
 
1146
1455
  // src/wallet/signPermit.ts
1147
- import { ethers as ethers5 } from "ethers";
1456
+ import { ethers as ethers6 } from "ethers";
1148
1457
  async function signPermit(config, params) {
1149
1458
  const chain = config.chain || "base";
1150
1459
  const chainConfig = getChain(chain);
@@ -1153,9 +1462,9 @@ async function signPermit(config, params) {
1153
1462
  throw new Error("privateKey is required");
1154
1463
  }
1155
1464
  const rpcUrl = config.rpcUrl || chainConfig.rpc;
1156
- const provider = new ethers5.JsonRpcProvider(rpcUrl);
1157
- const wallet = new ethers5.Wallet(privateKey, provider);
1158
- const usdcContract = new ethers5.Contract(chainConfig.usdc, ERC20_ABI, provider);
1465
+ const provider = new ethers6.JsonRpcProvider(rpcUrl);
1466
+ const wallet = new ethers6.Wallet(privateKey, provider);
1467
+ const usdcContract = new ethers6.Contract(chainConfig.usdc, ERC20_ABI, provider);
1159
1468
  const nonce = Number(await usdcContract.nonces(wallet.address));
1160
1469
  let deadline;
1161
1470
  if (!params.deadline) {
@@ -1189,7 +1498,7 @@ async function signPermit(config, params) {
1189
1498
  deadline
1190
1499
  };
1191
1500
  const signature = await wallet.signTypedData(domain, types, message);
1192
- const sig = ethers5.Signature.from(signature);
1501
+ const sig = ethers6.Signature.from(signature);
1193
1502
  return {
1194
1503
  owner: wallet.address,
1195
1504
  spender: params.spender,
@@ -1211,8 +1520,284 @@ var PermitSigner = class {
1211
1520
  }
1212
1521
  };
1213
1522
 
1523
+ // src/wallet/AllowanceWallet.ts
1524
+ import { ethers as ethers7 } from "ethers";
1525
+ var PERMIT_ABI3 = [
1526
+ ...ERC20_ABI,
1527
+ "function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
1528
+ "function transferFrom(address from, address to, uint256 amount) returns (bool)",
1529
+ "function allowance(address owner, address spender) view returns (uint256)",
1530
+ "function nonces(address owner) view returns (uint256)"
1531
+ ];
1532
+ var AllowanceWallet = class {
1533
+ chain;
1534
+ chainConfig;
1535
+ address;
1536
+ // Agent's address
1537
+ wallet;
1538
+ provider;
1539
+ usdcContract;
1540
+ /** Stored permits from owners */
1541
+ permits = /* @__PURE__ */ new Map();
1542
+ constructor(config) {
1543
+ this.chain = config.chain || "base_sepolia";
1544
+ this.chainConfig = getChain(this.chain);
1545
+ const rpcUrl = config.rpcUrl || this.chainConfig.rpc;
1546
+ this.provider = new ethers7.JsonRpcProvider(rpcUrl);
1547
+ this.wallet = new ethers7.Wallet(config.privateKey, this.provider);
1548
+ this.address = this.wallet.address;
1549
+ this.usdcContract = new ethers7.Contract(
1550
+ this.chainConfig.usdc,
1551
+ PERMIT_ABI3,
1552
+ this.wallet
1553
+ );
1554
+ }
1555
+ /**
1556
+ * Store a Permit received from Owner
1557
+ * Call this when Owner sends you a signed Permit
1558
+ */
1559
+ storePermit(permit) {
1560
+ const ownerLower = permit.owner.toLowerCase();
1561
+ this.permits.set(ownerLower, permit);
1562
+ }
1563
+ /**
1564
+ * Get stored Permit for an owner
1565
+ */
1566
+ getPermit(owner) {
1567
+ return this.permits.get(owner.toLowerCase());
1568
+ }
1569
+ /**
1570
+ * Check allowance status with an owner
1571
+ */
1572
+ async checkAllowance(owner) {
1573
+ const ownerAddress = ethers7.getAddress(owner);
1574
+ const [allowance, ownerBalance, agentGasBalance] = await Promise.all([
1575
+ this.usdcContract.allowance(ownerAddress, this.address),
1576
+ this.usdcContract.balanceOf(ownerAddress),
1577
+ this.provider.getBalance(this.address)
1578
+ ]);
1579
+ const allowanceNum = Number(allowance) / 1e6;
1580
+ const hasGas = Number(ethers7.formatEther(agentGasBalance)) >= 1e-4;
1581
+ return {
1582
+ owner: ownerAddress,
1583
+ agent: this.address,
1584
+ allowance: allowanceNum.toFixed(2),
1585
+ ownerBalance: (Number(ownerBalance) / 1e6).toFixed(2),
1586
+ agentGasBalance: ethers7.formatEther(agentGasBalance),
1587
+ canSpend: allowanceNum > 0 && hasGas,
1588
+ chain: this.chainConfig.name
1589
+ };
1590
+ }
1591
+ /**
1592
+ * Spend from Owner's wallet using Permit allowance
1593
+ *
1594
+ * @example
1595
+ * ```typescript
1596
+ * const agent = new AllowanceWallet({
1597
+ * chain: 'base',
1598
+ * privateKey: process.env.AGENT_KEY // Only needs gas
1599
+ * });
1600
+ *
1601
+ * // Owner gave us a Permit
1602
+ * agent.storePermit(ownerPermit);
1603
+ *
1604
+ * // Spend to pay for a service
1605
+ * const result = await agent.spend({
1606
+ * to: '0xServiceProvider...',
1607
+ * amount: 2.99,
1608
+ * });
1609
+ * ```
1610
+ */
1611
+ async spend(params) {
1612
+ const { to, amount, permit } = params;
1613
+ try {
1614
+ const toAddress = ethers7.getAddress(to);
1615
+ const amountWei = BigInt(Math.floor(amount * 1e6));
1616
+ let ownerPermit = permit;
1617
+ let ownerAddress;
1618
+ if (ownerPermit) {
1619
+ ownerAddress = ethers7.getAddress(ownerPermit.owner);
1620
+ this.storePermit(ownerPermit);
1621
+ } else {
1622
+ for (const [owner, p] of this.permits) {
1623
+ const allowance = await this.usdcContract.allowance(owner, this.address);
1624
+ if (BigInt(allowance) >= amountWei) {
1625
+ ownerAddress = ethers7.getAddress(owner);
1626
+ ownerPermit = p;
1627
+ break;
1628
+ }
1629
+ }
1630
+ if (!ownerPermit) {
1631
+ return {
1632
+ success: false,
1633
+ error: "No valid permit found. Ask Owner to sign a Permit first.",
1634
+ from: "",
1635
+ to: toAddress,
1636
+ amount
1637
+ };
1638
+ }
1639
+ }
1640
+ const currentAllowance = await this.usdcContract.allowance(ownerAddress, this.address);
1641
+ if (BigInt(currentAllowance) < amountWei) {
1642
+ const now = Math.floor(Date.now() / 1e3);
1643
+ if (ownerPermit.deadline < now) {
1644
+ return {
1645
+ success: false,
1646
+ error: `Permit expired at ${new Date(ownerPermit.deadline * 1e3).toISOString()}. Ask Owner for a new Permit.`,
1647
+ from: ownerAddress,
1648
+ to: toAddress,
1649
+ amount
1650
+ };
1651
+ }
1652
+ const currentNonce = await this.usdcContract.nonces(ownerAddress);
1653
+ if (Number(currentNonce) !== ownerPermit.nonce) {
1654
+ return {
1655
+ success: false,
1656
+ error: `Permit nonce mismatch (expected ${ownerPermit.nonce}, got ${currentNonce}). Owner may have used this permit or signed a new one.`,
1657
+ from: ownerAddress,
1658
+ to: toAddress,
1659
+ amount
1660
+ };
1661
+ }
1662
+ console.log("[AllowanceWallet] Submitting permit on-chain...");
1663
+ const permitTx = await this.usdcContract.permit(
1664
+ ownerAddress,
1665
+ this.address,
1666
+ ownerPermit.value,
1667
+ ownerPermit.deadline,
1668
+ ownerPermit.v,
1669
+ ownerPermit.r,
1670
+ ownerPermit.s
1671
+ );
1672
+ await permitTx.wait();
1673
+ console.log("[AllowanceWallet] Permit submitted:", permitTx.hash);
1674
+ }
1675
+ console.log("[AllowanceWallet] Executing transferFrom...");
1676
+ const tx = await this.usdcContract.transferFrom(
1677
+ ownerAddress,
1678
+ toAddress,
1679
+ amountWei
1680
+ );
1681
+ const receipt = await tx.wait();
1682
+ const newAllowance = await this.usdcContract.allowance(ownerAddress, this.address);
1683
+ return {
1684
+ success: true,
1685
+ txHash: tx.hash,
1686
+ from: ownerAddress,
1687
+ to: toAddress,
1688
+ amount,
1689
+ remainingAllowance: (Number(newAllowance) / 1e6).toFixed(2),
1690
+ explorerUrl: `${this.chainConfig.explorerTx}${tx.hash}`
1691
+ };
1692
+ } catch (error) {
1693
+ const message = error.message;
1694
+ if (message.includes("ERC20InsufficientAllowance")) {
1695
+ return {
1696
+ success: false,
1697
+ error: "Insufficient allowance. Ask Owner to sign a new Permit with higher amount.",
1698
+ from: "",
1699
+ to,
1700
+ amount
1701
+ };
1702
+ }
1703
+ if (message.includes("ERC20InsufficientBalance")) {
1704
+ return {
1705
+ success: false,
1706
+ error: "Owner's wallet has insufficient USDC balance.",
1707
+ from: "",
1708
+ to,
1709
+ amount
1710
+ };
1711
+ }
1712
+ return {
1713
+ success: false,
1714
+ error: message,
1715
+ from: "",
1716
+ to,
1717
+ amount
1718
+ };
1719
+ }
1720
+ }
1721
+ /**
1722
+ * Get Agent's gas balance (ETH)
1723
+ */
1724
+ async getGasBalance() {
1725
+ const balance = await this.provider.getBalance(this.address);
1726
+ return ethers7.formatEther(balance);
1727
+ }
1728
+ };
1729
+ function generatePermitInstructions(params) {
1730
+ const { ownerAddress, agentAddress, amount, deadlineHours = 24, chain = "base" } = params;
1731
+ const chainConfig = getChain(chain);
1732
+ const deadline = Math.floor(Date.now() / 1e3) + deadlineHours * 3600;
1733
+ const value = BigInt(Math.floor(amount * 1e6)).toString();
1734
+ const eip712Domain = {
1735
+ name: "USD Coin",
1736
+ version: "2",
1737
+ chainId: chainConfig.chainId,
1738
+ verifyingContract: chainConfig.usdc
1739
+ };
1740
+ const typedData = {
1741
+ types: {
1742
+ EIP712Domain: [
1743
+ { name: "name", type: "string" },
1744
+ { name: "version", type: "string" },
1745
+ { name: "chainId", type: "uint256" },
1746
+ { name: "verifyingContract", type: "address" }
1747
+ ],
1748
+ Permit: [
1749
+ { name: "owner", type: "address" },
1750
+ { name: "spender", type: "address" },
1751
+ { name: "value", type: "uint256" },
1752
+ { name: "nonce", type: "uint256" },
1753
+ { name: "deadline", type: "uint256" }
1754
+ ]
1755
+ },
1756
+ primaryType: "Permit",
1757
+ domain: eip712Domain,
1758
+ message: {
1759
+ owner: ownerAddress,
1760
+ spender: agentAddress,
1761
+ value,
1762
+ nonce: "<GET_FROM_USDC_CONTRACT>",
1763
+ // Owner needs to query this
1764
+ deadline
1765
+ }
1766
+ };
1767
+ const instructions = `
1768
+ \u{1F510} **Grant USDC Spending Allowance to Your Agent**
1769
+
1770
+ Your Agent (${agentAddress}) is requesting permission to spend up to ${amount} USDC from your wallet.
1771
+
1772
+ **What this does:**
1773
+ - Allows your Agent to pay for services on your behalf
1774
+ - Your USDC stays in YOUR wallet until spent
1775
+ - Agent can only spend up to the authorized amount
1776
+ - Expires in ${deadlineHours} hours
1777
+ - You can revoke anytime by moving your USDC
1778
+
1779
+ **How to sign (MetaMask / any web3 wallet):**
1780
+
1781
+ 1. Go to https://etherscan.io/address/${chainConfig.usdc}#readContract
1782
+ 2. Query \`nonces(${ownerAddress})\` to get your current nonce
1783
+ 3. Use eth_signTypedData_v4 with the data below (replace nonce)
1784
+ 4. Send the signature {v, r, s, deadline, nonce} to your Agent
1785
+
1786
+ **Chain:** ${chainConfig.name}
1787
+ **USDC Contract:** ${chainConfig.usdc}
1788
+
1789
+ **EIP-712 Typed Data:**
1790
+ \`\`\`json
1791
+ ${JSON.stringify(typedData, null, 2)}
1792
+ \`\`\`
1793
+
1794
+ \u26A0\uFE0F Never share your private key. This signature only authorizes spending, not wallet access.
1795
+ `;
1796
+ return { instructions, typedData, eip712Domain };
1797
+ }
1798
+
1214
1799
  // src/permit/Permit.ts
1215
- import { ethers as ethers6 } from "ethers";
1800
+ import { ethers as ethers8 } from "ethers";
1216
1801
  var PermitPayment = class {
1217
1802
  chain;
1218
1803
  chainConfig;
@@ -1225,13 +1810,13 @@ var PermitPayment = class {
1225
1810
  this.chainConfig = getChain(this.chain);
1226
1811
  this.spenderAddress = config.spenderAddress || process.env.PAYMENT_AGENT_WALLET || "";
1227
1812
  const rpcUrl = config.rpcUrl || this.chainConfig.rpc;
1228
- this.provider = new ethers6.JsonRpcProvider(rpcUrl);
1813
+ this.provider = new ethers8.JsonRpcProvider(rpcUrl);
1229
1814
  const privateKey = config.privateKey || process.env.PAYMENT_AGENT_PRIVATE_KEY;
1230
1815
  if (privateKey) {
1231
- this.wallet = new ethers6.Wallet(privateKey, this.provider);
1816
+ this.wallet = new ethers8.Wallet(privateKey, this.provider);
1232
1817
  this.spenderAddress = this.wallet.address;
1233
1818
  }
1234
- this.usdcContract = new ethers6.Contract(
1819
+ this.usdcContract = new ethers8.Contract(
1235
1820
  this.chainConfig.usdc,
1236
1821
  ERC20_ABI,
1237
1822
  this.wallet || this.provider
@@ -1514,8 +2099,8 @@ var OrderManager = class {
1514
2099
  };
1515
2100
 
1516
2101
  // src/verify/index.ts
1517
- import { ethers as ethers7 } from "ethers";
1518
- var TRANSFER_EVENT_TOPIC = ethers7.id("Transfer(address,address,uint256)");
2102
+ import { ethers as ethers9 } from "ethers";
2103
+ var TRANSFER_EVENT_TOPIC = ethers9.id("Transfer(address,address,uint256)");
1519
2104
  async function verifyPayment(params) {
1520
2105
  const { txHash, expectedAmount, expectedTo } = params;
1521
2106
  let chain;
@@ -1532,7 +2117,7 @@ async function verifyPayment(params) {
1532
2117
  return { verified: false, error: `Unsupported chain: ${params.chain}` };
1533
2118
  }
1534
2119
  try {
1535
- const provider = new ethers7.JsonRpcProvider(chain.rpc);
2120
+ const provider = new ethers9.JsonRpcProvider(chain.rpc);
1536
2121
  const receipt = await provider.getTransactionReceipt(txHash);
1537
2122
  if (!receipt) {
1538
2123
  return { verified: false, error: "Transaction not found or not confirmed" };
@@ -1592,7 +2177,7 @@ async function getTransactionStatus(txHash, chain = "base") {
1592
2177
  return { status: "not_found" };
1593
2178
  }
1594
2179
  try {
1595
- const provider = new ethers7.JsonRpcProvider(chainConfig.rpc);
2180
+ const provider = new ethers9.JsonRpcProvider(chainConfig.rpc);
1596
2181
  const receipt = await provider.getTransactionReceipt(txHash);
1597
2182
  if (!receipt) {
1598
2183
  const tx = await provider.getTransaction(txHash);
@@ -1629,7 +2214,7 @@ async function waitForTransaction(txHash, chain = "base", confirmations = 1, tim
1629
2214
  } catch (e) {
1630
2215
  return { verified: false, confirmed: false, error: `Unsupported chain: ${chain}` };
1631
2216
  }
1632
- const provider = new ethers7.JsonRpcProvider(chainConfig.rpc);
2217
+ const provider = new ethers9.JsonRpcProvider(chainConfig.rpc);
1633
2218
  try {
1634
2219
  const receipt = await provider.waitForTransaction(txHash, confirmations, timeoutMs);
1635
2220
  if (!receipt) {
@@ -2316,6 +2901,8 @@ function parseStatusMarker(message) {
2316
2901
  return { type: "unknown", data: { raw: content } };
2317
2902
  }
2318
2903
  export {
2904
+ AgentWallet,
2905
+ AllowanceWallet,
2319
2906
  AuditLog,
2320
2907
  BuyerTemplates,
2321
2908
  CHAINS,
@@ -2338,9 +2925,11 @@ export {
2338
2925
  formatReceiptText,
2339
2926
  generatePaymentGuide,
2340
2927
  generatePaymentReminder,
2928
+ generatePermitInstructions,
2341
2929
  generateReceipt,
2342
2930
  generateReceiptFromInvoice,
2343
2931
  generateWalletGuide,
2932
+ getAgentAddress,
2344
2933
  getChain,
2345
2934
  getChainById,
2346
2935
  getTransactionStatus,