wolverine-ai 5.0.2 → 5.0.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wolverine-ai",
3
- "version": "5.0.2",
3
+ "version": "5.0.4",
4
4
  "description": "Self-healing Node.js server framework powered by AI. Catches crashes, diagnoses errors, generates fixes, verifies, and restarts — automatically.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -130,11 +130,11 @@ async function x402Plugin(fastify, opts) {
130
130
  return;
131
131
  }
132
132
 
133
- // Payment present — verify
133
+ // Payment present — verify via facilitator or direct signature check
134
134
  const verified = await _verifyPayment(paymentSig, price);
135
135
  if (verified.valid) {
136
136
  reply.header("Payment-Response", JSON.stringify(verified.receipt || {}));
137
- request.x402 = { paid: true, amount: price, receipt: verified.receipt, txHash: verified.txHash };
137
+ request.x402 = { paid: true, amount: price, receipt: verified.receipt, txHash: verified.txHash, from: verified.from };
138
138
  return; // continue to route handler
139
139
  }
140
140
 
@@ -152,45 +152,118 @@ async function x402Plugin(fastify, opts) {
152
152
  }
153
153
 
154
154
  async function _verifyPayment(paymentSig, price) {
155
+ // Decode the payment signature (base64 JSON payload from frontend)
156
+ let payload;
155
157
  try {
156
- // Try @x402/core if available
157
- const { HTTPFacilitatorClient } = require("@x402/core/server");
158
- const facilitator = new HTTPFacilitatorClient({ url: _facilitatorUrl });
159
- const result = await facilitator.verify({
158
+ payload = JSON.parse(Buffer.from(paymentSig, "base64").toString());
159
+ } catch {
160
+ // Not base64 might be raw x402 format, try facilitator
161
+ return _verifyViaFacilitator(paymentSig, price);
162
+ }
163
+
164
+ // Direct verification: validate the EIP-3009 TransferWithAuthorization signature
165
+ if (payload.payload?.authorization && payload.payload?.signature) {
166
+ const auth = payload.payload.authorization;
167
+ const sig = payload.payload.signature;
168
+
169
+ // Verify the amount matches the price
170
+ const expectedUsdc = Math.round(parseFloat(price.replace("$", "")) * 1e6);
171
+ const actualUsdc = parseInt(auth.value, 16) || parseInt(auth.value, 10) || 0;
172
+ if (actualUsdc < expectedUsdc * 0.99) { // 1% tolerance for rounding
173
+ return { valid: false, reason: "Amount mismatch" };
174
+ }
175
+
176
+ // Verify payTo matches
177
+ if (auth.to?.toLowerCase() !== _payTo?.toLowerCase()) {
178
+ return { valid: false, reason: "Wrong recipient" };
179
+ }
180
+
181
+ // Verify not expired
182
+ if (auth.validBefore && auth.validBefore < Math.floor(Date.now() / 1000)) {
183
+ return { valid: false, reason: "Payment expired" };
184
+ }
185
+
186
+ // Recover signer from EIP-712 typed data signature
187
+ try {
188
+ const { ethers } = require("ethers");
189
+ // Normalize all addresses to proper EIP-55 checksum format
190
+ const fromAddr = ethers.getAddress(auth.from);
191
+ const toAddr = ethers.getAddress(auth.to);
192
+
193
+ const domain = {
194
+ name: "USD Coin",
195
+ version: "2",
196
+ chainId: 8453,
197
+ verifyingContract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
198
+ };
199
+ const types = {
200
+ TransferWithAuthorization: [
201
+ { name: "from", type: "address" },
202
+ { name: "to", type: "address" },
203
+ { name: "value", type: "uint256" },
204
+ { name: "validAfter", type: "uint256" },
205
+ { name: "validBefore", type: "uint256" },
206
+ { name: "nonce", type: "bytes32" },
207
+ ],
208
+ };
209
+ const message = {
210
+ from: fromAddr,
211
+ to: toAddr,
212
+ value: auth.value,
213
+ validAfter: auth.validAfter,
214
+ validBefore: auth.validBefore,
215
+ nonce: auth.nonce,
216
+ };
217
+
218
+ const recoveredAddress = ethers.verifyTypedData(domain, types, message, sig);
219
+ if (recoveredAddress.toLowerCase() !== fromAddr.toLowerCase()) {
220
+ return { valid: false, reason: "Signature mismatch" };
221
+ }
222
+
223
+ // Signature valid — the user authorized this USDC transfer
224
+ return {
225
+ valid: true,
226
+ from: auth.from,
227
+ receipt: { authorization: auth, signature: sig, verified: "direct" },
228
+ txHash: null, // on-chain tx happens when we call transferWithAuthorization
229
+ };
230
+ } catch (err) {
231
+ return { valid: false, reason: "Signature verification error: " + err.message };
232
+ }
233
+ }
234
+
235
+ // Unknown format — try facilitator
236
+ return _verifyViaFacilitator(paymentSig, price);
237
+ }
238
+
239
+ async function _verifyViaFacilitator(paymentSig, price) {
240
+ try {
241
+ const https = require("https");
242
+ const http = require("http");
243
+ const url = new (require("url").URL)(_facilitatorUrl + "/verify");
244
+ const body = JSON.stringify({
160
245
  paymentSignature: paymentSig,
161
246
  routeConfig: { accepts: [{ scheme: "exact", price, network: _network, payTo: _payTo }] },
162
247
  });
163
- return { valid: result.valid, receipt: result.receipt, txHash: result.txHash };
164
- } catch {
165
- // Fallback: raw HTTP to facilitator
166
- try {
167
- const https = require("https");
168
- const http = require("http");
169
- const url = new (require("url").URL)(_facilitatorUrl + "/verify");
170
- const body = JSON.stringify({
171
- paymentSignature: paymentSig,
172
- routeConfig: { accepts: [{ scheme: "exact", price, network: _network, payTo: _payTo }] },
173
- });
174
- return new Promise((resolve) => {
175
- const client = url.protocol === "https:" ? https : http;
176
- const req = client.request({
177
- hostname: url.hostname, port: url.port, path: url.pathname, method: "POST",
178
- headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) },
179
- timeout: 10000,
180
- }, (res) => {
181
- let data = "";
182
- res.on("data", (c) => data += c);
183
- res.on("end", () => {
184
- try { const p = JSON.parse(data); resolve({ valid: p.valid || p.success, receipt: p, txHash: p.txHash }); }
185
- catch { resolve({ valid: false }); }
186
- });
248
+ return new Promise((resolve) => {
249
+ const client = url.protocol === "https:" ? https : http;
250
+ const req = client.request({
251
+ hostname: url.hostname, port: url.port, path: url.pathname, method: "POST",
252
+ headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) },
253
+ timeout: 10000,
254
+ }, (res) => {
255
+ let data = "";
256
+ res.on("data", (c) => data += c);
257
+ res.on("end", () => {
258
+ try { const p = JSON.parse(data); resolve({ valid: p.valid || p.success, receipt: p, txHash: p.txHash }); }
259
+ catch { resolve({ valid: false }); }
187
260
  });
188
- req.on("error", () => resolve({ valid: false }));
189
- req.write(body);
190
- req.end();
191
261
  });
192
- } catch { return { valid: false }; }
193
- }
262
+ req.on("error", () => resolve({ valid: false }));
263
+ req.write(body);
264
+ req.end();
265
+ });
266
+ } catch { return { valid: false }; }
194
267
  }
195
268
 
196
269
  x402Plugin[Symbol.for("skip-override")] = true;