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 +113 -88
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +113 -88
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.js +132 -106
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +130 -104
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.d.mts +16 -9
- package/dist/server/index.d.ts +16 -9
- package/dist/server/index.js +110 -84
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +110 -84
- package/dist/server/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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]
|
|
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
|
|
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,
|
|
426
|
-
console.log(`[MoltsPay] Server listening on http://${
|
|
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
|
-
|
|
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 (
|
|
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,
|
|
530
|
-
let
|
|
523
|
+
console.log(`[MoltsPay] Skill succeeded, settling payment...`);
|
|
524
|
+
let settlement = null;
|
|
531
525
|
try {
|
|
532
|
-
|
|
533
|
-
console.log(`[MoltsPay] Payment
|
|
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]
|
|
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:
|
|
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
|
-
*
|
|
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
|
-
*
|
|
592
|
+
* Verify payment with facilitator
|
|
601
593
|
*/
|
|
602
|
-
async
|
|
603
|
-
|
|
604
|
-
|
|
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
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
return
|
|
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
|
-
|
|
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("--
|
|
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
|
|
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,
|
|
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
|
);
|