wolverine-ai 5.4.2 → 5.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wolverine-ai",
3
- "version": "5.4.2",
3
+ "version": "5.5.0",
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": {
@@ -4,15 +4,16 @@ const path = require("path");
4
4
  /**
5
5
  * x402 Fastify Plugin — add crypto payments to any route with one flag.
6
6
  *
7
- * Uses @coinbase/x402 facilitator + x402/verify for payment verification
8
- * and on-chain settlement. Matches the working pattern from blockaid-scanner.
7
+ * Uses x402 v2 protocol (@x402/core + @x402/evm) with @coinbase/x402
8
+ * facilitator for CDP authentication. Verify + settle both happen in
9
+ * preHandler — the route handler only runs after USDC moves on-chain.
9
10
  *
10
- * Two modes:
11
- * Fixed price: { x402: { price: "$0.01" } }
12
- * Variable price: { x402: { variable: true, min: "$1", max: "$10000" } }
11
+ * Setup:
12
+ * 1. wolverine --init-vault
13
+ * 2. Set CDP_API_KEY_ID + CDP_API_KEY_SECRET in .env.local
14
+ * 3. Add { config: { x402: { price: "$0.01" } } } to any route
13
15
  */
14
16
 
15
- // Node 18 needs globalThis.crypto
16
17
  if (!globalThis.crypto) {
17
18
  globalThis.crypto = require("crypto").webcrypto;
18
19
  }
@@ -21,8 +22,8 @@ const USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
21
22
  const USDC_EIP712 = { name: "USD Coin", version: "2" };
22
23
 
23
24
  let _payTo = null;
24
- let _network = "base"; // v1 format, not CAIP-2
25
- let _facilitatorClient = null;
25
+ let _network = "eip155:8453";
26
+ let _x402Server = null;
26
27
 
27
28
  async function x402Plugin(fastify, opts) {
28
29
  _payTo = opts.payTo || null;
@@ -43,14 +44,18 @@ async function x402Plugin(fastify, opts) {
43
44
  } catch {}
44
45
  }
45
46
 
46
- // Initialize facilitator from @coinbase/x402 (ESM packages, need dynamic import)
47
+ // Initialize v2 x402 SDK with @coinbase/x402 facilitator auth
47
48
  try {
48
- const { facilitator } = await import("@coinbase/x402");
49
- const { useFacilitator } = await import("x402/verify");
50
- _facilitatorClient = useFacilitator(facilitator);
51
- console.log(` 💰 x402: facilitator loaded (@coinbase/x402)`);
49
+ const { facilitator } = require("@coinbase/x402");
50
+ const { x402ResourceServer, HTTPFacilitatorClient } = require("@x402/core/server");
51
+ const { ExactEvmScheme } = require("@x402/evm/exact/server");
52
+
53
+ const client = new HTTPFacilitatorClient(facilitator);
54
+ _x402Server = new x402ResourceServer(client);
55
+ _x402Server.register("eip155:*", new ExactEvmScheme());
56
+ console.log(` 💰 x402: v2 SDK loaded (ExactEvmScheme + CDP facilitator)`);
52
57
  } catch (err) {
53
- console.log(` ⚠️ x402: facilitator init failed (${err.message}) — install @coinbase/x402 x402`);
58
+ console.log(` ⚠️ x402: SDK init failed (${err.message})`);
54
59
  }
55
60
 
