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.js CHANGED
@@ -30369,9 +30369,6 @@ init_cjs_shims();
30369
30369
  init_cjs_shims();
30370
30370
  var import_fs = require("fs");
30371
30371
  var import_http = require("http");
30372
-
30373
- // src/verify/index.ts
30374
- init_cjs_shims();
30375
30372
  var import_ethers = require("ethers");
30376
30373
 
30377
30374
  // src/chains/index.ts
@@ -30452,165 +30449,41 @@ var ERC20_ABI = [
30452
30449
  "event Approval(address indexed owner, address indexed spender, uint256 value)"
30453
30450
  ];
30454
30451
 
30455
- // src/verify/index.ts
30456
- var TRANSFER_EVENT_TOPIC = import_ethers.ethers.id("Transfer(address,address,uint256)");
30457
- async function verifyPayment(params) {
30458
- const { txHash, expectedAmount, expectedTo } = params;
30459
- let chain2;
30460
- try {
30461
- if (typeof params.chain === "number") {
30462
- chain2 = getChainById(params.chain);
30463
- } else {
30464
- chain2 = getChain(params.chain || "base");
30465
- }
30466
- if (!chain2) {
30467
- return { verified: false, error: `Unsupported chain: ${params.chain}` };
30468
- }
30469
- } catch (e) {
30470
- return { verified: false, error: `Unsupported chain: ${params.chain}` };
30471
- }
30472
- try {
30473
- const provider = new import_ethers.ethers.JsonRpcProvider(chain2.rpc);
30474
- const receipt = await provider.getTransactionReceipt(txHash);
30475
- if (!receipt) {
30476
- return { verified: false, error: "Transaction not found or not confirmed" };
30477
- }
30478
- if (receipt.status !== 1) {
30479
- return { verified: false, error: "Transaction failed" };
30480
- }
30481
- const usdcAddress = chain2.usdc?.toLowerCase();
30482
- if (!usdcAddress) {
30483
- return { verified: false, error: `Chain ${chain2.name} USDC address not configured` };
30484
- }
30485
- for (const log of receipt.logs) {
30486
- if (log.address.toLowerCase() !== usdcAddress) {
30487
- continue;
30488
- }
30489
- if (log.topics.length < 3 || log.topics[0] !== TRANSFER_EVENT_TOPIC) {
30490
- continue;
30491
- }
30492
- const from = "0x" + log.topics[1].slice(-40);
30493
- const to = "0x" + log.topics[2].slice(-40);
30494
- const amountRaw = BigInt(log.data);
30495
- const amount = Number(amountRaw) / 1e6;
30496
- if (expectedTo && to.toLowerCase() !== expectedTo.toLowerCase()) {
30497
- continue;
30498
- }
30499
- if (amount < expectedAmount) {
30500
- return {
30501
- verified: false,
30502
- error: `Insufficient amount: received ${amount} USDC, expected ${expectedAmount} USDC`,
30503
- amount,
30504
- from,
30505
- to,
30506
- txHash,
30507
- blockNumber: receipt.blockNumber
30508
- };
30509
- }
30510
- return {
30511
- verified: true,
30512
- amount,
30513
- from,
30514
- to,
30515
- txHash,
30516
- blockNumber: receipt.blockNumber
30517
- };
30518
- }
30519
- return { verified: false, error: "No USDC transfer found" };
30520
- } catch (e) {
30521
- return { verified: false, error: e.message || String(e) };
30522
- }
30523
- }
30524
- async function getTransactionStatus(txHash, chain2 = "base") {
30525
- let chainConfig;
30526
- try {
30527
- chainConfig = typeof chain2 === "number" ? getChainById(chain2) : getChain(chain2);
30528
- if (!chainConfig) return { status: "not_found" };
30529
- } catch {
30530
- return { status: "not_found" };
30531
- }
30532
- try {
30533
- const provider = new import_ethers.ethers.JsonRpcProvider(chainConfig.rpc);
30534
- const receipt = await provider.getTransactionReceipt(txHash);
30535
- if (!receipt) {
30536
- const tx = await provider.getTransaction(txHash);
30537
- if (tx) {
30538
- return { status: "pending" };
30539
- }
30540
- return { status: "not_found" };
30541
- }
30542
- const currentBlock = await provider.getBlockNumber();
30543
- const confirmations = currentBlock - receipt.blockNumber;
30544
- if (receipt.status === 1) {
30545
- return {
30546
- status: "confirmed",
30547
- blockNumber: receipt.blockNumber,
30548
- confirmations
30549
- };
30550
- } else {
30551
- return {
30552
- status: "failed",
30553
- blockNumber: receipt.blockNumber
30554
- };
30555
- }
30556
- } catch {
30557
- return { status: "not_found" };
30558
- }
30559
- }
30560
- async function waitForTransaction(txHash, chain2 = "base", confirmations = 1, timeoutMs = 6e4) {
30561
- let chainConfig;
30562
- try {
30563
- chainConfig = typeof chain2 === "number" ? getChainById(chain2) : getChain(chain2);
30564
- if (!chainConfig) {
30565
- return { verified: false, confirmed: false, error: `Unsupported chain: ${chain2}` };
30566
- }
30567
- } catch (e) {
30568
- return { verified: false, confirmed: false, error: `Unsupported chain: ${chain2}` };
30569
- }
30570
- const provider = new import_ethers.ethers.JsonRpcProvider(chainConfig.rpc);
30571
- try {
30572
- const receipt = await provider.waitForTransaction(txHash, confirmations, timeoutMs);
30573
- if (!receipt) {
30574
- return { verified: false, confirmed: false, error: "Timeout waiting" };
30575
- }
30576
- if (receipt.status !== 1) {
30577
- return { verified: false, confirmed: true, error: "Transaction failed" };
30578
- }
30579
- return {
30580
- verified: true,
30581
- confirmed: true,
30582
- txHash,
30583
- blockNumber: receipt.blockNumber
30584
- };
30585
- } catch (e) {
30586
- return { verified: false, confirmed: false, error: e.message || String(e) };
30587
- }
30588
- }
30589
-
30590
30452
  // src/server/types.ts
