moltspay 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -30332,9 +30332,6 @@ init_esm_shims();
30332
30332
  init_esm_shims();
30333
30333
  import { readFileSync } from "fs";
30334
30334
  import { createServer } from "http";
30335
-
30336
- // src/verify/index.ts
30337
- init_esm_shims();
30338
30335
  import { ethers } from "ethers";
30339
30336
 
30340
30337
  // src/chains/index.ts
@@ -30415,165 +30412,41 @@ var ERC20_ABI = [
30415
30412
  "event Approval(address indexed owner, address indexed spender, uint256 value)"
30416
30413
  ];
30417
30414
 
30418
- // src/verify/index.ts
30419
- var TRANSFER_EVENT_TOPIC = ethers.id("Transfer(address,address,uint256)");
30420
- async function verifyPayment(params) {
30421
- const { txHash, expectedAmount, expectedTo } = params;
30422
- let chain2;
30423
- try {
30424
- if (typeof params.chain === "number") {
30425
- chain2 = getChainById(params.chain);
30426
- } else {
30427
- chain2 = getChain(params.chain || "base");
30428
- }
30429
- if (!chain2) {
30430
- return { verified: false, error: `Unsupported chain: ${params.chain}` };
30431
- }
30432
- } catch (e) {
30433
- return { verified: false, error: `Unsupported chain: ${params.chain}` };
30434
- }
30435
- try {
30436
- const provider = new ethers.JsonRpcProvider(chain2.rpc);
30437
- const receipt = await provider.getTransactionReceipt(txHash);
30438
- if (!receipt) {
30439
- return { verified: false, error: "Transaction not found or not confirmed" };
30440
- }
30441
- if (receipt.status !== 1) {
30442
- return { verified: false, error: "Transaction failed" };
30443
- }
30444
- const usdcAddress = chain2.usdc?.toLowerCase();
30445
- if (!usdcAddress) {
30446
- return { verified: false, error: `Chain ${chain2.name} USDC address not configured` };
30447
- }
30448
- for (const log of receipt.logs) {
30449
- if (log.address.toLowerCase() !== usdcAddress) {
30450
- continue;
30451
- }
30452
- if (log.topics.length < 3 || log.topics[0] !== TRANSFER_EVENT_TOPIC) {
30453
- continue;
30454
- }
30455
- const from = "0x" + log.topics[1].slice(-40);
30456
- const to = "0x" + log.topics[2].slice(-40);
30457
- const amountRaw = BigInt(log.data);
30458
- const amount = Number(amountRaw) / 1e6;
30459
- if (expectedTo && to.toLowerCase() !== expectedTo.toLowerCase()) {
30460
- continue;
30461
- }
30462
- if (amount < expectedAmount) {
30463
- return {
30464
- verified: false,
30465
- error: `Insufficient amount: received ${amount} USDC, expected ${expectedAmount} USDC`,
30466
- amount,
30467
- from,
30468
- to,
30469
- txHash,
30470
- blockNumber: receipt.blockNumber
30471
- };
30472
- }
30473
- return {
30474
- verified: true,
30475
- amount,
30476
- from,
30477
- to,
30478
- txHash,
30479
- blockNumber: receipt.blockNumber
30480
- };
30481
- }
30482
- return { verified: false, error: "No USDC transfer found" };
30483
- } catch (e) {
30484
- return { verified: false, error: e.message || String(e) };
30485
- }
30486
- }
30487
- async function getTransactionStatus(txHash, chain2 = "base") {
30488
- let chainConfig;
30489
- try {
30490
- chainConfig = typeof chain2 === "number" ? getChainById(chain2) : getChain(chain2);
30491
- if (!chainConfig) return { status: "not_found" };
30492
- } catch {
30493
- return { status: "not_found" };
30494
- }
30495
- try {
30496
- const provider = new ethers.JsonRpcProvider(chainConfig.rpc);
30497
- const receipt = await provider.getTransactionReceipt(txHash);
30498
- if (!receipt) {
30499
- const tx = await provider.getTransaction(txHash);
30500
- if (tx) {
30501
- return { status: "pending" };
30502
- }
30503
- return { status: "not_found" };
30504
- }
30505
- const currentBlock = await provider.getBlockNumber();
30506
- const confirmations = currentBlock - receipt.blockNumber;
30507
- if (receipt.status === 1) {
30508
- return {
30509
- status: "confirmed",
30510
- blockNumber: receipt.blockNumber,
30511
- confirmations
30512
- };
30513
- } else {
30514
- return {
30515
- status: "failed",
30516
- blockNumber: receipt.blockNumber
30517
- };
30518
- }
30519
- } catch {
30520
- return { status: "not_found" };
30521
- }
30522
- }
30523
- async function waitForTransaction(txHash, chain2 = "base", confirmations = 1, timeoutMs = 6e4) {
30524
- let chainConfig;
30525
- try {
30526
- chainConfig = typeof chain2 === "number" ? getChainById(chain2) : getChain(chain2);
30527
- if (!chainConfig) {
30528
- return { verified: false, confirmed: false, error: `Unsupported chain: ${chain2}` };
30529
- }
30530
- } catch (e) {
30531
- return { verified: false, confirmed: false, error: `Unsupported chain: ${chain2}` };
30532
- }
30533
- const provider = new ethers.JsonRpcProvider(chainConfig.rpc);
30534
- try {
30535
- const receipt = await provider.waitForTransaction(txHash, confirmations, timeoutMs);
30536
- if (!receipt) {
30537
- return { verified: false, confirmed: false, error: "Timeout waiting" };
30538
- }
30539
- if (receipt.status !== 1) {
30540
- return { verified: false, confirmed: true, error: "Transaction failed" };
30541
- }
30542
- return {
30543
- verified: true,
30544
- confirmed: true,
30545
- txHash,
30546
- blockNumber: receipt.blockNumber
30547
- };
30548
- } catch (e) {
30549
- return { verified: false, confirmed: false, error: e.message || String(e) };
30550
- }
30551
- }
30552
-
30553
30415
  // src/server/types.ts
