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 +24 -0
- package/package.json +3 -2
- package/src/brain/brain.js +2 -2
- package/src/middleware/x402-fastify.js +197 -0
- package/src/skills/vault.js +2 -2
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": "
|
|
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"
|
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
|
|
194
|
-
metadata: { topic: "vault-
|
|
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;
|
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
|