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.
@@ -344,38 +344,29 @@ var MoltsPayClient = class {
344
344
  // src/server/index.ts
345
345
  import { readFileSync as readFileSync2 } from "fs";
346
346
  import { createServer } from "http";
347
- import { ethers as ethers2 } from "ethers";
348
347
  var X402_VERSION2 = 2;
349
348
  var PAYMENT_REQUIRED_HEADER2 = "x-payment-required";
350
349
  var PAYMENT_HEADER2 = "x-payment";
350
+ var PAYMENT_RESPONSE_HEADER = "x-payment-response";
351
+ var DEFAULT_FACILITATOR_URL = "https://x402.org/facilitator";
351
352
  var MoltsPayServer = class {
352
353
  manifest;
353
354
  skills = /* @__PURE__ */ new Map();
354
355
  options;
355
- provider = null;
356
- wallet = null;
356
+ facilitatorUrl;
357
357
  constructor(servicesPath, options = {}) {
358
358
  const content = readFileSync2(servicesPath, "utf-8");
359
359
  this.manifest = JSON.parse(content);
360
360
  this.options = {
361
361
  port: options.port || 3e3,
362
- host: options.host || "0.0.0.0",
363
- privateKey: options.privateKey || process.env.MOLTSPAY_PRIVATE_KEY
362
+ host: options.host || "0.0.0.0"
364
363
  };
365
- if (this.options.privateKey) {
366
- try {
367
- const chain = getChain(this.manifest.provider.chain);
368
- this.provider = new ethers2.JsonRpcProvider(chain.rpc);
369
- this.wallet = new ethers2.Wallet(this.options.privateKey, this.provider);
370
- console.log(`[MoltsPay] Payment wallet: ${this.wallet.address}`);
371
- } catch (err) {
372
- console.warn("[MoltsPay] Warning: Could not initialize wallet for payment claims");
373
- }
374
- }
364
+ this.facilitatorUrl = options.facilitatorUrl || DEFAULT_FACILITATOR_URL;
375
365
  console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);
376
366
  console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);
377
367
  console.log(`[MoltsPay] Receive wallet: ${this.manifest.provider.wallet}`);
378
- console.log(`[MoltsPay] Protocol: x402 (gasless, pay-for-success)`);
368
+ console.log(`[MoltsPay] Facilitator: ${this.facilitatorUrl}`);
369
+ console.log(`[MoltsPay] Protocol: x402 (gasless for both client AND server)`);
379
370
  }
380
371
  /**
381
372
  * Register a skill handler for a service
@@ -385,48 +376,45 @@ var MoltsPayServer = class {
385
376
  if (!config) {
386
377
  throw new Error(`Service '${serviceId}' not found in manifest`);
387
378
  }
388
- this.skills.set(serviceId, {
389
- id: serviceId,
390
- config,
391
- handler
392
- });
393
- console.log(`[MoltsPay] Registered skill: ${serviceId} ($${config.price} ${config.currency})`);
379
+ this.skills.set(serviceId, { id: serviceId, config, handler });
394
380
  return this;
395
381
  }
396
382
  /**
397
- * Start the server
383
+ * Start HTTP server
398
384
  */