30554
30416
  init_esm_shims();
30555
30417
 
30556
30418
  // src/server/index.ts
30557
- function generateChargeId() {
30558
- return "ch_" + Math.random().toString(36).substring(2, 15);
30559
- }
30419
+ var X402_VERSION = 2;
30420
+ var PAYMENT_REQUIRED_HEADER = "x-payment-required";
30421
+ var PAYMENT_HEADER = "x-payment";
30560
30422
  var MoltsPayServer = class {
30561
30423
  manifest;
30562
30424
  skills = /* @__PURE__ */ new Map();
30563
- charges = /* @__PURE__ */ new Map();
30564
30425
  options;
30426
+ provider = null;
30427
+ wallet = null;
30565
30428
  constructor(servicesPath, options = {}) {
30566
30429
  const content = readFileSync(servicesPath, "utf-8");
30567
30430
  this.manifest = JSON.parse(content);
30568
30431
  this.options = {
30569
30432
  port: options.port || 3e3,
30570
30433
  host: options.host || "0.0.0.0",
30571
- chargeExpirySecs: options.chargeExpirySecs || 300
30572
- // 5 minutes
30434
+ privateKey: options.privateKey || process.env.MOLTSPAY_PRIVATE_KEY
30573
30435
  };
30436
+ if (this.options.privateKey) {
30437
+ try {
30438
+ const chain2 = getChain(this.manifest.provider.chain);
30439
+ this.provider = new ethers.JsonRpcProvider(chain2.rpc);
30440
+ this.wallet = new ethers.Wallet(this.options.privateKey, this.provider);
30441
+ console.log(`[MoltsPay] Payment wallet: ${this.wallet.address}`);
30442
+ } catch (err) {
30443
+ console.warn("[MoltsPay] Warning: Could not initialize wallet for payment claims");
30444
+ }
30445
+ }
30574
30446
  console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);
30575
30447
  console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);
30576
- console.log(`[MoltsPay] Wallet: ${this.manifest.provider.wallet}`);
30448
+ console.log(`[MoltsPay] Receive wallet: ${this.manifest.provider.wallet}`);
30449
+ console.log(`[MoltsPay] Protocol: x402 (gasless, pay-for-success)`);
30577
30450
  }
30578
30451
  /**
30579
30452
  * Register a skill handler for a service
@@ -30600,10 +30473,8 @@ var MoltsPayServer = class {
30600
30473
  server.listen(p, this.options.host, () => {
30601
30474
  console.log(`[MoltsPay] Server listening on http://${this.options.host}:${p}`);
30602
30475
  console.log(`[MoltsPay] Endpoints:`);
30603
- console.log(` GET /services - List available services`);
30604
- console.log(` POST /pay - Create payment & execute service`);
30605
- console.log(` POST /verify - Verify payment & get result`);
30606
- console.log(` GET /status/:id - Check charge status`);
30476
+ console.log(` GET /services - List available services`);
30477
+ console.log(` POST /execute - Execute service (x402 payment)`);
30607
30478
  });
30608
30479
  }
30609
30480
  async handleRequest(req, res) {
@@ -30612,7 +30483,8 @@ var MoltsPayServer = class {
30612
30483
  const method = req.method || "GET";
30613
30484
  res.setHeader("Access-Control-Allow-Origin", "*");
30614
30485
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
30615
- res.setHeader("Access-Control-Allow-Headers", "Content-Type");
30486
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment");
30487
+ res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response");
30616
30488
  if (method === "OPTIONS") {
30617
30489
  res.writeHead(204);
30618
30490
  res.end();
@@ -30622,17 +30494,10 @@ var MoltsPayServer = class {
30622
30494
  if (method === "GET" && path3 === "/services") {
30623
30495
  return this.handleGetServices(res);
30624
30496
  }
30625
- if (method === "POST" && path3 === "/pay") {
30497
+ if (method === "POST" && path3 === "/execute") {
30626
30498
  const body = await this.readBody(req);
30627
- return this.handlePay(body, res);
30628
- }
30629
- if (method === "POST" && path3 === "/verify") {
30630
- const body = await this.readBody(req);
30631
- return this.handleVerify(body, res);
30632
- }
30633
- if (method === "GET" && path3.startsWith("/status/")) {
30634
- const chargeId = path3.replace("/status/", "");
30635
- return this.handleStatus(chargeId, res);
30499
+ const paymentHeader = req.headers[PAYMENT_HEADER];
30500
+ return this.handleExecute(body, paymentHeader, res);
30636
30501
  }
30637
30502
  this.sendJson(res, 404, { error: "Not found" });
30638
30503
  } catch (err) {
@@ -30644,6 +30509,7 @@ var MoltsPayServer = class {
30644
30509
  * GET /services - List available services
30645
30510
  */
