wolverine-ai 4.9.0 β†’ 5.0.1

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/bin/wolverine.js CHANGED
@@ -38,6 +38,7 @@ ${chalk.bold("Options:")}
38
38
  --workers <n> Force specific worker count
39
39
  --info Show system info and exit
40
40
  --init Scan server/ and build context map (routes, DB, config, deps)
41
+ --x402-info Show x402 payment configuration
41
42
 
42
43
  ${chalk.bold("Configuration:")}
43
44
  server/config/settings.json Models, telemetry, limits, health checks
@@ -66,6 +67,29 @@ if (args.includes("--info")) {
66
67
  }
67
68
 
68
69
  // --init: scan server/ and build context map
70
+ // --x402-info: show payment configuration
71
+ if (args.includes("--x402-info")) {
72
+ (async () => {
73
+ let payTo = "not configured";
74
+ try {
75
+ const { getWalletAddress } = require("../src/vault/wallet-ops");
76
+ payTo = await getWalletAddress();
77
+ } catch {}
78
+ console.log(chalk.blue("\n πŸ’° x402 Payment Configuration\n"));
79
+ console.log(chalk.gray(` Wallet: ${payTo}`));
80
+ console.log(chalk.gray(` Network: Base (eip155:8453)`));
81
+ console.log(chalk.gray(` Token: USDC`));
82
+ console.log(chalk.gray(` Protocol: x402 (HTTP 402 Payment Required)`));
83
+ console.log(chalk.gray(` Facilitator: x402.org`));
84
+ console.log(chalk.gray(`\n Add to any Fastify route:`));
85
+ console.log(chalk.cyan(` { config: { x402: { price: "$0.10" } } }`));
86
+ console.log(chalk.gray(`\n Variable pricing (credit purchases):`));
87
+ console.log(chalk.cyan(` { config: { x402: { variable: true, min: "$1", max: "$10000", priceField: "dollars" } } }\n`));
88
+ process.exit(0);
89
+ })();
90
+ return;
91
+ }
92
+
69
93
  if (args.includes("--init")) {
70
94
  const { scan } = require("../src/core/server-context");
71
95
  console.log(chalk.blue("\n πŸ” Scanning server/ directory...\n"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wolverine-ai",
3
- "version": "4.9.0",
3
+ "version": "5.0.1",
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": {
@@ -66,7 +66,8 @@
66
66
  "ethers": "^6.0.0",
67
67
  "ioredis": "^5.0.0",
68
68
  "pg": "^8.0.0",
69
- "stripe": "^18.0.0"
69
+ "stripe": "^18.0.0",
70
+ "viem": "^2.0.0"
70
71
  },
71
72
  "engines": {
72
73
  "node": ">=18.0.0"
@@ -190,8 +190,8 @@ const SEED_DOCS = [
190
190
  metadata: { topic: "notifications" },
191
191
  },
192
192
  {
193
- text: "Vault: encrypted key storage in .wolverine/vault/. AES-256-GCM encryption. master.key (32 bytes raw) encrypts eth.vault (Ethereum private key). Generated on first run if missing. Private key NEVER exists as a JS string — only Buffer, wiped after use. wallet-ops.js exposes getWalletAddress(), signTransaction(), signMessage() — all decrypt→use→wipe with generic error messages only. Injection detector blocks heal if 0x+64hex chars detected in error (key_leak_critical). Redactor scrubs all hex key patterns. Sandbox blocks agent access to vault paths. Backed up in every snapshot. Rollback-protected (NEVER_ROLLBACK list). Vault skill in src/skills/vault.js for agent discovery. Server code uses vault skill to earn/spend ETH without touching the private key.",
194
- metadata: { topic: "vault-security" },
193
+ text: "Vault + x402 payments: encrypted key storage in .wolverine/vault/. AES-256-GCM. Private key NEVER as JS string — Buffer only, wiped after use. x402 protocol: HTTP 402 Payment Required for USDC payments on Base. Fastify plugin (src/middleware/x402-fastify.js) adds crypto to any route via config flag. Fixed price: { config: { x402: { price: '$0.10' } } }. Variable price (credit purchases): { config: { x402: { variable: true, min: '$1', max: '$10000', priceField: 'dollars' } } } — reads amount from request body. Flow: client requests → 402 with Payment-Required header → client SDK signs USDC auth → retries with Payment-Signature → server verifies via facilitator → 200 + receipt. req.x402.paid/amount/txHash available in handler after payment. Vault wallet is auto-detected as payTo. GET /x402/info shows payment config. Buyer SDKs: @x402/fetch, @x402/axios auto-handle 402→sign→retry.",
194
+ metadata: { topic: "vault-x402" },
195
195
  },
196
196
  {
197
197
  text: "MCP integration: connect external tools via Model Context Protocol. Configure in .wolverine/mcp.json with per-server tool allowlists. Security: arg sanitization (secrets redacted before sending to MCP servers), result injection scanning, rate limiting per server, audit logging. Tools appear as mcp__server__tool in the agent. Supports stdio and HTTP transports.",
@@ -0,0 +1,197 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ /**
5
+ * x402 Fastify Plugin β€” add crypto payments to any route with one flag.
6
+ *
7
+ * Makes it dead simple to accept USDC payments on Base network.
8
+ * The developer marks a route with x402 config, and the middleware
9
+ * handles the 402 β†’ payment β†’ verification β†’ callback flow.
10
+ *
11
+ * Two modes:
12
+ * Fixed price: { x402: { price: "$0.01" } }
13
+ * Variable price: { x402: { variable: true, min: "$1", max: "$10000" } }
14
+ *
15
+ * Example β€” fixed price route:
16
+ * fastify.get("/premium-data", { config: { x402: { price: "$0.10" } } }, handler)
17
+ *
18
+ * Example β€” variable price (credit purchase):
19
+ * fastify.post("/buy-credits", {
20
+ * config: {
21
+ * x402: {
22
+ * variable: true,
23
+ * min: "$1",
24
+ * max: "$10000",
25
+ * priceField: "dollars", // reads amount from request body
26
+ * }
27
+ * }
28
+ * }, async (req, reply) => {
29
+ * // req.x402.paid === true, req.x402.amount === "$5.00"
30
+ * addCredits(req.x402.amount);
31
+ * return { credits: newBalance };
32
+ * })
33
+ *
34
+ * The payment receipt is in req.x402 after verification:
35
+ * { paid: true, amount: "$5.00", receipt: {...}, txHash: "0x..." }
36
+ */
37
+
38
+ let _payTo = null;
39
+ let _network = "eip155:8453";
40
+ let _facilitatorUrl = "https://x402.org/facilitator";
41
+
42
+ async function x402Plugin(fastify, opts) {
43
+ // Config
44
+ _network = opts.network || _network;
45
+ _facilitatorUrl = opts.facilitator || _facilitatorUrl;
46
+ _payTo = opts.payTo || null;
47
+
48
+ // Load from settings.json
49
+ try {
50
+ const settingsPath = path.join(process.cwd(), "server", "config", "settings.json");
51
+ const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
52
+ if (settings.x402?.network) _network = settings.x402.network;
53
+ if (settings.x402?.facilitator) _facilitatorUrl = settings.x402.facilitator;
54
+ if (settings.x402?.payTo) _payTo = settings.x402.payTo;
55
+ } catch {}
56
+
57
+ // Auto-detect payTo from vault
58
+ if (!_payTo) {
59
+ try {
60
+ const { getWalletAddress } = require("../vault/wallet-ops");
61
+ _payTo = await getWalletAddress();
62
+ } catch {}
63
+ }
64
+
65
+ if (_payTo) {
66
+ console.log(` πŸ’° x402: payments to ${_payTo.slice(0, 6)}...${_payTo.slice(-4)} on ${_network}`);
67
+ }
68
+
69
+ // ── Route-level x402 hook ──
70
+ fastify.addHook("onRequest", async (request, reply) => {
71
+ // Check if this route has x402 config
72
+ const routeConfig = request.routeOptions?.config?.x402 || request.context?.config?.x402;
73
+ if (!routeConfig) return; // Not an x402 route
74
+ if (!_payTo) {
75
+ reply.code(500).send({ error: "x402 not configured β€” no wallet address" });
76
+ return;
77
+ }
78
+
79
+ // Determine the price
80
+ let price;
81
+ if (routeConfig.variable) {
82
+ // Variable pricing β€” read amount from request body or query
83
+ const field = routeConfig.priceField || "dollars";
84
+ const raw = request.body?.[field] || request.query?.[field];
85
+ if (!raw) {
86
+ reply.code(400).send({
87
+ error: `${field} required`,
88
+ x402: { variable: true, min: routeConfig.min || "$1", max: routeConfig.max || "$10000" },
89
+ });
90
+ return;
91
+ }
92
+ const amount = parseFloat(String(raw).replace("$", ""));
93
+ const min = parseFloat((routeConfig.min || "$1").replace("$", ""));
94
+ const max = parseFloat((routeConfig.max || "$10000").replace("$", ""));
95
+ if (isNaN(amount) || amount < min || amount > max) {
96
+ reply.code(400).send({ error: `Amount must be $${min}-$${max}` });
97
+ return;
98
+ }
99
+ price = "$" + amount.toFixed(2);
100
+ } else {
101
+ price = routeConfig.price;
102
+ if (!price) { reply.code(500).send({ error: "x402 route missing price config" }); return; }
103
+ }
104
+
105
+ const paymentSig = request.headers["payment-signature"];
106
+
107
+ // No payment β€” return 402 with payment instructions
108
+ if (!paymentSig) {
109
+ const instructions = {
110
+ accepts: [{
111
+ scheme: "exact",
112
+ price,
113
+ network: _network,
114
+ payTo: _payTo,
115
+ }],
116
+ description: routeConfig.description || `Payment for ${request.method} ${request.url}`,
117
+ mimeType: "application/json",
118
+ };
119
+ reply
120
+ .code(402)
121
+ .header("Payment-Required", JSON.stringify(instructions))
122
+ .send({
123
+ error: "Payment Required",
124
+ price,
125
+ network: _network,
126
+ payTo: _payTo,
127
+ protocol: "x402",
128
+ ...(routeConfig.variable ? { variable: true, min: routeConfig.min, max: routeConfig.max, priceField: routeConfig.priceField || "dollars" } : {}),
129
+ });
130
+ return;
131
+ }
132
+
133
+ // Payment present β€” verify
134
+ const verified = await _verifyPayment(paymentSig, price);
135
+ if (verified.valid) {
136
+ reply.header("Payment-Response", JSON.stringify(verified.receipt || {}));
137
+ request.x402 = { paid: true, amount: price, receipt: verified.receipt, txHash: verified.txHash };
138
+ return; // continue to route handler
139
+ }
140
+
141
+ reply.code(402).send({ error: "Payment verification failed", price, payTo: _payTo });
142
+ });
143
+
144
+ // ── Public pricing endpoint ──
145
+ fastify.get("/x402/info", async () => ({
146
+ payTo: _payTo,
147
+ network: _network,
148
+ facilitator: _facilitatorUrl,
149
+ protocol: "x402",
150
+ docs: "https://docs.cdp.coinbase.com/x402/welcome",
151
+ }));
152
+ }
153
+
154
+ async function _verifyPayment(paymentSig, price) {
155
+ 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({
160
+ paymentSignature: paymentSig,
161
+ routeConfig: { accepts: [{ scheme: "exact", price, network: _network, payTo: _payTo }] },
162
+ });
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
+ });
187
+ });
188
+ req.on("error", () => resolve({ valid: false }));
189
+ req.write(body);
190
+ req.end();
191
+ });
192
+ } catch { return { valid: false }; }
193
+ }
194
+ }
195
+
196
+ x402Plugin[Symbol.for("skip-override")] = true;
197
+ module.exports = x402Plugin;
@@ -10,8 +10,8 @@
10
10
  */
11
11
 
12
12
  const SKILL_NAME = "vault";
13
- const SKILL_DESCRIPTION = "Secure Ethereum wallet β€” sign transactions, get address, check balance. Private key encrypted at rest, never exposed in code or errors.";
14
- const SKILL_KEYWORDS = ["wallet", "ethereum", "eth", "sign", "transaction", "vault", "private key", "address", "crypto", "blockchain", "send", "transfer"];
13
+ const SKILL_DESCRIPTION = "Secure Ethereum wallet + x402 payments β€” sign transactions, get address, manage paid API routes. Private key encrypted at rest via AES-256-GCM, never exposed in code or errors. x402 support: set prices on routes, receive USDC on Base network.";
14
+ const SKILL_KEYWORDS = ["wallet", "ethereum", "eth", "sign", "transaction", "vault", "private key", "address", "crypto", "blockchain", "send", "transfer", "x402", "payment", "usdc", "base", "price", "paid", "api"];
15
15
  const SKILL_USAGE = `
16
16
  vault.status() β€” check if vault is initialized, show address
17
17
  vault.address() β€” get the wallet's Ethereum address