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.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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]
|
|
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
|
|
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,
|
|
403
|
-
console.log(`[MoltsPay] Server listening on http://${
|
|
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
|
-
|
|
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 (
|
|
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,
|
|
507
|
-
let
|
|
500
|
+
console.log(`[MoltsPay] Skill succeeded, settling payment...`);
|
|
501
|
+
let settlement = null;
|
|
508
502
|
try {
|
|
509
|
-
|
|
510
|
-
console.log(`[MoltsPay] Payment
|
|
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]
|
|
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:
|
|
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
|
-
*
|
|
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
|
-
*
|
|
569
|
+
* Verify payment with facilitator
|
|
578
570
|
*/
|
|
579
|
-
async
|
|
580
|
-
|
|
581
|
-
|
|
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
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
const
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
return
|
|
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
|
-
|
|
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("--
|
|
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
|
|
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,
|
|
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
|
);
|