moltspay 0.2.4 → 0.2.6
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 +67 -1
- package/dist/cli.js +573 -93
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +577 -95
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +111 -1
- package/dist/index.d.ts +111 -1
- package/dist/index.js +383 -63
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +393 -75
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -280,8 +280,317 @@ After payment, reply with your tx hash:
|
|
|
280
280
|
}
|
|
281
281
|
};
|
|
282
282
|
|
|
283
|
-
// src/
|
|
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
|
|
301
|
-
this.wallet = new
|
|
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
|
|
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:
|
|
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 =
|
|
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
|
|
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
|
|
383
|
-
import * as
|
|
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 ||
|
|
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
|
-
|
|
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 (!
|
|
735
|
+
if (!fs2.existsSync(filePath)) {
|
|
427
736
|
return [];
|
|
428
737
|
}
|
|
429
|
-
const content =
|
|
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
|
|
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 (!
|
|
519
|
-
|
|
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
|
|
784
|
-
import { writeFileSync, readFileSync as
|
|
785
|
-
import { join as
|
|
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 =
|
|
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 ||
|
|
811
|
-
if (
|
|
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(
|
|
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 =
|
|
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 (!
|
|
846
|
-
|
|
1154
|
+
if (!existsSync3(dir)) {
|
|
1155
|
+
mkdirSync3(dir, { recursive: true });
|
|
847
1156
|
}
|
|
848
|
-
|
|
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 ||
|
|
864
|
-
if (!
|
|
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(
|
|
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
|
|
884
|
-
if (!
|
|
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(
|
|
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
|
|
896
|
-
return
|
|
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
|
|
901
|
-
var
|
|
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
|
|
933
|
-
this.wallet = new
|
|
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
|
|
1244
|
+
this.usdcContract = new ethers5.Contract(
|
|
936
1245
|
this.chainConfig.usdc,
|
|
937
|
-
|
|
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 =
|
|
981
|
-
const ownerAddress =
|
|
982
|
-
if (
|
|
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
|
|
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
|
|
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
|
|
1157
|
-
const wallet = new
|
|
1158
|
-
const usdcContract = new
|
|
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 =
|
|
1501
|
+
const sig = ethers6.Signature.from(signature);
|
|
1193
1502
|
return {
|
|
1194
1503
|
owner: wallet.address,
|
|
1195
1504
|
spender: params.spender,
|
|
@@ -1212,8 +1521,8 @@ var PermitSigner = class {
|
|
|
1212
1521
|
};
|
|
1213
1522
|
|
|
1214
1523
|
// src/wallet/AllowanceWallet.ts
|
|
1215
|
-
import { ethers as
|
|
1216
|
-
var
|
|
1524
|
+
import { ethers as ethers7 } from "ethers";
|
|
1525
|
+
var PERMIT_ABI3 = [
|
|
1217
1526
|
...ERC20_ABI,
|
|
1218
1527
|
"function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
|
|
1219
1528
|
"function transferFrom(address from, address to, uint256 amount) returns (bool)",
|
|
@@ -1234,12 +1543,12 @@ var AllowanceWallet = class {
|
|
|
1234
1543
|
this.chain = config.chain || "base_sepolia";
|
|
1235
1544
|
this.chainConfig = getChain(this.chain);
|
|
1236
1545
|
const rpcUrl = config.rpcUrl || this.chainConfig.rpc;
|
|
1237
|
-
this.provider = new
|
|
1238
|
-
this.wallet = new
|
|
1546
|
+
this.provider = new ethers7.JsonRpcProvider(rpcUrl);
|
|
1547
|
+
this.wallet = new ethers7.Wallet(config.privateKey, this.provider);
|
|
1239
1548
|
this.address = this.wallet.address;
|
|
1240
|
-
this.usdcContract = new
|
|
1549
|
+
this.usdcContract = new ethers7.Contract(
|
|
1241
1550
|
this.chainConfig.usdc,
|
|
1242
|
-
|
|
1551
|
+
PERMIT_ABI3,
|
|
1243
1552
|
this.wallet
|
|
1244
1553
|
);
|
|
1245
1554
|
}
|
|
@@ -1261,20 +1570,20 @@ var AllowanceWallet = class {
|
|
|
1261
1570
|
* Check allowance status with an owner
|
|
1262
1571
|
*/
|
|
1263
1572
|
async checkAllowance(owner) {
|
|
1264
|
-
const ownerAddress =
|
|
1573
|
+
const ownerAddress = ethers7.getAddress(owner);
|
|
1265
1574
|
const [allowance, ownerBalance, agentGasBalance] = await Promise.all([
|
|
1266
1575
|
this.usdcContract.allowance(ownerAddress, this.address),
|
|
1267
1576
|
this.usdcContract.balanceOf(ownerAddress),
|
|
1268
1577
|
this.provider.getBalance(this.address)
|
|
1269
1578
|
]);
|
|
1270
1579
|
const allowanceNum = Number(allowance) / 1e6;
|
|
1271
|
-
const hasGas = Number(
|
|
1580
|
+
const hasGas = Number(ethers7.formatEther(agentGasBalance)) >= 1e-4;
|
|
1272
1581
|
return {
|
|
1273
1582
|
owner: ownerAddress,
|
|
1274
1583
|
agent: this.address,
|
|
1275
1584
|
allowance: allowanceNum.toFixed(2),
|
|
1276
1585
|
ownerBalance: (Number(ownerBalance) / 1e6).toFixed(2),
|
|
1277
|
-
agentGasBalance:
|
|
1586
|
+
agentGasBalance: ethers7.formatEther(agentGasBalance),
|
|
1278
1587
|
canSpend: allowanceNum > 0 && hasGas,
|
|
1279
1588
|
chain: this.chainConfig.name
|
|
1280
1589
|
};
|
|
@@ -1302,18 +1611,18 @@ var AllowanceWallet = class {
|
|
|
1302
1611
|
async spend(params) {
|
|
1303
1612
|
const { to, amount, permit } = params;
|
|
1304
1613
|
try {
|
|
1305
|
-
const toAddress =
|
|
1614
|
+
const toAddress = ethers7.getAddress(to);
|
|
1306
1615
|
const amountWei = BigInt(Math.floor(amount * 1e6));
|
|
1307
1616
|
let ownerPermit = permit;
|
|
1308
1617
|
let ownerAddress;
|
|
1309
1618
|
if (ownerPermit) {
|
|
1310
|
-
ownerAddress =
|
|
1619
|
+
ownerAddress = ethers7.getAddress(ownerPermit.owner);
|
|
1311
1620
|
this.storePermit(ownerPermit);
|
|
1312
1621
|
} else {
|
|
1313
1622
|
for (const [owner, p] of this.permits) {
|
|
1314
1623
|
const allowance = await this.usdcContract.allowance(owner, this.address);
|
|
1315
1624
|
if (BigInt(allowance) >= amountWei) {
|
|
1316
|
-
ownerAddress =
|
|
1625
|
+
ownerAddress = ethers7.getAddress(owner);
|
|
1317
1626
|
ownerPermit = p;
|
|
1318
1627
|
break;
|
|
1319
1628
|
}
|
|
@@ -1414,7 +1723,7 @@ var AllowanceWallet = class {
|
|
|
1414
1723
|
*/
|
|
1415
1724
|
async getGasBalance() {
|
|
1416
1725
|
const balance = await this.provider.getBalance(this.address);
|
|
1417
|
-
return
|
|
1726
|
+
return ethers7.formatEther(balance);
|
|
1418
1727
|
}
|
|
1419
1728
|
};
|
|
1420
1729
|
function generatePermitInstructions(params) {
|
|
@@ -1488,7 +1797,7 @@ ${JSON.stringify(typedData, null, 2)}
|
|
|
1488
1797
|
}
|
|
1489
1798
|
|
|
1490
1799
|
// src/permit/Permit.ts
|
|
1491
|
-
import { ethers as
|
|
1800
|
+
import { ethers as ethers8 } from "ethers";
|
|
1492
1801
|
var PermitPayment = class {
|
|
1493
1802
|
chain;
|
|
1494
1803
|
chainConfig;
|
|
@@ -1501,13 +1810,13 @@ var PermitPayment = class {
|
|
|
1501
1810
|
this.chainConfig = getChain(this.chain);
|
|
1502
1811
|
this.spenderAddress = config.spenderAddress || process.env.PAYMENT_AGENT_WALLET || "";
|
|
1503
1812
|
const rpcUrl = config.rpcUrl || this.chainConfig.rpc;
|
|
1504
|
-
this.provider = new
|
|
1813
|
+
this.provider = new ethers8.JsonRpcProvider(rpcUrl);
|
|
1505
1814
|
const privateKey = config.privateKey || process.env.PAYMENT_AGENT_PRIVATE_KEY;
|
|
1506
1815
|
if (privateKey) {
|
|
1507
|
-
this.wallet = new
|
|
1816
|
+
this.wallet = new ethers8.Wallet(privateKey, this.provider);
|
|
1508
1817
|
this.spenderAddress = this.wallet.address;
|
|
1509
1818
|
}
|
|
1510
|
-
this.usdcContract = new
|
|
1819
|
+
this.usdcContract = new ethers8.Contract(
|
|
1511
1820
|
this.chainConfig.usdc,
|
|
1512
1821
|
ERC20_ABI,
|
|
1513
1822
|
this.wallet || this.provider
|
|
@@ -1790,8 +2099,8 @@ var OrderManager = class {
|
|
|
1790
2099
|
};
|
|
1791
2100
|
|
|
1792
2101
|
// src/verify/index.ts
|
|
1793
|
-
import { ethers as
|
|
1794
|
-
var TRANSFER_EVENT_TOPIC =
|
|
2102
|
+
import { ethers as ethers9 } from "ethers";
|
|
2103
|
+
var TRANSFER_EVENT_TOPIC = ethers9.id("Transfer(address,address,uint256)");
|
|
1795
2104
|
async function verifyPayment(params) {
|
|
1796
2105
|
const { txHash, expectedAmount, expectedTo } = params;
|
|
1797
2106
|
let chain;
|
|
@@ -1808,7 +2117,7 @@ async function verifyPayment(params) {
|
|
|
1808
2117
|
return { verified: false, error: `Unsupported chain: ${params.chain}` };
|
|
1809
2118
|
}
|
|
1810
2119
|
try {
|
|
1811
|
-
const provider = new
|
|
2120
|
+
const provider = new ethers9.JsonRpcProvider(chain.rpc);
|
|
1812
2121
|
const receipt = await provider.getTransactionReceipt(txHash);
|
|
1813
2122
|
if (!receipt) {
|
|
1814
2123
|
return { verified: false, error: "Transaction not found or not confirmed" };
|
|
@@ -1868,7 +2177,7 @@ async function getTransactionStatus(txHash, chain = "base") {
|
|
|
1868
2177
|
return { status: "not_found" };
|
|
1869
2178
|
}
|
|
1870
2179
|
try {
|
|
1871
|
-
const provider = new
|
|
2180
|
+
const provider = new ethers9.JsonRpcProvider(chainConfig.rpc);
|
|
1872
2181
|
const receipt = await provider.getTransactionReceipt(txHash);
|
|
1873
2182
|
if (!receipt) {
|
|
1874
2183
|
const tx = await provider.getTransaction(txHash);
|
|
@@ -1905,7 +2214,7 @@ async function waitForTransaction(txHash, chain = "base", confirmations = 1, tim
|
|
|
1905
2214
|
} catch (e) {
|
|
1906
2215
|
return { verified: false, confirmed: false, error: `Unsupported chain: ${chain}` };
|
|
1907
2216
|
}
|
|
1908
|
-
const provider = new
|
|
2217
|
+
const provider = new ethers9.JsonRpcProvider(chainConfig.rpc);
|
|
1909
2218
|
try {
|
|
1910
2219
|
const receipt = await provider.waitForTransaction(txHash, confirmations, timeoutMs);
|
|
1911
2220
|
if (!receipt) {
|
|
@@ -2218,8 +2527,14 @@ Can you follow my guidance to set this up?`;
|
|
|
2218
2527
|
* 3.1 Installation Guide - First step: install SDK
|
|
2219
2528
|
*/
|
|
2220
2529
|
guideInstall() {
|
|
2221
|
-
return `Step 1: Install the payment module
|
|
2222
|
-
|
|
2530
|
+
return `Step 1: Install and initialize the payment module:
|
|
2531
|
+
|
|
2532
|
+
\`\`\`bash
|
|
2533
|
+
npm install moltspay
|
|
2534
|
+
npx moltspay init --chain base
|
|
2535
|
+
\`\`\`
|
|
2536
|
+
|
|
2537
|
+
This will automatically create your wallet (no gas needed). Send me your wallet address (never send private key/mnemonic).`;
|
|
2223
2538
|
},
|
|
2224
2539
|
/**
|
|
2225
2540
|
* 3.2 Confirm Wallet Address
|
|
@@ -2411,7 +2726,8 @@ var BuyerTemplates = {
|
|
|
2411
2726
|
* 3.1 Report Wallet Created
|
|
2412
2727
|
*/
|
|
2413
2728
|
walletCreated(address) {
|
|
2414
|
-
return `I've
|
|
2729
|
+
return `I've installed moltspay and initialized my wallet.
|
|
2730
|
+
My address: ${address}
|
|
2415
2731
|
[status:wallet_ready]`;
|
|
2416
2732
|
},
|
|
2417
2733
|
/**
|
|
@@ -2592,6 +2908,7 @@ function parseStatusMarker(message) {
|
|
|
2592
2908
|
return { type: "unknown", data: { raw: content } };
|
|
2593
2909
|
}
|
|
2594
2910
|
export {
|
|
2911
|
+
AgentWallet,
|
|
2595
2912
|
AllowanceWallet,
|
|
2596
2913
|
AuditLog,
|
|
2597
2914
|
BuyerTemplates,
|
|
@@ -2619,6 +2936,7 @@ export {
|
|
|
2619
2936
|
generateReceipt,
|
|
2620
2937
|
generateReceiptFromInvoice,
|
|
2621
2938
|
generateWalletGuide,
|
|
2939
|
+
getAgentAddress,
|
|
2622
2940
|
getChain,
|
|
2623
2941
|
getChainById,
|
|
2624
2942
|
getTransactionStatus,
|