moltspay 0.7.2 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -30370,10 +30370,6 @@ init_cjs_shims();
30370
30370
  var import_fs = require("fs");
30371
30371
  var import_http = require("http");
30372
30372
 
30373
- // src/verify/index.ts
30374
- init_cjs_shims();
30375
- var import_ethers = require("ethers");
30376
-
30377
30373
  // src/chains/index.ts
30378
30374
  init_cjs_shims();
30379
30375
  var CHAINS = {
@@ -30452,165 +30448,33 @@ var ERC20_ABI = [
30452
30448
  "event Approval(address indexed owner, address indexed spender, uint256 value)"
30453
30449
  ];
30454
30450
 
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
30451
  // src/server/types.ts
30591
30452
  init_cjs_shims();
30592
30453
 
30593
30454
  // src/server/index.ts
30594
- function generateChargeId() {
30595
- return "ch_" + Math.random().toString(36).substring(2, 15);
30596
- }
30455
+ var X402_VERSION = 2;
30456
+ var PAYMENT_REQUIRED_HEADER = "x-payment-required";
30457
+ var PAYMENT_HEADER = "x-payment";
30458
+ var PAYMENT_RESPONSE_HEADER = "x-payment-response";
30459
+ var DEFAULT_FACILITATOR_URL = "https://x402.org/facilitator";
30597
30460
  var MoltsPayServer = class {
30598
30461
  manifest;
30599
30462
  skills = /* @__PURE__ */ new Map();
30600
- charges = /* @__PURE__ */ new Map();
30601
30463
  options;
30464
+ facilitatorUrl;
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
- host: options.host || "0.0.0.0",
30608
- chargeExpirySecs: options.chargeExpirySecs || 300
30609
- // 5 minutes
30470
+ host: options.host || "0.0.0.0"
30610
30471
  };
30472
+ this.facilitatorUrl = options.facilitatorUrl || DEFAULT_FACILITATOR_URL;
30611
30473
  console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);
30612
30474
  console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);
30613
- console.log(`[MoltsPay] Wallet: ${this.manifest.provider.wallet}`);
30475
+ console.log(`[MoltsPay] Receive wallet: ${this.manifest.provider.wallet}`);
30476
+ console.log(`[MoltsPay] Facilitator: ${this.facilitatorUrl}`);
30477
+ console.log(`[MoltsPay] Protocol: x402 (gasless for both client AND server)`);
30614
30478
  }
30615
30479
  /**
30616
30480
  * Register a skill handler for a service
@@ -30620,56 +30484,45 @@ var MoltsPayServer = class {
30620
30484
  if (!config) {
30621
30485
  throw new Error(`Service '${serviceId}' not found in manifest`);
30622
30486
  }
30623
- this.skills.set(serviceId, {
30624
- id: serviceId,
30625
- config,
30626
- handler
30627
- });
30628
- console.log(`[MoltsPay] Registered skill: ${serviceId} ($${config.price} ${config.currency})`);
30487
+ this.skills.set(serviceId, { id: serviceId, config, handler });
30629
30488
  return this;
30630
30489
  }
30631
30490
  /**
30632
- * Start the server
30491
+ * Start HTTP server
30633
30492
  */