30646
30511
  handleGetServices(res) {
30512
+ const chain2 = getChain(this.manifest.provider.chain);
30647
30513
  const services = this.manifest.services.map((s) => ({
30648
30514
  id: s.id,
30649
30515
  name: s.name,
@@ -30656,14 +30522,20 @@ var MoltsPayServer = class {
30656
30522
  }));
30657
30523
  this.sendJson(res, 200, {
30658
30524
  provider: this.manifest.provider,
30659
- services
30525
+ services,
30526
+ x402: {
30527
+ version: X402_VERSION,
30528
+ network: `eip155:${chain2.chainId}`,
30529
+ schemes: ["exact"]
30530
+ }
30660
30531
  });
30661
30532
  }
30662
30533
  /**
30663
- * POST /pay - Create payment request
30534
+ * POST /execute - Execute service with x402 payment
30664
30535
  * Body: { service: string, params: object }
30536
+ * Header: X-Payment (optional - if missing, returns 402)
30665
30537
  */
30666
- handlePay(body, res) {
30538
+ async handleExecute(body, paymentHeader, res) {
30667
30539
  const { service, params } = body;
30668
30540
  if (!service) {
30669
30541
  return this.sendJson(res, 400, { error: "Missing service" });
@@ -30677,113 +30549,129 @@ var MoltsPayServer = class {
30677
30549
  return this.sendJson(res, 400, { error: `Missing required param: ${key}` });
30678
30550
  }
30679
30551
  }
30680
- const chargeId = generateChargeId();
30681
- const now = Date.now();
30682
- const charge = {
30683
- id: chargeId,
30684
- service,
30685
- params: params || {},
30686
- amount: skill.config.price,
30687
- currency: skill.config.currency,
30688
- status: "pending",
30689
- createdAt: now,
30690
- expiresAt: now + this.options.chargeExpirySecs * 1e3
30691
- };
30692
- this.charges.set(chargeId, charge);
30693
- const paymentRequest = {
30694
- chargeId,
30695
- service,
30696
- amount: charge.amount,
30697
- currency: charge.currency,
30698
- wallet: this.manifest.provider.wallet,
30699
- chain: this.manifest.provider.chain,
30700
- expiresAt: charge.expiresAt
30701
- };
30702
- this.sendJson(res, 402, {
30703
- message: "Payment required",
30704
- payment: paymentRequest
30552
+ if (!paymentHeader) {
30553
+ return this.sendPaymentRequired(skill.config, res);
30554
+ }
30555
+ let payment;
30556
+ try {
30557
+ const decoded = Buffer.from(paymentHeader, "base64").toString("utf-8");
30558
+ payment = JSON.parse(decoded);
30559
+ } catch {
30560
+ return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
30561
+ }
30562
+ const validation = this.validatePayment(payment, skill.config);
30563
+ if (!validation.valid) {
30564
+ return this.sendJson(res, 402, { error: validation.error });
30565
+ }
30566
+ console.log(`[MoltsPay] Executing skill: ${service}`);
30567
+ let result;
30568
+ try {
30569
+ result = await skill.handler(params || {});
30570
+ } catch (err) {
30571
+ console.error("[MoltsPay] Skill execution failed:", err.message);
30572
+ return this.sendJson(res, 500, {
30573
+ error: "Service execution failed",
30574
+ message: err.message
30575
+ });
30576
+ }
30577
+ console.log(`[MoltsPay] Skill succeeded, claiming payment...`);
30578
+ let txHash = null;
30579
+ try {
30580
+ txHash = await this.claimPayment(payment);
30581
+ console.log(`[MoltsPay] Payment claimed: ${txHash}`);
30582
+ } catch (err) {
30583
+ console.error("[MoltsPay] Payment claim failed:", err.message);
30584
+ }
30585
+ this.sendJson(res, 200, {
30586
+ success: true,
30587
+ result,
30588
+ payment: txHash ? { txHash, status: "claimed" } : { status: "pending" }
30705
30589
  });
30706
30590
  }
30707
30591
  /**
30708
- * POST /verify - Verify payment and execute skill
30709
- * Body: { chargeId: string, txHash: string }
30592
+ * Return 402 with x402 payment requirements
30710
30593
  */
30711
- async handleVerify(body, res) {
30712
- const { chargeId, txHash } = body;
30713
- if (!chargeId || !txHash) {
30714
- return this.sendJson(res, 400, { error: "Missing chargeId or txHash" });
30594
+ sendPaymentRequired(config, res) {
30595
+ const chain2 = getChain(this.manifest.provider.chain);
30596
+ const amountInUnits = Math.floor(config.price * 1e6).toString();
30597
+ const requirements = [{
30598
+ scheme: "exact",
30599
+ network: `eip155:${chain2.chainId}`,
30600
+ maxAmountRequired: amountInUnits,
30601
+ resource: this.manifest.provider.wallet,
30602
+ description: `${config.name} - $${config.price} ${config.currency}`
30603
+ }];
30604
+ const encoded = Buffer.from(JSON.stringify(requirements)).toString("base64");
30605
+ res.writeHead(402, {
30606
+ "Content-Type": "application/json",
30607
+ [PAYMENT_REQUIRED_HEADER]: encoded
30608
+ });
30609
+ res.end(JSON.stringify({
30610
+ error: "Payment required",
30611
+ message: `Service requires $${config.price} ${config.currency}`,
30612
+ x402: requirements[0]
30613
+ }, null, 2));
30614
+ }
30615
+ /**
30616
+ * Validate x402 payment payload
30617
+ */
30618
+ validatePayment(payment, config) {
30619
+ if (payment.x402Version !== X402_VERSION) {
30620
+ return { valid: false, error: `Unsupported x402 version: ${payment.x402Version}` };
30715
30621
  }
30716
- const charge = this.charges.get(chargeId);
30717
- if (!charge) {
30718
- return this.sendJson(res, 404, { error: "Charge not found" });
30622
+ if (payment.scheme !== "exact") {
30623
+ return { valid: false, error: `Unsupported scheme: ${payment.scheme}` };
30719
30624
  }
30720
- if (Date.now() > charge.expiresAt) {
30721
- charge.status = "expired";
30722
- return this.sendJson(res, 400, { error: "Charge expired" });
30625
+ const chain2 = getChain(this.manifest.provider.chain);
30626
+ const expectedNetwork = `eip155:${chain2.chainId}`;
30627
+ if (payment.network !== expectedNetwork) {
30628
+ return { valid: false, error: `Network mismatch: expected ${expectedNetwork}` };
30723
30629
  }
30724
- if (charge.status === "completed") {
30725
- return this.sendJson(res, 200, {
30726
- status: "completed",
30727
- result: charge.result
30728
- });
30630
+ const auth = payment.payload.authorization;
30631
+ if (auth.to.toLowerCase() !== this.manifest.provider.wallet.toLowerCase()) {
30632
+ return { valid: false, error: "Payment recipient mismatch" };
30729
30633
  }
30730
- try {
30731
- const verification = await verifyPayment({
30732
- txHash,
30733
- expectedTo: this.manifest.provider.wallet,
30734
- expectedAmount: charge.amount,
30735
- chain: this.manifest.provider.chain
30736
- });
30737
- if (!verification.verified) {
30738
- charge.status = "failed";
30739
- return this.sendJson(res, 400, {
30740
- error: "Payment verification failed",
30741
- reason: verification.error
30742
- });
30743
- }
30744
- charge.status = "paid";
30745
- charge.txHash = txHash;
30746
- charge.paidAt = Date.now();
30747
- const skill = this.skills.get(charge.service);
30748
- console.log(`[MoltsPay] Executing skill: ${charge.service}`);
30749
- const result = await skill.handler(charge.params);
30750
- charge.status = "completed";
30751
- charge.result = result;
30752
- charge.completedAt = Date.now();
30753
- this.sendJson(res, 200, {
30754
- status: "completed",
30755
- chargeId,
30756
- txHash,
30757
- result
30758
- });
30759
- } catch (err) {
30760
- console.error("[MoltsPay] Skill execution error:", err);
30761
- charge.status = "failed";
30762
- this.sendJson(res, 500, {
30763
- error: "Skill execution failed",
30764
- message: err.message
30765
- });
30634
+ const amount = Number(auth.value) / 1e6;
30635
+ if (amount < config.price) {
30636
+ return { valid: false, error: `Insufficient amount: $${amount} < $${config.price}` };
30637
+ }
30638
+ const now = Math.floor(Date.now() / 1e3);
30639
+ if (Number(auth.validBefore) < now) {
30640
+ return { valid: false, error: "Payment authorization expired" };
30641
+ }
30642
+ if (Number(auth.validAfter) > now) {
30643
+ return { valid: false, error: "Payment authorization not yet valid" };
30766
30644
  }
30645
+ return { valid: true };
30767
30646
  }
30768
30647
  /**
30769
- * GET /status/:chargeId - Check charge status
30648
+ * Claim payment using transferWithAuthorization
30770
30649
  */
30771
- handleStatus(chargeId, res) {
30772
- const charge = this.charges.get(chargeId);
30773
- if (!charge) {
30774
- return this.sendJson(res, 404, { error: "Charge not found" });
30650
+ async claimPayment(payment) {
30651
+ if (!this.wallet || !this.provider) {
30652
+ throw new Error("Wallet not configured for payment claims");
30775
30653
  }
30776
- this.sendJson(res, 200, {
30777
- chargeId: charge.id,
30778
- service: charge.service,
30779
- amount: charge.amount,
30780
- currency: charge.currency,
30781
- status: charge.status,
30782
- txHash: charge.txHash,
30783
- result: charge.status === "completed" ? charge.result : void 0,
30784
- createdAt: charge.createdAt,
30785
- expiresAt: charge.expiresAt
30786
- });
30654
+ const chain2 = getChain(this.manifest.provider.chain);
30655
+ const auth = payment.payload.authorization;
30656
+ const sig = payment.payload.signature;
30657
+ const { r, s, v } = ethers.Signature.from(sig);
30658
+ const usdcAbi = [
30659
+ "function transferWithAuthorization(address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s)"
30660
+ ];
30661
+ const usdc = new ethers.Contract(chain2.usdc, usdcAbi, this.wallet);
30662
+ const tx = await usdc.transferWithAuthorization(
30663
+ auth.from,
30664
+ auth.to,
30665
+ auth.value,
30666
+ auth.validAfter,
30667
+ auth.validBefore,
30668
+ auth.nonce,
30669
+ v,
30670
+ r,
30671
+ s
30672
+ );
30673
+ const receipt = await tx.wait();
30674
+ return receipt.hash;
30787
30675
  }
30788
30676
  async readBody(req) {
30789
30677
  return new Promise((resolve, reject) => {
@@ -30810,12 +30698,15 @@ init_esm_shims();
30810
30698
  import { existsSync, readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
30811
30699
  import { homedir } from "os";
30812
30700
  import { join } from "path";
30813
- import { Wallet } from "ethers";
30701
+ import { Wallet, ethers as ethers2 } from "ethers";
30814
30702
 
30815
30703
  // src/client/types.ts
30816
30704
  init_esm_shims();
30817
30705
 
30818
30706
  // src/client/index.ts
30707
+ var X402_VERSION2 = 2;
30708
+ var PAYMENT_REQUIRED_HEADER2 = "x-payment-required";
30709
+ var PAYMENT_HEADER2 = "x-payment";
30819
30710
  var DEFAULT_CONFIG = {
30820
30711
  chain: "base",
30821
30712
  limits: {
@@ -30879,43 +30770,112 @@ var MoltsPayClient = class {
30879
30770
  return res.json();
30880
30771
  }
30881
30772
  /**
30882
- * Pay for a service and get the result
30773
+ * Pay for a service and get the result (x402 protocol)
30774
+ *
30775
+ * This is GASLESS for the client - server pays gas to claim payment.
30776
+ * This is PAY-FOR-SUCCESS - payment only claimed if service succeeds.
30883
30777
  */
30884
30778
  async pay(serverUrl, service, params) {
30885
- if (!this.wallet) {
30779
+ if (!this.wallet || !this.walletData) {
30886
30780
  throw new Error("Client not initialized. Run: npx moltspay init");
30887
30781
  }
30888
- const payRes = await fetch(`${serverUrl}/pay`, {
30782
+ console.log(`[MoltsPay] Requesting service: ${service}`);
30783
+ const initialRes = await fetch(`${serverUrl}/execute`, {
30889
30784
  method: "POST",
30890
30785
  headers: { "Content-Type": "application/json" },
30891
30786
  body: JSON.stringify({ service, params })
30892
30787
  });
30893
- if (payRes.status !== 402) {
30894
- const err = await payRes.json();
30895
- throw new Error(err.error || "Unexpected response");
30788
+ if (initialRes.status !== 402) {
30789
+ const data = await initialRes.json();
30790
+ if (initialRes.ok && data.result) {
30791
+ return data.result;
30792
+ }
30793
+ throw new Error(data.error || "Unexpected response");
30794
+ }
30795
+ const paymentRequiredHeader = initialRes.headers.get(PAYMENT_REQUIRED_HEADER2);
30796
+ if (!paymentRequiredHeader) {
30797
+ throw new Error("Missing x-payment-required header");
30896
30798
  }
30897
- const paymentReq = await payRes.json();
30898
- const { payment } = paymentReq;
30899
- this.checkLimits(payment.amount);
30900
- console.log(`[MoltsPay] Paying $${payment.amount} ${payment.currency} to ${payment.wallet}`);
30901
- const txHash = await this.executePayment(payment);
30902
- console.log(`[MoltsPay] Payment tx: ${txHash}`);
30903
- const verifyRes = await fetch(`${serverUrl}/verify`, {
30799
+ let requirements;
30800
+ try {
30801
+ const decoded = Buffer.from(paymentRequiredHeader, "base64").toString("utf-8");
30802
+ requirements = JSON.parse(decoded);
30803
+ if (!Array.isArray(requirements)) {
30804
+ requirements = [requirements];
30805
+ }
30806
+ } catch {
30807
+ throw new Error("Invalid x-payment-required header");
30808
+ }
30809
+ const chain2 = getChain(this.config.chain);
30810
+ const network = `eip155:${chain2.chainId}`;
30811
+ const req = requirements.find((r) => r.scheme === "exact" && r.network === network);
30812
+ if (!req) {
30813
+ throw new Error(`No matching payment option for ${network}`);
30814
+ }
30815
+ const amount = Number(req.maxAmountRequired) / 1e6;
30816
+ this.checkLimits(amount);
30817
+ console.log(`[MoltsPay] Signing payment: $${amount} USDC (gasless)`);
30818
+ const authorization = await this.signEIP3009(req.resource, amount, chain2);
30819
+ const payload = {
30820
+ x402Version: X402_VERSION2,
30821
+ scheme: "exact",
30822
+ network,
30823
+ payload: authorization
30824
+ };
30825
+ const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
30826
+ console.log(`[MoltsPay] Sending request with payment...`);
30827
+ const paidRes = await fetch(`${serverUrl}/execute`, {
30904
30828
  method: "POST",
30905
- headers: { "Content-Type": "application/json" },
30906
- body: JSON.stringify({
30907
- chargeId: payment.chargeId,
30908
- txHash
30909
- })
30829
+ headers: {
30830
+ "Content-Type": "application/json",
30831
+ [PAYMENT_HEADER2]: paymentHeader
30832
+ },
30833
+ body: JSON.stringify({ service, params })
30910
30834
  });
30911
- if (!verifyRes.ok) {
30912
- const err = await verifyRes.json();
30913
- throw new Error(err.error || "Verification failed");
30835
+ const result = await paidRes.json();
30836
+ if (!paidRes.ok) {
30837
+ throw new Error(result.error || "Service execution failed");
30914
30838
  }
30915
- const result = await verifyRes.json();
30916
- this.recordSpending(payment.amount);
30839
+ this.recordSpending(amount);
30840
+ console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || "claimed"}`);
30917
30841
  return result.result;
30918
30842
  }
30843
+ /**
30844
+ * Sign EIP-3009 transferWithAuthorization (GASLESS)
30845
+ * This only signs - no on-chain transaction, no gas needed.
30846
+ */
30847
+ async signEIP3009(to, amount, chain2) {
30848
+ const validAfter = 0;
30849
+ const validBefore = Math.floor(Date.now() / 1e3) + 3600;
30850
+ const nonce = ethers2.hexlify(ethers2.randomBytes(32));
30851
+ const value = BigInt(Math.floor(amount * 1e6)).toString();
30852
+ const authorization = {
30853
+ from: this.wallet.address,
30854
+ to,
30855
+ value,
30856
+ validAfter: validAfter.toString(),
30857
+ validBefore: validBefore.toString(),
30858
+ nonce
30859
+ };
30860
+ const domain = {
30861
+ name: "USD Coin",
30862
+ version: "2",
30863
+ chainId: chain2.chainId,
30864
+ verifyingContract: chain2.usdc
30865
+ };
30866
+ const types = {
30867
+ TransferWithAuthorization: [
30868
+ { name: "from", type: "address" },
30869
+ { name: "to", type: "address" },
30870
+ { name: "value", type: "uint256" },
30871
+ { name: "validAfter", type: "uint256" },
30872
+ { name: "validBefore", type: "uint256" },
30873
+ { name: "nonce", type: "bytes32" }
30874
+ ]
30875
+ };
30876
+ const signature = await this.wallet.signTypedData(domain, types, authorization);
30877
+ return { authorization, signature };
30878
+ }
30919
30879
  /**
30920
30880
  * Check spending limits
30921
30881
  */
@@ -30942,36 +30902,6 @@ var MoltsPayClient = class {
30942
30902
  recordSpending(amount) {
30943
30903
  this.todaySpending += amount;
30944
30904
  }
30945
- /**
30946
- * Execute payment on-chain
30947
- */
30948
- async executePayment(payment) {
30949
- let chain2;
30950
- try {
30951
- chain2 = getChain(payment.chain);
30952
- } catch {
30953
- throw new Error(`Unknown chain: ${payment.chain}`);
30954
- }
30955
- const { ethers: ethers4 } = await import("ethers");
30956
- const provider = new ethers4.JsonRpcProvider(chain2.rpc);
30957
- const signer = new ethers4.Wallet(this.walletData.privateKey, provider);
30958
- const usdcAddress = chain2.usdc;
30959
- const usdcAbi = [
30960
- "function transfer(address to, uint256 amount) returns (bool)",
30961
- "function balanceOf(address account) view returns (uint256)"
30962
- ];
30963
- const usdc = new ethers4.Contract(usdcAddress, usdcAbi, signer);
30964
- const amountInUnits = ethers4.parseUnits(payment.amount.toString(), 6);
30965
- const balance = await usdc.balanceOf(this.wallet.address);
30966
- if (balance < amountInUnits) {
30967
- throw new Error(
30968
- `Insufficient USDC balance: ${ethers4.formatUnits(balance, 6)} < ${payment.amount}`
30969
- );
30970
- }
30971
- const tx = await usdc.transfer(payment.wallet, amountInUnits);
30972
- const receipt = await tx.wait();
30973
- return receipt.hash;
30974
- }
30975
30905
  // --- Config & Wallet Management ---
30976
30906
  loadConfig() {
30977
30907
  const configPath = join(this.configDir, "config.json");
@@ -31031,15 +30961,14 @@ var MoltsPayClient = class {
31031
30961
  } catch {
31032
30962
  throw new Error(`Unknown chain: ${this.config.chain}`);
31033
30963
  }
31034
- const { ethers: ethers4 } = await import("ethers");
31035
- const provider = new ethers4.JsonRpcProvider(chain2.rpc);
30964
+ const provider = new ethers2.JsonRpcProvider(chain2.rpc);
31036
30965
  const nativeBalance = await provider.getBalance(this.wallet.address);
31037
30966
  const usdcAbi = ["function balanceOf(address) view returns (uint256)"];
31038
- const usdc = new ethers4.Contract(chain2.usdc, usdcAbi, provider);
30967
+ const usdc = new ethers2.Contract(chain2.usdc, usdcAbi, provider);
31039
30968
  const usdcBalance = await usdc.balanceOf(this.wallet.address);
31040
30969
  return {
31041
- usdc: parseFloat(ethers4.formatUnits(usdcBalance, 6)),
31042
- native: parseFloat(ethers4.formatEther(nativeBalance))
30970
+ usdc: parseFloat(ethers2.formatUnits(usdcBalance, 6)),
30971
+ native: parseFloat(ethers2.formatEther(nativeBalance))
31043
30972
  };
31044
30973
  }
31045
30974
  };
@@ -31049,11 +30978,11 @@ init_esm_shims();
31049
30978
 
31050
30979
  // src/wallet/Wallet.ts
31051
30980
  init_esm_shims();
31052
- import { ethers as ethers2 } from "ethers";
30981
+ import { ethers as ethers3 } from "ethers";
31053
30982
 
31054
30983
  // src/wallet/createWallet.ts
31055
30984
  init_esm_shims();
31056
- import { ethers as ethers3 } from "ethers";
30985
+ import { ethers as ethers4 } from "ethers";
31057
30986
  import { writeFileSync as writeFileSync2, readFileSync as readFileSync3, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
31058
30987
  import { join as join2, dirname } from "path";
31059
30988
  import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
@@ -31098,7 +31027,7 @@ function createWallet(options = {}) {
31098
31027
  }
31099
31028
  }
31100
31029
  try {
31101
- const wallet = ethers3.Wallet.createRandom();
31030
+ const wallet = ethers4.Wallet.createRandom();
31102
31031
  const walletData = {
31103
31032
  address: wallet.address,
31104
31033
  label: options.label,
@@ -31169,6 +31098,143 @@ function walletExists(storagePath) {
31169
31098
  return existsSync2(path3);
31170
31099
  }
31171
31100
 
31101
+ // src/verify/index.ts
31102
+ init_esm_shims();
31103
+ import { ethers as ethers5 } from "ethers";
31104
+ var TRANSFER_EVENT_TOPIC = ethers5.id("Transfer(address,address,uint256)");
31105
+ async function verifyPayment(params) {
31106
+ const { txHash, expectedAmount, expectedTo } = params;
31107
+ let chain2;
31108
+ try {
31109
+ if (typeof params.chain === "number") {
31110
+ chain2 = getChainById(params.chain);
31111
+ } else {
31112
+ chain2 = getChain(params.chain || "base");
31113
+ }
31114
+ if (!chain2) {
31115
+ return { verified: false, error: `Unsupported chain: ${params.chain}` };
31116
+ }
31117
+ } catch (e) {
31118
+ return { verified: false, error: `Unsupported chain: ${params.chain}` };
31119
+ }
31120
+ try {
31121
+ const provider = new ethers5.JsonRpcProvider(chain2.rpc);
31122
+ const receipt = await provider.getTransactionReceipt(txHash);
31123
+ if (!receipt) {
31124
+ return { verified: false, error: "Transaction not found or not confirmed" };
31125
+ }
31126
+ if (receipt.status !== 1) {
31127
+ return { verified: false, error: "Transaction failed" };
31128
+ }
31129
+ const usdcAddress = chain2.usdc?.toLowerCase();
31130
+ if (!usdcAddress) {
31131
+ return { verified: false, error: `Chain ${chain2.name} USDC address not configured` };
31132
+ }
31133
+ for (const log of receipt.logs) {
31134
+ if (log.address.toLowerCase() !== usdcAddress) {
31135
+ continue;
31136
+ }
31137
+ if (log.topics.length < 3 || log.topics[0] !== TRANSFER_EVENT_TOPIC) {
31138
+ continue;
31139
+ }
31140
+ const from = "0x" + log.topics[1].slice(-40);
31141
+ const to = "0x" + log.topics[2].slice(-40);
31142
+ const amountRaw = BigInt(log.data);
31143
+ const amount = Number(amountRaw) / 1e6;
31144
+ if (expectedTo && to.toLowerCase() !== expectedTo.toLowerCase()) {
31145
+ continue;
31146
+ }
31147
+ if (amount < expectedAmount) {
31148
+ return {
31149
+ verified: false,
31150
+ error: `Insufficient amount: received ${amount} USDC, expected ${expectedAmount} USDC`,
31151
+ amount,
31152
+ from,
31153
+ to,
31154
+ txHash,
31155
+ blockNumber: receipt.blockNumber
31156
+ };
31157
+ }
31158
+ return {
31159
+ verified: true,
31160
+ amount,
31161
+ from,
31162
+ to,
31163
+ txHash,
31164
+ blockNumber: receipt.blockNumber
31165
+ };
31166
+ }
31167
+ return { verified: false, error: "No USDC transfer found" };
31168
+ } catch (e) {
31169
+ return { verified: false, error: e.message || String(e) };
31170
+ }
31171
+ }
31172
+ async function getTransactionStatus(txHash, chain2 = "base") {
31173
+ let chainConfig;
31174
+ try {
31175
+ chainConfig = typeof chain2 === "number" ? getChainById(chain2) : getChain(chain2);
31176
+ if (!chainConfig) return { status: "not_found" };
31177
+ } catch {
31178
+ return { status: "not_found" };
31179
+ }
31180
+ try {
31181
+ const provider = new ethers5.JsonRpcProvider(chainConfig.rpc);
31182
+ const receipt = await provider.getTransactionReceipt(txHash);
31183
+ if (!receipt) {
31184
+ const tx = await provider.getTransaction(txHash);
31185
+ if (tx) {
31186
+ return { status: "pending" };
31187
+ }
31188
+ return { status: "not_found" };
31189
+ }
31190
+ const currentBlock = await provider.getBlockNumber();
31191
+ const confirmations = currentBlock - receipt.blockNumber;
31192
+ if (receipt.status === 1) {
31193
+ return {
31194
+ status: "confirmed",
31195
+ blockNumber: receipt.blockNumber,
31196
+ confirmations
31197
+ };
31198
+ } else {
31199
+ return {
31200
+ status: "failed",
31201
+ blockNumber: receipt.blockNumber
31202
+ };
31203
+ }
31204
+ } catch {
31205
+ return { status: "not_found" };
31206
+ }
31207
+ }
31208
+ async function waitForTransaction(txHash, chain2 = "base", confirmations = 1, timeoutMs = 6e4) {
31209
+ let chainConfig;
31210
+ try {
31211
+ chainConfig = typeof chain2 === "number" ? getChainById(chain2) : getChain(chain2);
31212
+ if (!chainConfig) {
31213
+ return { verified: false, confirmed: false, error: `Unsupported chain: ${chain2}` };
31214
+ }
31215
+ } catch (e) {
31216
+ return { verified: false, confirmed: false, error: `Unsupported chain: ${chain2}` };
31217
+ }
31218
+ const provider = new ethers5.JsonRpcProvider(chainConfig.rpc);
31219
+ try {
31220
+ const receipt = await provider.waitForTransaction(txHash, confirmations, timeoutMs);
31221
+ if (!receipt) {
31222
+ return { verified: false, confirmed: false, error: "Timeout waiting" };
31223
+ }
31224
+ if (receipt.status !== 1) {
31225
+ return { verified: false, confirmed: true, error: "Transaction failed" };
31226
+ }
31227
+ return {
31228
+ verified: true,
31229
+ confirmed: true,
31230
+ txHash,
31231
+ blockNumber: receipt.blockNumber
31232
+ };
31233
+ } catch (e) {
31234
+ return { verified: false, confirmed: false, error: e.message || String(e) };
31235
+ }
31236
+ }
31237
+
31172
31238
  // src/cdp/index.ts
31173
31239
  init_esm_shims();
31174
31240
  import * as fs from "fs";
@@ -31290,9 +31356,9 @@ var CDPWallet = class {
31290
31356
  * Get USDC balance
31291
31357
  */
31292
31358
  async getBalance() {
31293
- const { ethers: ethers4 } = await import("ethers");
31294
- const provider = new ethers4.JsonRpcProvider(this.chainConfig.rpc);
31295
- const usdcContract = new ethers4.Contract(
31359
+ const { ethers: ethers6 } = await import("ethers");
31360
+ const provider = new ethers6.JsonRpcProvider(this.chainConfig.rpc);
31361
+ const usdcContract = new ethers6.Contract(
31296
31362
  this.chainConfig.usdc,
31297
31363
  ["function balanceOf(address) view returns (uint256)"],
31298
31364
  provider
@@ -31303,7 +31369,7 @@ var CDPWallet = class {
31303
31369
  ]);
31304
31370
  return {
31305
31371
  usdc: (Number(usdcBalance) / 1e6).toFixed(2),
31306
- eth: ethers4.formatEther(ethBalance)
31372
+ eth: ethers6.formatEther(ethBalance)
31307
31373
  };
31308
31374
  }
31309
31375
  /**
@@ -31321,7 +31387,7 @@ var CDPWallet = class {
31321
31387
  }
31322
31388
  try {
31323
31389
  const { CdpClient } = await import("@coinbase/cdp-sdk");
31324
- const { ethers: ethers4 } = await import("ethers");
31390
+ const { ethers: ethers6 } = await import("ethers");
31325
31391
  const cdp = new CdpClient({
31326
31392
  apiKeyId: creds.apiKeyId,
31327
31393
  apiKeySecret: creds.apiKeySecret,
@@ -31329,7 +31395,7 @@ var CDPWallet = class {
31329
31395
  });
31330
31396
  const account = await cdp.evm.getAccount({ address: this.address });
31331
31397
  const amountWei = BigInt(Math.floor(params.amount * 1e6));
31332
- const iface = new ethers4.Interface([
31398
+ const iface = new ethers6.Interface([
31333
31399
  "function transfer(address to, uint256 amount) returns (bool)"
31334
31400
  ]);
31335
31401
  const callData = iface.encodeFunctionData("transfer", [params.to, amountWei]);