wolverine-ai 4.9.0 → 5.0.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/bin/wolverine.js +17 -0
- package/package.json +3 -2
- package/src/brain/brain.js +2 -2
- package/src/middleware/x402-fastify.js +290 -0
- package/src/skills/vault.js +26 -3
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-price Set x402 price: wolverine --x402-price "POST /api" "$0.01"
|
|
41
42
|
|
|
42
43
|
${chalk.bold("Configuration:")}
|
|
43
44
|
server/config/settings.json Models, telemetry, limits, health checks
|
|
@@ -66,6 +67,22 @@ if (args.includes("--info")) {
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
// --init: scan server/ and build context map
|
|
70
|
+
// --x402-price: set route pricing live
|
|
71
|
+
if (args.includes("--x402-price")) {
|
|
72
|
+
const idx = args.indexOf("--x402-price");
|
|
73
|
+
const route = args[idx + 1];
|
|
74
|
+
const price = args[idx + 2];
|
|
75
|
+
if (!route || !price) {
|
|
76
|
+
console.log(chalk.red(' Usage: wolverine --x402-price "POST /v1/chat/completions" "$0.001"'));
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
const { setPrice } = require("../src/middleware/x402-fastify");
|
|
80
|
+
const result = setPrice(route, price);
|
|
81
|
+
console.log(chalk.green(` ✅ x402 price updated: ${result.route} → ${result.price}`));
|
|
82
|
+
console.log(chalk.gray(" Change is live — no restart needed."));
|
|
83
|
+
process.exit(0);
|
|
84
|
+
}
|
|
85
|
+
|
|
69
86
|
if (args.includes("--init")) {
|
|
70
87
|
const { scan } = require("../src/core/server-context");
|
|
71
88
|
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": "
|
|
3
|
+
"version": "5.0.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": {
|
|
@@ -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"
|
package/src/brain/brain.js
CHANGED
|
@@ -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
|
|
194
|
-
metadata: { topic: "vault-
|
|
193
|
+
text: "Vault + x402 payments: encrypted key storage in .wolverine/vault/. AES-256-GCM encryption. master.key encrypts eth.vault (Ethereum private key). Generated on first run. Private key NEVER as JS string — Buffer only, wiped after use. wallet-ops: getWalletAddress(), signTransaction(), signMessage(). x402 protocol: HTTP 402 Payment Required for API monetization. Fastify middleware (src/middleware/x402-fastify.js) auto-gates routes with USDC payments on Base network. Config in settings.json x402.routes: { 'POST /api': { price: '$0.01' } }. Live price updates: PUT /x402/price or wolverine --x402-price 'POST /api' '$0.01'. Vault wallet is the payTo address. Facilitator (x402.org or Coinbase CDP) verifies and settles payments. Buyer SDKs (@x402/fetch, @x402/axios) auto-handle the 402→sign→retry flow. Vault skill actions: x402_pricing, x402_set_price, x402_remove_price.",
|
|
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,290 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* x402 Payment Middleware for Fastify — monetize any API route with USDC on Base.
|
|
6
|
+
*
|
|
7
|
+
* Implements the x402 protocol (HTTP 402 Payment Required) for Fastify.
|
|
8
|
+
* No official @x402/fastify exists, so we build directly on @x402/core.
|
|
9
|
+
*
|
|
10
|
+
* Usage in server/index.js:
|
|
11
|
+
* fastify.register(require('wolverine-ai/src/middleware/x402-fastify'), {
|
|
12
|
+
* payTo: '0xYourAddress', // or auto from vault
|
|
13
|
+
* network: 'eip155:8453', // Base mainnet
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* Route pricing in settings.json:
|
|
17
|
+
* "x402": {
|
|
18
|
+
* "enabled": true,
|
|
19
|
+
* "routes": {
|
|
20
|
+
* "POST /v1/chat/completions": { "price": "$0.001" },
|
|
21
|
+
* "GET /api/premium": { "price": "$0.01" }
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* Live price updates:
|
|
26
|
+
* PUT /x402/price { route: "POST /v1/chat/completions", price: "$0.002" }
|
|
27
|
+
* wolverine --x402-price "POST /v1/chat/completions" "$0.002"
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
// In-memory route pricing — survives hot updates without restart
|
|
31
|
+
let _routePricing = {};
|
|
32
|
+
let _payTo = null;
|
|
33
|
+
let _network = "eip155:8453"; // Base mainnet default
|
|
34
|
+
let _facilitatorUrl = "https://x402.org/facilitator";
|
|
35
|
+
let _initialized = false;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get current route pricing (for external access).
|
|
39
|
+
*/
|
|
40
|
+
function getPricing() { return { ..._routePricing }; }
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Update a single route's price live — no restart needed.
|
|
44
|
+
*/
|
|
45
|
+
function setPrice(routeKey, price) {
|
|
46
|
+
if (!price.startsWith("$")) price = "$" + price;
|
|
47
|
+
_routePricing[routeKey] = { price };
|
|
48
|
+
// Persist to settings.json
|
|
49
|
+
_persistPricing();
|
|
50
|
+
return { route: routeKey, price };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Remove pricing from a route (make it free).
|
|
55
|
+
*/
|
|
56
|
+
function removePrice(routeKey) {
|
|
57
|
+
delete _routePricing[routeKey];
|
|
58
|
+
_persistPricing();
|
|
59
|
+
return { route: routeKey, price: "free" };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function _persistPricing() {
|
|
63
|
+
try {
|
|
64
|
+
const settingsPath = path.join(process.cwd(), "server", "config", "settings.json");
|
|
65
|
+
if (fs.existsSync(settingsPath)) {
|
|
66
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
67
|
+
if (!settings.x402) settings.x402 = {};
|
|
68
|
+
settings.x402.routes = {};
|
|
69
|
+
for (const [route, cfg] of Object.entries(_routePricing)) {
|
|
70
|
+
settings.x402.routes[route] = { price: cfg.price };
|
|
71
|
+
}
|
|
72
|
+
const tmp = settingsPath + ".tmp";
|
|
73
|
+
fs.writeFileSync(tmp, JSON.stringify(settings, null, 2), "utf-8");
|
|
74
|
+
fs.renameSync(tmp, settingsPath);
|
|
75
|
+
}
|
|
76
|
+
} catch {}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Fastify plugin — registers x402 payment middleware.
|
|
81
|
+
*/
|
|
82
|
+
async function x402Plugin(fastify, opts) {
|
|
83
|
+
// Load config
|
|
84
|
+
_payTo = opts.payTo || null;
|
|
85
|
+
_network = opts.network || "eip155:8453";
|
|
86
|
+
_facilitatorUrl = opts.facilitator || "https://x402.org/facilitator";
|
|
87
|
+
|
|
88
|
+
// Auto-detect payTo from vault if not provided
|
|
89
|
+
if (!_payTo) {
|
|
90
|
+
try {
|
|
91
|
+
const { getWalletAddress } = require("../vault/wallet-ops");
|
|
92
|
+
_payTo = await getWalletAddress();
|
|
93
|
+
} catch {}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Load route pricing from settings.json
|
|
97
|
+
try {
|
|
98
|
+
const settingsPath = path.join(process.cwd(), "server", "config", "settings.json");
|
|
99
|
+
if (fs.existsSync(settingsPath)) {
|
|
100
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
101
|
+
if (settings.x402?.routes) {
|
|
102
|
+
for (const [route, cfg] of Object.entries(settings.x402.routes)) {
|
|
103
|
+
_routePricing[route] = { price: cfg.price };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (settings.x402?.network) _network = settings.x402.network;
|
|
107
|
+
if (settings.x402?.facilitator) _facilitatorUrl = settings.x402.facilitator;
|
|
108
|
+
}
|
|
109
|
+
} catch {}
|
|
110
|
+
|
|
111
|
+
if (!_payTo) {
|
|
112
|
+
console.log(" ⚠️ x402: no payTo address (set in settings.json or init vault)");
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
_initialized = true;
|
|
117
|
+
const routeCount = Object.keys(_routePricing).length;
|
|
118
|
+
if (routeCount > 0) {
|
|
119
|
+
console.log(` 💰 x402: ${routeCount} paid route(s), receiving at ${_payTo.slice(0, 6)}...${_payTo.slice(-4)}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── Main payment gate — onRequest hook ──
|
|
123
|
+
fastify.addHook("onRequest", async (request, reply) => {
|
|
124
|
+
if (!_initialized || Object.keys(_routePricing).length === 0) return;
|
|
125
|
+
|
|
126
|
+
const routeKey = `${request.method} ${request.url.split("?")[0]}`;
|
|
127
|
+
const routeConfig = _routePricing[routeKey];
|
|
128
|
+
if (!routeConfig) return; // Free route
|
|
129
|
+
|
|
130
|
+
const paymentSig = request.headers["payment-signature"];
|
|
131
|
+
|
|
132
|
+
// No payment — return 402 with payment instructions
|
|
133
|
+
if (!paymentSig) {
|
|
134
|
+
const paymentRequired = {
|
|
135
|
+
accepts: [{
|
|
136
|
+
scheme: "exact",
|
|
137
|
+
price: routeConfig.price,
|
|
138
|
+
network: _network,
|
|
139
|
+
payTo: _payTo,
|
|
140
|
+
}],
|
|
141
|
+
description: `Payment required for ${routeKey}`,
|
|
142
|
+
mimeType: "application/json",
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
reply
|
|
146
|
+
.code(402)
|
|
147
|
+
.header("Payment-Required", JSON.stringify(paymentRequired))
|
|
148
|
+
.header("X-402-Version", "1.0")
|
|
149
|
+
.send({
|
|
150
|
+
error: "Payment Required",
|
|
151
|
+
price: routeConfig.price,
|
|
152
|
+
network: _network,
|
|
153
|
+
payTo: _payTo,
|
|
154
|
+
protocol: "x402",
|
|
155
|
+
});
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Payment present — verify via facilitator
|
|
160
|
+
try {
|
|
161
|
+
const verified = await _verifyPayment(paymentSig, routeConfig);
|
|
162
|
+
if (verified.valid) {
|
|
163
|
+
// Payment good — add receipt header and continue
|
|
164
|
+
reply.header("Payment-Response", JSON.stringify(verified.receipt || {}));
|
|
165
|
+
request.x402 = { paid: true, amount: routeConfig.price, txHash: verified.txHash };
|
|
166
|
+
return; // continue to route handler
|
|
167
|
+
}
|
|
168
|
+
} catch {}
|
|
169
|
+
|
|
170
|
+
// Verification failed
|
|
171
|
+
reply.code(402).send({
|
|
172
|
+
error: "Payment verification failed",
|
|
173
|
+
price: routeConfig.price,
|
|
174
|
+
network: _network,
|
|
175
|
+
payTo: _payTo,
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// ── Live price management API ──
|
|
180
|
+
fastify.put("/x402/price", async (request, reply) => {
|
|
181
|
+
// Admin only
|
|
182
|
+
const token = request.headers.authorization?.replace("Bearer ", "");
|
|
183
|
+
let settings = {};
|
|
184
|
+
try { settings = JSON.parse(fs.readFileSync(path.join(process.cwd(), "server", "config", "settings.json"), "utf-8")); } catch {}
|
|
185
|
+
if (token !== settings.platform?.apiKey) {
|
|
186
|
+
return reply.code(403).send({ error: "Admin only" });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const { route, price } = request.body || {};
|
|
190
|
+
if (!route || !price) return reply.code(400).send({ error: "route and price required" });
|
|
191
|
+
const result = setPrice(route, price);
|
|
192
|
+
return { updated: true, ...result };
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
fastify.delete("/x402/price", async (request, reply) => {
|
|
196
|
+
const token = request.headers.authorization?.replace("Bearer ", "");
|
|
197
|
+
let settings = {};
|
|
198
|
+
try { settings = JSON.parse(fs.readFileSync(path.join(process.cwd(), "server", "config", "settings.json"), "utf-8")); } catch {}
|
|
199
|
+
if (token !== settings.platform?.apiKey) {
|
|
200
|
+
return reply.code(403).send({ error: "Admin only" });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const { route } = request.body || {};
|
|
204
|
+
if (!route) return reply.code(400).send({ error: "route required" });
|
|
205
|
+
return removePrice(route);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
fastify.get("/x402/pricing", async () => {
|
|
209
|
+
return {
|
|
210
|
+
payTo: _payTo,
|
|
211
|
+
network: _network,
|
|
212
|
+
routes: _routePricing,
|
|
213
|
+
};
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Verify a payment signature via the facilitator.
|
|
219
|
+
*/
|
|
220
|
+
async function _verifyPayment(paymentSig, routeConfig) {
|
|
221
|
+
try {
|
|
222
|
+
// Try @x402/core facilitator client if available
|
|
223
|
+
const { HTTPFacilitatorClient } = require("@x402/core/server");
|
|
224
|
+
const { ExactEvmScheme } = require("@x402/evm/exact/server");
|
|
225
|
+
|
|
226
|
+
const facilitator = new HTTPFacilitatorClient({ url: _facilitatorUrl });
|
|
227
|
+
const result = await facilitator.verify({
|
|
228
|
+
paymentSignature: paymentSig,
|
|
229
|
+
routeConfig: {
|
|
230
|
+
accepts: [{
|
|
231
|
+
scheme: "exact",
|
|
232
|
+
price: routeConfig.price,
|
|
233
|
+
network: _network,
|
|
234
|
+
payTo: _payTo,
|
|
235
|
+
}],
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
return { valid: result.valid, receipt: result.receipt, txHash: result.txHash };
|
|
239
|
+
} catch {
|
|
240
|
+
// Fallback: manual HTTP verification to facilitator
|
|
241
|
+
try {
|
|
242
|
+
const https = require("https");
|
|
243
|
+
const http = require("http");
|
|
244
|
+
const url = new (require("url").URL)(_facilitatorUrl + "/verify");
|
|
245
|
+
const body = JSON.stringify({
|
|
246
|
+
paymentSignature: paymentSig,
|
|
247
|
+
routeConfig: {
|
|
248
|
+
accepts: [{
|
|
249
|
+
scheme: "exact",
|
|
250
|
+
price: routeConfig.price,
|
|
251
|
+
network: _network,
|
|
252
|
+
payTo: _payTo,
|
|
253
|
+
}],
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
return new Promise((resolve) => {
|
|
258
|
+
const client = url.protocol === "https:" ? https : http;
|
|
259
|
+
const req = client.request({
|
|
260
|
+
hostname: url.hostname,
|
|
261
|
+
port: url.port,
|
|
262
|
+
path: url.pathname,
|
|
263
|
+
method: "POST",
|
|
264
|
+
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) },
|
|
265
|
+
timeout: 10000,
|
|
266
|
+
}, (res) => {
|
|
267
|
+
let data = "";
|
|
268
|
+
res.on("data", (c) => data += c);
|
|
269
|
+
res.on("end", () => {
|
|
270
|
+
try {
|
|
271
|
+
const parsed = JSON.parse(data);
|
|
272
|
+
resolve({ valid: parsed.valid || parsed.success, receipt: parsed, txHash: parsed.txHash });
|
|
273
|
+
} catch { resolve({ valid: false }); }
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
req.on("error", () => resolve({ valid: false }));
|
|
277
|
+
req.on("timeout", () => { req.destroy(); resolve({ valid: false }); });
|
|
278
|
+
req.write(body);
|
|
279
|
+
req.end();
|
|
280
|
+
});
|
|
281
|
+
} catch { return { valid: false }; }
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
x402Plugin[Symbol.for("skip-override")] = true; // Fastify plugin compat
|
|
286
|
+
|
|
287
|
+
module.exports = x402Plugin;
|
|
288
|
+
module.exports.getPricing = getPricing;
|
|
289
|
+
module.exports.setPrice = setPrice;
|
|
290
|
+
module.exports.removePrice = removePrice;
|
package/src/skills/vault.js
CHANGED
|
@@ -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,
|
|
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
|
|
@@ -61,8 +61,31 @@ async function execute(action, params = {}) {
|
|
|
61
61
|
return { signedTransaction: signed };
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
// x402 payment actions
|
|
65
|
+
case "x402_pricing":
|
|
66
|
+
try {
|
|
67
|
+
const { getPricing } = require("../middleware/x402-fastify");
|
|
68
|
+
return getPricing();
|
|
69
|
+
} catch { return { error: "x402 middleware not loaded" }; }
|
|
70
|
+
|
|
71
|
+
case "x402_set_price": {
|
|
72
|
+
if (!params.route || !params.price) return { error: "route and price required" };
|
|
73
|
+
try {
|
|
74
|
+
const { setPrice } = require("../middleware/x402-fastify");
|
|
75
|
+
return setPrice(params.route, params.price);
|
|
76
|
+
} catch { return { error: "x402 middleware not loaded" }; }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
case "x402_remove_price": {
|
|
80
|
+
if (!params.route) return { error: "route required" };
|
|
81
|
+
try {
|
|
82
|
+
const { removePrice } = require("../middleware/x402-fastify");
|
|
83
|
+
return removePrice(params.route);
|
|
84
|
+
} catch { return { error: "x402 middleware not loaded" }; }
|
|
85
|
+
}
|
|
86
|
+
|
|
64
87
|
default:
|
|
65
|
-
return { error: `Unknown vault action: ${action}. Use: status, address, sign_tx, sign_message, public_key` };
|
|
88
|
+
return { error: `Unknown vault action: ${action}. Use: status, address, sign_tx, sign_message, public_key, x402_pricing, x402_set_price, x402_remove_price` };
|
|
66
89
|
}
|
|
67
90
|
}
|
|
68
91
|
|