wolverine-ai 5.2.8 → 5.2.9
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 +88 -146
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wolverine-ai",
|
|
3
|
-
"version": "5.2.
|
|
3
|
+
"version": "5.2.9",
|
|
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": {
|
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const path = require("path");
|
|
3
3
|
|
|
4
|
-
// Node 18 needs globalThis.crypto for CDP SDK (Ed25519 JWT signing)
|
|
5
|
-
if (!globalThis.crypto) {
|
|
6
|
-
globalThis.crypto = require("crypto").webcrypto;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
4
|
/**
|
|
10
5
|
* x402 Fastify Plugin — add crypto payments to any route with one flag.
|
|
11
6
|
*
|
|
12
|
-
*
|
|
13
|
-
* Compatible with
|
|
7
|
+
* Uses the official @x402/core SDK for payment verification and settlement
|
|
8
|
+
* via the CDP facilitator. Compatible with x402 Bazaar for API discovery.
|
|
14
9
|
*
|
|
15
10
|
* Two modes:
|
|
16
11
|
* Fixed price: { x402: { price: "$0.01" } }
|
|
17
12
|
* Variable price: { x402: { variable: true, min: "$1", max: "$10000" } }
|
|
18
13
|
*/
|
|
19
14
|
|
|
15
|
+
// Node 18 needs globalThis.crypto for @x402/evm
|
|
16
|
+
if (!globalThis.crypto) {
|
|
17
|
+
globalThis.crypto = require("crypto").webcrypto;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
20
|
const USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
21
21
|
|
|
22
22
|
let _payTo = null;
|
|
23
23
|
let _network = "eip155:8453";
|
|
24
24
|
let _facilitatorUrl = "https://api.cdp.coinbase.com/platform/v2/x402";
|
|
25
|
+
let _x402Server = null; // x402ResourceServer instance
|
|
25
26
|
|
|
26
27
|
async function x402Plugin(fastify, opts) {
|
|
27
28
|
_network = opts.network || _network;
|
|
@@ -45,18 +46,46 @@ async function x402Plugin(fastify, opts) {
|
|
|
45
46
|
} catch {}
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
// Auto-select facilitator based on network
|
|
49
|
-
if (!opts.facilitator) {
|
|
50
|
-
if (!process.env.CDP_API_KEY_ID) {
|
|
51
|
-
console.log(` ⚠️ x402: CDP_API_KEY_ID not set — facilitator auth may fail`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
49
|
if (_payTo) {
|
|
56
50
|
console.log(` 💰 x402: payments to ${_payTo.slice(0, 6)}...${_payTo.slice(-4)} on ${_network}`);
|
|
57
51
|
console.log(` 💰 x402: facilitator ${_facilitatorUrl}`);
|
|
58
52
|
}
|
|
59
53
|
|
|
54
|
+
// Initialize x402 SDK server with CDP facilitator
|
|
55
|
+
try {
|
|
56
|
+
const { x402ResourceServer, HTTPFacilitatorClient } = require("@x402/core/server");
|
|
57
|
+
const { ExactEvmScheme } = require("@x402/evm/exact/server");
|
|
58
|
+
|
|
59
|
+
const facilitatorConfig = { url: _facilitatorUrl };
|
|
60
|
+
|
|
61
|
+
// Add CDP auth if keys are available
|
|
62
|
+
if (process.env.CDP_API_KEY_ID && process.env.CDP_API_KEY_SECRET) {
|
|
63
|
+
facilitatorConfig.createAuthHeaders = async () => {
|
|
64
|
+
const { getAuthHeaders } = await import("@coinbase/cdp-sdk/auth");
|
|
65
|
+
const endpoints = ["verify", "settle", "supported"];
|
|
66
|
+
const result = {};
|
|
67
|
+
for (const ep of endpoints) {
|
|
68
|
+
const headers = await getAuthHeaders({
|
|
69
|
+
apiKeyId: process.env.CDP_API_KEY_ID,
|
|
70
|
+
apiKeySecret: process.env.CDP_API_KEY_SECRET,
|
|
71
|
+
requestMethod: "POST",
|
|
72
|
+
requestHost: `https://${new URL(_facilitatorUrl).host}`,
|
|
73
|
+
requestPath: `${new URL(_facilitatorUrl).pathname}/${ep}`,
|
|
74
|
+
});
|
|
75
|
+
result[ep] = headers;
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const client = new HTTPFacilitatorClient(facilitatorConfig);
|
|
82
|
+
_x402Server = new x402ResourceServer(client);
|
|
83
|
+
_x402Server.register("eip155:*", new ExactEvmScheme());
|
|
84
|
+
console.log(` 💰 x402: SDK initialized (ExactEvmScheme)`);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.log(` ⚠️ x402: SDK init failed (${err.message}) — using direct verification`);
|
|
87
|
+
}
|
|
88
|
+
|
|
60
89
|
// ── Route-level x402 hook ──
|
|
61
90
|
fastify.addHook("preHandler", async (request, reply) => {
|
|
62
91
|
const routeConfig = request.routeOptions?.config?.x402 || request.routeConfig?.x402 || request.context?.config?.x402;
|
|
@@ -90,13 +119,11 @@ async function x402Plugin(fastify, opts) {
|
|
|
90
119
|
if (!dollarAmount) { reply.code(500).send({ error: "x402 route missing price config" }); return; }
|
|
91
120
|
}
|
|
92
121
|
|
|
93
|
-
// USDC amount in atomic units (6 decimals)
|
|
94
122
|
const usdcAmount = String(Math.round(dollarAmount * 1e6));
|
|
95
123
|
const price = "$" + dollarAmount.toFixed(2);
|
|
96
|
-
|
|
97
124
|
const paymentHeader = request.headers["payment-signature"] || request.headers["x-payment"];
|
|
98
125
|
|
|
99
|
-
// No payment — return 402 with
|
|
126
|
+
// No payment — return 402 with payment requirements
|
|
100
127
|
if (!paymentHeader) {
|
|
101
128
|
const paymentRequirements = {
|
|
102
129
|
scheme: "exact",
|
|
@@ -105,7 +132,7 @@ async function x402Plugin(fastify, opts) {
|
|
|
105
132
|
asset: USDC_BASE,
|
|
106
133
|
payTo: _payTo,
|
|
107
134
|
maxTimeoutSeconds: 300,
|
|
108
|
-
extra: { name: "USD Coin", version: "2" },
|
|
135
|
+
extra: { name: "USD Coin", version: "2" },
|
|
109
136
|
};
|
|
110
137
|
const paymentRequired = {
|
|
111
138
|
x402Version: 2,
|
|
@@ -134,7 +161,17 @@ async function x402Plugin(fastify, opts) {
|
|
|
134
161
|
return;
|
|
135
162
|
}
|
|
136
163
|
|
|
137
|
-
// Payment present — decode
|
|
164
|
+
// Payment present — decode
|
|
165
|
+
let paymentPayload;
|
|
166
|
+
try {
|
|
167
|
+
paymentPayload = JSON.parse(Buffer.from(paymentHeader, "base64").toString());
|
|
168
|
+
} catch {
|
|
169
|
+
reply.code(400).send({ error: "Invalid payment encoding" });
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!paymentPayload.x402Version) paymentPayload.x402Version = 2;
|
|
174
|
+
|
|
138
175
|
const paymentRequirements = {
|
|
139
176
|
scheme: "exact",
|
|
140
177
|
network: _network,
|
|
@@ -145,57 +182,40 @@ async function x402Plugin(fastify, opts) {
|
|
|
145
182
|
extra: { name: "USD Coin", version: "2" },
|
|
146
183
|
};
|
|
147
184
|
|
|
148
|
-
|
|
149
|
-
let paymentPayload;
|
|
150
|
-
try {
|
|
151
|
-
const decoded = Buffer.from(paymentHeader, "base64").toString();
|
|
152
|
-
paymentPayload = JSON.parse(decoded);
|
|
153
|
-
} catch {
|
|
154
|
-
reply.code(400).send({ error: "Invalid payment encoding" });
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Ensure x402Version is set
|
|
159
|
-
if (!paymentPayload.x402Version) {
|
|
160
|
-
paymentPayload.x402Version = 2;
|
|
161
|
-
}
|
|
185
|
+
if (!paymentPayload.accepted) paymentPayload.accepted = paymentRequirements;
|
|
162
186
|
|
|
163
|
-
//
|
|
164
|
-
if (
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
187
|
+
// Use x402 SDK if available
|
|
188
|
+
if (_x402Server) {
|
|
189
|
+
try {
|
|
190
|
+
// Verify
|
|
191
|
+
const verifyResult = await _x402Server.verify(paymentPayload, paymentRequirements);
|
|
192
|
+
if (!verifyResult.isValid) {
|
|
193
|
+
reply.code(402).send({ error: "Payment verification failed", reason: verifyResult.invalidReason, price, payTo: _payTo });
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Settle
|
|
198
|
+
const settleResult = await _x402Server.settle(paymentPayload, paymentRequirements);
|
|
199
|
+
if (!settleResult.success) {
|
|
200
|
+
reply.code(402).send({ error: "Payment settlement failed", reason: settleResult.errorReason, price, payTo: _payTo });
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const txHash = settleResult.transaction || null;
|
|
205
|
+
const payer = settleResult.payer || verifyResult.payer || paymentPayload.payload?.authorization?.from || null;
|
|
206
|
+
|
|
207
|
+
request.x402 = { paid: true, amount: price, receipt: settleResult, txHash, from: payer };
|
|
208
|
+
_logPayment({ route: request.url, method: request.method, amount: price, from: payer, txHash, verified: true, settled: "facilitator", timestamp: Date.now() });
|
|
209
|
+
return;
|
|
210
|
+
} catch (err) {
|
|
211
|
+
console.log(` ⚠️ x402 SDK error: ${err.message}`);
|
|
212
|
+
reply.code(402).send({ error: "Payment processing failed", reason: err.message, price, payTo: _payTo });
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
190
215
|
}
|
|
191
216
|
|
|
192
|
-
//
|
|
193
|
-
|
|
194
|
-
const payer = settleResult.data?.payer || verifyResult.data?.payer || paymentPayload.payload?.authorization?.from || null;
|
|
195
|
-
|
|
196
|
-
reply.header("Payment-Response", JSON.stringify(settleResult.data || {}));
|
|
197
|
-
request.x402 = { paid: true, amount: price, receipt: settleResult.data, txHash, from: payer };
|
|
198
|
-
_logPayment({ route: request.url, method: request.method, amount: price, from: payer, txHash, verified: true, settled: "facilitator", timestamp: Date.now() });
|
|
217
|
+
// Fallback: direct verification (no on-chain settlement)
|
|
218
|
+
reply.code(500).send({ error: "x402 SDK not available — install @x402/core @x402/evm" });
|
|
199
219
|
});
|
|
200
220
|
|
|
201
221
|
// ── Public pricing endpoint ──
|
|
@@ -205,89 +225,11 @@ async function x402Plugin(fastify, opts) {
|
|
|
205
225
|
facilitator: _facilitatorUrl,
|
|
206
226
|
protocol: "x402",
|
|
207
227
|
x402Version: 2,
|
|
228
|
+
sdkLoaded: !!_x402Server,
|
|
208
229
|
docs: "https://docs.cdp.coinbase.com/x402/welcome",
|
|
209
230
|
}));
|
|
210
231
|
}
|
|
211
232
|
|
|
212
|
-
/**
|
|
213
|
-
* Call the x402 facilitator — matches the exact format from @x402/core HTTPFacilitatorClient.
|
|
214
|
-
* Uses fetch() for automatic redirect following (x402.org → www.x402.org).
|
|
215
|
-
*
|
|
216
|
-
* POST {facilitatorUrl}/verify or /settle
|
|
217
|
-
* Body: { x402Version, paymentPayload, paymentRequirements }
|
|
218
|
-
*/
|
|
219
|
-
async function _facilitatorCall(endpoint, paymentPayload, paymentRequirements) {
|
|
220
|
-
try {
|
|
221
|
-
const url = _facilitatorUrl + endpoint;
|
|
222
|
-
const body = JSON.stringify({
|
|
223
|
-
x402Version: paymentPayload.x402Version || 2,
|
|
224
|
-
paymentPayload,
|
|
225
|
-
paymentRequirements,
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
const controller = new AbortController();
|
|
229
|
-
const timeout = setTimeout(() => controller.abort(), 30000);
|
|
230
|
-
|
|
231
|
-
// Build headers — add CDP JWT auth if using CDP facilitator
|
|
232
|
-
const headers = { "Content-Type": "application/json" };
|
|
233
|
-
if (url.includes("api.cdp.coinbase.com") && process.env.CDP_API_KEY_ID) {
|
|
234
|
-
try {
|
|
235
|
-
// Use dynamic import for @coinbase/cdp-sdk (ESM-only jose dependency)
|
|
236
|
-
const { generateJwt } = await import("@coinbase/cdp-sdk/auth");
|
|
237
|
-
const parsedUrl = new URL(url);
|
|
238
|
-
const jwt = await generateJwt({
|
|
239
|
-
apiKeyId: process.env.CDP_API_KEY_ID,
|
|
240
|
-
apiKeySecret: process.env.CDP_API_KEY_SECRET,
|
|
241
|
-
requestMethod: "POST",
|
|
242
|
-
requestHost: `https://${parsedUrl.host}`,
|
|
243
|
-
requestPath: parsedUrl.pathname,
|
|
244
|
-
});
|
|
245
|
-
headers["Authorization"] = `Bearer ${jwt}`;
|
|
246
|
-
} catch (authErr) {
|
|
247
|
-
console.log(` ⚠️ x402 CDP auth failed: ${authErr.message}`);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const response = await fetch(url, {
|
|
252
|
-
method: "POST",
|
|
253
|
-
headers,
|
|
254
|
-
body,
|
|
255
|
-
redirect: "follow",
|
|
256
|
-
signal: controller.signal,
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
clearTimeout(timeout);
|
|
260
|
-
const text = await response.text();
|
|
261
|
-
let parsed;
|
|
262
|
-
try { parsed = JSON.parse(text); } catch {
|
|
263
|
-
console.log(` ⚠️ x402 facilitator ${endpoint} ${response.status}: unparseable response`);
|
|
264
|
-
return { ok: false, reason: `facilitator_parse_error_${response.status}` };
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (!response.ok) {
|
|
268
|
-
const reason = parsed.invalidReason || parsed.errorReason || parsed.error || `facilitator_${response.status}`;
|
|
269
|
-
console.log(` ⚠️ x402 facilitator ${endpoint} ${response.status}: ${reason}`);
|
|
270
|
-
return { ok: false, reason, data: parsed };
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Verify: check isValid. Settle: check success.
|
|
274
|
-
if (endpoint === "/verify" && parsed.isValid === false) {
|
|
275
|
-
console.log(` ⚠️ x402 verify rejected: ${parsed.invalidReason || "unknown"}`);
|
|
276
|
-
return { ok: false, reason: parsed.invalidReason || "verification_rejected", data: parsed };
|
|
277
|
-
}
|
|
278
|
-
if (endpoint === "/settle" && parsed.success === false) {
|
|
279
|
-
console.log(` ⚠️ x402 settle rejected: ${parsed.errorReason || "unknown"}`);
|
|
280
|
-
return { ok: false, reason: parsed.errorReason || "settlement_rejected", data: parsed };
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
return { ok: true, data: parsed };
|
|
284
|
-
} catch (err) {
|
|
285
|
-
const reason = err.name === "AbortError" ? "facilitator_timeout" : "facilitator_unavailable: " + err.message;
|
|
286
|
-
console.log(` ⚠️ x402 facilitator ${endpoint}: ${reason}`);
|
|
287
|
-
return { ok: false, reason };
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
233
|
function _logPayment(entry) {
|
|
292
234
|
try {
|
|
293
235
|
const logPath = path.join(process.cwd(), ".wolverine", "x402-payments.json");
|