moltspay 0.8.0 → 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/cli/index.js CHANGED
@@ -367,38 +367,29 @@ var MoltsPayClient = class {
367
367
  // src/server/index.ts
368
368
  var import_fs2 = require("fs");
369
369
  var import_http = require("http");
370
- var import_ethers2 = require("ethers");
371
370
  var X402_VERSION2 = 2;
372
371
  var PAYMENT_REQUIRED_HEADER2 = "x-payment-required";
373
372
  var PAYMENT_HEADER2 = "x-payment";
373
+ var PAYMENT_RESPONSE_HEADER = "x-payment-response";
374
+ var DEFAULT_FACILITATOR_URL = "https://x402.org/facilitator";
374
375
  var MoltsPayServer = class {
375
376
  manifest;
376
377
  skills = /* @__PURE__ */ new Map();
377
378
  options;
378
- provider = null;
379
- wallet = null;
379
+ facilitatorUrl;
380
380
  constructor(servicesPath, options = {}) {
381
381
  const content = (0, import_fs2.readFileSync)(servicesPath, "utf-8");
382
382
  this.manifest = JSON.parse(content);
383
383
  this.options = {
384
384
  port: options.port || 3e3,
385
- host: options.host || "0.0.0.0",
386
- privateKey: options.privateKey || process.env.MOLTSPAY_PRIVATE_KEY
385
+ host: options.host || "0.0.0.0"
387
386
  };
388
- if (this.options.privateKey) {
389
- try {
390
- const chain = getChain(this.manifest.provider.chain);
391
- this.provider = new import_ethers2.ethers.JsonRpcProvider(chain.rpc);
392
- this.wallet = new import_ethers2.ethers.Wallet(this.options.privateKey, this.provider);
393
- console.log(`[MoltsPay] Payment wallet: ${this.wallet.address}`);
394
- } catch (err) {
395
- console.warn("[MoltsPay] Warning: Could not initialize wallet for payment claims");
396
- }
397
- }
387
+ this.facilitatorUrl = options.facilitatorUrl || DEFAULT_FACILITATOR_URL;
398
388
  console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);
399
389
  console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);
400
390
  console.log(`[MoltsPay] Receive wallet: ${this.manifest.provider.wallet}`);
401
- console.log(`[MoltsPay] Protocol: x402 (gasless, pay-for-success)`);
391
+ console.log(`[MoltsPay] Facilitator: ${this.facilitatorUrl}`);
392
+ console.log(`[MoltsPay] Protocol: x402 (gasless for both client AND server)`);
402
393
  }
403
394
  /**
404
395
  * Register a skill handler for a service
@@ -408,48 +399,45 @@ var MoltsPayServer = class {
408
399
  if (!config) {
409
400
  throw new Error(`Service '${serviceId}' not found in manifest`);
410
401
  }
411
- this.skills.set(serviceId, {
412
- id: serviceId,
413
- config,
414
- handler
415
- });
416
- console.log(`[MoltsPay] Registered skill: ${serviceId} ($${config.price} ${config.currency})`);
402
+ this.skills.set(serviceId, { id: serviceId, config, handler });
417
403
  return this;
418
404
  }
419
405
  /**
420
- * Start the server
406
+ * Start HTTP server
421
407
  */