56
61
  if (_payTo) {
@@ -61,14 +66,8 @@ async function x402Plugin(fastify, opts) {
61
66
  fastify.addHook("preHandler", async (request, reply) => {
62
67
  const routeConfig = request.routeOptions?.config?.x402 || request.routeConfig?.x402 || request.context?.config?.x402;
63
68
  if (!routeConfig) return;
64
- if (!_payTo) {
65
- reply.code(500).send({ error: "x402 not configuredno wallet address" });
66
- return;
67
- }
68
- if (!_facilitatorClient) {
69
- reply.code(500).send({ error: "x402 facilitator not loaded — install @coinbase/x402 x402" });
70
- return;
71
- }
69
+ if (!_payTo) { reply.code(500).send({ error: "x402 not configured — run wolverine --init-vault" }); return; }
70
+ if (!_x402Server) { reply.code(500).send({ error: "x402 SDK not loadedinstall @coinbase/x402 @x402/core @x402/evm" }); return; }
72
71
 
73
72
  // Determine dollar amount
74
73
  let dollarAmount;
@@ -91,36 +90,29 @@ async function x402Plugin(fastify, opts) {
91
90
  if (!dollarAmount) { reply.code(500).send({ error: "x402 route missing price config" }); return; }
92
91
  }
93
92
 
94
- const usdcAtomicAmount = String(Math.round(dollarAmount * 1e6));
93
+ const usdcAmount = String(Math.round(dollarAmount * 1e6));
95
94
  const price = "$" + dollarAmount.toFixed(2);
96
-
97
- // Check for payment header (X-PAYMENT for v1 compat, payment-signature for v2)
98
95
  const paymentHeader = request.headers["x-payment"] || request.headers["payment-signature"];
99
96
 
100
- // Build payment requirements (v1 format matching @coinbase/x402)
97
+ // Build v2 payment requirements
101
98
  const { getAddress } = await import("viem");
102
- // Build full resource URL (required by facilitator)
103
99
  const proto = request.headers["x-forwarded-proto"] || "https";
104
100
  const host = request.headers["x-forwarded-host"] || request.headers.host || "localhost";
105
- const resourceUrl = `${proto}://${host}${request.url}`;
106
101
 
107
102
  const paymentRequirements = {
108
103
  scheme: "exact",
109
104
  network: _network,
110
- maxAmountRequired: usdcAtomicAmount,
111
- resource: resourceUrl,
112
- description: routeConfig.description || `Payment of $${dollarAmount.toFixed(2)} USDC`,
113
- mimeType: "application/json",
105
+ amount: usdcAmount,
106
+ asset: getAddress(USDC_ADDRESS),
114
107
  payTo: getAddress(_payTo),
115
108
  maxTimeoutSeconds: 60,
116
- asset: getAddress(USDC_ADDRESS),
117
109
  extra: USDC_EIP712,
118
110
  };
119
111
 
120
112
  // No payment — return 402
121
113
  if (!paymentHeader) {
122
114
  reply.code(402).send({
123
- x402Version: 1,
115
+ x402Version: 2,
124
116
  error: "Payment Required",
125
117
  accepts: [paymentRequirements],
126
118
  price,
@@ -132,55 +124,52 @@ async function x402Plugin(fastify, opts) {
132
124
  return;
133
125
  }
134
126
 
135
- // Decode payment — parse raw payload directly (matching working project pattern)
136
- let decodedPayment;
127
+ // Decode payment
128
+ let paymentPayload;
137
129
  try {
138
130
  const raw = JSON.parse(Buffer.from(paymentHeader, "base64").toString("utf-8"));
131
+ if (!raw.payload?.authorization || !raw.payload?.signature) throw new Error("Missing authorization or signature");
139
132
 
140
- // Validate required fields
141
- if (!raw.payload?.authorization || !raw.payload?.signature) {
142
- throw new Error("Missing authorization or signature");
143
- }
144
-
145
- decodedPayment = {
146
- x402Version: raw.x402Version || 1,
133
+ paymentPayload = {
134
+ x402Version: raw.x402Version || 2,
147
135
  scheme: raw.scheme || "exact",
148
136
  network: raw.network || _network,
149
137
  payload: raw.payload,
150
138
  };
139
+ if (raw.accepted) paymentPayload.accepted = raw.accepted;
140
+ if (raw.resource) paymentPayload.resource = raw.resource;
151
141
  } catch (err) {
152
- reply.code(402).send({ error: "Invalid payment format: " + err.message, accepts: [paymentRequirements] });
142
+ reply.code(402).send({ error: "Invalid payment: " + err.message, accepts: [paymentRequirements] });
153
143
  return;
154
144
  }
155
145
 
156
- // For variable pricing, use user's actual payment value as maxAmountRequired
157
- const userValue = decodedPayment.payload.authorization.value;
158
- const actualRequirements = { ...paymentRequirements, maxAmountRequired: userValue };
146
+ // For variable pricing, match maxAmountRequired to user's payment
147
+ const userValue = paymentPayload.payload.authorization.value;
148
+ const actualRequirements = { ...paymentRequirements, amount: userValue };
159
149
 
160
150
  // Verify via facilitator
161
151
  try {
162
- console.log(` 💰 x402 verify: from=${decodedPayment.payload?.authorization?.from} value=${decodedPayment.payload?.authorization?.value} network=${decodedPayment.network}`);
163
- const verifyResult = await _facilitatorClient.verify(decodedPayment, actualRequirements);
152
+ console.log(` 💰 x402 verify: from=${paymentPayload.payload?.authorization?.from} value=${userValue} network=${paymentPayload.network}`);
153
+ const verifyResult = await _x402Server.verifyPayment(paymentPayload, actualRequirements);
164
154
  if (!verifyResult.isValid) {
165
155
  console.log(` ⚠️ x402 verify failed: ${verifyResult.invalidReason} ${verifyResult.invalidMessage || ""}`);
166
- reply.code(402).send({ error: verifyResult.invalidReason || "Payment verification failed", message: verifyResult.invalidMessage, accepts: [paymentRequirements], payer: verifyResult.payer });
156
+ reply.code(402).send({ error: verifyResult.invalidReason, message: verifyResult.invalidMessage, payer: verifyResult.payer });
167
157
  return;
168
158
  }
169
159
  } catch (err) {
170
- // Extract detailed error from the x402 SDK
171
160
  const detail = err.invalidReason || err.invalidMessage || err.cause?.message || "";
172
161
  console.log(` ⚠️ x402 verify error: ${err.message} | ${detail}`);
173
- reply.code(402).send({ error: err.invalidReason || err.message, message: err.invalidMessage || detail, accepts: [paymentRequirements] });
162
+ reply.code(402).send({ error: err.invalidReason || err.message, message: err.invalidMessage || detail });
174
163
  return;
175
164
  }
176
165
 
177
- // Settle via facilitator BEFORE handler runs — USDC must move before granting access
178
- const payer = decodedPayment.payload.authorization.from;
166
+ // Settle via facilitator — USDC moves on-chain BEFORE handler runs
167
+ const payer = paymentPayload.payload.authorization.from;
179
168
  try {
180
- const settleResult = await _facilitatorClient.settle(decodedPayment, actualRequirements);
169
+ const settleResult = await _x402Server.settlePayment(paymentPayload, actualRequirements);
181
170
  if (!settleResult.success) {
182
171
  console.log(` ⚠️ x402 settle failed: ${settleResult.errorReason || "unknown"}`);
183
- reply.code(402).send({ error: "Payment settlement failed", reason: settleResult.errorReason });
172
+ reply.code(402).send({ error: "Settlement failed", reason: settleResult.errorReason });
184
173
  return;
185
174
  }
186
175
  const txHash = settleResult.transaction || null;
@@ -190,17 +179,15 @@ async function x402Plugin(fastify, opts) {
190
179
  _logPayment({ route: request.url, method: request.method, amount: price, from: payer, txHash, verified: true, settled: true, timestamp: Date.now() });
191
180
  } catch (err) {
192
181
  console.log(` ⚠️ x402 settle error: ${err.message}`);
193
- reply.code(402).send({ error: "Payment settlement failed: " + err.message });
182
+ reply.code(402).send({ error: "Settlement failed: " + err.message });
194
183
  return;
195
184
  }
196
185
  });
197
186
 
198
- // No onSend settle needed — settlement happens in preHandler before handler runs
199
-
200
187
  // Public info endpoint
201
188
  fastify.get("/x402/info", async () => ({
202
- payTo: _payTo, network: _network, protocol: "x402", x402Version: 1,
203
- facilitatorLoaded: !!_facilitatorClient,
189
+ payTo: _payTo, network: _network, protocol: "x402", x402Version: 2,
190
+ sdkLoaded: !!_x402Server,
204
191
  docs: "https://docs.cdp.coinbase.com/x402/welcome",
205
192
  }));
206
193
  }
@@ -22,6 +22,9 @@ if (clusterEnabled && cluster.isPrimary && workerCount > 1) {
22
22
  // Single worker or cluster worker — run the server
23
23
  const fastify = require("fastify")({ logger: false });
24
24
 
25
+ // x402 payment middleware (auto-detects vault, no-op if no vault)
26
+ try { fastify.register(require("wolverine-ai/src/middleware/x402-fastify")); } catch {}
27
+
25
28
  // Routes
26
29
  fastify.register(require("./routes/health"), { prefix: "/health" });
27
30
  fastify.register(require("./routes/api"), { prefix: "/api" });
@@ -1,26 +1,25 @@
1
1
  /**
2
- * Demo API routes — shows both free endpoints and x402 paid APIs.
2
+ * Demo API routes — free endpoints and x402 paid APIs.
3
3
  *
4
- * Wolverine's vault provides an encrypted Ethereum wallet (AES-256-GCM)
5
- * for each server instance. The wallet is used as the payment receiver
6
- * for x402 paid APIs on the Base network (USDC).
4
+ * x402 Setup (one-time):
5
+ * 1. wolverine --init-vault Create encrypted wallet
6
+ * 2. Set CDP_API_KEY_ID + CDP_API_KEY_SECRET in .env.local
7
+ * (free at https://cdp.coinbase.com)
8
+ * 3. Add { config: { x402: { price: "$0.01" } } } to any route
7
9
  *
8
- * Vault commands:
9
- * wolverine --init-vault Create encrypted wallet
10
- * wolverine --x402-info Show wallet address & config
10
+ * That's it — the route now requires USDC payment on Base.
11
+ * The middleware handles 402 responses, wallet signing, facilitator
12
+ * verification, and on-chain settlement automatically.
11
13
  *
12
- * The vault is stored in .wolverine/vault/ and is:
13
- * - Auto-backed up in every server snapshot
14
- * - Protected from agent access (sandbox-blocked)
15
- * - Private keys never exist as JS strings (Buffer only)
16
- * - Rollback-protected (never overwritten by restore)
17
- *
18
- * Dashboard: localhost:3001 — view wallet balances, x402 revenue,
19
- * payment history, and all server health metrics in real time.
14
+ * Protocol: https://docs.cdp.coinbase.com/x402/welcome
15
+ * Network: Base mainnet USDC payments
16
+ * Wallet: Auto-detected from vault
17
+ * Node: 22+ required
20
18
  */
21
19
 
22
20
  async function routes(fastify) {
23
- // ── Free endpoints ──
21
+ // ── Free endpoints (no x402 config = no payment required) ──
22
+
24
23
  fastify.get("/", async () => ({ message: "Hello from Wolverine API" }));
25
24
 
26
25
  fastify.get("/users", async () => ({
@@ -30,29 +29,19 @@ async function routes(fastify) {
30
29
  ],
31
30
  }));
32
31
 
33
- // ── x402 Paid API ──────────────────────────────────────────────
34
- // Any route becomes a paid API by adding x402 config.
35
- // Callers without a valid USDC payment get a 402 with payment instructions.
36
- // Callers with a valid Payment-Signature header get the response.
37
- //
38
- // Protocol: https://docs.cdp.coinbase.com/x402/welcome
39
- // Network: Base (eip155:8453) — USDC payments
40
- // Wallet: Auto-detected from vault (wolverine --init-vault)
41
- //
42
- // To change pricing live without restart:
43
- // Update the price in config and the next request picks it up.
44
- // Or use the dashboard command: "change /api/premium price to $0.05"
45
-
46
- // Fixed price — charge $0.01 per call:
32
+ // ── Paid endpoint — fixed price ──
33
+ // Just add x402 config. Handler only runs after USDC settles on-chain.
47
34
  fastify.get("/premium", {
48
35
  config: { x402: { price: "$0.01", description: "Premium data endpoint" } },
49
36
  }, async (req) => ({
50
37
  data: "This response cost $0.01 in USDC on Base",
51
- paid: req.x402?.amount,
52
- from: req.x402?.from,
38
+ paid: req.x402.amount,
39
+ from: req.x402.from,
40
+ txHash: req.x402.txHash,
53
41
  }));
54
42
 
55
- // Variable pricecaller chooses amount (e.g. buying credits):
43
+ // ── Paid endpoint variable price ──
44
+ // Caller specifies amount in request body. Good for credit purchases.
56
45
  // POST /api/purchase { "dollars": "5.00" }
57
46
  fastify.post("/purchase", {
58
47
  config: {
@@ -65,9 +54,10 @@ async function routes(fastify) {
65
54
  },
66
55
  },
67
56
  }, async (req) => ({
68
- message: `Received ${req.x402?.amount} USDC payment`,
69
- from: req.x402?.from,
70
- receipt: req.x402?.receipt ? "valid" : "none",
57
+ message: `Received ${req.x402.amount} USDC payment`,
58
+ from: req.x402.from,
59
+ txHash: req.x402.txHash,
60
+ settled: req.x402.settled,
71
61
  }));
72
62
  }
73
63