moltspay 0.7.2 → 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 +293 -274
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +294 -275
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/index.d.mts +11 -5
- package/dist/client/index.d.ts +11 -5
- package/dist/client/index.js +99 -68
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +96 -55
- package/dist/client/index.mjs.map +1 -1
- package/dist/index.js +435 -343
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +430 -338
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.d.mts +25 -9
- package/dist/server/index.d.ts +25 -9
- package/dist/server/index.js +187 -210
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +187 -210
- package/dist/server/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/server/index.d.mts
CHANGED
|
@@ -69,11 +69,16 @@ interface MoltsPayServerOptions {
|
|
|
69
69
|
port?: number;
|
|
70
70
|
host?: string;
|
|
71
71
|
chargeExpirySecs?: number;
|
|
72
|
+
/** x402 Facilitator URL (default: https://x402.org/facilitator) */
|
|
73
|
+
facilitatorUrl?: string;
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
/**
|
|
75
77
|
* MoltsPay Server - Payment infrastructure for AI Agents
|
|
76
78
|
*
|
|
79
|
+
* Uses x402 protocol with public facilitator (https://x402.org/facilitator).
|
|
80
|
+
* Server does NOT need private key - facilitator handles on-chain settlement.
|
|
81
|
+
*
|
|
77
82
|
* Usage:
|
|
78
83
|
* const server = new MoltsPayServer('./moltspay.services.json');
|
|
79
84
|
* server.skill('text-to-video', async (params) => { ... });
|
|
@@ -83,36 +88,47 @@ interface MoltsPayServerOptions {
|
|
|
83
88
|
declare class MoltsPayServer {
|
|
84
89
|
private manifest;
|
|
85
90
|
private skills;
|
|
86
|
-
private charges;
|
|
87
91
|
private options;
|
|
92
|
+
private facilitatorUrl;
|
|
88
93
|
constructor(servicesPath: string, options?: MoltsPayServerOptions);
|
|
89
94
|
/**
|
|
90
95
|
* Register a skill handler for a service
|
|
91
96
|
*/
|
|
92
97
|
skill(serviceId: string, handler: SkillFunction): this;
|
|
93
98
|
/**
|
|
94
|
-
* Start
|
|
99
|
+
* Start HTTP server
|
|
95
100
|
*/
|
|
96
101
|
listen(port?: number): void;
|
|
102
|
+
/**
|
|
103
|
+
* Handle incoming request
|
|
104
|
+
*/
|
|
97
105
|
private handleRequest;
|
|
98
106
|
/**
|
|
99
107
|
* GET /services - List available services
|
|
100
108
|
*/
|
|
101
109
|
private handleGetServices;
|
|
102
110
|
/**
|
|
103
|
-
* POST /
|
|
111
|
+
* POST /execute - Execute service with x402 payment
|
|
104
112
|
* Body: { service: string, params: object }
|
|
113
|
+
* Header: X-Payment (optional - if missing, returns 402)
|
|
114
|
+
*/
|
|
115
|
+
private handleExecute;
|
|
116
|
+
/**
|
|
117
|
+
* Return 402 with x402 payment requirements
|
|
118
|
+
*/
|
|
119
|
+
private sendPaymentRequired;
|
|
120
|
+
/**
|
|
121
|
+
* Basic payment validation (before calling facilitator)
|
|
105
122
|
*/
|
|
106
|
-
private
|
|
123
|
+
private validatePayment;
|
|
107
124
|
/**
|
|
108
|
-
*
|
|
109
|
-
* Body: { chargeId: string, txHash: string }
|
|
125
|
+
* Verify payment with facilitator
|
|
110
126
|
*/
|
|
111
|
-
private
|
|
127
|
+
private verifyWithFacilitator;
|
|
112
128
|
/**
|
|
113
|
-
*
|
|
129
|
+
* Settle payment with facilitator (execute on-chain transfer)
|
|
114
130
|
*/
|
|
115
|
-
private
|
|
131
|
+
private settleWithFacilitator;
|
|
116
132
|
private readBody;
|
|
117
133
|
private sendJson;
|
|
118
134
|
}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -69,11 +69,16 @@ interface MoltsPayServerOptions {
|
|
|
69
69
|
port?: number;
|
|
70
70
|
host?: string;
|
|
71
71
|
chargeExpirySecs?: number;
|
|
72
|
+
/** x402 Facilitator URL (default: https://x402.org/facilitator) */
|
|
73
|
+
facilitatorUrl?: string;
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
/**
|
|
75
77
|
* MoltsPay Server - Payment infrastructure for AI Agents
|
|
76
78
|
*
|
|
79
|
+
* Uses x402 protocol with public facilitator (https://x402.org/facilitator).
|
|
80
|
+
* Server does NOT need private key - facilitator handles on-chain settlement.
|
|
81
|
+
*
|
|
77
82
|
* Usage:
|
|
78
83
|
* const server = new MoltsPayServer('./moltspay.services.json');
|
|
79
84
|
* server.skill('text-to-video', async (params) => { ... });
|
|
@@ -83,36 +88,47 @@ interface MoltsPayServerOptions {
|
|
|
83
88
|
declare class MoltsPayServer {
|
|
84
89
|
private manifest;
|
|
85
90
|
private skills;
|
|
86
|
-
private charges;
|
|
87
91
|
private options;
|
|
92
|
+
private facilitatorUrl;
|
|
88
93
|
constructor(servicesPath: string, options?: MoltsPayServerOptions);
|
|
89
94
|
/**
|
|
90
95
|
* Register a skill handler for a service
|
|
91
96
|
*/
|
|
92
97
|
skill(serviceId: string, handler: SkillFunction): this;
|
|
93
98
|
/**
|
|
94
|
-
* Start
|
|
99
|
+
* Start HTTP server
|
|
95
100
|
*/
|
|
96
101
|
listen(port?: number): void;
|
|
102
|
+
/**
|
|
103
|
+
* Handle incoming request
|
|
104
|
+
*/
|
|
97
105
|
private handleRequest;
|
|
98
106
|
/**
|
|
99
107
|
* GET /services - List available services
|
|
100
108
|
*/
|
|
101
109
|
private handleGetServices;
|
|
102
110
|
/**
|
|
103
|
-
* POST /
|
|
111
|
+
* POST /execute - Execute service with x402 payment
|
|
104
112
|
* Body: { service: string, params: object }
|
|
113
|
+
* Header: X-Payment (optional - if missing, returns 402)
|
|
114
|
+
*/
|
|
115
|
+
private handleExecute;
|
|
116
|
+
/**
|
|
117
|
+
* Return 402 with x402 payment requirements
|
|
118
|
+
*/
|
|
119
|
+
private sendPaymentRequired;
|
|
120
|
+
/**
|
|
121
|
+
* Basic payment validation (before calling facilitator)
|
|
105
122
|
*/
|
|
106
|
-
private
|
|
123
|
+
private validatePayment;
|
|
107
124
|
/**
|
|
108
|
-
*
|
|
109
|
-
* Body: { chargeId: string, txHash: string }
|
|
125
|
+
* Verify payment with facilitator
|
|
110
126
|
*/
|
|
111
|
-
private
|
|
127
|
+
private verifyWithFacilitator;
|
|
112
128
|
/**
|
|
113
|
-
*
|
|
129
|
+
* Settle payment with facilitator (execute on-chain transfer)
|
|
114
130
|
*/
|
|
115
|
-
private
|
|
131
|
+
private settleWithFacilitator;
|
|
116
132
|
private readBody;
|
|
117
133
|
private sendJson;
|
|
118
134
|
}
|
package/dist/server/index.js
CHANGED
|
@@ -26,9 +26,6 @@ module.exports = __toCommonJS(server_exports);
|
|
|
26
26
|
var import_fs = require("fs");
|
|
27
27
|
var import_http = require("http");
|
|
28
28
|
|
|
29
|
-
// src/verify/index.ts
|
|
30
|
-
var import_ethers = require("ethers");
|
|
31
|
-
|
|
32
29
|
// src/chains/index.ts
|
|
33
30
|
var CHAINS = {
|
|
34
31
|
// ============ Mainnet ============
|
|
@@ -86,101 +83,31 @@ function getChain(name) {
|
|
|
86
83
|
}
|
|
87
84
|
return config;
|
|
88
85
|
}
|
|
89
|
-
function getChainById(chainId) {
|
|
90
|
-
return Object.values(CHAINS).find((c) => c.chainId === chainId);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// src/verify/index.ts
|
|
94
|
-
var TRANSFER_EVENT_TOPIC = import_ethers.ethers.id("Transfer(address,address,uint256)");
|
|
95
|
-
async function verifyPayment(params) {
|
|
96
|
-
const { txHash, expectedAmount, expectedTo } = params;
|
|
97
|
-
let chain;
|
|
98
|
-
try {
|
|
99
|
-
if (typeof params.chain === "number") {
|
|
100
|
-
chain = getChainById(params.chain);
|
|
101
|
-
} else {
|
|
102
|
-
chain = getChain(params.chain || "base");
|
|
103
|
-
}
|
|
104
|
-
if (!chain) {
|
|
105
|
-
return { verified: false, error: `Unsupported chain: ${params.chain}` };
|
|
106
|
-
}
|
|
107
|
-
} catch (e) {
|
|
108
|
-
return { verified: false, error: `Unsupported chain: ${params.chain}` };
|
|
109
|
-
}
|
|
110
|
-
try {
|
|
111
|
-
const provider = new import_ethers.ethers.JsonRpcProvider(chain.rpc);
|
|
112
|
-
const receipt = await provider.getTransactionReceipt(txHash);
|
|
113
|
-
if (!receipt) {
|
|
114
|
-
return { verified: false, error: "Transaction not found or not confirmed" };
|
|
115
|
-
}
|
|
116
|
-
if (receipt.status !== 1) {
|
|
117
|
-
return { verified: false, error: "Transaction failed" };
|
|
118
|
-
}
|
|
119
|
-
const usdcAddress = chain.usdc?.toLowerCase();
|
|
120
|
-
if (!usdcAddress) {
|
|
121
|
-
return { verified: false, error: `Chain ${chain.name} USDC address not configured` };
|
|
122
|
-
}
|
|
123
|
-
for (const log of receipt.logs) {
|
|
124
|
-
if (log.address.toLowerCase() !== usdcAddress) {
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
if (log.topics.length < 3 || log.topics[0] !== TRANSFER_EVENT_TOPIC) {
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
const from = "0x" + log.topics[1].slice(-40);
|
|
131
|
-
const to = "0x" + log.topics[2].slice(-40);
|
|
132
|
-
const amountRaw = BigInt(log.data);
|
|
133
|
-
const amount = Number(amountRaw) / 1e6;
|
|
134
|
-
if (expectedTo && to.toLowerCase() !== expectedTo.toLowerCase()) {
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
if (amount < expectedAmount) {
|
|
138
|
-
return {
|
|
139
|
-
verified: false,
|
|
140
|
-
error: `Insufficient amount: received ${amount} USDC, expected ${expectedAmount} USDC`,
|
|
141
|
-
amount,
|
|
142
|
-
from,
|
|
143
|
-
to,
|
|
144
|
-
txHash,
|
|
145
|
-
blockNumber: receipt.blockNumber
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
return {
|
|
149
|
-
verified: true,
|
|
150
|
-
amount,
|
|
151
|
-
from,
|
|
152
|
-
to,
|
|
153
|
-
txHash,
|
|
154
|
-
blockNumber: receipt.blockNumber
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
return { verified: false, error: "No USDC transfer found" };
|
|
158
|
-
} catch (e) {
|
|
159
|
-
return { verified: false, error: e.message || String(e) };
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
86
|
|
|
163
87
|
// src/server/index.ts
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
88
|
+
var X402_VERSION = 2;
|
|
89
|
+
var PAYMENT_REQUIRED_HEADER = "x-payment-required";
|
|
90
|
+
var PAYMENT_HEADER = "x-payment";
|
|
91
|
+
var PAYMENT_RESPONSE_HEADER = "x-payment-response";
|
|
92
|
+
var DEFAULT_FACILITATOR_URL = "https://x402.org/facilitator";
|
|
167
93
|
var MoltsPayServer = class {
|
|
168
94
|
manifest;
|
|
169
95
|
skills = /* @__PURE__ */ new Map();
|
|
170
|
-
charges = /* @__PURE__ */ new Map();
|
|
171
96
|
options;
|
|
97
|
+
facilitatorUrl;
|
|
172
98
|
constructor(servicesPath, options = {}) {
|
|
173
99
|
const content = (0, import_fs.readFileSync)(servicesPath, "utf-8");
|
|
174
100
|
this.manifest = JSON.parse(content);
|
|
175
101
|
this.options = {
|
|
176
102
|
port: options.port || 3e3,
|
|
177
|
-
host: options.host || "0.0.0.0"
|
|
178
|
-
chargeExpirySecs: options.chargeExpirySecs || 300
|
|
179
|
-
// 5 minutes
|
|
103
|
+
host: options.host || "0.0.0.0"
|
|
180
104
|
};
|
|
105
|
+
this.facilitatorUrl = options.facilitatorUrl || DEFAULT_FACILITATOR_URL;
|
|
181
106
|
console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);
|
|
182
107
|
console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);
|
|
183
|
-
console.log(`[MoltsPay]
|
|
108
|
+
console.log(`[MoltsPay] Receive wallet: ${this.manifest.provider.wallet}`);
|
|
109
|
+
console.log(`[MoltsPay] Facilitator: ${this.facilitatorUrl}`);
|
|
110
|
+
console.log(`[MoltsPay] Protocol: x402 (gasless for both client AND server)`);
|
|
184
111
|
}
|
|
185
112
|
/**
|
|
186
113
|
* Register a skill handler for a service
|
|
@@ -190,56 +117,45 @@ var MoltsPayServer = class {
|
|
|
190
117
|
if (!config) {
|
|
191
118
|
throw new Error(`Service '${serviceId}' not found in manifest`);
|
|
192
119
|
}
|
|
193
|
-
this.skills.set(serviceId, {
|
|
194
|
-
id: serviceId,
|
|
195
|
-
config,
|
|
196
|
-
handler
|
|
197
|
-
});
|
|
198
|
-
console.log(`[MoltsPay] Registered skill: ${serviceId} ($${config.price} ${config.currency})`);
|
|
120
|
+
this.skills.set(serviceId, { id: serviceId, config, handler });
|
|
199
121
|
return this;
|
|
200
122
|
}
|
|
201
123
|
/**
|
|
202
|
-
* Start
|
|
124
|
+
* Start HTTP server
|
|
203
125
|
*/
|
|
204
126
|
listen(port) {
|
|
205
|
-
const p = port || this.options.port;
|
|
127
|
+
const p = port || this.options.port || 3e3;
|
|
128
|
+
const host = this.options.host || "0.0.0.0";
|
|
206
129
|
const server = (0, import_http.createServer)((req, res) => this.handleRequest(req, res));
|
|
207
|
-
server.listen(p,
|
|
208
|
-
console.log(`[MoltsPay] Server listening on http://${
|
|
130
|
+
server.listen(p, host, () => {
|
|
131
|
+
console.log(`[MoltsPay] Server listening on http://${host}:${p}`);
|
|
209
132
|
console.log(`[MoltsPay] Endpoints:`);
|
|
210
|
-
console.log(` GET /services
|
|
211
|
-
console.log(` POST /
|
|
212
|
-
console.log(` POST /verify - Verify payment & get result`);
|
|
213
|
-
console.log(` GET /status/:id - Check charge status`);
|
|
133
|
+
console.log(` GET /services - List available services`);
|
|
134
|
+
console.log(` POST /execute - Execute service (x402 payment)`);
|
|
214
135
|
});
|
|
215
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Handle incoming request
|
|
139
|
+
*/
|
|
216
140
|
async handleRequest(req, res) {
|
|
217
|
-
const url = new URL(req.url || "/", `http://${req.headers.host}`);
|
|
218
|
-
const path = url.pathname;
|
|
219
|
-
const method = req.method || "GET";
|
|
220
141
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
221
142
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
222
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
223
|
-
|
|
143
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment");
|
|
144
|
+
res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response");
|
|
145
|
+
if (req.method === "OPTIONS") {
|
|
224
146
|
res.writeHead(204);
|
|
225
147
|
res.end();
|
|
226
148
|
return;
|
|
227
149
|
}
|
|
228
150
|
try {
|
|
229
|
-
|
|
151
|
+
const url = new URL(req.url || "/", `http://${req.headers.host}`);
|
|
152
|
+
if (url.pathname === "/services" && req.method === "GET") {
|
|
230
153
|
return this.handleGetServices(res);
|
|
231
154
|
}
|
|
232
|
-
if (
|
|
233
|
-
const body = await this.readBody(req);
|
|
234
|
-
return this.handlePay(body, res);
|
|
235
|
-
}
|
|
236
|
-
if (method === "POST" && path === "/verify") {
|
|
155
|
+
if (url.pathname === "/execute" && req.method === "POST") {
|
|
237
156
|
const body = await this.readBody(req);
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
if (method === "GET" && path.startsWith("/status/")) {
|
|
241
|
-
const chargeId = path.replace("/status/", "");
|
|
242
|
-
return this.handleStatus(chargeId, res);
|
|
157
|
+
const paymentHeader = req.headers[PAYMENT_HEADER];
|
|
158
|
+
return await this.handleExecute(body, paymentHeader, res);
|
|
243
159
|
}
|
|
244
160
|
this.sendJson(res, 404, { error: "Not found" });
|
|
245
161
|
} catch (err) {
|
|
@@ -251,6 +167,7 @@ var MoltsPayServer = class {
|
|
|
251
167
|
* GET /services - List available services
|
|
252
168
|
*/
|
|
253
169
|
handleGetServices(res) {
|
|
170
|
+
const chain = getChain(this.manifest.provider.chain);
|
|
254
171
|
const services = this.manifest.services.map((s) => ({
|
|
255
172
|
id: s.id,
|
|
256
173
|
name: s.name,
|
|
@@ -263,14 +180,21 @@ var MoltsPayServer = class {
|
|
|
263
180
|
}));
|
|
264
181
|
this.sendJson(res, 200, {
|
|
265
182
|
provider: this.manifest.provider,
|
|
266
|
-
services
|
|
183
|
+
services,
|
|
184
|
+
x402: {
|
|
185
|
+
version: X402_VERSION,
|
|
186
|
+
network: `eip155:${chain.chainId}`,
|
|
187
|
+
schemes: ["exact"],
|
|
188
|
+
facilitator: this.facilitatorUrl
|
|
189
|
+
}
|
|
267
190
|
});
|
|
268
191
|
}
|
|
269
192
|
/**
|
|
270
|
-
* POST /
|
|
193
|
+
* POST /execute - Execute service with x402 payment
|
|
271
194
|
* Body: { service: string, params: object }
|
|
195
|
+
* Header: X-Payment (optional - if missing, returns 402)
|
|
272
196
|
*/
|
|
273
|
-
|
|
197
|
+
async handleExecute(body, paymentHeader, res) {
|
|
274
198
|
const { service, params } = body;
|
|
275
199
|
if (!service) {
|
|
276
200
|
return this.sendJson(res, 400, { error: "Missing service" });
|
|
@@ -284,113 +208,162 @@ var MoltsPayServer = class {
|
|
|
284
208
|
return this.sendJson(res, 400, { error: `Missing required param: ${key}` });
|
|
285
209
|
}
|
|
286
210
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
211
|
+
if (!paymentHeader) {
|
|
212
|
+
return this.sendPaymentRequired(skill.config, res);
|
|
213
|
+
}
|
|
214
|
+
let payment;
|
|
215
|
+
try {
|
|
216
|
+
const decoded = Buffer.from(paymentHeader, "base64").toString("utf-8");
|
|
217
|
+
payment = JSON.parse(decoded);
|
|
218
|
+
} catch {
|
|
219
|
+
return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
|
|
220
|
+
}
|
|
221
|
+
const validation = this.validatePayment(payment, skill.config);
|
|
222
|
+
if (!validation.valid) {
|
|
223
|
+
return this.sendJson(res, 402, { error: validation.error });
|
|
224
|
+
}
|
|
225
|
+
console.log(`[MoltsPay] Verifying payment with facilitator...`);
|
|
226
|
+
const verifyResult = await this.verifyWithFacilitator(payment, skill.config);
|
|
227
|
+
if (!verifyResult.valid) {
|
|
228
|
+
return this.sendJson(res, 402, { error: `Payment verification failed: ${verifyResult.error}` });
|
|
229
|
+
}
|
|
230
|
+
console.log(`[MoltsPay] Executing skill: ${service}`);
|
|
231
|
+
let result;
|
|
232
|
+
try {
|
|
233
|
+
result = await skill.handler(params || {});
|
|
234
|
+
} catch (err) {
|
|
235
|
+
console.error("[MoltsPay] Skill execution failed:", err.message);
|
|
236
|
+
return this.sendJson(res, 500, {
|
|
237
|
+
error: "Service execution failed",
|
|
238
|
+
message: err.message
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
console.log(`[MoltsPay] Skill succeeded, settling payment...`);
|
|
242
|
+
let settlement = null;
|
|
243
|
+
try {
|
|
244
|
+
settlement = await this.settleWithFacilitator(payment, skill.config);
|
|
245
|
+
console.log(`[MoltsPay] Payment settled: ${settlement.transaction || "pending"}`);
|
|
246
|
+
} catch (err) {
|
|
247
|
+
console.error("[MoltsPay] Settlement failed:", err.message);
|
|
248
|
+
}
|
|
249
|
+
const responseHeaders = {};
|
|
250
|
+
if (settlement) {
|
|
251
|
+
const responsePayload = {
|
|
252
|
+
success: true,
|
|
253
|
+
transaction: settlement.transaction,
|
|
254
|
+
network: payment.network
|
|
255
|
+
};
|
|
256
|
+
responseHeaders[PAYMENT_RESPONSE_HEADER] = Buffer.from(
|
|
257
|
+
JSON.stringify(responsePayload)
|
|
258
|
+
).toString("base64");
|
|
259
|
+
}
|
|
260
|
+
this.sendJson(res, 200, {
|
|
261
|
+
success: true,
|
|
262
|
+
result,
|
|
263
|
+
payment: settlement ? { transaction: settlement.transaction, status: "settled" } : { status: "pending" }
|
|
264
|
+
}, responseHeaders);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Return 402 with x402 payment requirements
|
|
268
|
+
*/
|
|
269
|
+
sendPaymentRequired(config, res) {
|
|
270
|
+
const chain = getChain(this.manifest.provider.chain);
|
|
271
|
+
const amountInUnits = Math.floor(config.price * 1e6).toString();
|
|
272
|
+
const requirements = [{
|
|
273
|
+
scheme: "exact",
|
|
274
|
+
network: `eip155:${chain.chainId}`,
|
|
275
|
+
maxAmountRequired: amountInUnits,
|
|
276
|
+
resource: this.manifest.provider.wallet,
|
|
277
|
+
description: `${config.name} - $${config.price} ${config.currency}`,
|
|
278
|
+
// Include facilitator info for client
|
|
279
|
+
extra: JSON.stringify({ facilitator: this.facilitatorUrl })
|
|
280
|
+
}];
|
|
281
|
+
const encoded = Buffer.from(JSON.stringify(requirements)).toString("base64");
|
|
282
|
+
res.writeHead(402, {
|
|
283
|
+
"Content-Type": "application/json",
|
|
284
|
+
[PAYMENT_REQUIRED_HEADER]: encoded
|
|
312
285
|
});
|
|
286
|
+
res.end(JSON.stringify({
|
|
287
|
+
error: "Payment required",
|
|
288
|
+
message: `Service requires $${config.price} ${config.currency}`,
|
|
289
|
+
x402: requirements[0]
|
|
290
|
+
}, null, 2));
|
|
313
291
|
}
|
|
314
292
|
/**
|
|
315
|
-
*
|
|
316
|
-
* Body: { chargeId: string, txHash: string }
|
|
293
|
+
* Basic payment validation (before calling facilitator)
|
|
317
294
|
*/
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
return this.sendJson(res, 400, { error: "Missing chargeId or txHash" });
|
|
295
|
+
validatePayment(payment, config) {
|
|
296
|
+
if (payment.x402Version !== X402_VERSION) {
|
|
297
|
+
return { valid: false, error: `Unsupported x402 version: ${payment.x402Version}` };
|
|
322
298
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
return this.sendJson(res, 404, { error: "Charge not found" });
|
|
299
|
+
if (payment.scheme !== "exact") {
|
|
300
|
+
return { valid: false, error: `Unsupported scheme: ${payment.scheme}` };
|
|
326
301
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
if (charge.status === "completed") {
|
|
332
|
-
return this.sendJson(res, 200, {
|
|
333
|
-
status: "completed",
|
|
334
|
-
result: charge.result
|
|
335
|
-
});
|
|
302
|
+
const chain = getChain(this.manifest.provider.chain);
|
|
303
|
+
const expectedNetwork = `eip155:${chain.chainId}`;
|
|
304
|
+
if (payment.network !== expectedNetwork) {
|
|
305
|
+
return { valid: false, error: `Network mismatch: expected ${expectedNetwork}` };
|
|
336
306
|
}
|
|
307
|
+
return { valid: true };
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Verify payment with facilitator
|
|
311
|
+
*/
|
|
312
|
+
async verifyWithFacilitator(payment, config) {
|
|
337
313
|
try {
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
314
|
+
const chain = getChain(this.manifest.provider.chain);
|
|
315
|
+
const amountInUnits = Math.floor(config.price * 1e6).toString();
|
|
316
|
+
const requirements = {
|
|
317
|
+
scheme: "exact",
|
|
318
|
+
network: `eip155:${chain.chainId}`,
|
|
319
|
+
maxAmountRequired: amountInUnits,
|
|
320
|
+
resource: this.manifest.provider.wallet
|
|
321
|
+
};
|
|
322
|
+
const response = await fetch(`${this.facilitatorUrl}/verify`, {
|
|
323
|
+
method: "POST",
|
|
324
|
+
headers: { "Content-Type": "application/json" },
|
|
325
|
+
body: JSON.stringify({
|
|
326
|
+
paymentPayload: payment,
|
|
327
|
+
paymentRequirements: requirements
|
|
328
|
+
})
|
|
343
329
|
});
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
return
|
|
347
|
-
error: "Payment verification failed",
|
|
348
|
-
reason: verification.error
|
|
349
|
-
});
|
|
330
|
+
const result = await response.json();
|
|
331
|
+
if (!response.ok || !result.isValid) {
|
|
332
|
+
return { valid: false, error: result.invalidReason || "Verification failed" };
|
|
350
333
|
}
|
|
351
|
-
|
|
352
|
-
charge.txHash = txHash;
|
|
353
|
-
charge.paidAt = Date.now();
|
|
354
|
-
const skill = this.skills.get(charge.service);
|
|
355
|
-
console.log(`[MoltsPay] Executing skill: ${charge.service}`);
|
|
356
|
-
const result = await skill.handler(charge.params);
|
|
357
|
-
charge.status = "completed";
|
|
358
|
-
charge.result = result;
|
|
359
|
-
charge.completedAt = Date.now();
|
|
360
|
-
this.sendJson(res, 200, {
|
|
361
|
-
status: "completed",
|
|
362
|
-
chargeId,
|
|
363
|
-
txHash,
|
|
364
|
-
result
|
|
365
|
-
});
|
|
334
|
+
return { valid: true };
|
|
366
335
|
} catch (err) {
|
|
367
|
-
|
|
368
|
-
charge.status = "failed";
|
|
369
|
-
this.sendJson(res, 500, {
|
|
370
|
-
error: "Skill execution failed",
|
|
371
|
-
message: err.message
|
|
372
|
-
});
|
|
336
|
+
return { valid: false, error: `Facilitator error: ${err.message}` };
|
|
373
337
|
}
|
|
374
338
|
}
|
|
375
339
|
/**
|
|
376
|
-
*
|
|
340
|
+
* Settle payment with facilitator (execute on-chain transfer)
|
|
377
341
|
*/
|
|
378
|
-
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
342
|
+
async settleWithFacilitator(payment, config) {
|
|
343
|
+
const chain = getChain(this.manifest.provider.chain);
|
|
344
|
+
const amountInUnits = Math.floor(config.price * 1e6).toString();
|
|
345
|
+
const requirements = {
|
|
346
|
+
scheme: "exact",
|
|
347
|
+
network: `eip155:${chain.chainId}`,
|
|
348
|
+
maxAmountRequired: amountInUnits,
|
|
349
|
+
resource: this.manifest.provider.wallet
|
|
350
|
+
};
|
|
351
|
+
const response = await fetch(`${this.facilitatorUrl}/settle`, {
|
|
352
|
+
method: "POST",
|
|
353
|
+
headers: { "Content-Type": "application/json" },
|
|
354
|
+
body: JSON.stringify({
|
|
355
|
+
paymentPayload: payment,
|
|
356
|
+
paymentRequirements: requirements
|
|
357
|
+
})
|
|
393
358
|
});
|
|
359
|
+
const result = await response.json();
|
|
360
|
+
if (!response.ok) {
|
|
361
|
+
throw new Error(result.error || "Settlement failed");
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
transaction: result.transaction,
|
|
365
|
+
status: result.status || "settled"
|
|
366
|
+
};
|
|
394
367
|
}
|
|
395
368
|
async readBody(req) {
|
|
396
369
|
return new Promise((resolve, reject) => {
|
|
@@ -406,8 +379,12 @@ var MoltsPayServer = class {
|
|
|
406
379
|
req.on("error", reject);
|
|
407
380
|
});
|
|
408
381
|
}
|
|
409
|
-
sendJson(res, status, data) {
|
|
410
|
-
|
|
382
|
+
sendJson(res, status, data, extraHeaders) {
|
|
383
|
+
const headers = { "Content-Type": "application/json" };
|
|
384
|
+
if (extraHeaders) {
|
|
385
|
+
Object.assign(headers, extraHeaders);
|
|
386
|
+
}
|
|
387
|
+
res.writeHead(status, headers);
|
|
411
388
|
res.end(JSON.stringify(data, null, 2));
|
|
412
389
|
}
|
|
413
390
|
};
|