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.mjs CHANGED
@@ -30333,10 +30333,6 @@ init_esm_shims();
30333
30333
  import { readFileSync } from "fs";
30334
30334
  import { createServer } from "http";
30335
30335
 
30336
- // src/verify/index.ts
30337
- init_esm_shims();
30338
- import { ethers } from "ethers";
30339
-
30340
30336
  // src/chains/index.ts
30341
30337
  init_esm_shims();
30342
30338
  var CHAINS = {
@@ -30415,165 +30411,33 @@ var ERC20_ABI = [
30415
30411
  "event Approval(address indexed owner, address indexed spender, uint256 value)"
30416
30412
  ];
30417
30413
 
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
30414
  // src/server/types.ts
30554
30415
  init_esm_shims();
30555
30416
 
30556
30417
  // src/server/index.ts
30557
- function generateChargeId() {
30558
- return "ch_" + Math.random().toString(36).substring(2, 15);
30559
- }
30418
+ var X402_VERSION = 2;
30419
+ var PAYMENT_REQUIRED_HEADER = "x-payment-required";
30420
+ var PAYMENT_HEADER = "x-payment";
30421
+ var PAYMENT_RESPONSE_HEADER = "x-payment-response";
30422
+ var DEFAULT_FACILITATOR_URL = "https://x402.org/facilitator";
30560
30423
  var MoltsPayServer = class {
30561
30424
  manifest;
30562
30425
  skills = /* @__PURE__ */ new Map();
30563
- charges = /* @__PURE__ */ new Map();
30564
30426
  options;
30427
+ facilitatorUrl;
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
- host: options.host || "0.0.0.0",
30571
- chargeExpirySecs: options.chargeExpirySecs || 300
30572
- // 5 minutes
30433
+ host: options.host || "0.0.0.0"
30573
30434
  };
30435
+ this.facilitatorUrl = options.facilitatorUrl || DEFAULT_FACILITATOR_URL;
30574
30436
  console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);
30575
30437
  console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);
30576
- console.log(`[MoltsPay] Wallet: ${this.manifest.provider.wallet}`);
30438
+ console.log(`[MoltsPay] Receive wallet: ${this.manifest.provider.wallet}`);
30439
+ console.log(`[MoltsPay] Facilitator: ${this.facilitatorUrl}`);
30440
+ console.log(`[MoltsPay] Protocol: x402 (gasless for both client AND server)`);
30577
30441
  }
30578
30442
  /**
30579
30443
  * Register a skill handler for a service
@@ -30583,56 +30447,45 @@ var MoltsPayServer = class {
30583
30447
  if (!config) {
30584
30448
  throw new Error(`Service '${serviceId}' not found in manifest`);
30585
30449
  }
30586
- this.skills.set(serviceId, {
30587
- id: serviceId,
30588
- config,
30589
- handler
30590
- });
30591
- console.log(`[MoltsPay] Registered skill: ${serviceId} ($${config.price} ${config.currency})`);
30450
+ this.skills.set(serviceId, { id: serviceId, config, handler });
30592
30451
  return this;
30593
30452
  }
30594
30453
  /**
30595
- * Start the server
30454
+ * Start HTTP server
30596
30455
  */