399
385
  listen(port) {
400
- const p = port || this.options.port;
386
+ const p = port || this.options.port || 3e3;
387
+ const host = this.options.host || "0.0.0.0";
401
388
  const server = createServer((req, res) => this.handleRequest(req, res));
402
- server.listen(p, this.options.host, () => {
403
- console.log(`[MoltsPay] Server listening on http://${this.options.host}:${p}`);
389
+ server.listen(p, host, () => {
390
+ console.log(`[MoltsPay] Server listening on http://${host}:${p}`);
404
391
  console.log(`[MoltsPay] Endpoints:`);
405
392
  console.log(` GET /services - List available services`);
406
393
  console.log(` POST /execute - Execute service (x402 payment)`);
407
394
  });
408
395
  }
396
+ /**
397
+ * Handle incoming request
398
+ */
409
399
  async handleRequest(req, res) {
410
- const url = new URL(req.url || "/", `http://${req.headers.host}`);
411
- const path = url.pathname;
412
- const method = req.method || "GET";
413
400
  res.setHeader("Access-Control-Allow-Origin", "*");
414
401
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
415
402
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment");
416
403
  res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response");
417
- if (method === "OPTIONS") {
404
+ if (req.method === "OPTIONS") {
418
405
  res.writeHead(204);
419
406
  res.end();
420
407
  return;
421
408
  }
422
409
  try {
423
- if (method === "GET" && path === "/services") {
410
+ const url = new URL(req.url || "/", `http://${req.headers.host}`);
411
+ if (url.pathname === "/services" && req.method === "GET") {
424
412
  return this.handleGetServices(res);
425
413
  }
426
- if (method === "POST" && path === "/execute") {
414
+ if (url.pathname === "/execute" && req.method === "POST") {
427
415
  const body = await this.readBody(req);
428
416
  const paymentHeader = req.headers[PAYMENT_HEADER2];
429
- return this.handleExecute(body, paymentHeader, res);
417
+ return await this.handleExecute(body, paymentHeader, res);
430
418
  }
431
419
  this.sendJson(res, 404, { error: "Not found" });
432
420
  } catch (err) {
@@ -455,7 +443,8 @@ var MoltsPayServer = class {
455
443
  x402: {
456
444
  version: X402_VERSION2,
457
445
  network: `eip155:${chain.chainId}`,
458
- schemes: ["exact"]
446
+ schemes: ["exact"],
447
+ facilitator: this.facilitatorUrl
459
448
  }
460
449
  });
461
450
  }
@@ -492,6 +481,11 @@ var MoltsPayServer = class {
492
481
  if (!validation.valid) {
493
482
  return this.sendJson(res, 402, { error: validation.error });
494
483
  }
484
+ console.log(`[MoltsPay] Verifying payment with facilitator...`);
485
+ const verifyResult = await this.verifyWithFacilitator(payment, skill.config);
486
+ if (!verifyResult.valid) {
487
+ return this.sendJson(res, 402, { error: `Payment verification failed: ${verifyResult.error}` });
488
+ }
495
489
  console.log(`[MoltsPay] Executing skill: ${service}`);
496
490
  let result;
497
491
  try {
@@ -503,19 +497,30 @@ var MoltsPayServer = class {
503
497
  message: err.message
504
498
  });
505
499
  }
506
- console.log(`[MoltsPay] Skill succeeded, claiming payment...`);
507
- let txHash = null;
500
+ console.log(`[MoltsPay] Skill succeeded, settling payment...`);
501
+ let settlement = null;
508
502
  try {
509
- txHash = await this.claimPayment(payment);
510
- console.log(`[MoltsPay] Payment claimed: ${txHash}`);
503
+ settlement = await this.settleWithFacilitator(payment, skill.config);
504
+ console.log(`[MoltsPay] Payment settled: ${settlement.transaction || "pending"}`);
511
505
  } catch (err) {
512
- console.error("[MoltsPay] Payment claim failed:", err.message);
506
+ console.error("[MoltsPay] Settlement failed:", err.message);
507
+ }
508
+ const responseHeaders = {};
509
+ if (settlement) {
510
+ const responsePayload = {
511
+ success: true,
512
+ transaction: settlement.transaction,
513
+ network: payment.network
514
+ };
515
+ responseHeaders[PAYMENT_RESPONSE_HEADER] = Buffer.from(
516
+ JSON.stringify(responsePayload)
517
+ ).toString("base64");
513
518
  }
514
519
  this.sendJson(res, 200, {
515
520
  success: true,
516
521
  result,
517
- payment: txHash ? { txHash, status: "claimed" } : { status: "pending" }
518
- });
522
+ payment: settlement ? { transaction: settlement.transaction, status: "settled" } : { status: "pending" }
523
+ }, responseHeaders);
519
524
  }
520
525
  /**
521
526
  * Return 402 with x402 payment requirements
@@ -528,7 +533,9 @@ var MoltsPayServer = class {
528
533
  network: `eip155:${chain.chainId}`,
529
534
  maxAmountRequired: amountInUnits,
530
535
  resource: this.manifest.provider.wallet,
531
- description: `${config.name} - $${config.price} ${config.currency}`
536
+ description: `${config.name} - $${config.price} ${config.currency}`,
537
+ // Include facilitator info for client
538
+ extra: JSON.stringify({ facilitator: this.facilitatorUrl })
532
539
  }];
533
540
  const encoded = Buffer.from(JSON.stringify(requirements)).toString("base64");
534
541
  res.writeHead(402, {
@@ -542,7 +549,7 @@ var MoltsPayServer = class {
542
549
  }, null, 2));
543
550
  }
544
551
  /**
545
- * Validate x402 payment payload
552
+ * Basic payment validation (before calling facilitator)
546
553
  */
547
554
  validatePayment(payment, config) {
548
555
  if (payment.x402Version !== X402_VERSION2) {
@@ -556,51 +563,66 @@ var MoltsPayServer = class {
556
563
  if (payment.network !== expectedNetwork) {
557
564
  return { valid: false, error: `Network mismatch: expected ${expectedNetwork}` };
558
565
  }
559
- const auth = payment.payload.authorization;
560
- if (auth.to.toLowerCase() !== this.manifest.provider.wallet.toLowerCase()) {
561
- return { valid: false, error: "Payment recipient mismatch" };
562
- }
563
- const amount = Number(auth.value) / 1e6;
564
- if (amount < config.price) {
565
- return { valid: false, error: `Insufficient amount: $${amount} < $${config.price}` };
566
- }
567
- const now = Math.floor(Date.now() / 1e3);
568
- if (Number(auth.validBefore) < now) {
569
- return { valid: false, error: "Payment authorization expired" };
570
- }
571
- if (Number(auth.validAfter) > now) {
572
- return { valid: false, error: "Payment authorization not yet valid" };
573
- }
574
566
  return { valid: true };
575
567
  }
576
568
  /**
577
- * Claim payment using transferWithAuthorization
569
+ * Verify payment with facilitator
578
570
  */
579
- async claimPayment(payment) {
580
- if (!this.wallet || !this.provider) {
581
- throw new Error("Wallet not configured for payment claims");
571
+ async verifyWithFacilitator(payment, config) {
572
+ try {
573
+ const chain = getChain(this.manifest.provider.chain);
574
+ const amountInUnits = Math.floor(config.price * 1e6).toString();
575
+ const requirements = {
576
+ scheme: "exact",
577
+ network: `eip155:${chain.chainId}`,
578
+ maxAmountRequired: amountInUnits,
579
+ resource: this.manifest.provider.wallet
580
+ };
581
+ const response = await fetch(`${this.facilitatorUrl}/verify`, {
582
+ method: "POST",
583
+ headers: { "Content-Type": "application/json" },
584
+ body: JSON.stringify({
585
+ paymentPayload: payment,
586
+ paymentRequirements: requirements
587
+ })
588
+ });
589
+ const result = await response.json();
590
+ if (!response.ok || !result.isValid) {
591
+ return { valid: false, error: result.invalidReason || "Verification failed" };
592
+ }
593
+ return { valid: true };
594
+ } catch (err) {
595
+ return { valid: false, error: `Facilitator error: ${err.message}` };
582
596
  }
597
+ }
598
+ /**
599
+ * Settle payment with facilitator (execute on-chain transfer)
600
+ */
601
+ async settleWithFacilitator(payment, config) {
583
602
  const chain = getChain(this.manifest.provider.chain);
584
- const auth = payment.payload.authorization;
585
- const sig = payment.payload.signature;
586
- const { r, s, v } = ethers2.Signature.from(sig);
587
- const usdcAbi = [
588
- "function transferWithAuthorization(address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s)"
589
- ];
590
- const usdc = new ethers2.Contract(chain.usdc, usdcAbi, this.wallet);
591
- const tx = await usdc.transferWithAuthorization(
592
- auth.from,
593
- auth.to,
594
- auth.value,
595
- auth.validAfter,
596
- auth.validBefore,
597
- auth.nonce,
598
- v,
599
- r,
600
- s
601
- );
602
- const receipt = await tx.wait();
603
- return receipt.hash;
603
+ const amountInUnits = Math.floor(config.price * 1e6).toString();
604
+ const requirements = {
605
+ scheme: "exact",
606
+ network: `eip155:${chain.chainId}`,
607
+ maxAmountRequired: amountInUnits,
608
+ resource: this.manifest.provider.wallet
609
+ };
610
+ const response = await fetch(`${this.facilitatorUrl}/settle`, {
611
+ method: "POST",
612
+ headers: { "Content-Type": "application/json" },
613
+ body: JSON.stringify({
614
+ paymentPayload: payment,
615
+ paymentRequirements: requirements
616
+ })
617
+ });
618
+ const result = await response.json();
619
+ if (!response.ok) {
620
+ throw new Error(result.error || "Settlement failed");
621
+ }
622
+ return {
623
+ transaction: result.transaction,
624
+ status: result.status || "settled"
625
+ };
604
626
  }
605
627
  async readBody(req) {
606
628
  return new Promise((resolve2, reject) => {
@@ -616,8 +638,12 @@ var MoltsPayServer = class {
616
638
  req.on("error", reject);
617
639
  });
618
640
  }
619
- sendJson(res, status, data) {
620
- res.writeHead(status, { "Content-Type": "application/json" });
641
+ sendJson(res, status, data, extraHeaders) {
642
+ const headers = { "Content-Type": "application/json" };
643
+ if (extraHeaders) {
644
+ Object.assign(headers, extraHeaders);
645
+ }
646
+ res.writeHead(status, headers);
621
647
  res.end(JSON.stringify(data, null, 2));
622
648
  }
623
649
  };
@@ -778,7 +804,7 @@ program.command("services <url>").description("List services from a provider").o
778
804
  console.error("\u274C Error:", err.message);
779
805
  }
780
806
  });
781
- 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) => {
807
+ 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) => {
782
808
  const manifestPath = resolve(manifest);
783
809
  if (!existsSync2(manifestPath)) {
784
810
  console.error(`\u274C Manifest not found: ${manifestPath}`);
@@ -786,16 +812,15 @@ program.command("start <manifest>").description("Start MoltsPay server from serv
786
812
  }
787
813
  const port = parseInt(options.port, 10);
788
814
  const host = options.host;
789
- const privateKey = options.privateKey || process.env.MOLTSPAY_PRIVATE_KEY;
815
+ const facilitatorUrl = options.facilitator;
790
816
  console.log(`
791
817
  \u{1F680} Starting MoltsPay Server (x402 protocol)
792
818
  `);
793
819
  console.log(` Manifest: ${manifestPath}`);
794
820
  console.log(` Port: ${port}`);
795
- console.log(` Payment claims: ${privateKey ? "enabled" : "disabled (no private key)"}`);
796
821
  console.log("");
797
822
  try {
798
- const server = new MoltsPayServer(manifestPath, { port, host, privateKey });
823
+ const server = new MoltsPayServer(manifestPath, { port, host, facilitatorUrl });
799
824
  const manifestContent = await import("fs").then(
800
825
  (fs) => JSON.parse(fs.readFileSync(manifestPath, "utf-8"))
801
826
  );