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.
|
|
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 @
|
|
8
|
-
*
|
|
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
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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 = "
|
|
25
|
-
let
|
|
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
|
|
47
|
+
// Initialize v2 x402 SDK with @coinbase/x402 facilitator auth
|
|
47
48
|
try {
|
|
48
|
-
const { facilitator } =
|
|
49
|
-
const {
|
|
50
|
-
|
|
51
|
-
|
|
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:
|
|
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
|
-
|
|
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 loaded — install @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
|
|
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
|
|
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
|
-
|
|
111
|
-
|
|
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:
|
|
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
|
|
136
|
-
let
|
|
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
|
-
|
|
141
|
-
|
|
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
|
|
142
|
+
reply.code(402).send({ error: "Invalid payment: " + err.message, accepts: [paymentRequirements] });
|
|
153
143
|
return;
|
|
154
144
|
}
|
|
155
145
|
|
|
156
|
-
// For variable pricing,
|
|
157
|
-
const userValue =
|
|
158
|
-
const actualRequirements = { ...paymentRequirements,
|
|
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=${
|
|
163
|
-
const verifyResult = await
|
|
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
|
|
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
|
|
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
|
|
178
|
-
const payer =
|
|
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
|
|
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: "
|
|
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: "
|
|
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:
|
|
203
|
-
|
|
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 —
|
|
2
|
+
* Demo API routes — free endpoints and x402 paid APIs.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* -
|
|
15
|
-
*
|
|
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
|
-
// ──
|
|
34
|
-
//
|
|
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
|
|
52
|
-
from: req.x402
|
|
38
|
+
paid: req.x402.amount,
|
|
39
|
+
from: req.x402.from,
|
|
40
|
+
txHash: req.x402.txHash,
|
|
53
41
|
}));
|
|
54
42
|
|
|
55
|
-
//
|
|
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
|
|
69
|
-
from: req.x402
|
|
70
|
-
|
|
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
|
|