30634
30493
  listen(port) {
30635
- const p = port || this.options.port;
30494
+ const p = port || this.options.port || 3e3;
30495
+ const host = this.options.host || "0.0.0.0";
30636
30496
  const server = (0, import_http.createServer)((req, res) => this.handleRequest(req, res));
30637
- server.listen(p, this.options.host, () => {
30638
- console.log(`[MoltsPay] Server listening on http://${this.options.host}:${p}`);
30497
+ server.listen(p, host, () => {
30498
+ console.log(`[MoltsPay] Server listening on http://${host}:${p}`);
30639
30499
  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`);
30500
+ console.log(` GET /services - List available services`);
30501
+ console.log(` POST /execute - Execute service (x402 payment)`);
30644
30502
  });
30645
30503
  }
30504
+ /**
30505
+ * Handle incoming request
30506
+ */
30646
30507
  async handleRequest(req, res) {
30647
- const url = new URL(req.url || "/", `http://${req.headers.host}`);
30648
- const path2 = url.pathname;
30649
- const method = req.method || "GET";
30650
30508
  res.setHeader("Access-Control-Allow-Origin", "*");
30651
30509
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
30652
- res.setHeader("Access-Control-Allow-Headers", "Content-Type");
30653
- if (method === "OPTIONS") {
30510
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment");
30511
+ res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response");
30512
+ if (req.method === "OPTIONS") {
30654
30513
  res.writeHead(204);
30655
30514
  res.end();
30656
30515
  return;
30657
30516
  }
30658
30517
  try {
30659
- if (method === "GET" && path2 === "/services") {
30518
+ const url = new URL(req.url || "/", `http://${req.headers.host}`);
30519
+ if (url.pathname === "/services" && req.method === "GET") {
30660
30520
  return this.handleGetServices(res);
30661
30521
  }
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") {
30522
+ if (url.pathname === "/execute" && req.method === "POST") {
30667
30523
  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);
30524
+ const paymentHeader = req.headers[PAYMENT_HEADER];
30525
+ return await this.handleExecute(body, paymentHeader, res);
30673
30526
  }
30674
30527
  this.sendJson(res, 404, { error: "Not found" });
30675
30528
  } catch (err) {
@@ -30681,6 +30534,7 @@ var MoltsPayServer = class {
30681
30534
  * GET /services - List available services
30682
30535
  */
30683
30536
  handleGetServices(res) {
30537
+ const chain2 = getChain(this.manifest.provider.chain);
30684
30538
  const services = this.manifest.services.map((s) => ({
30685
30539
  id: s.id,
30686
30540
  name: s.name,
@@ -30693,14 +30547,21 @@ var MoltsPayServer = class {
30693
30547
  }));
30694
30548
  this.sendJson(res, 200, {
30695
30549
  provider: this.manifest.provider,
30696
- services
30550
+ services,
30551
+ x402: {
30552
+ version: X402_VERSION,
30553
+ network: `eip155:${chain2.chainId}`,
30554
+ schemes: ["exact"],
30555
+ facilitator: this.facilitatorUrl
30556
+ }
30697
30557
  });
30698
30558
  }
30699
30559
  /**
30700
- * POST /pay - Create payment request
30560
+ * POST /execute - Execute service with x402 payment
30701
30561
  * Body: { service: string, params: object }
30562
+ * Header: X-Payment (optional - if missing, returns 402)
30702
30563
  */
30703
- handlePay(body, res) {
30564
+ async handleExecute(body, paymentHeader, res) {
30704
30565
  const { service, params } = body;
30705
30566
  if (!service) {
30706
30567
  return this.sendJson(res, 400, { error: "Missing service" });
@@ -30714,113 +30575,162 @@ var MoltsPayServer = class {
30714
30575
  return this.sendJson(res, 400, { error: `Missing required param: ${key}` });
30715
30576
  }
30716
30577
  }
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
30578
+ if (!paymentHeader) {
30579
+ return this.sendPaymentRequired(skill.config, res);
30580
+ }
30581
+ let payment;
30582
+ try {
30583
+ const decoded = Buffer.from(paymentHeader, "base64").toString("utf-8");
30584
+ payment = JSON.parse(decoded);
30585
+ } catch {
30586
+ return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
30587
+ }
30588
+ const validation = this.validatePayment(payment, skill.config);
30589
+ if (!validation.valid) {
30590
+ return this.sendJson(res, 402, { error: validation.error });
30591
+ }
30592
+ console.log(`[MoltsPay] Verifying payment with facilitator...`);
30593
+ const verifyResult = await this.verifyWithFacilitator(payment, skill.config);
30594
+ if (!verifyResult.valid) {
30595
+ return this.sendJson(res, 402, { error: `Payment verification failed: ${verifyResult.error}` });
30596
+ }
30597
+ console.log(`[MoltsPay] Executing skill: ${service}`);
30598
+ let result;
30599
+ try {
30600
+ result = await skill.handler(params || {});
30601
+ } catch (err) {
30602
+ console.error("[MoltsPay] Skill execution failed:", err.message);
30603
+ return this.sendJson(res, 500, {
30604
+ error: "Service execution failed",
30605
+ message: err.message
30606
+ });
30607
+ }
30608
+ console.log(`[MoltsPay] Skill succeeded, settling payment...`);
30609
+ let settlement = null;
30610
+ try {
30611
+ settlement = await this.settleWithFacilitator(payment, skill.config);
30612
+ console.log(`[MoltsPay] Payment settled: ${settlement.transaction || "pending"}`);
30613
+ } catch (err) {
30614
+ console.error("[MoltsPay] Settlement failed:", err.message);
30615
+ }
30616
+ const responseHeaders = {};
30617
+ if (settlement) {
30618
+ const responsePayload = {
30619
+ success: true,
30620
+ transaction: settlement.transaction,
30621
+ network: payment.network
30622
+ };
30623
+ responseHeaders[PAYMENT_RESPONSE_HEADER] = Buffer.from(
30624
+ JSON.stringify(responsePayload)
30625
+ ).toString("base64");
30626
+ }
30627
+ this.sendJson(res, 200, {
30628
+ success: true,
30629
+ result,
30630
+ payment: settlement ? { transaction: settlement.transaction, status: "settled" } : { status: "pending" }
30631
+ }, responseHeaders);
30632
+ }
30633
+ /**
30634
+ * Return 402 with x402 payment requirements
30635
+ */
30636
+ sendPaymentRequired(config, res) {
30637
+ const chain2 = getChain(this.manifest.provider.chain);
30638
+ const amountInUnits = Math.floor(config.price * 1e6).toString();
30639
+ const requirements = [{
30640
+ scheme: "exact",
30641
+ network: `eip155:${chain2.chainId}`,
30642
+ maxAmountRequired: amountInUnits,
30643
+ resource: this.manifest.provider.wallet,
30644
+ description: `${config.name} - $${config.price} ${config.currency}`,
30645
+ // Include facilitator info for client
30646
+ extra: JSON.stringify({ facilitator: this.facilitatorUrl })
30647
+ }];
30648
+ const encoded = Buffer.from(JSON.stringify(requirements)).toString("base64");
30649
+ res.writeHead(402, {
30650
+ "Content-Type": "application/json",
30651
+ [PAYMENT_REQUIRED_HEADER]: encoded
30742
30652
  });
30653
+ res.end(JSON.stringify({
30654
+ error: "Payment required",
30655
+ message: `Service requires $${config.price} ${config.currency}`,
30656
+ x402: requirements[0]
30657
+ }, null, 2));
30743
30658
  }
30744
30659
  /**
30745
- * POST /verify - Verify payment and execute skill
30746
- * Body: { chargeId: string, txHash: string }
30660
+ * Basic payment validation (before calling facilitator)
30747
30661
  */
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" });
30752
- }
30753
- const charge = this.charges.get(chargeId);
30754
- if (!charge) {
30755
- return this.sendJson(res, 404, { error: "Charge not found" });
30662
+ validatePayment(payment, config) {
30663
+ if (payment.x402Version !== X402_VERSION) {
30664
+ return { valid: false, error: `Unsupported x402 version: ${payment.x402Version}` };
30756
30665
  }
30757
- if (Date.now() > charge.expiresAt) {
30758
- charge.status = "expired";
30759
- return this.sendJson(res, 400, { error: "Charge expired" });
30666
+ if (payment.scheme !== "exact") {
30667
+ return { valid: false, error: `Unsupported scheme: ${payment.scheme}` };
30760
30668
  }
30761
- if (charge.status === "completed") {
30762
- return this.sendJson(res, 200, {
30763
- status: "completed",
30764
- result: charge.result
30765
- });
30669
+ const chain2 = getChain(this.manifest.provider.chain);
30670
+ const expectedNetwork = `eip155:${chain2.chainId}`;
30671
+ if (payment.network !== expectedNetwork) {
30672
+ return { valid: false, error: `Network mismatch: expected ${expectedNetwork}` };
30766
30673
  }
30674
+ return { valid: true };
30675
+ }
30676
+ /**
30677
+ * Verify payment with facilitator
30678
+ */
30679
+ async verifyWithFacilitator(payment, config) {
30767
30680
  try {
30768
- const verification = await verifyPayment({
30769
- txHash,
30770
- expectedTo: this.manifest.provider.wallet,
30771
- expectedAmount: charge.amount,
30772
- chain: this.manifest.provider.chain
30681
+ const chain2 = getChain(this.manifest.provider.chain);
30682
+ const amountInUnits = Math.floor(config.price * 1e6).toString();
30683
+ const requirements = {
30684
+ scheme: "exact",
30685
+ network: `eip155:${chain2.chainId}`,
30686
+ maxAmountRequired: amountInUnits,
30687
+ resource: this.manifest.provider.wallet
30688
+ };
30689
+ const response = await fetch(`${this.facilitatorUrl}/verify`, {
30690
+ method: "POST",
30691
+ headers: { "Content-Type": "application/json" },
30692
+ body: JSON.stringify({
30693
+ paymentPayload: payment,
30694
+ paymentRequirements: requirements
30695
+ })
30773
30696
  });
30774
- if (!verification.verified) {
30775
- charge.status = "failed";
30776
- return this.sendJson(res, 400, {
30777
- error: "Payment verification failed",
30778
- reason: verification.error
30779
- });
30697
+ const result = await response.json();
30698
+ if (!response.ok || !result.isValid) {
30699
+ return { valid: false, error: result.invalidReason || "Verification failed" };
30780
30700
  }
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
- });
30701
+ return { valid: true };
30796
30702
  } 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
- });
30703
+ return { valid: false, error: `Facilitator error: ${err.message}` };
30803
30704
  }
30804
30705
  }
30805
30706
  /**
30806
- * GET /status/:chargeId - Check charge status
30707
+ * Settle payment with facilitator (execute on-chain transfer)
30807
30708
  */
30808
- handleStatus(chargeId, res) {
30809
- const charge = this.charges.get(chargeId);
30810
- if (!charge) {
30811
- return this.sendJson(res, 404, { error: "Charge not found" });
30812
- }
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
30709
+ async settleWithFacilitator(payment, config) {
30710
+ const chain2 = getChain(this.manifest.provider.chain);
30711
+ const amountInUnits = Math.floor(config.price * 1e6).toString();
30712
+ const requirements = {
30713
+ scheme: "exact",
30714
+ network: `eip155:${chain2.chainId}`,
30715
+ maxAmountRequired: amountInUnits,
30716
+ resource: this.manifest.provider.wallet
30717
+ };
30718
+ const response = await fetch(`${this.facilitatorUrl}/settle`, {
30719
+ method: "POST",
30720
+ headers: { "Content-Type": "application/json" },
30721
+ body: JSON.stringify({
30722
+ paymentPayload: payment,
30723
+ paymentRequirements: requirements
30724
+ })
30823
30725
  });
30726
+ const result = await response.json();
30727
+ if (!response.ok) {
30728
+ throw new Error(result.error || "Settlement failed");
30729
+ }
30730
+ return {
30731
+ transaction: result.transaction,
30732
+ status: result.status || "settled"
30733
+ };
30824
30734
  }
30825
30735
  async readBody(req) {
30826
30736
  return new Promise((resolve, reject) => {
@@ -30836,8 +30746,12 @@ var MoltsPayServer = class {
30836
30746
  req.on("error", reject);
30837
30747
  });
30838
30748
  }
30839
- sendJson(res, status, data) {
30840
- res.writeHead(status, { "Content-Type": "application/json" });
30749
+ sendJson(res, status, data, extraHeaders) {
30750
+ const headers = { "Content-Type": "application/json" };
30751
+ if (extraHeaders) {
30752
+ Object.assign(headers, extraHeaders);
30753
+ }
30754
+ res.writeHead(status, headers);
30841
30755
  res.end(JSON.stringify(data, null, 2));
30842
30756
  }
30843
30757
  };
@@ -30847,12 +30761,15 @@ init_cjs_shims();
30847
30761
  var import_fs2 = require("fs");
30848
30762
  var import_os = require("os");
30849
30763
  var import_path = require("path");
30850
- var import_ethers2 = require("ethers");
30764
+ var import_ethers = require("ethers");
30851
30765
 
30852
30766
  // src/client/types.ts
30853
30767
  init_cjs_shims();
30854
30768
 
30855
30769
  // src/client/index.ts
30770
+ var X402_VERSION2 = 2;
30771
+ var PAYMENT_REQUIRED_HEADER2 = "x-payment-required";
30772
+ var PAYMENT_HEADER2 = "x-payment";
30856
30773
  var DEFAULT_CONFIG = {
30857
30774
  chain: "base",
30858
30775
  limits: {
@@ -30872,7 +30789,7 @@ var MoltsPayClient = class {
30872
30789
  this.config = this.loadConfig();
30873
30790
  this.walletData = this.loadWallet();
30874
30791
  if (this.walletData) {
30875
- this.wallet = new import_ethers2.Wallet(this.walletData.privateKey);
30792
+ this.wallet = new import_ethers.Wallet(this.walletData.privateKey);
30876
30793
  }
30877
30794
  }
30878
30795
  /**
@@ -30916,43 +30833,112 @@ var MoltsPayClient = class {
30916
30833
  return res.json();
30917
30834
  }
30918
30835
  /**
30919
- * Pay for a service and get the result
30836
+ * Pay for a service and get the result (x402 protocol)
30837
+ *
30838
+ * This is GASLESS for the client - server pays gas to claim payment.
30839
+ * This is PAY-FOR-SUCCESS - payment only claimed if service succeeds.
30920
30840
  */
30921
30841
  async pay(serverUrl, service, params) {
30922
- if (!this.wallet) {
30842
+ if (!this.wallet || !this.walletData) {
30923
30843
  throw new Error("Client not initialized. Run: npx moltspay init");
30924
30844
  }
30925
- const payRes = await fetch(`${serverUrl}/pay`, {
30845
+ console.log(`[MoltsPay] Requesting service: ${service}`);
30846
+ const initialRes = await fetch(`${serverUrl}/execute`, {
30926
30847
  method: "POST",
30927
30848
  headers: { "Content-Type": "application/json" },
30928
30849
  body: JSON.stringify({ service, params })
30929
30850
  });
30930
- if (payRes.status !== 402) {
30931
- const err = await payRes.json();
30932
- throw new Error(err.error || "Unexpected response");
30851
+ if (initialRes.status !== 402) {
30852
+ const data = await initialRes.json();
30853
+ if (initialRes.ok && data.result) {
30854
+ return data.result;
30855
+ }
30856
+ throw new Error(data.error || "Unexpected response");
30857
+ }
30858
+ const paymentRequiredHeader = initialRes.headers.get(PAYMENT_REQUIRED_HEADER2);
30859
+ if (!paymentRequiredHeader) {
30860
+ throw new Error("Missing x-payment-required header");
30861
+ }
30862
+ let requirements;
30863
+ try {
30864
+ const decoded = Buffer.from(paymentRequiredHeader, "base64").toString("utf-8");
30865
+ requirements = JSON.parse(decoded);
30866
+ if (!Array.isArray(requirements)) {
30867
+ requirements = [requirements];
30868
+ }
30869
+ } catch {
30870
+ throw new Error("Invalid x-payment-required header");
30933
30871
  }
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`, {
30872
+ const chain2 = getChain(this.config.chain);
30873
+ const network = `eip155:${chain2.chainId}`;
30874
+ const req = requirements.find((r) => r.scheme === "exact" && r.network === network);
30875
+ if (!req) {
30876
+ throw new Error(`No matching payment option for ${network}`);
30877
+ }
30878
+ const amount = Number(req.maxAmountRequired) / 1e6;
30879
+ this.checkLimits(amount);
30880
+ console.log(`[MoltsPay] Signing payment: $${amount} USDC (gasless)`);
30881
+ const authorization = await this.signEIP3009(req.resource, amount, chain2);
30882
+ const payload = {
30883
+ x402Version: X402_VERSION2,
30884
+ scheme: "exact",
30885
+ network,
30886
+ payload: authorization
30887
+ };
30888
+ const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
30889
+ console.log(`[MoltsPay] Sending request with payment...`);
30890
+ const paidRes = await fetch(`${serverUrl}/execute`, {
30941
30891
  method: "POST",
30942
- headers: { "Content-Type": "application/json" },
30943
- body: JSON.stringify({
30944
- chargeId: payment.chargeId,
30945
- txHash
30946
- })
30892
+ headers: {
30893
+ "Content-Type": "application/json",
30894
+ [PAYMENT_HEADER2]: paymentHeader
30895
+ },
30896
+ body: JSON.stringify({ service, params })
30947
30897
  });
30948
- if (!verifyRes.ok) {
30949
- const err = await verifyRes.json();
30950
- throw new Error(err.error || "Verification failed");
30898
+ const result = await paidRes.json();
30899
+ if (!paidRes.ok) {
30900
+ throw new Error(result.error || "Service execution failed");
30951
30901
  }
30952
- const result = await verifyRes.json();
30953
- this.recordSpending(payment.amount);
30902
+ this.recordSpending(amount);
30903
+ console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || "claimed"}`);
30954
30904
  return result.result;
30955
30905
  }
30906
+ /**
30907
+ * Sign EIP-3009 transferWithAuthorization (GASLESS)
30908
+ * This only signs - no on-chain transaction, no gas needed.
30909
+ */
30910
+ async signEIP3009(to, amount, chain2) {
30911
+ const validAfter = 0;
30912
+ const validBefore = Math.floor(Date.now() / 1e3) + 3600;
30913
+ const nonce = import_ethers.ethers.hexlify(import_ethers.ethers.randomBytes(32));
30914
+ const value = BigInt(Math.floor(amount * 1e6)).toString();
30915
+ const authorization = {
30916
+ from: this.wallet.address,
30917
+ to,
30918
+ value,
30919
+ validAfter: validAfter.toString(),
30920
+ validBefore: validBefore.toString(),
30921
+ nonce
30922
+ };
30923
+ const domain = {
30924
+ name: "USD Coin",
30925
+ version: "2",
30926
+ chainId: chain2.chainId,
30927
+ verifyingContract: chain2.usdc
30928
+ };
30929
+ const types = {
30930
+ TransferWithAuthorization: [
30931
+ { name: "from", type: "address" },
30932
+ { name: "to", type: "address" },
30933
+ { name: "value", type: "uint256" },
30934
+ { name: "validAfter", type: "uint256" },
30935
+ { name: "validBefore", type: "uint256" },
30936
+ { name: "nonce", type: "bytes32" }
30937
+ ]
30938
+ };
30939
+ const signature = await this.wallet.signTypedData(domain, types, authorization);
30940
+ return { authorization, signature };
30941
+ }
30956
30942
  /**
30957
30943
  * Check spending limits
30958
30944
  */
@@ -30979,36 +30965,6 @@ var MoltsPayClient = class {
30979
30965
  recordSpending(amount) {
30980
30966
  this.todaySpending += amount;
30981
30967
  }
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
30968
  // --- Config & Wallet Management ---
31013
30969
  loadConfig() {
31014
30970
  const configPath = (0, import_path.join)(this.configDir, "config.json");
@@ -31036,7 +30992,7 @@ var MoltsPayClient = class {
31036
30992
  */
31037
30993
  static init(configDir, options) {
31038
30994
  (0, import_fs2.mkdirSync)(configDir, { recursive: true });
31039
- const wallet = import_ethers2.Wallet.createRandom();
30995
+ const wallet = import_ethers.Wallet.createRandom();
31040
30996
  const walletData = {
31041
30997
  address: wallet.address,
31042
30998
  privateKey: wallet.privateKey,
@@ -31068,15 +31024,14 @@ var MoltsPayClient = class {
31068
31024
  } catch {
31069
31025
  throw new Error(`Unknown chain: ${this.config.chain}`);
31070
31026
  }
31071
- const { ethers: ethers4 } = await import("ethers");
31072
- const provider = new ethers4.JsonRpcProvider(chain2.rpc);
31027
+ const provider = new import_ethers.ethers.JsonRpcProvider(chain2.rpc);
31073
31028
  const nativeBalance = await provider.getBalance(this.wallet.address);
31074
31029
  const usdcAbi = ["function balanceOf(address) view returns (uint256)"];
31075
- const usdc = new ethers4.Contract(chain2.usdc, usdcAbi, provider);
31030
+ const usdc = new import_ethers.ethers.Contract(chain2.usdc, usdcAbi, provider);
31076
31031
  const usdcBalance = await usdc.balanceOf(this.wallet.address);
31077
31032
  return {
31078
- usdc: parseFloat(ethers4.formatUnits(usdcBalance, 6)),
31079
- native: parseFloat(ethers4.formatEther(nativeBalance))
31033
+ usdc: parseFloat(import_ethers.ethers.formatUnits(usdcBalance, 6)),
31034
+ native: parseFloat(import_ethers.ethers.formatEther(nativeBalance))
31080
31035
  };
31081
31036
  }
31082
31037
  };
@@ -31086,11 +31041,11 @@ init_cjs_shims();
31086
31041
 
31087
31042
  // src/wallet/Wallet.ts
31088
31043
  init_cjs_shims();
31089
- var import_ethers3 = require("ethers");
31044
+ var import_ethers2 = require("ethers");
31090
31045
 
31091
31046
  // src/wallet/createWallet.ts
31092
31047
  init_cjs_shims();
31093
- var import_ethers4 = require("ethers");
31048
+ var import_ethers3 = require("ethers");
31094
31049
  var import_fs3 = require("fs");
31095
31050
  var import_path2 = require("path");
31096
31051
  var import_crypto = require("crypto");
@@ -31135,7 +31090,7 @@ function createWallet(options = {}) {
31135
31090
  }
31136
31091
  }
31137
31092
  try {
31138
- const wallet = import_ethers4.ethers.Wallet.createRandom();
31093
+ const wallet = import_ethers3.ethers.Wallet.createRandom();
31139
31094
  const walletData = {
31140
31095
  address: wallet.address,
31141
31096
  label: options.label,
@@ -31206,6 +31161,143 @@ function walletExists(storagePath) {
31206
31161
  return (0, import_fs3.existsSync)(path2);
31207
31162
  }
31208
31163
 
31164
+ // src/verify/index.ts
31165
+ init_cjs_shims();
31166
+ var import_ethers4 = require("ethers");
31167
+ var TRANSFER_EVENT_TOPIC = import_ethers4.ethers.id("Transfer(address,address,uint256)");
31168
+ async function verifyPayment(params) {
31169
+ const { txHash, expectedAmount, expectedTo } = params;
31170
+ let chain2;
31171
+ try {
31172
+ if (typeof params.chain === "number") {
31173
+ chain2 = getChainById(params.chain);
31174
+ } else {
31175
+ chain2 = getChain(params.chain || "base");
31176
+ }
31177
+ if (!chain2) {
31178
+ return { verified: false, error: `Unsupported chain: ${params.chain}` };
31179
+ }
31180
+ } catch (e) {
31181
+ return { verified: false, error: `Unsupported chain: ${params.chain}` };
31182
+ }
31183
+ try {
31184
+ const provider = new import_ethers4.ethers.JsonRpcProvider(chain2.rpc);
31185
+ const receipt = await provider.getTransactionReceipt(txHash);
31186
+ if (!receipt) {
31187
+ return { verified: false, error: "Transaction not found or not confirmed" };
31188
+ }
31189
+ if (receipt.status !== 1) {
31190
+ return { verified: false, error: "Transaction failed" };
31191
+ }
31192
+ const usdcAddress = chain2.usdc?.toLowerCase();
31193
+ if (!usdcAddress) {
31194
+ return { verified: false, error: `Chain ${chain2.name} USDC address not configured` };
31195
+ }
31196
+ for (const log of receipt.logs) {
31197
+ if (log.address.toLowerCase() !== usdcAddress) {
31198
+ continue;
31199
+ }
31200
+ if (log.topics.length < 3 || log.topics[0] !== TRANSFER_EVENT_TOPIC) {
31201
+ continue;
31202
+ }
31203
+ const from = "0x" + log.topics[1].slice(-40);
31204
+ const to = "0x" + log.topics[2].slice(-40);
31205
+ const amountRaw = BigInt(log.data);
31206
+ const amount = Number(amountRaw) / 1e6;
31207
+ if (expectedTo && to.toLowerCase() !== expectedTo.toLowerCase()) {
31208
+ continue;
31209
+ }
31210
+ if (amount < expectedAmount) {
31211
+ return {
31212
+ verified: false,
31213
+ error: `Insufficient amount: received ${amount} USDC, expected ${expectedAmount} USDC`,
31214
+ amount,
31215
+ from,
31216
+ to,
31217
+ txHash,
31218
+ blockNumber: receipt.blockNumber
31219
+ };
31220
+ }
31221
+ return {
31222
+ verified: true,
31223
+ amount,
31224
+ from,
31225
+ to,
31226
+ txHash,
31227
+ blockNumber: receipt.blockNumber
31228
+ };
31229
+ }
31230
+ return { verified: false, error: "No USDC transfer found" };
31231
+ } catch (e) {
31232
+ return { verified: false, error: e.message || String(e) };
31233
+ }
31234
+ }
31235
+ async function getTransactionStatus(txHash, chain2 = "base") {
31236
+ let chainConfig;
31237
+ try {
31238
+ chainConfig = typeof chain2 === "number" ? getChainById(chain2) : getChain(chain2);
31239
+ if (!chainConfig) return { status: "not_found" };
31240
+ } catch {
31241
+ return { status: "not_found" };
31242
+ }
31243
+ try {
31244
+ const provider = new import_ethers4.ethers.JsonRpcProvider(chainConfig.rpc);
31245
+ const receipt = await provider.getTransactionReceipt(txHash);
31246
+ if (!receipt) {
31247
+ const tx = await provider.getTransaction(txHash);
31248
+ if (tx) {
31249
+ return { status: "pending" };
31250
+ }
31251
+ return { status: "not_found" };
31252
+ }
31253
+ const currentBlock = await provider.getBlockNumber();
31254
+ const confirmations = currentBlock - receipt.blockNumber;
31255
+ if (receipt.status === 1) {
31256
+ return {
31257
+ status: "confirmed",
31258
+ blockNumber: receipt.blockNumber,
31259
+ confirmations
31260
+ };
31261
+ } else {
31262
+ return {
31263
+ status: "failed",
31264
+ blockNumber: receipt.blockNumber
31265
+ };
31266
+ }
31267
+ } catch {
31268
+ return { status: "not_found" };
31269
+ }
31270
+ }
31271
+ async function waitForTransaction(txHash, chain2 = "base", confirmations = 1, timeoutMs = 6e4) {
31272
+ let chainConfig;
31273
+ try {
31274
+ chainConfig = typeof chain2 === "number" ? getChainById(chain2) : getChain(chain2);
31275
+ if (!chainConfig) {
31276
+ return { verified: false, confirmed: false, error: `Unsupported chain: ${chain2}` };
31277
+ }
31278
+ } catch (e) {
31279
+ return { verified: false, confirmed: false, error: `Unsupported chain: ${chain2}` };
31280
+ }
31281
+ const provider = new import_ethers4.ethers.JsonRpcProvider(chainConfig.rpc);
31282
+ try {
31283
+ const receipt = await provider.waitForTransaction(txHash, confirmations, timeoutMs);
31284
+ if (!receipt) {
31285
+ return { verified: false, confirmed: false, error: "Timeout waiting" };
31286
+ }
31287
+ if (receipt.status !== 1) {
31288
+ return { verified: false, confirmed: true, error: "Transaction failed" };
31289
+ }
31290
+ return {
31291
+ verified: true,
31292
+ confirmed: true,
31293
+ txHash,
31294
+ blockNumber: receipt.blockNumber
31295
+ };
31296
+ } catch (e) {
31297
+ return { verified: false, confirmed: false, error: e.message || String(e) };
31298
+ }
31299
+ }
31300
+
31209
31301
  // src/cdp/index.ts
31210
31302
  init_cjs_shims();
31211
31303
  var fs = __toESM(require("fs"));
@@ -31327,9 +31419,9 @@ var CDPWallet = class {
31327
31419
  * Get USDC balance
31328
31420
  */
31329
31421
  async getBalance() {
31330
- const { ethers: ethers4 } = await import("ethers");
31331
- const provider = new ethers4.JsonRpcProvider(this.chainConfig.rpc);
31332
- const usdcContract = new ethers4.Contract(
31422
+ const { ethers: ethers5 } = await import("ethers");
31423
+ const provider = new ethers5.JsonRpcProvider(this.chainConfig.rpc);
31424
+ const usdcContract = new ethers5.Contract(
31333
31425
  this.chainConfig.usdc,
31334
31426
  ["function balanceOf(address) view returns (uint256)"],
31335
31427
  provider
@@ -31340,7 +31432,7 @@ var CDPWallet = class {
31340
31432
  ]);
31341
31433
  return {
31342
31434
  usdc: (Number(usdcBalance) / 1e6).toFixed(2),
31343
- eth: ethers4.formatEther(ethBalance)
31435
+ eth: ethers5.formatEther(ethBalance)
31344
31436
  };
31345
31437
  }
31346
31438
  /**
@@ -31358,7 +31450,7 @@ var CDPWallet = class {
31358
31450
  }
31359
31451
  try {
31360
31452
  const { CdpClient } = await import("@coinbase/cdp-sdk");
31361
- const { ethers: ethers4 } = await import("ethers");
31453
+ const { ethers: ethers5 } = await import("ethers");
31362
31454
  const cdp = new CdpClient({
31363
31455
  apiKeyId: creds.apiKeyId,
31364
31456
  apiKeySecret: creds.apiKeySecret,
@@ -31366,7 +31458,7 @@ var CDPWallet = class {
31366
31458
  });
31367
31459
  const account = await cdp.evm.getAccount({ address: this.address });
31368
31460
  const amountWei = BigInt(Math.floor(params.amount * 1e6));
31369
- const iface = new ethers4.Interface([
31461
+ const iface = new ethers5.Interface([
31370
31462
  "function transfer(address to, uint256 amount) returns (bool)"
31371
31463
  ]);
31372
31464
  const callData = iface.encodeFunctionData("transfer", [params.to, amountWei]);