30591
30453
  init_cjs_shims();
30592
30454
 
30593
30455
  // src/server/index.ts
30594
- function generateChargeId() {
30595
- return "ch_" + Math.random().toString(36).substring(2, 15);
30596
- }
30456
+ var X402_VERSION = 2;
30457
+ var PAYMENT_REQUIRED_HEADER = "x-payment-required";
30458
+ var PAYMENT_HEADER = "x-payment";
30597
30459
  var MoltsPayServer = class {
30598
30460
  manifest;
30599
30461
  skills = /* @__PURE__ */ new Map();
30600
- charges = /* @__PURE__ */ new Map();
30601
30462
  options;
30463
+ provider = null;
30464
+ wallet = null;
30602
30465
  constructor(servicesPath, options = {}) {
30603
30466
  const content = (0, import_fs.readFileSync)(servicesPath, "utf-8");
30604
30467
  this.manifest = JSON.parse(content);
30605
30468
  this.options = {
30606
30469
  port: options.port || 3e3,
30607
30470
  host: options.host || "0.0.0.0",
30608
- chargeExpirySecs: options.chargeExpirySecs || 300
30609
- // 5 minutes
30471
+ privateKey: options.privateKey || process.env.MOLTSPAY_PRIVATE_KEY
30610
30472
  };
30473
+ if (this.options.privateKey) {
30474
+ try {
30475
+ const chain2 = getChain(this.manifest.provider.chain);
30476
+ this.provider = new import_ethers.ethers.JsonRpcProvider(chain2.rpc);
30477
+ this.wallet = new import_ethers.ethers.Wallet(this.options.privateKey, this.provider);
30478
+ console.log(`[MoltsPay] Payment wallet: ${this.wallet.address}`);
30479
+ } catch (err) {
30480
+ console.warn("[MoltsPay] Warning: Could not initialize wallet for payment claims");
30481
+ }
30482
+ }
30611
30483
  console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);
30612
30484
  console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);
30613
- console.log(`[MoltsPay] Wallet: ${this.manifest.provider.wallet}`);
30485
+ console.log(`[MoltsPay] Receive wallet: ${this.manifest.provider.wallet}`);
30486
+ console.log(`[MoltsPay] Protocol: x402 (gasless, pay-for-success)`);
30614
30487
  }
30615
30488
  /**
30616
30489
  * Register a skill handler for a service
@@ -30637,10 +30510,8 @@ var MoltsPayServer = class {
30637
30510
  server.listen(p, this.options.host, () => {
30638
30511
  console.log(`[MoltsPay] Server listening on http://${this.options.host}:${p}`);
30639
30512
  console.log(`[MoltsPay] Endpoints:`);
30640
- console.log(` GET /services - List available services`);
30641
- console.log(` POST /pay - Create payment & execute service`);
30642
- console.log(` POST /verify - Verify payment & get result`);
30643
- console.log(` GET /status/:id - Check charge status`);
30513
+ console.log(` GET /services - List available services`);
30514
+ console.log(` POST /execute - Execute service (x402 payment)`);
30644
30515
  });
30645
30516
  }
30646
30517
  async handleRequest(req, res) {
@@ -30649,7 +30520,8 @@ var MoltsPayServer = class {
30649
30520
  const method = req.method || "GET";
30650
30521
  res.setHeader("Access-Control-Allow-Origin", "*");
30651
30522
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
30652
- res.setHeader("Access-Control-Allow-Headers", "Content-Type");
30523
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment");
30524
+ res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response");
30653
30525
  if (method === "OPTIONS") {
30654
30526
  res.writeHead(204);
30655
30527
  res.end();
@@ -30659,17 +30531,10 @@ var MoltsPayServer = class {
30659
30531
  if (method === "GET" && path2 === "/services") {
30660
30532
  return this.handleGetServices(res);
30661
30533
  }
30662
- if (method === "POST" && path2 === "/pay") {
30663
- const body = await this.readBody(req);
30664
- return this.handlePay(body, res);
30665
- }
30666
- if (method === "POST" && path2 === "/verify") {
30534
+ if (method === "POST" && path2 === "/execute") {
30667
30535
  const body = await this.readBody(req);
30668
- return this.handleVerify(body, res);
30669
- }
30670
- if (method === "GET" && path2.startsWith("/status/")) {
30671
- const chargeId = path2.replace("/status/", "");
30672
- return this.handleStatus(chargeId, res);
30536
+ const paymentHeader = req.headers[PAYMENT_HEADER];
30537
+ return this.handleExecute(body, paymentHeader, res);
30673
30538
  }
30674
30539
  this.sendJson(res, 404, { error: "Not found" });
30675
30540
  } catch (err) {
@@ -30681,6 +30546,7 @@ var MoltsPayServer = class {
30681
30546
  * GET /services - List available services
30682
30547
  */
