wolverine-ai 5.0.2 → 5.0.3
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 +1 -1
- package/src/middleware/x402-fastify.js +104 -35
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wolverine-ai",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.3",
|
|
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,114 @@ 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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
+
const domain = {
|
|
190
|
+
name: "USD Coin",
|
|
191
|
+
version: "2",
|
|
192
|
+
chainId: 8453,
|
|
193
|
+
verifyingContract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
194
|
+
};
|
|
195
|
+
const types = {
|
|
196
|
+
TransferWithAuthorization: [
|
|
197
|
+
{ name: "from", type: "address" },
|
|
198
|
+
{ name: "to", type: "address" },
|
|
199
|
+
{ name: "value", type: "uint256" },
|
|
200
|
+
{ name: "validAfter", type: "uint256" },
|
|
201
|
+
{ name: "validBefore", type: "uint256" },
|
|
202
|
+
{ name: "nonce", type: "bytes32" },
|
|
203
|
+
],
|
|
204
|
+
};
|
|
205
|
+
const message = {
|
|
206
|
+
from: auth.from,
|
|
207
|
+
to: auth.to,
|
|
208
|
+
value: auth.value,
|
|
209
|
+
validAfter: auth.validAfter,
|
|
210
|
+
validBefore: auth.validBefore,
|
|
211
|
+
nonce: auth.nonce,
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const recoveredAddress = ethers.verifyTypedData(domain, types, message, sig);
|
|
215
|
+
if (recoveredAddress.toLowerCase() !== auth.from.toLowerCase()) {
|
|
216
|
+
return { valid: false, reason: "Signature mismatch" };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Signature valid — the user authorized this USDC transfer
|
|
220
|
+
return {
|
|
221
|
+
valid: true,
|
|
222
|
+
from: auth.from,
|
|
223
|
+
receipt: { authorization: auth, signature: sig, verified: "direct" },
|
|
224
|
+
txHash: null, // on-chain tx happens when we call transferWithAuthorization
|
|
225
|
+
};
|
|
226
|
+
} catch (err) {
|
|
227
|
+
return { valid: false, reason: "Signature verification error: " + err.message };
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Unknown format — try facilitator
|
|
232
|
+
return _verifyViaFacilitator(paymentSig, price);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function _verifyViaFacilitator(paymentSig, price) {
|
|
236
|
+
try {
|
|
237
|
+
const https = require("https");
|
|
238
|
+
const http = require("http");
|
|
239
|
+
const url = new (require("url").URL)(_facilitatorUrl + "/verify");
|
|
240
|
+
const body = JSON.stringify({
|
|
160
241
|
paymentSignature: paymentSig,
|
|
161
242
|
routeConfig: { accepts: [{ scheme: "exact", price, network: _network, payTo: _payTo }] },
|
|
162
243
|
});
|
|
163
|
-
return
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
});
|
|
244
|
+
return new Promise((resolve) => {
|
|
245
|
+
const client = url.protocol === "https:" ? https : http;
|
|
246
|
+
const req = client.request({
|
|
247
|
+
hostname: url.hostname, port: url.port, path: url.pathname, method: "POST",
|
|
248
|
+
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) },
|
|
249
|
+
timeout: 10000,
|
|
250
|
+
}, (res) => {
|
|
251
|
+
let data = "";
|
|
252
|
+
res.on("data", (c) => data += c);
|
|
253
|
+
res.on("end", () => {
|
|
254
|
+
try { const p = JSON.parse(data); resolve({ valid: p.valid || p.success, receipt: p, txHash: p.txHash }); }
|
|
255
|
+
catch { resolve({ valid: false }); }
|
|
187
256
|
});
|
|
188
|
-
req.on("error", () => resolve({ valid: false }));
|
|
189
|
-
req.write(body);
|
|
190
|
-
req.end();
|
|
191
257
|
});
|
|
192
|
-
|
|
193
|
-
|
|
258
|
+
req.on("error", () => resolve({ valid: false }));
|
|
259
|
+
req.write(body);
|
|
260
|
+
req.end();
|
|
261
|
+
});
|
|
262
|
+
} catch { return { valid: false }; }
|
|
194
263
|
}
|
|
195
264
|
|
|
196
265
|
x402Plugin[Symbol.for("skip-override")] = true;
|