422
408
  listen(port) {
423
- const p = port || this.options.port;
409
+ const p = port || this.options.port || 3e3;
410
+ const host = this.options.host || "0.0.0.0";
424
411
  const server = (0, import_http.createServer)((req, res) => this.handleRequest(req, res));
425
- server.listen(p, this.options.host, () => {
426
- console.log(`[MoltsPay] Server listening on http://${this.options.host}:${p}`);
412
+ server.listen(p, host, () => {
413
+ console.log(`[MoltsPay] Server listening on http://${host}:${p}`);
427
414
  console.log(`[MoltsPay] Endpoints:`);
428
415
  console.log(` GET /services - List available services`);
429
416
  console.log(` POST /execute - Execute service (x402 payment)`);
430
417
  });
431
418
  }
419
+ /**
420
+ * Handle incoming request
421
+ */
432
422
  async handleRequest(req, res) {
433
- const url = new URL(req.url || "/", `http://${req.headers.host}`);
434
- const path = url.pathname;
435
- const method = req.method || "GET";
436
423
  res.setHeader("Access-Control-Allow-Origin", "*");
437
424
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
438
425
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment");
439
426
  res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response");
440
- if (method === "OPTIONS") {
427
+ if (req.method === "OPTIONS") {
441
428
  res.writeHead(204);
442
429
  res.end();
443
430
  return;
444
431
  }
445
432
  try {
446
- if (method === "GET" && path === "/services") {
433
+ const url = new URL(req.url || "/", `http://${req.headers.host}`);
434
+ if (url.pathname === "/services" && req.method === "GET") {
447
435
  return this.handleGetServices(res);
448
436
  }
449
- if (method === "POST" && path === "/execute") {
437
+ if (url.pathname === "/execute" && req.method === "POST") {
450
438
  const body = await this.readBody(req);
451
439
  const paymentHeader = req.headers[PAYMENT_HEADER2];
452
- return this.handleExecute(body, paymentHeader, res);
440
+ return await this.handleExecute(body, paymentHeader, res);
453
441
  }
454
442
  this.sendJson(res, 404, { error: "Not found" });
455
443
  } catch (err) {
@@ -478,7 +466,8 @@ var MoltsPayServer = class {
478
466
  x402: {
479
467
  version: X402_VERSION2,
480
468
  network: `eip155:${chain.chainId}`,
481
- schemes: ["exact"]
469
+ schemes: ["exact"],
470
+ facilitator: this.facilitatorUrl
482
471
  }
483
472
  });
484
473
  }
@@ -515,6 +504,11 @@ var MoltsPayServer = class {
515
504
  if (!validation.valid) {
516
505
  return this.sendJson(res, 402, { error: validation.error });
517
506
  }
507
+ console.log(`[MoltsPay] Verifying payment with facilitator...`);
508
+ const verifyResult = await this.verifyWithFacilitator(payment, skill.config);
509
+ if (!verifyResult.valid) {
510
+ return this.sendJson(res, 402, { error: `Payment verification failed: ${verifyResult.error}` });
511
+ }
518
512
  console.log(`[MoltsPay] Executing skill: ${service}`);
519
513
  let result;
520
514
  try {
@@ -526,19 +520,30 @@ var MoltsPayServer = class {
526
520
  message: err.message
527
521
  });
528
522
  }
529
- console.log(`[MoltsPay] Skill succeeded, claiming payment...`);
530
- let txHash = null;
523
+ console.log(`[MoltsPay] Skill succeeded, settling payment...`);
524
+ let settlement = null;
531
525
  try {
532
- txHash = await this.claimPayment(payment);
533
- console.log(`[MoltsPay] Payment claimed: ${txHash}`);
526
+ settlement = await this.settleWithFacilitator(payment, skill.config);
527
+ console.log(`[MoltsPay] Payment settled: ${settlement.transaction || "pending"}`);
534
528
  } catch (err) {
535
- console.error("[MoltsPay] Payment claim failed:", err.message);
529
+ console.error("[MoltsPay] Settlement failed:", err.message);
530
+ }
531
+ const responseHeaders = {};
532
+ if (settlement) {
533
+ const responsePayload = {
534
+ success: true,
535
+ transaction: settlement.transaction,
536
+ network: payment.network
537
+ };
538
+ responseHeaders[PAYMENT_RESPONSE_HEADER] = Buffer.from(
539
+ JSON.stringify(responsePayload)
540
+ ).toString("base64");
536
541
  }
537
542
  this.sendJson(res, 200, {
538
543
  success: true,
539
544
  result,
540
- payment: txHash ? { txHash, status: "claimed" } : { status: "pending" }
541
- });
545
+ payment: settlement ? { transaction: settlement.transaction, status: "settled" } : { status: "pending" }
546
+ }, responseHeaders);
542
547
  }
543
548
  /**
544
549
  * Return 402 with x402 payment requirements
@@ -551,7 +556,9 @@ var MoltsPayServer = class {
551
556
  network: `eip155:${chain.chainId}`,
552
557
  maxAmountRequired: amountInUnits,
553
558
  resource: this.manifest.provider.wallet,
554
- description: `${config.name} - $${config.price} ${config.currency}`
559
+ description: `${config.name} - $${config.price} ${config.currency}`,
560
+ // Include facilitator info for client
561
+ extra: JSON.stringify({ facilitator: this.facilitatorUrl })
555
562
  }];
556
563
  const encoded = Buffer.from(JSON.stringify(requirements)).toString("base64");
557
564
  res.writeHead(402, {
@@ -565,7 +572,7 @@ var MoltsPayServer = class {
565
572
  }, null, 2));
566
573
  }
567
574
  /**
568
- * Validate x402 payment payload
575
+ * Basic payment validation (before calling facilitator)
569
576
  */
570
577
  validatePayment(payment, config) {
571
578
  if (payment.x402Version !== X402_VERSION2) {
@@ -579,51 +586,66 @@ var MoltsPayServer = class {
579
586
  if (payment.network !== expectedNetwork) {
580
587
  return { valid: false, error: `Network mismatch: expected ${expectedNetwork}` };
581
588
  }
582
- const auth = payment.payload.authorization;
583
- if (auth.to.toLowerCase() !== this.manifest.provider.wallet.toLowerCase()) {
584
- return { valid: false, error: "Payment recipient mismatch" };
585
- }
586
- const amount = Number(auth.value) / 1e6;
587
- if (amount < config.price) {
588
- return { valid: false, error: `Insufficient amount: $${amount} < $${config.price}` };
589
- }
590
- const now = Math.floor(Date.now() / 1e3);
591
- if (Number(auth.validBefore) < now) {
592
- return { valid: false, error: "Payment authorization expired" };
593
- }
594
- if (Number(auth.validAfter) > now) {
595
- return { valid: false, error: "Payment authorization not yet valid" };
596
- }
597
589
  return { valid: true };
598
590
  }
599
591
  /**
600
- * Claim payment using transferWithAuthorization
592
+ * Verify payment with facilitator
601
593
  */
602
- async claimPayment(payment) {
603
- if (!this.wallet || !this.provider) {
604
- throw new Error("Wallet not configured for payment claims");
594
+ async verifyWithFacilitator(payment, config) {
595
+ try {
596
+ const chain = getChain(this.manifest.provider.chain);
597
+ const amountInUnits = Math.floor(config.price * 1e6).toString();
598
+ const requirements = {
599
+ scheme: "exact",
600
+ network: `eip155:${chain.chainId}`,
601
+ maxAmountRequired: amountInUnits,
602
+ resource: this.manifest.provider.wallet
603
+ };
604
+ const response = await fetch(`${this.facilitatorUrl}/verify`, {
605
+ method: "POST",
606
+ headers: { "Content-Type": "application/json" },
607
+ body: JSON.stringify({
608
+ paymentPayload: payment,
609
+ paymentRequirements: requirements
610
+ })
611
+ });
612
+ const result = await response.json();
613
+ if (!response.ok || !result.isValid) {
614
+ return { valid: false, error: result.invalidReason || "Verification failed" };
615
+ }
616
+ return { valid: true };
617
+ } catch (err) {
618
+ return { valid: false, error: `Facilitator error: ${err.message}` };
605
619
  }
620
+ }
621
+ /**
622
+ * Settle payment with facilitator (execute on-chain transfer)
623
+ */
624
+ async settleWithFacilitator(payment, config) {
606
625
  const chain = getChain(this.manifest.provider.chain);
607
- const auth = payment.payload.authorization;
608
- const sig = payment.payload.signature;
609
- const { r, s, v } = import_ethers2.ethers.Signature.from(sig);
610
- const usdcAbi = [
611
- "function transferWithAuthorization(address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s)"
612
- ];
613
- const usdc = new import_ethers2.ethers.Contract(chain.usdc, usdcAbi, this.wallet);
614
- const tx = await usdc.transferWithAuthorization(
615
- auth.from,
616
- auth.to,
617
- auth.value,
618
- auth.validAfter,
619
- auth.validBefore,
620
- auth.nonce,
621
- v,
622
- r,
623
- s
624
- );
625
- const receipt = await tx.wait();
626
- return receipt.hash;
626
+ const amountInUnits = Math.floor(config.price * 1e6).toString();
627
+ const requirements = {
628
+ scheme: "exact",
629
+ network: `eip155:${chain.chainId}`,
630
+ maxAmountRequired: amountInUnits,
631
+ resource: this.manifest.provider.wallet
632
+ };
633
+ const response = await fetch(`${this.facilitatorUrl}/settle`, {
634
+ method: "POST",
635
+ headers: { "Content-Type": "application/json" },
636
+ body: JSON.stringify({
637
+ paymentPayload: payment,
638
+ paymentRequirements: requirements
639
+ })
640
+ });
641
+ const result = await response.json();
642
+ if (!response.ok) {
643
+ throw new Error(result.error || "Settlement failed");
644
+ }
645
+ return {
646
+ transaction: result.transaction,
647
+ status: result.status || "settled"
648
+ };
627
649
  }
628
650
  async readBody(req) {
629
651
  return new Promise((resolve2, reject) => {
@@ -639,8 +661,12 @@ var MoltsPayServer = class {
639
661
  req.on("error", reject);
640
662
  });
641
663
  }
642
- sendJson(res, status, data) {
643
- res.writeHead(status, { "Content-Type": "application/json" });
664
+ sendJson(res, status, data, extraHeaders) {
665
+ const headers = { "Content-Type": "application/json" };
666
+ if (extraHeaders) {
667
+ Object.assign(headers, extraHeaders);
668
+ }
669
+ res.writeHead(status, headers);
644
670
  res.end(JSON.stringify(data, null, 2));
645
671
  }
646
672
  };
@@ -801,7 +827,7 @@ program.command("services <url>").description("List services from a provider").o
801
827
  console.error("\u274C Error:", err.message);
802
828
  }
803
829
  });
804
- program.command("start <manifest>").description("Start MoltsPay server from services manifest").option("-p, --port <port>", "Port to listen on", "3000").option("--host <host>", "Host to bind", "0.0.0.0").option("--private-key <key>", "Private key for claiming payments (or use MOLTSPAY_PRIVATE_KEY env)").action(async (manifest, options) => {
830
+ program.command("start <manifest>").description("Start MoltsPay server from services manifest").option("-p, --port <port>", "Port to listen on", "3000").option("--host <host>", "Host to bind", "0.0.0.0").option("--facilitator <url>", "x402 facilitator URL (default: https://x402.org/facilitator)").action(async (manifest, options) => {
805
831
  const manifestPath = (0, import_path2.resolve)(manifest);
806
832
  if (!(0, import_fs3.existsSync)(manifestPath)) {
807
833
  console.error(`\u274C Manifest not found: ${manifestPath}`);
@@ -809,16 +835,15 @@ program.command("start <manifest>").description("Start MoltsPay server from serv
809
835
  }
810
836
  const port = parseInt(options.port, 10);
811
837
  const host = options.host;
812
- const privateKey = options.privateKey || process.env.MOLTSPAY_PRIVATE_KEY;
838
+ const facilitatorUrl = options.facilitator;
813
839
  console.log(`
814
840
  \u{1F680} Starting MoltsPay Server (x402 protocol)
815
841
  `);
816
842
  console.log(` Manifest: ${manifestPath}`);
817
843
  console.log(` Port: ${port}`);
818
- console.log(` Payment claims: ${privateKey ? "enabled" : "disabled (no private key)"}`);
819
844
  console.log("");
820
845
  try {
821
- const server = new MoltsPayServer(manifestPath, { port, host, privateKey });
846
+ const server = new MoltsPayServer(manifestPath, { port, host, facilitatorUrl });
822
847
  const manifestContent = await import("fs").then(
823
848
  (fs) => JSON.parse(fs.readFileSync(manifestPath, "utf-8"))
824
849
  );