30597
30456
  listen(port) {
30598
- const p = port || this.options.port;
30457
+ const p = port || this.options.port || 3e3;
30458
+ const host = this.options.host || "0.0.0.0";
30599
30459
  const server = createServer((req, res) => this.handleRequest(req, res));
30600
- server.listen(p, this.options.host, () => {
30601
- console.log(`[MoltsPay] Server listening on http://${this.options.host}:${p}`);
30460
+ server.listen(p, host, () => {
30461
+ console.log(`[MoltsPay] Server listening on http://${host}:${p}`);
30602
30462
  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`);
30463
+ console.log(` GET /services - List available services`);
30464
+ console.log(` POST /execute - Execute service (x402 payment)`);
30607
30465
  });
30608
30466
  }
30467
+ /**
30468
+ * Handle incoming request
30469
+ */
30609
30470
  async handleRequest(req, res) {
30610
- const url = new URL(req.url || "/", `http://${req.headers.host}`);
30611
- const path3 = url.pathname;
30612
- const method = req.method || "GET";
30613
30471
  res.setHeader("Access-Control-Allow-Origin", "*");
30614
30472
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
30615
- res.setHeader("Access-Control-Allow-Headers", "Content-Type");
30616
- if (method === "OPTIONS") {
30473
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment");
30474
+ res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response");
30475
+ if (req.method === "OPTIONS") {
30617
30476
  res.writeHead(204);
30618
30477
  res.end();
30619
30478
  return;
30620
30479
  }
30621
30480
  try {
30622
- if (method === "GET" && path3 === "/services") {
30481
+ const url = new URL(req.url || "/", `http://${req.headers.host}`);
30482
+ if (url.pathname === "/services" && req.method === "GET") {
30623
30483
  return this.handleGetServices(res);
30624
30484
  }
30625
- if (method === "POST" && path3 === "/pay") {
30626
- const body = await this.readBody(req);
30627
- return this.handlePay(body, res);
30628
- }
30629
- if (method === "POST" && path3 === "/verify") {
30485
+ if (url.pathname === "/execute" && req.method === "POST") {
30630
30486
  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);
30487
+ const paymentHeader = req.headers[PAYMENT_HEADER];
30488
+ return await this.handleExecute(body, paymentHeader, res);
30636
30489
  }
30637
30490
  this.sendJson(res, 404, { error: "Not found" });
30638
30491
  } catch (err) {
@@ -30644,6 +30497,7 @@ var MoltsPayServer = class {
30644
30497
  * GET /services - List available services
30645
30498
  */
30646
30499
  handleGetServices(res) {
30500
+ const chain2 = getChain(this.manifest.provider.chain);
30647
30501
  const services = this.manifest.services.map((s) => ({
30648
30502
  id: s.id,
30649
30503
  name: s.name,
@@ -30656,14 +30510,21 @@ var MoltsPayServer = class {
30656
30510
  }));
30657
30511
  this.sendJson(res, 200, {
30658
30512
  provider: this.manifest.provider,
30659
- services
30513
+ services,
30514
+ x402: {
30515
+ version: X402_VERSION,
30516
+ network: `eip155:${chain2.chainId}`,
30517
+ schemes: ["exact"],
30518
+ facilitator: this.facilitatorUrl
30519
+ }
30660
30520
  });
30661
30521
  }
30662
30522
  /**
30663
- * POST /pay - Create payment request
30523
+ * POST /execute - Execute service with x402 payment
30664
30524
  * Body: { service: string, params: object }
30525
+ * Header: X-Payment (optional - if missing, returns 402)
30665
30526
  */
30666
- handlePay(body, res) {
30527
+ async handleExecute(body, paymentHeader, res) {
30667
30528
  const { service, params } = body;
30668
30529
  if (!service) {
30669
30530
  return this.sendJson(res, 400, { error: "Missing service" });
@@ -30677,113 +30538,162 @@ var MoltsPayServer = class {
30677
30538
  return this.sendJson(res, 400, { error: `Missing required param: ${key}` });
30678
30539
  }
30679
30540
  }
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
30541
+ if (!paymentHeader) {
30542
+ return this.sendPaymentRequired(skill.config, res);
30543
+ }
30544
+ let payment;
30545
+ try {
30546
+ const decoded = Buffer.from(paymentHeader, "base64").toString("utf-8");
30547
+ payment = JSON.parse(decoded);
30548
+ } catch {
30549
+ return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
30550
+ }
30551
+ const validation = this.validatePayment(payment, skill.config);
30552
+ if (!validation.valid) {
30553
+ return this.sendJson(res, 402, { error: validation.error });
30554
+ }
30555
+ console.log(`[MoltsPay] Verifying payment with facilitator...`);
30556
+ const verifyResult = await this.verifyWithFacilitator(payment, skill.config);
30557
+ if (!verifyResult.valid) {
30558
+ return this.sendJson(res, 402, { error: `Payment verification failed: ${verifyResult.error}` });
30559
+ }
30560
+ console.log(`[MoltsPay] Executing skill: ${service}`);
30561
+ let result;
30562
+ try {
30563
+ result = await skill.handler(params || {});
30564
+ } catch (err) {
30565
+ console.error("[MoltsPay] Skill execution failed:", err.message);
30566
+ return this.sendJson(res, 500, {
30567
+ error: "Service execution failed",
30568
+ message: err.message
30569
+ });
30570
+ }
30571
+ console.log(`[MoltsPay] Skill succeeded, settling payment...`);
30572
+ let settlement = null;
30573
+ try {
30574
+ settlement = await this.settleWithFacilitator(payment, skill.config);
30575
+ console.log(`[MoltsPay] Payment settled: ${settlement.transaction || "pending"}`);
30576
+ } catch (err) {
30577
+ console.error("[MoltsPay] Settlement failed:", err.message);
30578
+ }
30579
+ const responseHeaders = {};
30580
+ if (settlement) {
30581
+ const responsePayload = {
30582
+ success: true,
30583
+ transaction: settlement.transaction,
30584
+ network: payment.network
30585
+ };
30586
+ responseHeaders[PAYMENT_RESPONSE_HEADER] = Buffer.from(
30587
+ JSON.stringify(responsePayload)
30588
+ ).toString("base64");
30589
+ }
30590
+ this.sendJson(res, 200, {
30591
+ success: true,
30592
+ result,
30593
+ payment: settlement ? { transaction: settlement.transaction, status: "settled" } : { status: "pending" }
30594
+ }, responseHeaders);
30595
+ }
30596
+ /**
30597
+ * Return 402 with x402 payment requirements
30598
+ */
30599
+ sendPaymentRequired(config, res) {
30600
+ const chain2 = getChain(this.manifest.provider.chain);
30601
+ const amountInUnits = Math.floor(config.price * 1e6).toString();
30602
+ const requirements = [{
30603
+ scheme: "exact",
30604
+ network: `eip155:${chain2.chainId}`,
30605
+ maxAmountRequired: amountInUnits,
30606
+ resource: this.manifest.provider.wallet,
30607
+ description: `${config.name} - $${config.price} ${config.currency}`,
30608
+ // Include facilitator info for client
30609
+ extra: JSON.stringify({ facilitator: this.facilitatorUrl })
30610
+ }];
30611
+ const encoded = Buffer.from(JSON.stringify(requirements)).toString("base64");
30612
+ res.writeHead(402, {
30613
+ "Content-Type": "application/json",
30614
+ [PAYMENT_REQUIRED_HEADER]: encoded
30705
30615
  });
30616
+ res.end(JSON.stringify({
30617
+ error: "Payment required",
30618
+ message: `Service requires $${config.price} ${config.currency}`,
30619
+ x402: requirements[0]
30620
+ }, null, 2));
30706
30621
  }
30707
30622
  /**
30708
- * POST /verify - Verify payment and execute skill
30709
- * Body: { chargeId: string, txHash: string }
30623
+ * Basic payment validation (before calling facilitator)
30710
30624
  */
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" });
30625
+ validatePayment(payment, config) {
30626
+ if (payment.x402Version !== X402_VERSION) {
30627
+ return { valid: false, error: `Unsupported x402 version: ${payment.x402Version}` };
30715
30628
  }
30716
- const charge = this.charges.get(chargeId);
30717
- if (!charge) {
30718
- return this.sendJson(res, 404, { error: "Charge not found" });
30629
+ if (payment.scheme !== "exact") {
30630
+ return { valid: false, error: `Unsupported scheme: ${payment.scheme}` };
30719
30631
  }
30720
- if (Date.now() > charge.expiresAt) {
30721
- charge.status = "expired";
30722
- return this.sendJson(res, 400, { error: "Charge expired" });
30723
- }
30724
- if (charge.status === "completed") {
30725
- return this.sendJson(res, 200, {
30726
- status: "completed",
30727
- result: charge.result
30728
- });
30632
+ const chain2 = getChain(this.manifest.provider.chain);
30633
+ const expectedNetwork = `eip155:${chain2.chainId}`;
30634
+ if (payment.network !== expectedNetwork) {
30635
+ return { valid: false, error: `Network mismatch: expected ${expectedNetwork}` };
30729
30636
  }
30637
+ return { valid: true };
30638
+ }
30639
+ /**
30640
+ * Verify payment with facilitator
30641
+ */
30642
+ async verifyWithFacilitator(payment, config) {
30730
30643
  try {
30731
- const verification = await verifyPayment({
30732
- txHash,
30733
- expectedTo: this.manifest.provider.wallet,
30734
- expectedAmount: charge.amount,
30735
- chain: this.manifest.provider.chain
30644
+ const chain2 = getChain(this.manifest.provider.chain);
30645
+ const amountInUnits = Math.floor(config.price * 1e6).toString();
30646
+ const requirements = {
30647
+ scheme: "exact",
30648
+ network: `eip155:${chain2.chainId}`,
30649
+ maxAmountRequired: amountInUnits,
30650
+ resource: this.manifest.provider.wallet
30651
+ };
30652
+ const response = await fetch(`${this.facilitatorUrl}/verify`, {
30653
+ method: "POST",
30654
+ headers: { "Content-Type": "application/json" },
30655
+ body: JSON.stringify({
30656
+ paymentPayload: payment,
30657
+ paymentRequirements: requirements
30658
+ })
30736
30659
  });
30737
- if (!verification.verified) {
30738
- charge.status = "failed";
30739
- return this.sendJson(res, 400, {
30740
- error: "Payment verification failed",
30741
- reason: verification.error
30742
- });
30660
+ const result = await response.json();
30661
+ if (!response.ok || !result.isValid) {
30662
+ return { valid: false, error: result.invalidReason || "Verification failed" };
30743
30663
  }
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
- });
30664
+ return { valid: true };
30759
30665
  } 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
- });
30666
+ return { valid: false, error: `Facilitator error: ${err.message}` };
30766
30667
  }
30767
30668
  }
30768
30669
  /**
30769
- * GET /status/:chargeId - Check charge status
30670
+ * Settle payment with facilitator (execute on-chain transfer)
30770
30671
  */
30771
- handleStatus(chargeId, res) {
30772
- const charge = this.charges.get(chargeId);
30773
- if (!charge) {
30774
- return this.sendJson(res, 404, { error: "Charge not found" });
30775
- }
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
30672
+ async settleWithFacilitator(payment, config) {
30673
+ const chain2 = getChain(this.manifest.provider.chain);
30674
+ const amountInUnits = Math.floor(config.price * 1e6).toString();
30675
+ const requirements = {
30676
+ scheme: "exact",
30677
+ network: `eip155:${chain2.chainId}`,
30678
+ maxAmountRequired: amountInUnits,
30679
+ resource: this.manifest.provider.wallet
30680
+ };
30681
+ const response = await fetch(`${this.facilitatorUrl}/settle`, {
30682
+ method: "POST",
30683
+ headers: { "Content-Type": "application/json" },
30684
+ body: JSON.stringify({
30685
+ paymentPayload: payment,
30686
+ paymentRequirements: requirements
30687
+ })
30786
30688
  });
30689
+ const result = await response.json();
30690
+ if (!response.ok) {
30691
+ throw new Error(result.error || "Settlement failed");
30692
+ }
30693
+ return {
30694
+ transaction: result.transaction,
30695
+ status: result.status || "settled"
30696
+ };
30787
30697
  }
30788
30698
  async readBody(req) {
30789
30699
  return new Promise((resolve, reject) => {
@@ -30799,8 +30709,12 @@ var MoltsPayServer = class {
30799
30709
  req.on("error", reject);
30800
30710
  });
30801
30711
  }
30802
- sendJson(res, status, data) {
30803
- res.writeHead(status, { "Content-Type": "application/json" });
30712
+ sendJson(res, status, data, extraHeaders) {
30713
+ const headers = { "Content-Type": "application/json" };
30714
+ if (extraHeaders) {
30715
+ Object.assign(headers, extraHeaders);
30716
+ }
30717
+ res.writeHead(status, headers);
30804
30718
  res.end(JSON.stringify(data, null, 2));
30805
30719
  }
30806
30720
  };
@@ -30810,12 +30724,15 @@ init_esm_shims();
30810
30724
  import { existsSync, readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
30811
30725
  import { homedir } from "os";
30812
30726
  import { join } from "path";
30813
- import { Wallet } from "ethers";
30727
+ import { Wallet, ethers } from "ethers";
30814
30728
 
30815
30729
  // src/client/types.ts
30816
30730
  init_esm_shims();
30817
30731
 
30818
30732
  // src/client/index.ts
30733
+ var X402_VERSION2 = 2;
30734
+ var PAYMENT_REQUIRED_HEADER2 = "x-payment-required";
30735
+ var PAYMENT_HEADER2 = "x-payment";
30819
30736
  var DEFAULT_CONFIG = {
30820
30737
  chain: "base",
30821
30738
  limits: {
@@ -30879,43 +30796,112 @@ var MoltsPayClient = class {
30879
30796
  return res.json();
30880
30797
  }
30881
30798
  /**
30882
- * Pay for a service and get the result
30799
+ * Pay for a service and get the result (x402 protocol)
30800
+ *
30801
+ * This is GASLESS for the client - server pays gas to claim payment.
30802
+ * This is PAY-FOR-SUCCESS - payment only claimed if service succeeds.
30883
30803
  */
30884
30804
  async pay(serverUrl, service, params) {
30885
- if (!this.wallet) {
30805
+ if (!this.wallet || !this.walletData) {
30886
30806
  throw new Error("Client not initialized. Run: npx moltspay init");
30887
30807
  }
30888
- const payRes = await fetch(`${serverUrl}/pay`, {
30808
+ console.log(`[MoltsPay] Requesting service: ${service}`);
30809
+ const initialRes = await fetch(`${serverUrl}/execute`, {
30889
30810
  method: "POST",
30890
30811
  headers: { "Content-Type": "application/json" },
30891
30812
  body: JSON.stringify({ service, params })
30892
30813
  });
30893
- if (payRes.status !== 402) {
30894
- const err = await payRes.json();
30895
- throw new Error(err.error || "Unexpected response");
30814
+ if (initialRes.status !== 402) {
30815
+ const data = await initialRes.json();
30816
+ if (initialRes.ok && data.result) {
30817
+ return data.result;
30818
+ }
30819
+ throw new Error(data.error || "Unexpected response");
30820
+ }
30821
+ const paymentRequiredHeader = initialRes.headers.get(PAYMENT_REQUIRED_HEADER2);
30822
+ if (!paymentRequiredHeader) {
30823
+ throw new Error("Missing x-payment-required header");
30824
+ }
30825
+ let requirements;
30826
+ try {
30827
+ const decoded = Buffer.from(paymentRequiredHeader, "base64").toString("utf-8");
30828
+ requirements = JSON.parse(decoded);
30829
+ if (!Array.isArray(requirements)) {
30830
+ requirements = [requirements];
30831
+ }
30832
+ } catch {
30833
+ throw new Error("Invalid x-payment-required header");
30896
30834
  }
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`, {
30835
+ const chain2 = getChain(this.config.chain);
30836
+ const network = `eip155:${chain2.chainId}`;
30837
+ const req = requirements.find((r) => r.scheme === "exact" && r.network === network);
30838
+ if (!req) {
30839
+ throw new Error(`No matching payment option for ${network}`);
30840
+ }
30841
+ const amount = Number(req.maxAmountRequired) / 1e6;
30842
+ this.checkLimits(amount);
30843
+ console.log(`[MoltsPay] Signing payment: $${amount} USDC (gasless)`);
30844
+ const authorization = await this.signEIP3009(req.resource, amount, chain2);
30845
+ const payload = {
30846
+ x402Version: X402_VERSION2,
30847
+ scheme: "exact",
30848
+ network,
30849
+ payload: authorization
30850
+ };
30851
+ const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
30852
+ console.log(`[MoltsPay] Sending request with payment...`);
30853
+ const paidRes = await fetch(`${serverUrl}/execute`, {
30904
30854
  method: "POST",
30905
- headers: { "Content-Type": "application/json" },
30906
- body: JSON.stringify({
30907
- chargeId: payment.chargeId,
30908
- txHash
30909
- })
30855
+ headers: {
30856
+ "Content-Type": "application/json",
30857
+ [PAYMENT_HEADER2]: paymentHeader
30858
+ },
30859
+ body: JSON.stringify({ service, params })
30910
30860
  });
30911
- if (!verifyRes.ok) {
30912
- const err = await verifyRes.json();
30913
- throw new Error(err.error || "Verification failed");
30861
+ const result = await paidRes.json();
30862
+ if (!paidRes.ok) {
30863
+ throw new Error(result.error || "Service execution failed");
30914
30864
  }
30915
- const result = await verifyRes.json();
30916
- this.recordSpending(payment.amount);
30865
+ this.recordSpending(amount);
30866
+ console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || "claimed"}`);
30917
30867
  return result.result;
30918
30868
  }
30869
+ /**
30870
+ * Sign EIP-3009 transferWithAuthorization (GASLESS)
30871
+ * This only signs - no on-chain transaction, no gas needed.
30872
+ */
30873
+ async signEIP3009(to, amount, chain2) {
30874
+ const validAfter = 0;
30875
+ const validBefore = Math.floor(Date.now() / 1e3) + 3600;
30876
+ const nonce = ethers.hexlify(ethers.randomBytes(32));
30877
+ const value = BigInt(Math.floor(amount * 1e6)).toString();
30878
+ const authorization = {
30879
+ from: this.wallet.address,
30880
+ to,
30881
+ value,
30882
+ validAfter: validAfter.toString(),
30883
+ validBefore: validBefore.toString(),
30884
+ nonce
30885
+ };
30886
+ const domain = {
30887
+ name: "USD Coin",
30888
+ version: "2",
30889
+ chainId: chain2.chainId,
30890
+ verifyingContract: chain2.usdc
30891
+ };
30892
+ const types = {
30893
+ TransferWithAuthorization: [
30894
+ { name: "from", type: "address" },
30895
+ { name: "to", type: "address" },
30896
+ { name: "value", type: "uint256" },
30897
+ { name: "validAfter", type: "uint256" },
30898
+ { name: "validBefore", type: "uint256" },
30899
+ { name: "nonce", type: "bytes32" }
30900
+ ]
30901
+ };
30902
+ const signature = await this.wallet.signTypedData(domain, types, authorization);
30903
+ return { authorization, signature };
30904
+ }
30919
30905
  /**
30920
30906
  * Check spending limits
30921
30907
  */
@@ -30942,36 +30928,6 @@ var MoltsPayClient = class {
30942
30928
  recordSpending(amount) {
30943
30929
  this.todaySpending += amount;
30944
30930
  }
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
30931
  // --- Config & Wallet Management ---
30976
30932
  loadConfig() {
30977
30933
  const configPath = join(this.configDir, "config.json");
@@ -31031,15 +30987,14 @@ var MoltsPayClient = class {
31031
30987
  } catch {
31032
30988
  throw new Error(`Unknown chain: ${this.config.chain}`);
31033
30989
  }
31034
- const { ethers: ethers4 } = await import("ethers");
31035
- const provider = new ethers4.JsonRpcProvider(chain2.rpc);
30990
+ const provider = new ethers.JsonRpcProvider(chain2.rpc);
31036
30991
  const nativeBalance = await provider.getBalance(this.wallet.address);
31037
30992
  const usdcAbi = ["function balanceOf(address) view returns (uint256)"];
31038
- const usdc = new ethers4.Contract(chain2.usdc, usdcAbi, provider);
30993
+ const usdc = new ethers.Contract(chain2.usdc, usdcAbi, provider);
31039
30994
  const usdcBalance = await usdc.balanceOf(this.wallet.address);
31040
30995
  return {
31041
- usdc: parseFloat(ethers4.formatUnits(usdcBalance, 6)),
31042
- native: parseFloat(ethers4.formatEther(nativeBalance))
30996
+ usdc: parseFloat(ethers.formatUnits(usdcBalance, 6)),
30997
+ native: parseFloat(ethers.formatEther(nativeBalance))
31043
30998
  };
31044
30999
  }
31045
31000
  };
@@ -31169,6 +31124,143 @@ function walletExists(storagePath) {
31169
31124
  return existsSync2(path3);
31170
31125
  }
31171
31126
 
31127
+ // src/verify/index.ts
31128
+ init_esm_shims();
31129
+ import { ethers as ethers4 } from "ethers";
31130
+ var TRANSFER_EVENT_TOPIC = ethers4.id("Transfer(address,address,uint256)");
31131
+ async function verifyPayment(params) {
31132
+ const { txHash, expectedAmount, expectedTo } = params;
31133
+ let chain2;
31134
+ try {
31135
+ if (typeof params.chain === "number") {
31136
+ chain2 = getChainById(params.chain);
31137
+ } else {
31138
+ chain2 = getChain(params.chain || "base");
31139
+ }
31140
+ if (!chain2) {
31141
+ return { verified: false, error: `Unsupported chain: ${params.chain}` };
31142
+ }
31143
+ } catch (e) {
31144
+ return { verified: false, error: `Unsupported chain: ${params.chain}` };
31145
+ }
31146
+ try {
31147
+ const provider = new ethers4.JsonRpcProvider(chain2.rpc);
31148
+ const receipt = await provider.getTransactionReceipt(txHash);
31149
+ if (!receipt) {
31150
+ return { verified: false, error: "Transaction not found or not confirmed" };
31151
+ }
31152
+ if (receipt.status !== 1) {
31153
+ return { verified: false, error: "Transaction failed" };
31154
+ }
31155
+ const usdcAddress = chain2.usdc?.toLowerCase();
31156
+ if (!usdcAddress) {
31157
+ return { verified: false, error: `Chain ${chain2.name} USDC address not configured` };
31158
+ }
31159
+ for (const log of receipt.logs) {
31160
+ if (log.address.toLowerCase() !== usdcAddress) {
31161
+ continue;
31162
+ }
31163
+ if (log.topics.length < 3 || log.topics[0] !== TRANSFER_EVENT_TOPIC) {
31164
+ continue;
31165
+ }
31166
+ const from = "0x" + log.topics[1].slice(-40);
31167
+ const to = "0x" + log.topics[2].slice(-40);
31168
+ const amountRaw = BigInt(log.data);
31169
+ const amount = Number(amountRaw) / 1e6;
31170
+ if (expectedTo && to.toLowerCase() !== expectedTo.toLowerCase()) {
31171
+ continue;
31172
+ }
31173
+ if (amount < expectedAmount) {
31174
+ return {
31175
+ verified: false,
31176
+ error: `Insufficient amount: received ${amount} USDC, expected ${expectedAmount} USDC`,
31177
+ amount,
31178
+ from,
31179
+ to,
31180
+ txHash,
31181
+ blockNumber: receipt.blockNumber
31182
+ };
31183
+ }
31184
+ return {
31185
+ verified: true,
31186
+ amount,
31187
+ from,
31188
+ to,
31189
+ txHash,
31190
+ blockNumber: receipt.blockNumber
31191
+ };
31192
+ }
31193
+ return { verified: false, error: "No USDC transfer found" };
31194
+ } catch (e) {
31195
+ return { verified: false, error: e.message || String(e) };
31196
+ }
31197
+ }
31198
+ async function getTransactionStatus(txHash, chain2 = "base") {
31199
+ let chainConfig;
31200
+ try {
31201
+ chainConfig = typeof chain2 === "number" ? getChainById(chain2) : getChain(chain2);
31202
+ if (!chainConfig) return { status: "not_found" };
31203
+ } catch {
31204
+ return { status: "not_found" };
31205
+ }
31206
+ try {
31207
+ const provider = new ethers4.JsonRpcProvider(chainConfig.rpc);
31208
+ const receipt = await provider.getTransactionReceipt(txHash);
31209
+ if (!receipt) {
31210
+ const tx = await provider.getTransaction(txHash);
31211
+ if (tx) {
31212
+ return { status: "pending" };
31213
+ }
31214
+ return { status: "not_found" };
31215
+ }
31216
+ const currentBlock = await provider.getBlockNumber();
31217
+ const confirmations = currentBlock - receipt.blockNumber;
31218
+ if (receipt.status === 1) {
31219
+ return {
31220
+ status: "confirmed",
31221
+ blockNumber: receipt.blockNumber,
31222
+ confirmations
31223
+ };
31224
+ } else {
31225
+ return {
31226
+ status: "failed",
31227
+ blockNumber: receipt.blockNumber
31228
+ };
31229
+ }
31230
+ } catch {
31231
+ return { status: "not_found" };
31232
+ }
31233
+ }
31234
+ async function waitForTransaction(txHash, chain2 = "base", confirmations = 1, timeoutMs = 6e4) {
31235
+ let chainConfig;
31236
+ try {
31237
+ chainConfig = typeof chain2 === "number" ? getChainById(chain2) : getChain(chain2);
31238
+ if (!chainConfig) {
31239
+ return { verified: false, confirmed: false, error: `Unsupported chain: ${chain2}` };
31240
+ }
31241
+ } catch (e) {
31242
+ return { verified: false, confirmed: false, error: `Unsupported chain: ${chain2}` };
31243
+ }
31244
+ const provider = new ethers4.JsonRpcProvider(chainConfig.rpc);
31245
+ try {
31246
+ const receipt = await provider.waitForTransaction(txHash, confirmations, timeoutMs);
31247
+ if (!receipt) {
31248
+ return { verified: false, confirmed: false, error: "Timeout waiting" };
31249
+ }
31250
+ if (receipt.status !== 1) {
31251
+ return { verified: false, confirmed: true, error: "Transaction failed" };
31252
+ }
31253
+ return {
31254
+ verified: true,
31255
+ confirmed: true,
31256
+ txHash,
31257
+ blockNumber: receipt.blockNumber
31258
+ };
31259
+ } catch (e) {
31260
+ return { verified: false, confirmed: false, error: e.message || String(e) };
31261
+ }
31262
+ }
31263
+
31172
31264
  // src/cdp/index.ts
31173
31265
  init_esm_shims();
31174
31266
  import * as fs from "fs";
@@ -31290,9 +31382,9 @@ var CDPWallet = class {
31290
31382
  * Get USDC balance
31291
31383
  */
31292
31384
  async getBalance() {
31293
- const { ethers: ethers4 } = await import("ethers");
31294
- const provider = new ethers4.JsonRpcProvider(this.chainConfig.rpc);
31295
- const usdcContract = new ethers4.Contract(
31385
+ const { ethers: ethers5 } = await import("ethers");
31386
+ const provider = new ethers5.JsonRpcProvider(this.chainConfig.rpc);
31387
+ const usdcContract = new ethers5.Contract(
31296
31388
  this.chainConfig.usdc,
31297
31389
  ["function balanceOf(address) view returns (uint256)"],
31298
31390
  provider
@@ -31303,7 +31395,7 @@ var CDPWallet = class {
31303
31395
  ]);
31304
31396
  return {
31305
31397
  usdc: (Number(usdcBalance) / 1e6).toFixed(2),
31306
- eth: ethers4.formatEther(ethBalance)
31398
+ eth: ethers5.formatEther(ethBalance)
31307
31399
  };
31308
31400
  }
31309
31401
  /**
@@ -31321,7 +31413,7 @@ var CDPWallet = class {
31321
31413
  }
31322
31414
  try {
31323
31415
  const { CdpClient } = await import("@coinbase/cdp-sdk");
31324
- const { ethers: ethers4 } = await import("ethers");
31416
+ const { ethers: ethers5 } = await import("ethers");
31325
31417
  const cdp = new CdpClient({
31326
31418
  apiKeyId: creds.apiKeyId,
31327
31419
  apiKeySecret: creds.apiKeySecret,
@@ -31329,7 +31421,7 @@ var CDPWallet = class {
31329
31421
  });
31330
31422
  const account = await cdp.evm.getAccount({ address: this.address });
31331
31423
  const amountWei = BigInt(Math.floor(params.amount * 1e6));
31332
- const iface = new ethers4.Interface([
31424
+ const iface = new ethers5.Interface([
31333
31425
  "function transfer(address to, uint256 amount) returns (bool)"
31334
31426
  ]);
31335
31427
  const callData = iface.encodeFunctionData("transfer", [params.to, amountWei]);