moltspay 0.7.2 → 0.8.0
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 +253 -259
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +255 -261
- 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 +387 -321
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +391 -325
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.d.mts +17 -8
- package/dist/server/index.d.ts +17 -8
- package/dist/server/index.js +145 -194
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +145 -194
- package/dist/server/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/server/index.d.mts
CHANGED
|
@@ -69,11 +69,15 @@ interface MoltsPayServerOptions {
|
|
|
69
69
|
port?: number;
|
|
70
70
|
host?: string;
|
|
71
71
|
chargeExpirySecs?: number;
|
|
72
|
+
/** Private key for claiming payments (can also use MOLTSPAY_PRIVATE_KEY env) */
|
|
73
|
+
privateKey?: string;
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
/**
|
|
75
77
|
* MoltsPay Server - Payment infrastructure for AI Agents
|
|
76
78
|
*
|
|
79
|
+
* Uses x402 protocol for gasless, pay-for-success payments.
|
|
80
|
+
*
|
|
77
81
|
* Usage:
|
|
78
82
|
* const server = new MoltsPayServer('./moltspay.services.json');
|
|
79
83
|
* server.skill('text-to-video', async (params) => { ... });
|
|
@@ -83,8 +87,9 @@ interface MoltsPayServerOptions {
|
|
|
83
87
|
declare class MoltsPayServer {
|
|
84
88
|
private manifest;
|
|
85
89
|
private skills;
|
|
86
|
-
private charges;
|
|
87
90
|
private options;
|
|
91
|
+
private provider;
|
|
92
|
+
private wallet;
|
|
88
93
|
constructor(servicesPath: string, options?: MoltsPayServerOptions);
|
|
89
94
|
/**
|
|
90
95
|
* Register a skill handler for a service
|
|
@@ -100,19 +105,23 @@ declare class MoltsPayServer {
|
|
|
100
105
|
*/
|
|
101
106
|
private handleGetServices;
|
|
102
107
|
/**
|
|
103
|
-
* POST /
|
|
108
|
+
* POST /execute - Execute service with x402 payment
|
|
104
109
|
* Body: { service: string, params: object }
|
|
110
|
+
* Header: X-Payment (optional - if missing, returns 402)
|
|
111
|
+
*/
|
|
112
|
+
private handleExecute;
|
|
113
|
+
/**
|
|
114
|
+
* Return 402 with x402 payment requirements
|
|
105
115
|
*/
|
|
106
|
-
private
|
|
116
|
+
private sendPaymentRequired;
|
|
107
117
|
/**
|
|
108
|
-
*
|
|
109
|
-
* Body: { chargeId: string, txHash: string }
|
|
118
|
+
* Validate x402 payment payload
|
|
110
119
|
*/
|
|
111
|
-
private
|
|
120
|
+
private validatePayment;
|
|
112
121
|
/**
|
|
113
|
-
*
|
|
122
|
+
* Claim payment using transferWithAuthorization
|
|
114
123
|
*/
|
|
115
|
-
private
|
|
124
|
+
private claimPayment;
|
|
116
125
|
private readBody;
|
|
117
126
|
private sendJson;
|
|
118
127
|
}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -69,11 +69,15 @@ interface MoltsPayServerOptions {
|
|
|
69
69
|
port?: number;
|
|
70
70
|
host?: string;
|
|
71
71
|
chargeExpirySecs?: number;
|
|
72
|
+
/** Private key for claiming payments (can also use MOLTSPAY_PRIVATE_KEY env) */
|
|
73
|
+
privateKey?: string;
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
/**
|
|
75
77
|
* MoltsPay Server - Payment infrastructure for AI Agents
|
|
76
78
|
*
|
|
79
|
+
* Uses x402 protocol for gasless, pay-for-success payments.
|
|
80
|
+
*
|
|
77
81
|
* Usage:
|
|
78
82
|
* const server = new MoltsPayServer('./moltspay.services.json');
|
|
79
83
|
* server.skill('text-to-video', async (params) => { ... });
|
|
@@ -83,8 +87,9 @@ interface MoltsPayServerOptions {
|
|
|
83
87
|
declare class MoltsPayServer {
|
|
84
88
|
private manifest;
|
|
85
89
|
private skills;
|
|
86
|
-
private charges;
|
|
87
90
|
private options;
|
|
91
|
+
private provider;
|
|
92
|
+
private wallet;
|
|
88
93
|
constructor(servicesPath: string, options?: MoltsPayServerOptions);
|
|
89
94
|
/**
|
|
90
95
|
* Register a skill handler for a service
|
|
@@ -100,19 +105,23 @@ declare class MoltsPayServer {
|
|
|
100
105
|
*/
|
|
101
106
|
private handleGetServices;
|
|
102
107
|
/**
|
|
103
|
-
* POST /
|
|
108
|
+
* POST /execute - Execute service with x402 payment
|
|
104
109
|
* Body: { service: string, params: object }
|
|
110
|
+
* Header: X-Payment (optional - if missing, returns 402)
|
|
111
|
+
*/
|
|
112
|
+
private handleExecute;
|
|
113
|
+
/**
|
|
114
|
+
* Return 402 with x402 payment requirements
|
|
105
115
|
*/
|
|
106
|
-
private
|
|
116
|
+
private sendPaymentRequired;
|
|
107
117
|
/**
|
|
108
|
-
*
|
|
109
|
-
* Body: { chargeId: string, txHash: string }
|
|
118
|
+
* Validate x402 payment payload
|
|
110
119
|
*/
|
|
111
|
-
private
|
|
120
|
+
private validatePayment;
|
|
112
121
|
/**
|
|
113
|
-
*
|
|
122
|
+
* Claim payment using transferWithAuthorization
|
|
114
123
|
*/
|
|
115
|
-
private
|
|
124
|
+
private claimPayment;
|
|
116
125
|
private readBody;
|
|
117
126
|
private sendJson;
|
|
118
127
|
}
|
package/dist/server/index.js
CHANGED
|
@@ -25,8 +25,6 @@ __export(server_exports, {
|
|
|
25
25
|
module.exports = __toCommonJS(server_exports);
|
|
26
26
|
var import_fs = require("fs");
|
|
27
27
|
var import_http = require("http");
|
|
28
|
-
|
|
29
|
-
// src/verify/index.ts
|
|
30
28
|
var import_ethers = require("ethers");
|
|
31
29
|
|
|
32
30
|
// src/chains/index.ts
|
|
@@ -86,101 +84,39 @@ function getChain(name) {
|
|
|
86
84
|
}
|
|
87
85
|
return config;
|
|
88
86
|
}
|
|
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
87
|
|
|
163
88
|
// src/server/index.ts
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
89
|
+
var X402_VERSION = 2;
|
|
90
|
+
var PAYMENT_REQUIRED_HEADER = "x-payment-required";
|
|
91
|
+
var PAYMENT_HEADER = "x-payment";
|
|
167
92
|
var MoltsPayServer = class {
|
|
168
93
|
manifest;
|
|
169
94
|
skills = /* @__PURE__ */ new Map();
|
|
170
|
-
charges = /* @__PURE__ */ new Map();
|
|
171
95
|
options;
|
|
96
|
+
provider = null;
|
|
97
|
+
wallet = null;
|
|
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
103
|
host: options.host || "0.0.0.0",
|
|
178
|
-
|
|
179
|
-
// 5 minutes
|
|
104
|
+
privateKey: options.privateKey || process.env.MOLTSPAY_PRIVATE_KEY
|
|
180
105
|
};
|
|
106
|
+
if (this.options.privateKey) {
|
|
107
|
+
try {
|
|
108
|
+
const chain = getChain(this.manifest.provider.chain);
|
|
109
|
+
this.provider = new import_ethers.ethers.JsonRpcProvider(chain.rpc);
|
|
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");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
181
116
|
console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);
|
|
182
117
|
console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);
|
|
183
|
-
console.log(`[MoltsPay]
|
|
118
|
+
console.log(`[MoltsPay] Receive wallet: ${this.manifest.provider.wallet}`);
|
|
119
|
+
console.log(`[MoltsPay] Protocol: x402 (gasless, pay-for-success)`);
|
|
184
120
|
}
|
|
185
121
|
/**
|
|
186
122
|
* Register a skill handler for a service
|
|
@@ -207,10 +143,8 @@ var MoltsPayServer = class {
|
|
|
207
143
|
server.listen(p, this.options.host, () => {
|
|
208
144
|
console.log(`[MoltsPay] Server listening on http://${this.options.host}:${p}`);
|
|
209
145
|
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`);
|
|
146
|
+
console.log(` GET /services - List available services`);
|
|
147
|
+
console.log(` POST /execute - Execute service (x402 payment)`);
|
|
214
148
|
});
|
|
215
149
|
}
|
|
216
150
|
async handleRequest(req, res) {
|
|
@@ -219,7 +153,8 @@ var MoltsPayServer = class {
|
|
|
219
153
|
const method = req.method || "GET";
|
|
220
154
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
221
155
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
222
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
156
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment");
|
|
157
|
+
res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response");
|
|
223
158
|
if (method === "OPTIONS") {
|
|
224
159
|
res.writeHead(204);
|
|
225
160
|
res.end();
|
|
@@ -229,17 +164,10 @@ var MoltsPayServer = class {
|
|
|
229
164
|
if (method === "GET" && path === "/services") {
|
|
230
165
|
return this.handleGetServices(res);
|
|
231
166
|
}
|
|
232
|
-
if (method === "POST" && path === "/
|
|
233
|
-
const body = await this.readBody(req);
|
|
234
|
-
return this.handlePay(body, res);
|
|
235
|
-
}
|
|
236
|
-
if (method === "POST" && path === "/verify") {
|
|
167
|
+
if (method === "POST" && path === "/execute") {
|
|
237
168
|
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);
|
|
169
|
+
const paymentHeader = req.headers[PAYMENT_HEADER];
|
|
170
|
+
return this.handleExecute(body, paymentHeader, res);
|
|
243
171
|
}
|
|
244
172
|
this.sendJson(res, 404, { error: "Not found" });
|
|
245
173
|
} catch (err) {
|
|
@@ -251,6 +179,7 @@ var MoltsPayServer = class {
|
|
|
251
179
|
* GET /services - List available services
|
|
252
180
|
*/
|
|
253
181
|
handleGetServices(res) {
|
|
182
|
+
const chain = getChain(this.manifest.provider.chain);
|
|
254
183
|
const services = this.manifest.services.map((s) => ({
|
|
255
184
|
id: s.id,
|
|
256
185
|
name: s.name,
|
|
@@ -263,14 +192,20 @@ var MoltsPayServer = class {
|
|
|
263
192
|
}));
|
|
264
193
|
this.sendJson(res, 200, {
|
|
265
194
|
provider: this.manifest.provider,
|
|
266
|
-
services
|
|
195
|
+
services,
|
|
196
|
+
x402: {
|
|
197
|
+
version: X402_VERSION,
|
|
198
|
+
network: `eip155:${chain.chainId}`,
|
|
199
|
+
schemes: ["exact"]
|
|
200
|
+
}
|
|
267
201
|
});
|
|
268
202
|
}
|
|
269
203
|
/**
|
|
270
|
-
* POST /
|
|
204
|
+
* POST /execute - Execute service with x402 payment
|
|
271
205
|
* Body: { service: string, params: object }
|
|
206
|
+
* Header: X-Payment (optional - if missing, returns 402)
|
|
272
207
|
*/
|
|
273
|
-
|
|
208
|
+
async handleExecute(body, paymentHeader, res) {
|
|
274
209
|
const { service, params } = body;
|
|
275
210
|
if (!service) {
|
|
276
211
|
return this.sendJson(res, 400, { error: "Missing service" });
|
|
@@ -284,113 +219,129 @@ var MoltsPayServer = class {
|
|
|
284
219
|
return this.sendJson(res, 400, { error: `Missing required param: ${key}` });
|
|
285
220
|
}
|
|
286
221
|
}
|
|
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
|
-
|
|
222
|
+
if (!paymentHeader) {
|
|
223
|
+
return this.sendPaymentRequired(skill.config, res);
|
|
224
|
+
}
|
|
225
|
+
let payment;
|
|
226
|
+
try {
|
|
227
|
+
const decoded = Buffer.from(paymentHeader, "base64").toString("utf-8");
|
|
228
|
+
payment = JSON.parse(decoded);
|
|
229
|
+
} catch {
|
|
230
|
+
return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
|
|
231
|
+
}
|
|
232
|
+
const validation = this.validatePayment(payment, skill.config);
|
|
233
|
+
if (!validation.valid) {
|
|
234
|
+
return this.sendJson(res, 402, { error: validation.error });
|
|
235
|
+
}
|
|
236
|
+
console.log(`[MoltsPay] Executing skill: ${service}`);
|
|
237
|
+
let result;
|
|
238
|
+
try {
|
|
239
|
+
result = await skill.handler(params || {});
|
|
240
|
+
} catch (err) {
|
|
241
|
+
console.error("[MoltsPay] Skill execution failed:", err.message);
|
|
242
|
+
return this.sendJson(res, 500, {
|
|
243
|
+
error: "Service execution failed",
|
|
244
|
+
message: err.message
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
console.log(`[MoltsPay] Skill succeeded, claiming payment...`);
|
|
248
|
+
let txHash = null;
|
|
249
|
+
try {
|
|
250
|
+
txHash = await this.claimPayment(payment);
|
|
251
|
+
console.log(`[MoltsPay] Payment claimed: ${txHash}`);
|
|
252
|
+
} catch (err) {
|
|
253
|
+
console.error("[MoltsPay] Payment claim failed:", err.message);
|
|
254
|
+
}
|
|
255
|
+
this.sendJson(res, 200, {
|
|
256
|
+
success: true,
|
|
257
|
+
result,
|
|
258
|
+
payment: txHash ? { txHash, status: "claimed" } : { status: "pending" }
|
|
312
259
|
});
|
|
313
260
|
}
|
|
314
261
|
/**
|
|
315
|
-
*
|
|
316
|
-
* Body: { chargeId: string, txHash: string }
|
|
262
|
+
* Return 402 with x402 payment requirements
|
|
317
263
|
*/
|
|
318
|
-
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
264
|
+
sendPaymentRequired(config, res) {
|
|
265
|
+
const chain = getChain(this.manifest.provider.chain);
|
|
266
|
+
const amountInUnits = Math.floor(config.price * 1e6).toString();
|
|
267
|
+
const requirements = [{
|
|
268
|
+
scheme: "exact",
|
|
269
|
+
network: `eip155:${chain.chainId}`,
|
|
270
|
+
maxAmountRequired: amountInUnits,
|
|
271
|
+
resource: this.manifest.provider.wallet,
|
|
272
|
+
description: `${config.name} - $${config.price} ${config.currency}`
|
|
273
|
+
}];
|
|
274
|
+
const encoded = Buffer.from(JSON.stringify(requirements)).toString("base64");
|
|
275
|
+
res.writeHead(402, {
|
|
276
|
+
"Content-Type": "application/json",
|
|
277
|
+
[PAYMENT_REQUIRED_HEADER]: encoded
|
|
278
|
+
});
|
|
279
|
+
res.end(JSON.stringify({
|
|
280
|
+
error: "Payment required",
|
|
281
|
+
message: `Service requires $${config.price} ${config.currency}`,
|
|
282
|
+
x402: requirements[0]
|
|
283
|
+
}, null, 2));
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Validate x402 payment payload
|
|
287
|
+
*/
|
|
288
|
+
validatePayment(payment, config) {
|
|
289
|
+
if (payment.x402Version !== X402_VERSION) {
|
|
290
|
+
return { valid: false, error: `Unsupported x402 version: ${payment.x402Version}` };
|
|
322
291
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
return this.sendJson(res, 404, { error: "Charge not found" });
|
|
292
|
+
if (payment.scheme !== "exact") {
|
|
293
|
+
return { valid: false, error: `Unsupported scheme: ${payment.scheme}` };
|
|
326
294
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
295
|
+
const chain = getChain(this.manifest.provider.chain);
|
|
296
|
+
const expectedNetwork = `eip155:${chain.chainId}`;
|
|
297
|
+
if (payment.network !== expectedNetwork) {
|
|
298
|
+
return { valid: false, error: `Network mismatch: expected ${expectedNetwork}` };
|
|
330
299
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
result: charge.result
|
|
335
|
-
});
|
|
300
|
+
const auth = payment.payload.authorization;
|
|
301
|
+
if (auth.to.toLowerCase() !== this.manifest.provider.wallet.toLowerCase()) {
|
|
302
|
+
return { valid: false, error: "Payment recipient mismatch" };
|
|
336
303
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
if (!verification.verified) {
|
|
345
|
-
charge.status = "failed";
|
|
346
|
-
return this.sendJson(res, 400, {
|
|
347
|
-
error: "Payment verification failed",
|
|
348
|
-
reason: verification.error
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
charge.status = "paid";
|
|
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
|
-
});
|
|
366
|
-
} catch (err) {
|
|
367
|
-
console.error("[MoltsPay] Skill execution error:", err);
|
|
368
|
-
charge.status = "failed";
|
|
369
|
-
this.sendJson(res, 500, {
|
|
370
|
-
error: "Skill execution failed",
|
|
371
|
-
message: err.message
|
|
372
|
-
});
|
|
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" };
|
|
373
311
|
}
|
|
312
|
+
if (Number(auth.validAfter) > now) {
|
|
313
|
+
return { valid: false, error: "Payment authorization not yet valid" };
|
|
314
|
+
}
|
|
315
|
+
return { valid: true };
|
|
374
316
|
}
|
|
375
317
|
/**
|
|
376
|
-
*
|
|
318
|
+
* Claim payment using transferWithAuthorization
|
|
377
319
|
*/
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
return this.sendJson(res, 404, { error: "Charge not found" });
|
|
320
|
+
async claimPayment(payment) {
|
|
321
|
+
if (!this.wallet || !this.provider) {
|
|
322
|
+
throw new Error("Wallet not configured for payment claims");
|
|
382
323
|
}
|
|
383
|
-
this.
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
324
|
+
const chain = getChain(this.manifest.provider.chain);
|
|
325
|
+
const auth = payment.payload.authorization;
|
|
326
|
+
const sig = payment.payload.signature;
|
|
327
|
+
const { r, s, v } = import_ethers.ethers.Signature.from(sig);
|
|
328
|
+
const usdcAbi = [
|
|
329
|
+
"function transferWithAuthorization(address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s)"
|
|
330
|
+
];
|
|
331
|
+
const usdc = new import_ethers.ethers.Contract(chain.usdc, usdcAbi, this.wallet);
|
|
332
|
+
const tx = await usdc.transferWithAuthorization(
|
|
333
|
+
auth.from,
|
|
334
|
+
auth.to,
|
|
335
|
+
auth.value,
|
|
336
|
+
auth.validAfter,
|
|
337
|
+
auth.validBefore,
|
|
338
|
+
auth.nonce,
|
|
339
|
+
v,
|
|
340
|
+
r,
|
|
341
|
+
s
|
|
342
|
+
);
|
|
343
|
+
const receipt = await tx.wait();
|
|
344
|
+
return receipt.hash;
|
|
394
345
|
}
|
|
395
346
|
async readBody(req) {
|
|
396
347
|
return new Promise((resolve, reject) => {
|