30683
30548
  handleGetServices(res) {
30549
+ const chain2 = getChain(this.manifest.provider.chain);
30684
30550
  const services = this.manifest.services.map((s) => ({
30685
30551
  id: s.id,
30686
30552
  name: s.name,
@@ -30693,14 +30559,20 @@ var MoltsPayServer = class {
30693
30559
  }));
30694
30560
  this.sendJson(res, 200, {
30695
30561
  provider: this.manifest.provider,
30696
- services
30562
+ services,
30563
+ x402: {
30564
+ version: X402_VERSION,
30565
+ network: `eip155:${chain2.chainId}`,
30566
+ schemes: ["exact"]
30567
+ }
30697
30568
  });
30698
30569
  }
30699
30570
  /**
30700
- * POST /pay - Create payment request
30571
+ * POST /execute - Execute service with x402 payment
30701
30572
  * Body: { service: string, params: object }
30573
+ * Header: X-Payment (optional - if missing, returns 402)
30702
30574
  */
30703
- handlePay(body, res) {
30575
+ async handleExecute(body, paymentHeader, res) {
30704
30576
  const { service, params } = body;
30705
30577
  if (!service) {
30706
30578
  return this.sendJson(res, 400, { error: "Missing service" });
@@ -30714,113 +30586,129 @@ var MoltsPayServer = class {
30714
30586
  return this.sendJson(res, 400, { error: `Missing required param: ${key}` });
30715
30587
  }
30716
30588
  }
30717
- const chargeId = generateChargeId();
30718
- const now = Date.now();
30719
- const charge = {
30720
- id: chargeId,
30721
- service,
30722
- params: params || {},
30723
- amount: skill.config.price,
30724
- currency: skill.config.currency,
30725
- status: "pending",
30726
- createdAt: now,
30727
- expiresAt: now + this.options.chargeExpirySecs * 1e3
30728
- };
30729
- this.charges.set(chargeId, charge);
30730
- const paymentRequest = {
30731
- chargeId,
30732
- service,
30733
- amount: charge.amount,
30734
- currency: charge.currency,
30735
- wallet: this.manifest.provider.wallet,
30736
- chain: this.manifest.provider.chain,
30737
- expiresAt: charge.expiresAt
30738
- };
30739
- this.sendJson(res, 402, {
30740
- message: "Payment required",
30741
- payment: paymentRequest
30589
+ if (!paymentHeader) {
30590
+ return this.sendPaymentRequired(skill.config, res);
30591
+ }
30592
+ let payment;
30593
+ try {
30594
+ const decoded = Buffer.from(paymentHeader, "base64").toString("utf-8");
30595
+ payment = JSON.parse(decoded);
30596
+ } catch {
30597
+ return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
30598
+ }
30599
+ const validation = this.validatePayment(payment, skill.config);
30600
+ if (!validation.valid) {
30601
+ return this.sendJson(res, 402, { error: validation.error });
30602
+ }
30603
+ console.log(`[MoltsPay] Executing skill: ${service}`);
30604
+ let result;
30605
+ try {
30606
+ result = await skill.handler(params || {});
30607
+ } catch (err) {
30608
+ console.error("[MoltsPay] Skill execution failed:", err.message);
30609
+ return this.sendJson(res, 500, {
30610
+ error: "Service execution failed",
30611
+ message: err.message
30612
+ });
30613
+ }
30614
+ console.log(`[MoltsPay] Skill succeeded, claiming payment...`);
30615
+ let txHash = null;
30616
+ try {
30617
+ txHash = await this.claimPayment(payment);
30618
+ console.log(`[MoltsPay] Payment claimed: ${txHash}`);
30619
+ } catch (err) {
30620
+ console.error("[MoltsPay] Payment claim failed:", err.message);
30621
+ }
30622
+ this.sendJson(res, 200, {
30623
+ success: true,
30624
+ result,
30625
+ payment: txHash ? { txHash, status: "claimed" } : { status: "pending" }
30742
30626
  });
30743
30627
  }
30744
30628
  /**
30745
- * POST /verify - Verify payment and execute skill
30746
- * Body: { chargeId: string, txHash: string }
30629
+ * Return 402 with x402 payment requirements
30747
30630
  */
30748
- async handleVerify(body, res) {
30749
- const { chargeId, txHash } = body;
30750
- if (!chargeId || !txHash) {
30751
- return this.sendJson(res, 400, { error: "Missing chargeId or txHash" });
30631
+ sendPaymentRequired(config, res) {
30632
+ const chain2 = getChain(this.manifest.provider.chain);
30633
+ const amountInUnits = Math.floor(config.price * 1e6).toString();
30634
+ const requirements = [{
30635
+ scheme: "exact",
30636
+ network: `eip155:${chain2.chainId}`,
30637
+ maxAmountRequired: amountInUnits,
30638
+ resource: this.manifest.provider.wallet,
30639
+ description: `${config.name} - $${config.price} ${config.currency}`
30640
+ }];
30641
+ const encoded = Buffer.from(JSON.stringify(requirements)).toString("base64");
30642
+ res.writeHead(402, {
30643
+ "Content-Type": "application/json",
30644
+ [PAYMENT_REQUIRED_HEADER]: encoded
30645
+ });
30646
+ res.end(JSON.stringify({
30647
+ error: "Payment required",
30648
+ message: `Service requires $${config.price} ${config.currency}`,
30649
+ x402: requirements[0]
30650
+ }, null, 2));
30651
+ }
30652
+ /**
30653
+ * Validate x402 payment payload
30654
+ */
30655
+ validatePayment(payment, config) {
30656
+ if (payment.x402Version !== X402_VERSION) {
30657
+ return { valid: false, error: `Unsupported x402 version: ${payment.x402Version}` };
30752
30658
  }
30753
- const charge = this.charges.get(chargeId);
30754
- if (!charge) {
30755
- return this.sendJson(res, 404, { error: "Charge not found" });
30659
+ if (payment.scheme !== "exact") {
30660
+ return { valid: false, error: `Unsupported scheme: ${payment.scheme}` };
30756
30661
  }
30757
- if (Date.now() > charge.expiresAt) {
30758
- charge.status = "expired";
30759
- return this.sendJson(res, 400, { error: "Charge expired" });
30662
+ const chain2 = getChain(this.manifest.provider.chain);
30663
+ const expectedNetwork = `eip155:${chain2.chainId}`;
30664
+ if (payment.network !== expectedNetwork) {
30665
+ return { valid: false, error: `Network mismatch: expected ${expectedNetwork}` };
30760
30666
  }
30761
- if (charge.status === "completed") {
30762
- return this.sendJson(res, 200, {
30763
- status: "completed",
30764
- result: charge.result
30765
- });
30667
+ const auth = payment.payload.authorization;
30668
+ if (auth.to.toLowerCase() !== this.manifest.provider.wallet.toLowerCase()) {
30669
+ return { valid: false, error: "Payment recipient mismatch" };
30766
30670
  }
30767
- try {
30768
- const verification = await verifyPayment({
30769
- txHash,
30770
- expectedTo: this.manifest.provider.wallet,
30771
- expectedAmount: charge.amount,
30772
- chain: this.manifest.provider.chain
30773
- });
30774
- if (!verification.verified) {
30775
- charge.status = "failed";
30776
- return this.sendJson(res, 400, {
30777
- error: "Payment verification failed",
30778
- reason: verification.error
30779
- });
30780
- }
30781
- charge.status = "paid";
30782
- charge.txHash = txHash;
30783
- charge.paidAt = Date.now();
30784
- const skill = this.skills.get(charge.service);
30785
- console.log(`[MoltsPay] Executing skill: ${charge.service}`);
30786
- const result = await skill.handler(charge.params);
30787
- charge.status = "completed";
30788
- charge.result = result;
30789
- charge.completedAt = Date.now();
30790
- this.sendJson(res, 200, {
30791
- status: "completed",
30792
- chargeId,
30793
- txHash,
30794
- result
30795
- });
30796
- } catch (err) {
30797
- console.error("[MoltsPay] Skill execution error:", err);
30798
- charge.status = "failed";
30799
- this.sendJson(res, 500, {
30800
- error: "Skill execution failed",
30801
- message: err.message
30802
- });
30671
+ const amount = Number(auth.value) / 1e6;
30672
+ if (amount < config.price) {
30673
+ return { valid: false, error: `Insufficient amount: $${amount} < $${config.price}` };
30674
+ }
30675
+ const now = Math.floor(Date.now() / 1e3);
30676
+ if (Number(auth.validBefore) < now) {
30677
+ return { valid: false, error: "Payment authorization expired" };
30803
30678
  }
30679
+ if (Number(auth.validAfter) > now) {
30680
+ return { valid: false, error: "Payment authorization not yet valid" };
30681
+ }
30682
+ return { valid: true };
30804
30683
  }
30805
30684
  /**
30806
- * GET /status/:chargeId - Check charge status
30685
+ * Claim payment using transferWithAuthorization
30807
30686
  */
30808
- handleStatus(chargeId, res) {
30809
- const charge = this.charges.get(chargeId);
30810
- if (!charge) {
30811
- return this.sendJson(res, 404, { error: "Charge not found" });
30687
+ async claimPayment(payment) {
30688
+ if (!this.wallet || !this.provider) {
30689
+ throw new Error("Wallet not configured for payment claims");
30812
30690
  }
30813
- this.sendJson(res, 200, {
30814
- chargeId: charge.id,
30815
- service: charge.service,
30816
- amount: charge.amount,
30817
- currency: charge.currency,
30818
- status: charge.status,
30819
- txHash: charge.txHash,
30820
- result: charge.status === "completed" ? charge.result : void 0,
30821
- createdAt: charge.createdAt,
30822
- expiresAt: charge.expiresAt
30823
- });
30691
+ const chain2 = getChain(this.manifest.provider.chain);
30692
+ const auth = payment.payload.authorization;
30693
+ const sig = payment.payload.signature;
30694
+ const { r, s, v } = import_ethers.ethers.Signature.from(sig);
30695
+ const usdcAbi = [
30696
+ "function transferWithAuthorization(address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s)"
30697
+ ];
30698
+ const usdc = new import_ethers.ethers.Contract(chain2.usdc, usdcAbi, this.wallet);
30699
+ const tx = await usdc.transferWithAuthorization(
30700
+ auth.from,
30701
+ auth.to,
30702
+ auth.value,
30703
+ auth.validAfter,
30704
+ auth.validBefore,
30705
+ auth.nonce,
30706
+ v,
30707
+ r,
30708
+ s
30709
+ );
30710
+ const receipt = await tx.wait();
30711
+ return receipt.hash;
30824
30712
  }
30825
30713
  async readBody(req) {
30826
30714
  return new Promise((resolve, reject) => {
@@ -30853,6 +30741,9 @@ var import_ethers2 = require("ethers");
30853
30741
  init_cjs_shims();
30854
30742
 
30855
30743
  // src/client/index.ts
30744
+ var X402_VERSION2 = 2;
30745
+ var PAYMENT_REQUIRED_HEADER2 = "x-payment-required";
30746
+ var PAYMENT_HEADER2 = "x-payment";
30856
30747
  var DEFAULT_CONFIG = {
30857
30748
  chain: "base",
30858
30749
  limits: {
@@ -30916,43 +30807,112 @@ var MoltsPayClient = class {
30916
30807
  return res.json();
30917
30808
  }
30918
30809
  /**
30919
- * Pay for a service and get the result
30810
+ * Pay for a service and get the result (x402 protocol)
30811
+ *
30812
+ * This is GASLESS for the client - server pays gas to claim payment.
30813
+ * This is PAY-FOR-SUCCESS - payment only claimed if service succeeds.
30920
30814
  */
30921
30815
  async pay(serverUrl, service, params) {
30922
- if (!this.wallet) {
30816
+ if (!this.wallet || !this.walletData) {
30923
30817
  throw new Error("Client not initialized. Run: npx moltspay init");
30924
30818
  }
30925
- const payRes = await fetch(`${serverUrl}/pay`, {
30819
+ console.log(`[MoltsPay] Requesting service: ${service}`);
30820
+ const initialRes = await fetch(`${serverUrl}/execute`, {
30926
30821
  method: "POST",
30927
30822
  headers: { "Content-Type": "application/json" },
30928
30823
  body: JSON.stringify({ service, params })
30929
30824
  });
30930
- if (payRes.status !== 402) {
30931
- const err = await payRes.json();
30932
- throw new Error(err.error || "Unexpected response");
30825
+ if (initialRes.status !== 402) {
30826
+ const data = await initialRes.json();
30827
+ if (initialRes.ok && data.result) {
30828
+ return data.result;
30829
+ }
30830
+ throw new Error(data.error || "Unexpected response");
30933
30831
  }
30934
- const paymentReq = await payRes.json();
30935
- const { payment } = paymentReq;
30936
- this.checkLimits(payment.amount);
30937
- console.log(`[MoltsPay] Paying $${payment.amount} ${payment.currency} to ${payment.wallet}`);
30938
- const txHash = await this.executePayment(payment);
30939
- console.log(`[MoltsPay] Payment tx: ${txHash}`);
30940
- const verifyRes = await fetch(`${serverUrl}/verify`, {
30832
+ const paymentRequiredHeader = initialRes.headers.get(PAYMENT_REQUIRED_HEADER2);
30833
+ if (!paymentRequiredHeader) {
30834
+ throw new Error("Missing x-payment-required header");
30835
+ }
30836
+ let requirements;
30837
+ try {
30838
+ const decoded = Buffer.from(paymentRequiredHeader, "base64").toString("utf-8");
30839
+ requirements = JSON.parse(decoded);
30840
+ if (!Array.isArray(requirements)) {
30841
+ requirements = [requirements];
30842
+ }
30843
+ } catch {
30844
+ throw new Error("Invalid x-payment-required header");
30845
+ }
30846
+ const chain2 = getChain(this.config.chain);
30847
+ const network = `eip155:${chain2.chainId}`;
30848
+ const req = requirements.find((r) => r.scheme === "exact" && r.network === network);
30849
+ if (!req) {
30850
+ throw new Error(`No matching payment option for ${network}`);
30851
+ }
30852
+ const amount = Number(req.maxAmountRequired) / 1e6;
30853
+ this.checkLimits(amount);
30854
+ console.log(`[MoltsPay] Signing payment: $${amount} USDC (gasless)`);
30855
+ const authorization = await this.signEIP3009(req.resource, amount, chain2);
30856
+ const payload = {
30857
+ x402Version: X402_VERSION2,
30858
+ scheme: "exact",
30859
+ network,
30860
+ payload: authorization
30861
+ };
30862
+ const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
30863
+ console.log(`[MoltsPay] Sending request with payment...`);
30864
+ const paidRes = await fetch(`${serverUrl}/execute`, {
30941
30865
  method: "POST",
30942
- headers: { "Content-Type": "application/json" },
30943
- body: JSON.stringify({
30944
- chargeId: payment.chargeId,
30945
- txHash
30946
- })
30866
+ headers: {
30867
+ "Content-Type": "application/json",
30868
+ [PAYMENT_HEADER2]: paymentHeader
30869
+ },
30870
+ body: JSON.stringify({ service, params })
30947
30871
  });
30948
- if (!verifyRes.ok) {
30949
- const err = await verifyRes.json();
30950
- throw new Error(err.error || "Verification failed");
30872
+ const result = await paidRes.json();
30873
+ if (!paidRes.ok) {
30874
+ throw new Error(result.error || "Service execution failed");
30951
30875
  }
30952
- const result = await verifyRes.json();
30953
- this.recordSpending(payment.amount);
30876
+ this.recordSpending(amount);
30877
+ console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || "claimed"}`);
30954
30878
  return result.result;
30955
30879
  }
30880
+ /**
30881
+ * Sign EIP-3009 transferWithAuthorization (GASLESS)
30882
+ * This only signs - no on-chain transaction, no gas needed.
30883
+ */
30884
+ async signEIP3009(to, amount, chain2) {
30885
+ const validAfter = 0;
30886
+ const validBefore = Math.floor(Date.now() / 1e3) + 3600;
30887
+ const nonce = import_ethers2.ethers.hexlify(import_ethers2.ethers.randomBytes(32));
30888
+ const value = BigInt(Math.floor(amount * 1e6)).toString();
30889
+ const authorization = {
30890
+ from: this.wallet.address,
30891
+ to,
30892
+ value,
30893
+ validAfter: validAfter.toString(),
30894
+ validBefore: validBefore.toString(),
30895
+ nonce
30896
+ };
30897
+ const domain = {
30898
+ name: "USD Coin",
30899
+ version: "2",
30900
+ chainId: chain2.chainId,
30901
+ verifyingContract: chain2.usdc
30902
+ };
30903
+ const types = {
30904
+ TransferWithAuthorization: [
30905
+ { name: "from", type: "address" },
30906
+ { name: "to", type: "address" },
30907
+ { name: "value", type: "uint256" },
30908
+ { name: "validAfter", type: "uint256" },
30909
+ { name: "validBefore", type: "uint256" },
30910
+ { name: "nonce", type: "bytes32" }
30911
+ ]
30912
+ };
30913
+ const signature = await this.wallet.signTypedData(domain, types, authorization);
30914
+ return { authorization, signature };
30915
+ }
30956
30916
  /**
30957
30917
  * Check spending limits
30958
30918
  */
@@ -30979,36 +30939,6 @@ var MoltsPayClient = class {
30979
30939
  recordSpending(amount) {
30980
30940
  this.todaySpending += amount;
30981
30941
  }
30982
- /**
30983
- * Execute payment on-chain
30984
- */
30985
- async executePayment(payment) {
30986
- let chain2;
30987
- try {
30988
- chain2 = getChain(payment.chain);
30989
- } catch {
30990
- throw new Error(`Unknown chain: ${payment.chain}`);
30991
- }
30992
- const { ethers: ethers4 } = await import("ethers");
30993
- const provider = new ethers4.JsonRpcProvider(chain2.rpc);
30994
- const signer = new ethers4.Wallet(this.walletData.privateKey, provider);
30995
- const usdcAddress = chain2.usdc;
30996
- const usdcAbi = [
30997
- "function transfer(address to, uint256 amount) returns (bool)",
30998
- "function balanceOf(address account) view returns (uint256)"
30999
- ];
31000
- const usdc = new ethers4.Contract(usdcAddress, usdcAbi, signer);
31001
- const amountInUnits = ethers4.parseUnits(payment.amount.toString(), 6);
31002
- const balance = await usdc.balanceOf(this.wallet.address);
31003
- if (balance < amountInUnits) {
31004
- throw new Error(
31005
- `Insufficient USDC balance: ${ethers4.formatUnits(balance, 6)} < ${payment.amount}`
31006
- );
31007
- }
31008
- const tx = await usdc.transfer(payment.wallet, amountInUnits);
31009
- const receipt = await tx.wait();
31010
- return receipt.hash;
31011
- }
31012
30942
  // --- Config & Wallet Management ---
31013
30943
  loadConfig() {
31014
30944
  const configPath = (0, import_path.join)(this.configDir, "config.json");
@@ -31068,15 +30998,14 @@ var MoltsPayClient = class {
31068
30998
  } catch {
31069
30999
  throw new Error(`Unknown chain: ${this.config.chain}`);
31070
31000
  }
31071
- const { ethers: ethers4 } = await import("ethers");
31072
- const provider = new ethers4.JsonRpcProvider(chain2.rpc);
31001
+ const provider = new import_ethers2.ethers.JsonRpcProvider(chain2.rpc);
31073
31002
  const nativeBalance = await provider.getBalance(this.wallet.address);
31074
31003
  const usdcAbi = ["function balanceOf(address) view returns (uint256)"];
31075
- const usdc = new ethers4.Contract(chain2.usdc, usdcAbi, provider);
31004
+ const usdc = new import_ethers2.ethers.Contract(chain2.usdc, usdcAbi, provider);
31076
31005
  const usdcBalance = await usdc.balanceOf(this.wallet.address);
31077
31006
  return {
31078
- usdc: parseFloat(ethers4.formatUnits(usdcBalance, 6)),
31079
- native: parseFloat(ethers4.formatEther(nativeBalance))
31007
+ usdc: parseFloat(import_ethers2.ethers.formatUnits(usdcBalance, 6)),
31008
+ native: parseFloat(import_ethers2.ethers.formatEther(nativeBalance))
31080
31009
  };
31081
31010
  }
31082
31011
  };
@@ -31206,6 +31135,143 @@ function walletExists(storagePath) {
31206
31135
  return (0, import_fs3.existsSync)(path2);
31207
31136
  }
31208
31137
 
31138
+ // src/verify/index.ts
31139
+ init_cjs_shims();
31140
+ var import_ethers5 = require("ethers");
31141
+ var TRANSFER_EVENT_TOPIC = import_ethers5.ethers.id("Transfer(address,address,uint256)");
31142
+ async function verifyPayment(params) {
31143
+ const { txHash, expectedAmount, expectedTo } = params;
31144
+ let chain2;
31145
+ try {
31146
+ if (typeof params.chain === "number") {
31147
+ chain2 = getChainById(params.chain);
31148
+ } else {
31149
+ chain2 = getChain(params.chain || "base");
31150
+ }
31151
+ if (!chain2) {
31152
+ return { verified: false, error: `Unsupported chain: ${params.chain}` };
31153
+ }
31154
+ } catch (e) {
31155
+ return { verified: false, error: `Unsupported chain: ${params.chain}` };
31156
+ }
31157
+ try {
31158
+ const provider = new import_ethers5.ethers.JsonRpcProvider(chain2.rpc);
31159
+ const receipt = await provider.getTransactionReceipt(txHash);
31160
+ if (!receipt) {
31161
+ return { verified: false, error: "Transaction not found or not confirmed" };
31162
+ }
31163
+ if (receipt.status !== 1) {
31164
+ return { verified: false, error: "Transaction failed" };
31165
+ }
31166
+ const usdcAddress = chain2.usdc?.toLowerCase();
31167
+ if (!usdcAddress) {
31168
+ return { verified: false, error: `Chain ${chain2.name} USDC address not configured` };
31169
+ }
31170
+ for (const log of receipt.logs) {
31171
+ if (log.address.toLowerCase() !== usdcAddress) {
31172
+ continue;
31173
+ }
31174
+ if (log.topics.length < 3 || log.topics[0] !== TRANSFER_EVENT_TOPIC) {
31175
+ continue;
31176
+ }
31177
+ const from = "0x" + log.topics[1].slice(-40);
31178
+ const to = "0x" + log.topics[2].slice(-40);
31179
+ const amountRaw = BigInt(log.data);
31180
+ const amount = Number(amountRaw) / 1e6;
31181
+ if (expectedTo && to.toLowerCase() !== expectedTo.toLowerCase()) {
31182
+ continue;
31183
+ }
31184
+ if (amount < expectedAmount) {
31185
+ return {
31186
+ verified: false,
31187
+ error: `Insufficient amount: received ${amount} USDC, expected ${expectedAmount} USDC`,
31188
+ amount,
31189
+ from,
31190
+ to,
31191
+ txHash,
31192
+ blockNumber: receipt.blockNumber
31193
+ };
31194
+ }
31195
+ return {
31196
+ verified: true,
31197
+ amount,
31198
+ from,
31199
+ to,
31200
+ txHash,
31201
+ blockNumber: receipt.blockNumber
31202
+ };
31203
+ }
31204
+ return { verified: false, error: "No USDC transfer found" };
31205
+ } catch (e) {
31206
+ return { verified: false, error: e.message || String(e) };
31207
+ }
31208
+ }
31209
+ async function getTransactionStatus(txHash, chain2 = "base") {
31210
+ let chainConfig;
31211
+ try {
31212
+ chainConfig = typeof chain2 === "number" ? getChainById(chain2) : getChain(chain2);
31213
+ if (!chainConfig) return { status: "not_found" };
31214
+ } catch {
31215
+ return { status: "not_found" };
31216
+ }
31217
+ try {
31218
+ const provider = new import_ethers5.ethers.JsonRpcProvider(chainConfig.rpc);
31219
+ const receipt = await provider.getTransactionReceipt(txHash);
31220
+ if (!receipt) {
31221
+ const tx = await provider.getTransaction(txHash);
31222
+ if (tx) {
31223
+ return { status: "pending" };
31224
+ }
31225
+ return { status: "not_found" };
31226
+ }
31227
+ const currentBlock = await provider.getBlockNumber();
31228
+ const confirmations = currentBlock - receipt.blockNumber;
31229
+ if (receipt.status === 1) {
31230
+ return {
31231
+ status: "confirmed",
31232
+ blockNumber: receipt.blockNumber,
31233
+ confirmations
31234
+ };
31235
+ } else {
31236
+ return {
31237
+ status: "failed",
31238
+ blockNumber: receipt.blockNumber
31239
+ };
31240
+ }
31241
+ } catch {
31242
+ return { status: "not_found" };
31243
+ }
31244
+ }
31245
+ async function waitForTransaction(txHash, chain2 = "base", confirmations = 1, timeoutMs = 6e4) {
31246
+ let chainConfig;
31247
+ try {
31248
+ chainConfig = typeof chain2 === "number" ? getChainById(chain2) : getChain(chain2);
31249
+ if (!chainConfig) {
31250
+ return { verified: false, confirmed: false, error: `Unsupported chain: ${chain2}` };
31251
+ }
31252
+ } catch (e) {
31253
+ return { verified: false, confirmed: false, error: `Unsupported chain: ${chain2}` };
31254
+ }
31255
+ const provider = new import_ethers5.ethers.JsonRpcProvider(chainConfig.rpc);
31256
+ try {
31257
+ const receipt = await provider.waitForTransaction(txHash, confirmations, timeoutMs);
31258
+ if (!receipt) {
31259
+ return { verified: false, confirmed: false, error: "Timeout waiting" };
31260
+ }
31261
+ if (receipt.status !== 1) {
31262
+ return { verified: false, confirmed: true, error: "Transaction failed" };
31263
+ }
31264
+ return {
31265
+ verified: true,
31266
+ confirmed: true,
31267
+ txHash,
31268
+ blockNumber: receipt.blockNumber
31269
+ };
31270
+ } catch (e) {
31271
+ return { verified: false, confirmed: false, error: e.message || String(e) };
31272
+ }
31273
+ }
31274
+
31209
31275
  // src/cdp/index.ts
31210
31276
  init_cjs_shims();
31211
31277
  var fs = __toESM(require("fs"));
@@ -31327,9 +31393,9 @@ var CDPWallet = class {
31327
31393
  * Get USDC balance
31328
31394
  */
31329
31395
  async getBalance() {
31330
- const { ethers: ethers4 } = await import("ethers");
31331
- const provider = new ethers4.JsonRpcProvider(this.chainConfig.rpc);
31332
- const usdcContract = new ethers4.Contract(
31396
+ const { ethers: ethers6 } = await import("ethers");
31397
+ const provider = new ethers6.JsonRpcProvider(this.chainConfig.rpc);
31398
+ const usdcContract = new ethers6.Contract(
31333
31399
  this.chainConfig.usdc,
31334
31400
  ["function balanceOf(address) view returns (uint256)"],
31335
31401
  provider
@@ -31340,7 +31406,7 @@ var CDPWallet = class {
31340
31406
  ]);
31341
31407
  return {
31342
31408
  usdc: (Number(usdcBalance) / 1e6).toFixed(2),
31343
- eth: ethers4.formatEther(ethBalance)
31409
+ eth: ethers6.formatEther(ethBalance)
31344
31410
  };
31345
31411
  }
31346
31412
  /**
@@ -31358,7 +31424,7 @@ var CDPWallet = class {
31358
31424
  }
31359
31425
  try {
31360
31426
  const { CdpClient } = await import("@coinbase/cdp-sdk");
31361
- const { ethers: ethers4 } = await import("ethers");
31427
+ const { ethers: ethers6 } = await import("ethers");
31362
31428
  const cdp = new CdpClient({
31363
31429
  apiKeyId: creds.apiKeyId,
31364
31430
  apiKeySecret: creds.apiKeySecret,
@@ -31366,7 +31432,7 @@ var CDPWallet = class {
31366
31432
  });
31367
31433
  const account = await cdp.evm.getAccount({ address: this.address });
31368
31434
  const amountWei = BigInt(Math.floor(params.amount * 1e6));
31369
- const iface = new ethers4.Interface([
31435
+ const iface = new ethers6.Interface([
31370
31436
  "function transfer(address to, uint256 amount) returns (bool)"
31371
31437
  ]);
31372
31438
  const callData = iface.encodeFunctionData("transfer", [params.to, amountWei]);