moltspay 0.8.0 → 0.8.2
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/.env.example +10 -0
- package/dist/cli/index.js +207 -98
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +226 -111
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.js +321 -211
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +338 -228
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.d.mts +23 -11
- package/dist/server/index.d.ts +23 -11
- package/dist/server/index.js +213 -153
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +211 -154
- package/dist/server/index.mjs.map +1 -1
- package/package.json +3 -1
package/dist/server/index.d.mts
CHANGED
|
@@ -69,14 +69,20 @@ interface MoltsPayServerOptions {
|
|
|
69
69
|
port?: number;
|
|
70
70
|
host?: string;
|
|
71
71
|
chargeExpirySecs?: number;
|
|
72
|
-
/**
|
|
73
|
-
|
|
72
|
+
/** x402 Facilitator URL (default: https://x402.org/facilitator) */
|
|
73
|
+
facilitatorUrl?: string;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
/**
|
|
77
77
|
* MoltsPay Server - Payment infrastructure for AI Agents
|
|
78
78
|
*
|
|
79
|
-
*
|
|
79
|
+
* Supports both testnet (x402.org) and mainnet (CDP) facilitators.
|
|
80
|
+
* Server does NOT need private key - facilitator handles on-chain settlement.
|
|
81
|
+
*
|
|
82
|
+
* Environment variables (from ~/.moltspay/.env or process.env):
|
|
83
|
+
* USE_MAINNET=true - Use Base mainnet (requires CDP keys)
|
|
84
|
+
* CDP_API_KEY_ID=xxx - Coinbase Developer Platform API key ID
|
|
85
|
+
* CDP_API_KEY_SECRET=xxx - CDP API key secret
|
|
80
86
|
*
|
|
81
87
|
* Usage:
|
|
82
88
|
* const server = new MoltsPayServer('./moltspay.services.json');
|
|
@@ -88,17 +94,21 @@ declare class MoltsPayServer {
|
|
|
88
94
|
private manifest;
|
|
89
95
|
private skills;
|
|
90
96
|
private options;
|
|
91
|
-
private
|
|
92
|
-
private
|
|
97
|
+
private cdpConfig;
|
|
98
|
+
private facilitatorUrl;
|
|
99
|
+
private networkId;
|
|
93
100
|
constructor(servicesPath: string, options?: MoltsPayServerOptions);
|
|
94
101
|
/**
|
|
95
102
|
* Register a skill handler for a service
|
|
96
103
|
*/
|
|
97
104
|
skill(serviceId: string, handler: SkillFunction): this;
|
|
98
105
|
/**
|
|
99
|
-
* Start
|
|
106
|
+
* Start HTTP server
|
|
100
107
|
*/
|
|
101
108
|
listen(port?: number): void;
|
|
109
|
+
/**
|
|
110
|
+
* Handle incoming request
|
|
111
|
+
*/
|
|
102
112
|
private handleRequest;
|
|
103
113
|
/**
|
|
104
114
|
* GET /services - List available services
|
|
@@ -106,8 +116,6 @@ declare class MoltsPayServer {
|
|
|
106
116
|
private handleGetServices;
|
|
107
117
|
/**
|
|
108
118
|
* POST /execute - Execute service with x402 payment
|
|
109
|
-
* Body: { service: string, params: object }
|
|
110
|
-
* Header: X-Payment (optional - if missing, returns 402)
|
|
111
119
|
*/
|
|
112
120
|
private handleExecute;
|
|
113
121
|
/**
|
|
@@ -115,13 +123,17 @@ declare class MoltsPayServer {
|
|
|
115
123
|
*/
|
|
116
124
|
private sendPaymentRequired;
|
|
117
125
|
/**
|
|
118
|
-
*
|
|
126
|
+
* Basic payment validation
|
|
119
127
|
*/
|
|
120
128
|
private validatePayment;
|
|
121
129
|
/**
|
|
122
|
-
*
|
|
130
|
+
* Verify payment with facilitator (testnet or CDP)
|
|
131
|
+
*/
|
|
132
|
+
private verifyWithFacilitator;
|
|
133
|
+
/**
|
|
134
|
+
* Settle payment with facilitator (execute on-chain transfer)
|
|
123
135
|
*/
|
|
124
|
-
private
|
|
136
|
+
private settleWithFacilitator;
|
|
125
137
|
private readBody;
|
|
126
138
|
private sendJson;
|
|
127
139
|
}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -69,14 +69,20 @@ interface MoltsPayServerOptions {
|
|
|
69
69
|
port?: number;
|
|
70
70
|
host?: string;
|
|
71
71
|
chargeExpirySecs?: number;
|
|
72
|
-
/**
|
|
73
|
-
|
|
72
|
+
/** x402 Facilitator URL (default: https://x402.org/facilitator) */
|
|
73
|
+
facilitatorUrl?: string;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
/**
|
|
77
77
|
* MoltsPay Server - Payment infrastructure for AI Agents
|
|
78
78
|
*
|
|
79
|
-
*
|
|
79
|
+
* Supports both testnet (x402.org) and mainnet (CDP) facilitators.
|
|
80
|
+
* Server does NOT need private key - facilitator handles on-chain settlement.
|
|
81
|
+
*
|
|
82
|
+
* Environment variables (from ~/.moltspay/.env or process.env):
|
|
83
|
+
* USE_MAINNET=true - Use Base mainnet (requires CDP keys)
|
|
84
|
+
* CDP_API_KEY_ID=xxx - Coinbase Developer Platform API key ID
|
|
85
|
+
* CDP_API_KEY_SECRET=xxx - CDP API key secret
|
|
80
86
|
*
|
|
81
87
|
* Usage:
|
|
82
88
|
* const server = new MoltsPayServer('./moltspay.services.json');
|
|
@@ -88,17 +94,21 @@ declare class MoltsPayServer {
|
|
|
88
94
|
private manifest;
|
|
89
95
|
private skills;
|
|
90
96
|
private options;
|
|
91
|
-
private
|
|
92
|
-
private
|
|
97
|
+
private cdpConfig;
|
|
98
|
+
private facilitatorUrl;
|
|
99
|
+
private networkId;
|
|
93
100
|
constructor(servicesPath: string, options?: MoltsPayServerOptions);
|
|
94
101
|
/**
|
|
95
102
|
* Register a skill handler for a service
|
|
96
103
|
*/
|
|
97
104
|
skill(serviceId: string, handler: SkillFunction): this;
|
|
98
105
|
/**
|
|
99
|
-
* Start
|
|
106
|
+
* Start HTTP server
|
|
100
107
|
*/
|
|
101
108
|
listen(port?: number): void;
|
|
109
|
+
/**
|
|
110
|
+
* Handle incoming request
|
|
111
|
+
*/
|
|
102
112
|
private handleRequest;
|
|
103
113
|
/**
|
|
104
114
|
* GET /services - List available services
|
|
@@ -106,8 +116,6 @@ declare class MoltsPayServer {
|
|
|
106
116
|
private handleGetServices;
|
|
107
117
|
/**
|
|
108
118
|
* POST /execute - Execute service with x402 payment
|
|
109
|
-
* Body: { service: string, params: object }
|
|
110
|
-
* Header: X-Payment (optional - if missing, returns 402)
|
|
111
119
|
*/
|
|
112
120
|
private handleExecute;
|
|
113
121
|
/**
|
|
@@ -115,13 +123,17 @@ declare class MoltsPayServer {
|
|
|
115
123
|
*/
|
|
116
124
|
private sendPaymentRequired;
|
|
117
125
|
/**
|
|
118
|
-
*
|
|
126
|
+
* Basic payment validation
|
|
119
127
|
*/
|
|
120
128
|
private validatePayment;
|
|
121
129
|
/**
|
|
122
|
-
*
|
|
130
|
+
* Verify payment with facilitator (testnet or CDP)
|
|
131
|
+
*/
|
|
132
|
+
private verifyWithFacilitator;
|
|
133
|
+
/**
|
|
134
|
+
* Settle payment with facilitator (execute on-chain transfer)
|
|
123
135
|
*/
|
|
124
|
-
private
|
|
136
|
+
private settleWithFacilitator;
|
|
125
137
|
private readBody;
|
|
126
138
|
private sendJson;
|
|
127
139
|
}
|
package/dist/server/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/server/index.ts
|
|
@@ -25,98 +35,96 @@ __export(server_exports, {
|
|
|
25
35
|
module.exports = __toCommonJS(server_exports);
|
|
26
36
|
var import_fs = require("fs");
|
|
27
37
|
var import_http = require("http");
|
|
28
|
-
var
|
|
29
|
-
|
|
30
|
-
// src/chains/index.ts
|
|
31
|
-
var CHAINS = {
|
|
32
|
-
// ============ Mainnet ============
|
|
33
|
-
base: {
|
|
34
|
-
name: "Base",
|
|
35
|
-
chainId: 8453,
|
|
36
|
-
rpc: "https://mainnet.base.org",
|
|
37
|
-
usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
38
|
-
explorer: "https://basescan.org/address/",
|
|
39
|
-
explorerTx: "https://basescan.org/tx/",
|
|
40
|
-
avgBlockTime: 2
|
|
41
|
-
},
|
|
42
|
-
polygon: {
|
|
43
|
-
name: "Polygon",
|
|
44
|
-
chainId: 137,
|
|
45
|
-
rpc: "https://polygon-rpc.com",
|
|
46
|
-
usdc: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
47
|
-
explorer: "https://polygonscan.com/address/",
|
|
48
|
-
explorerTx: "https://polygonscan.com/tx/",
|
|
49
|
-
avgBlockTime: 2
|
|
50
|
-
},
|
|
51
|
-
ethereum: {
|
|
52
|
-
name: "Ethereum",
|
|
53
|
-
chainId: 1,
|
|
54
|
-
rpc: "https://eth.llamarpc.com",
|
|
55
|
-
usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
56
|
-
explorer: "https://etherscan.io/address/",
|
|
57
|
-
explorerTx: "https://etherscan.io/tx/",
|
|
58
|
-
avgBlockTime: 12
|
|
59
|
-
},
|
|
60
|
-
// ============ Testnet ============
|
|
61
|
-
base_sepolia: {
|
|
62
|
-
name: "Base Sepolia",
|
|
63
|
-
chainId: 84532,
|
|
64
|
-
rpc: "https://sepolia.base.org",
|
|
65
|
-
usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
66
|
-
explorer: "https://sepolia.basescan.org/address/",
|
|
67
|
-
explorerTx: "https://sepolia.basescan.org/tx/",
|
|
68
|
-
avgBlockTime: 2
|
|
69
|
-
},
|
|
70
|
-
sepolia: {
|
|
71
|
-
name: "Sepolia",
|
|
72
|
-
chainId: 11155111,
|
|
73
|
-
rpc: "https://rpc.sepolia.org",
|
|
74
|
-
usdc: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
|
|
75
|
-
explorer: "https://sepolia.etherscan.io/address/",
|
|
76
|
-
explorerTx: "https://sepolia.etherscan.io/tx/",
|
|
77
|
-
avgBlockTime: 12
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
function getChain(name) {
|
|
81
|
-
const config = CHAINS[name];
|
|
82
|
-
if (!config) {
|
|
83
|
-
throw new Error(`Unsupported chain: ${name}. Supported: ${Object.keys(CHAINS).join(", ")}`);
|
|
84
|
-
}
|
|
85
|
-
return config;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// src/server/index.ts
|
|
38
|
+
var path = __toESM(require("path"));
|
|
89
39
|
var X402_VERSION = 2;
|
|
90
40
|
var PAYMENT_REQUIRED_HEADER = "x-payment-required";
|
|
91
41
|
var PAYMENT_HEADER = "x-payment";
|
|
42
|
+
var PAYMENT_RESPONSE_HEADER = "x-payment-response";
|
|
43
|
+
var FACILITATOR_TESTNET = "https://www.x402.org/facilitator";
|
|
44
|
+
var FACILITATOR_MAINNET = "https://api.cdp.coinbase.com/platform/v2/x402";
|
|
45
|
+
function loadEnvFiles() {
|
|
46
|
+
try {
|
|
47
|
+
const dotenv = require("dotenv");
|
|
48
|
+
const envPaths = [
|
|
49
|
+
path.join(process.cwd(), ".env"),
|
|
50
|
+
path.join(process.env.HOME || "", ".moltspay", ".env")
|
|
51
|
+
];
|
|
52
|
+
for (const envPath of envPaths) {
|
|
53
|
+
if ((0, import_fs.existsSync)(envPath)) {
|
|
54
|
+
dotenv.config({ path: envPath });
|
|
55
|
+
console.log(`[MoltsPay] Loaded config from ${envPath}`);
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function getCDPConfig() {
|
|
63
|
+
loadEnvFiles();
|
|
64
|
+
return {
|
|
65
|
+
useMainnet: process.env.USE_MAINNET?.toLowerCase() === "true",
|
|
66
|
+
apiKeyId: process.env.CDP_API_KEY_ID,
|
|
67
|
+
apiKeySecret: process.env.CDP_API_KEY_SECRET
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
async function getCDPAuthHeaders(method, urlPath, body) {
|
|
71
|
+
const config = getCDPConfig();
|
|
72
|
+
if (!config.apiKeyId || !config.apiKeySecret) {
|
|
73
|
+
throw new Error("CDP_API_KEY_ID and CDP_API_KEY_SECRET required for mainnet");
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const { getAuthHeaders } = await import("@coinbase/cdp-sdk/auth");
|
|
77
|
+
const headers = await getAuthHeaders({
|
|
78
|
+
apiKeyId: config.apiKeyId,
|
|
79
|
+
apiKeySecret: config.apiKeySecret,
|
|
80
|
+
requestMethod: method,
|
|
81
|
+
requestHost: "api.cdp.coinbase.com",
|
|
82
|
+
requestPath: urlPath,
|
|
83
|
+
requestBody: body
|
|
84
|
+
});
|
|
85
|
+
return headers;
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.error("[MoltsPay] Failed to generate CDP auth headers:", err.message);
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
92
91
|
var MoltsPayServer = class {
|
|
93
92
|
manifest;
|
|
94
93
|
skills = /* @__PURE__ */ new Map();
|
|
95
94
|
options;
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
cdpConfig;
|
|
96
|
+
facilitatorUrl;
|
|
97
|
+
networkId;
|
|
98
98
|
constructor(servicesPath, options = {}) {
|
|
99
|
+
this.cdpConfig = getCDPConfig();
|
|
99
100
|
const content = (0, import_fs.readFileSync)(servicesPath, "utf-8");
|
|
100
101
|
this.manifest = JSON.parse(content);
|
|
101
102
|
this.options = {
|
|
102
103
|
port: options.port || 3e3,
|
|
103
|
-
host: options.host || "0.0.0.0"
|
|
104
|
-
privateKey: options.privateKey || process.env.MOLTSPAY_PRIVATE_KEY
|
|
104
|
+
host: options.host || "0.0.0.0"
|
|
105
105
|
};
|
|
106
|
-
if (this.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
this.wallet = new import_ethers.ethers.Wallet(this.options.privateKey, this.provider);
|
|
111
|
-
console.log(`[MoltsPay] Payment wallet: ${this.wallet.address}`);
|
|
112
|
-
} catch (err) {
|
|
113
|
-
console.warn("[MoltsPay] Warning: Could not initialize wallet for payment claims");
|
|
106
|
+
if (this.cdpConfig.useMainnet) {
|
|
107
|
+
if (!this.cdpConfig.apiKeyId || !this.cdpConfig.apiKeySecret) {
|
|
108
|
+
console.warn("[MoltsPay] WARNING: USE_MAINNET=true but CDP keys not set!");
|
|
109
|
+
console.warn("[MoltsPay] Set CDP_API_KEY_ID and CDP_API_KEY_SECRET in ~/.moltspay/.env");
|
|
114
110
|
}
|
|
111
|
+
this.facilitatorUrl = FACILITATOR_MAINNET;
|
|
112
|
+
this.networkId = "eip155:8453";
|
|
113
|
+
} else {
|
|
114
|
+
this.facilitatorUrl = options.facilitatorUrl || FACILITATOR_TESTNET;
|
|
115
|
+
this.networkId = "eip155:84532";
|
|
115
116
|
}
|
|
117
|
+
const networkName = this.cdpConfig.useMainnet ? "Base mainnet" : "Base Sepolia (testnet)";
|
|
118
|
+
const facilitatorName = this.cdpConfig.useMainnet ? "CDP" : "x402.org";
|
|
116
119
|
console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);
|
|
117
120
|
console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);
|
|
118
121
|
console.log(`[MoltsPay] Receive wallet: ${this.manifest.provider.wallet}`);
|
|
119
|
-
console.log(`[MoltsPay]
|
|
122
|
+
console.log(`[MoltsPay] Network: ${this.networkId} (${networkName})`);
|
|
123
|
+
console.log(`[MoltsPay] Facilitator: ${facilitatorName} (${this.facilitatorUrl})`);
|
|
124
|
+
if (this.cdpConfig.useMainnet && this.cdpConfig.apiKeyId) {
|
|
125
|
+
console.log(`[MoltsPay] CDP API Key: ${this.cdpConfig.apiKeyId.slice(0, 8)}...`);
|
|
126
|
+
}
|
|
127
|
+
console.log(`[MoltsPay] Protocol: x402 (gasless for both client AND server)`);
|
|
120
128
|
}
|
|
121
129
|
/**
|
|
122
130
|
* Register a skill handler for a service
|
|
@@ -126,48 +134,45 @@ var MoltsPayServer = class {
|
|
|
126
134
|
if (!config) {
|
|
127
135
|
throw new Error(`Service '${serviceId}' not found in manifest`);
|
|
128
136
|
}
|
|
129
|
-
this.skills.set(serviceId, {
|
|
130
|
-
id: serviceId,
|
|
131
|
-
config,
|
|
132
|
-
handler
|
|
133
|
-
});
|
|
134
|
-
console.log(`[MoltsPay] Registered skill: ${serviceId} ($${config.price} ${config.currency})`);
|
|
137
|
+
this.skills.set(serviceId, { id: serviceId, config, handler });
|
|
135
138
|
return this;
|
|
136
139
|
}
|
|
137
140
|
/**
|
|
138
|
-
* Start
|
|
141
|
+
* Start HTTP server
|
|
139
142
|
*/
|
|
140
143
|
listen(port) {
|
|
141
|
-
const p = port || this.options.port;
|
|
144
|
+
const p = port || this.options.port || 3e3;
|
|
145
|
+
const host = this.options.host || "0.0.0.0";
|
|
142
146
|
const server = (0, import_http.createServer)((req, res) => this.handleRequest(req, res));
|
|
143
|
-
server.listen(p,
|
|
144
|
-
console.log(`[MoltsPay] Server listening on http://${
|
|
147
|
+
server.listen(p, host, () => {
|
|
148
|
+
console.log(`[MoltsPay] Server listening on http://${host}:${p}`);
|
|
145
149
|
console.log(`[MoltsPay] Endpoints:`);
|
|
146
150
|
console.log(` GET /services - List available services`);
|
|
147
151
|
console.log(` POST /execute - Execute service (x402 payment)`);
|
|
148
152
|
});
|
|
149
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* Handle incoming request
|
|
156
|
+
*/
|
|
150
157
|
async handleRequest(req, res) {
|
|
151
|
-
const url = new URL(req.url || "/", `http://${req.headers.host}`);
|
|
152
|
-
const path = url.pathname;
|
|
153
|
-
const method = req.method || "GET";
|
|
154
158
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
155
159
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
156
160
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment");
|
|
157
161
|
res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response");
|
|
158
|
-
if (method === "OPTIONS") {
|
|
162
|
+
if (req.method === "OPTIONS") {
|
|
159
163
|
res.writeHead(204);
|
|
160
164
|
res.end();
|
|
161
165
|
return;
|
|
162
166
|
}
|
|
163
167
|
try {
|
|
164
|
-
|
|
168
|
+
const url = new URL(req.url || "/", `http://${req.headers.host}`);
|
|
169
|
+
if (url.pathname === "/services" && req.method === "GET") {
|
|
165
170
|
return this.handleGetServices(res);
|
|
166
171
|
}
|
|
167
|
-
if (
|
|
172
|
+
if (url.pathname === "/execute" && req.method === "POST") {
|
|
168
173
|
const body = await this.readBody(req);
|
|
169
174
|
const paymentHeader = req.headers[PAYMENT_HEADER];
|
|
170
|
-
return this.handleExecute(body, paymentHeader, res);
|
|
175
|
+
return await this.handleExecute(body, paymentHeader, res);
|
|
171
176
|
}
|
|
172
177
|
this.sendJson(res, 404, { error: "Not found" });
|
|
173
178
|
} catch (err) {
|
|
@@ -179,7 +184,6 @@ var MoltsPayServer = class {
|
|
|
179
184
|
* GET /services - List available services
|
|
180
185
|
*/
|
|
181
186
|
handleGetServices(res) {
|
|
182
|
-
const chain = getChain(this.manifest.provider.chain);
|
|
183
187
|
const services = this.manifest.services.map((s) => ({
|
|
184
188
|
id: s.id,
|
|
185
189
|
name: s.name,
|
|
@@ -195,15 +199,15 @@ var MoltsPayServer = class {
|
|
|
195
199
|
services,
|
|
196
200
|
x402: {
|
|
197
201
|
version: X402_VERSION,
|
|
198
|
-
network:
|
|
199
|
-
schemes: ["exact"]
|
|
202
|
+
network: this.networkId,
|
|
203
|
+
schemes: ["exact"],
|
|
204
|
+
facilitator: this.cdpConfig.useMainnet ? "cdp" : "x402.org",
|
|
205
|
+
mainnet: this.cdpConfig.useMainnet
|
|
200
206
|
}
|
|
201
207
|
});
|
|
202
208
|
}
|
|
203
209
|
/**
|
|
204
210
|
* POST /execute - Execute service with x402 payment
|
|
205
|
-
* Body: { service: string, params: object }
|
|
206
|
-
* Header: X-Payment (optional - if missing, returns 402)
|
|
207
211
|
*/
|
|
208
212
|
async handleExecute(body, paymentHeader, res) {
|
|
209
213
|
const { service, params } = body;
|
|
@@ -233,6 +237,11 @@ var MoltsPayServer = class {
|
|
|
233
237
|
if (!validation.valid) {
|
|
234
238
|
return this.sendJson(res, 402, { error: validation.error });
|
|
235
239
|
}
|
|
240
|
+
console.log(`[MoltsPay] Verifying payment with facilitator...`);
|
|
241
|
+
const verifyResult = await this.verifyWithFacilitator(payment, skill.config);
|
|
242
|
+
if (!verifyResult.valid) {
|
|
243
|
+
return this.sendJson(res, 402, { error: `Payment verification failed: ${verifyResult.error}` });
|
|
244
|
+
}
|
|
236
245
|
console.log(`[MoltsPay] Executing skill: ${service}`);
|
|
237
246
|
let result;
|
|
238
247
|
try {
|
|
@@ -244,32 +253,46 @@ var MoltsPayServer = class {
|
|
|
244
253
|
message: err.message
|
|
245
254
|
});
|
|
246
255
|
}
|
|
247
|
-
console.log(`[MoltsPay] Skill succeeded,
|
|
248
|
-
let
|
|
256
|
+
console.log(`[MoltsPay] Skill succeeded, settling payment...`);
|
|
257
|
+
let settlement = null;
|
|
249
258
|
try {
|
|
250
|
-
|
|
251
|
-
console.log(`[MoltsPay] Payment
|
|
259
|
+
settlement = await this.settleWithFacilitator(payment, skill.config);
|
|
260
|
+
console.log(`[MoltsPay] Payment settled: ${settlement.transaction || "pending"}`);
|
|
252
261
|
} catch (err) {
|
|
253
|
-
console.error("[MoltsPay]
|
|
262
|
+
console.error("[MoltsPay] Settlement failed:", err.message);
|
|
263
|
+
}
|
|
264
|
+
const responseHeaders = {};
|
|
265
|
+
if (settlement) {
|
|
266
|
+
const responsePayload = {
|
|
267
|
+
success: true,
|
|
268
|
+
transaction: settlement.transaction,
|
|
269
|
+
network: payment.network
|
|
270
|
+
};
|
|
271
|
+
responseHeaders[PAYMENT_RESPONSE_HEADER] = Buffer.from(
|
|
272
|
+
JSON.stringify(responsePayload)
|
|
273
|
+
).toString("base64");
|
|
254
274
|
}
|
|
255
275
|
this.sendJson(res, 200, {
|
|
256
276
|
success: true,
|
|
257
277
|
result,
|
|
258
|
-
payment:
|
|
259
|
-
});
|
|
278
|
+
payment: settlement ? { transaction: settlement.transaction, status: "settled" } : { status: "pending" }
|
|
279
|
+
}, responseHeaders);
|
|
260
280
|
}
|
|
261
281
|
/**
|
|
262
282
|
* Return 402 with x402 payment requirements
|
|
263
283
|
*/
|
|
264
284
|
sendPaymentRequired(config, res) {
|
|
265
|
-
const chain = getChain(this.manifest.provider.chain);
|
|
266
285
|
const amountInUnits = Math.floor(config.price * 1e6).toString();
|
|
267
286
|
const requirements = [{
|
|
268
287
|
scheme: "exact",
|
|
269
|
-
network:
|
|
288
|
+
network: this.networkId,
|
|
270
289
|
maxAmountRequired: amountInUnits,
|
|
271
290
|
resource: this.manifest.provider.wallet,
|
|
272
|
-
description: `${config.name} - $${config.price} ${config.currency}
|
|
291
|
+
description: `${config.name} - $${config.price} ${config.currency}`,
|
|
292
|
+
extra: JSON.stringify({
|
|
293
|
+
facilitator: this.cdpConfig.useMainnet ? "cdp" : "x402.org",
|
|
294
|
+
mainnet: this.cdpConfig.useMainnet
|
|
295
|
+
})
|
|
273
296
|
}];
|
|
274
297
|
const encoded = Buffer.from(JSON.stringify(requirements)).toString("base64");
|
|
275
298
|
res.writeHead(402, {
|
|
@@ -283,7 +306,7 @@ var MoltsPayServer = class {
|
|
|
283
306
|
}, null, 2));
|
|
284
307
|
}
|
|
285
308
|
/**
|
|
286
|
-
*
|
|
309
|
+
* Basic payment validation
|
|
287
310
|
*/
|
|
288
311
|
validatePayment(payment, config) {
|
|
289
312
|
if (payment.x402Version !== X402_VERSION) {
|
|
@@ -292,56 +315,89 @@ var MoltsPayServer = class {
|
|
|
292
315
|
if (payment.scheme !== "exact") {
|
|
293
316
|
return { valid: false, error: `Unsupported scheme: ${payment.scheme}` };
|
|
294
317
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (payment.network !== expectedNetwork) {
|
|
298
|
-
return { valid: false, error: `Network mismatch: expected ${expectedNetwork}` };
|
|
299
|
-
}
|
|
300
|
-
const auth = payment.payload.authorization;
|
|
301
|
-
if (auth.to.toLowerCase() !== this.manifest.provider.wallet.toLowerCase()) {
|
|
302
|
-
return { valid: false, error: "Payment recipient mismatch" };
|
|
303
|
-
}
|
|
304
|
-
const amount = Number(auth.value) / 1e6;
|
|
305
|
-
if (amount < config.price) {
|
|
306
|
-
return { valid: false, error: `Insufficient amount: $${amount} < $${config.price}` };
|
|
307
|
-
}
|
|
308
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
309
|
-
if (Number(auth.validBefore) < now) {
|
|
310
|
-
return { valid: false, error: "Payment authorization expired" };
|
|
311
|
-
}
|
|
312
|
-
if (Number(auth.validAfter) > now) {
|
|
313
|
-
return { valid: false, error: "Payment authorization not yet valid" };
|
|
318
|
+
if (payment.network !== this.networkId) {
|
|
319
|
+
return { valid: false, error: `Network mismatch: expected ${this.networkId}, got ${payment.network}` };
|
|
314
320
|
}
|
|
315
321
|
return { valid: true };
|
|
316
322
|
}
|
|
317
323
|
/**
|
|
318
|
-
*
|
|
324
|
+
* Verify payment with facilitator (testnet or CDP)
|
|
319
325
|
*/
|
|
320
|
-
async
|
|
321
|
-
|
|
322
|
-
|
|
326
|
+
async verifyWithFacilitator(payment, config) {
|
|
327
|
+
try {
|
|
328
|
+
const amountInUnits = Math.floor(config.price * 1e6).toString();
|
|
329
|
+
const requirements = {
|
|
330
|
+
scheme: "exact",
|
|
331
|
+
network: this.networkId,
|
|
332
|
+
maxAmountRequired: amountInUnits,
|
|
333
|
+
resource: this.manifest.provider.wallet,
|
|
334
|
+
payTo: this.manifest.provider.wallet
|
|
335
|
+
};
|
|
336
|
+
const requestBody = {
|
|
337
|
+
paymentPayload: payment,
|
|
338
|
+
paymentRequirements: requirements
|
|
339
|
+
};
|
|
340
|
+
let headers = { "Content-Type": "application/json" };
|
|
341
|
+
if (this.cdpConfig.useMainnet) {
|
|
342
|
+
const authHeaders = await getCDPAuthHeaders(
|
|
343
|
+
"POST",
|
|
344
|
+
"/platform/v2/x402/verify",
|
|
345
|
+
requestBody
|
|
346
|
+
);
|
|
347
|
+
headers = { ...headers, ...authHeaders };
|
|
348
|
+
}
|
|
349
|
+
const response = await fetch(`${this.facilitatorUrl}/verify`, {
|
|
350
|
+
method: "POST",
|
|
351
|
+
headers,
|
|
352
|
+
body: JSON.stringify(requestBody)
|
|
353
|
+
});
|
|
354
|
+
const result = await response.json();
|
|
355
|
+
if (!response.ok || !result.isValid) {
|
|
356
|
+
return { valid: false, error: result.invalidReason || result.error || "Verification failed" };
|
|
357
|
+
}
|
|
358
|
+
return { valid: true };
|
|
359
|
+
} catch (err) {
|
|
360
|
+
return { valid: false, error: `Facilitator error: ${err.message}` };
|
|
323
361
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
)
|
|
343
|
-
|
|
344
|
-
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Settle payment with facilitator (execute on-chain transfer)
|
|
365
|
+
*/
|
|
366
|
+
async settleWithFacilitator(payment, config) {
|
|
367
|
+
const amountInUnits = Math.floor(config.price * 1e6).toString();
|
|
368
|
+
const requirements = {
|
|
369
|
+
scheme: "exact",
|
|
370
|
+
network: this.networkId,
|
|
371
|
+
maxAmountRequired: amountInUnits,
|
|
372
|
+
resource: this.manifest.provider.wallet,
|
|
373
|
+
payTo: this.manifest.provider.wallet
|
|
374
|
+
};
|
|
375
|
+
const requestBody = {
|
|
376
|
+
paymentPayload: payment,
|
|
377
|
+
paymentRequirements: requirements
|
|
378
|
+
};
|
|
379
|
+
let headers = { "Content-Type": "application/json" };
|
|
380
|
+
if (this.cdpConfig.useMainnet) {
|
|
381
|
+
const authHeaders = await getCDPAuthHeaders(
|
|
382
|
+
"POST",
|
|
383
|
+
"/platform/v2/x402/settle",
|
|
384
|
+
requestBody
|
|
385
|
+
);
|
|
386
|
+
headers = { ...headers, ...authHeaders };
|
|
387
|
+
}
|
|
388
|
+
const response = await fetch(`${this.facilitatorUrl}/settle`, {
|
|
389
|
+
method: "POST",
|
|
390
|
+
headers,
|
|
391
|
+
body: JSON.stringify(requestBody)
|
|
392
|
+
});
|
|
393
|
+
const result = await response.json();
|
|
394
|
+
if (!response.ok || !result.success) {
|
|
395
|
+
throw new Error(result.error || result.errorReason || "Settlement failed");
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
transaction: result.transaction,
|
|
399
|
+
status: result.status || "settled"
|
|
400
|
+
};
|
|
345
401
|
}
|
|
346
402
|
async readBody(req) {
|
|
347
403
|
return new Promise((resolve, reject) => {
|
|
@@ -357,8 +413,12 @@ var MoltsPayServer = class {
|
|
|
357
413
|
req.on("error", reject);
|
|
358
414
|
});
|
|
359
415
|
}
|
|
360
|
-
sendJson(res, status, data) {
|
|
361
|
-
|
|
416
|
+
sendJson(res, status, data, extraHeaders) {
|
|
417
|
+
const headers = { "Content-Type": "application/json" };
|
|
418
|
+
if (extraHeaders) {
|
|
419
|
+
Object.assign(headers, extraHeaders);
|
|
420
|
+
}
|
|
421
|
+
res.writeHead(status, headers);
|
|
362
422
|
res.end(JSON.stringify(data, null, 2));
|
|
363
423
|
}
|
|
364
424
|
};
|