wolverine-ai 5.2.12 → 5.3.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/package.json +4 -2
- package/src/middleware/x402-fastify.js +126 -139
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wolverine-ai",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.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": {
|
|
@@ -52,13 +52,15 @@
|
|
|
52
52
|
],
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@anthropic-ai/sdk": "^0.82.0",
|
|
55
|
+
"@coinbase/x402": "^2.1.0",
|
|
55
56
|
"@fastify/compress": "^8.0.0",
|
|
56
57
|
"@fastify/cors": "^10.0.0",
|
|
57
58
|
"chalk": "^4.1.2",
|
|
58
59
|
"diff": "^7.0.0",
|
|
59
60
|
"dotenv": "^16.4.7",
|
|
60
61
|
"fastify": "^5.8.4",
|
|
61
|
-
"openai": "^4.73.0"
|
|
62
|
+
"openai": "^4.73.0",
|
|
63
|
+
"x402": "^1.1.0"
|
|
62
64
|
},
|
|
63
65
|
"optionalDependencies": {
|
|
64
66
|
"@coinbase/cdp-sdk": "^1.46.1",
|
|
@@ -4,38 +4,35 @@ 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 @coinbase/x402 facilitator + x402/verify for payment verification
|
|
8
|
+
* and on-chain settlement. Matches the working pattern from blockaid-scanner.
|
|
9
9
|
*
|
|
10
10
|
* Two modes:
|
|
11
11
|
* Fixed price: { x402: { price: "$0.01" } }
|
|
12
12
|
* Variable price: { x402: { variable: true, min: "$1", max: "$10000" } }
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
// Node 18 needs globalThis.crypto
|
|
15
|
+
// Node 18 needs globalThis.crypto
|
|
16
16
|
if (!globalThis.crypto) {
|
|
17
17
|
globalThis.crypto = require("crypto").webcrypto;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const
|
|
20
|
+
const USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
21
|
+
const USDC_EIP712 = { name: "USD Coin", version: "2" };
|
|
21
22
|
|
|
22
23
|
let _payTo = null;
|
|
23
|
-
let _network = "
|
|
24
|
-
let
|
|
25
|
-
let _x402Server = null; // x402ResourceServer instance
|
|
24
|
+
let _network = "base"; // v1 format, not CAIP-2
|
|
25
|
+
let _facilitatorClient = null;
|
|
26
26
|
|
|
27
27
|
async function x402Plugin(fastify, opts) {
|
|
28
|
-
_network = opts.network || _network;
|
|
29
|
-
_facilitatorUrl = opts.facilitator || _facilitatorUrl;
|
|
30
28
|
_payTo = opts.payTo || null;
|
|
31
29
|
|
|
32
30
|
// Load from settings.json
|
|
33
31
|
try {
|
|
34
32
|
const settingsPath = path.join(process.cwd(), "server", "config", "settings.json");
|
|
35
33
|
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
36
|
-
if (settings.x402?.network) _network = settings.x402.network;
|
|
37
|
-
if (settings.x402?.facilitator) _facilitatorUrl = settings.x402.facilitator;
|
|
38
34
|
if (settings.x402?.payTo) _payTo = settings.x402.payTo;
|
|
35
|
+
if (settings.x402?.network) _network = settings.x402.network;
|
|
39
36
|
} catch {}
|
|
40
37
|
|
|
41
38
|
// Auto-detect payTo from vault
|
|
@@ -46,44 +43,18 @@ async function x402Plugin(fastify, opts) {
|
|
|
46
43
|
} catch {}
|
|
47
44
|
}
|
|
48
45
|
|
|
49
|
-
|
|
50
|
-
console.log(` 💰 x402: payments to ${_payTo.slice(0, 6)}...${_payTo.slice(-4)} on ${_network}`);
|
|
51
|
-
console.log(` 💰 x402: facilitator ${_facilitatorUrl}`);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Initialize x402 SDK server with CDP facilitator
|
|
46
|
+
// Initialize facilitator from @coinbase/x402 (ESM packages, need dynamic import)
|
|
55
47
|
try {
|
|
56
|
-
const {
|
|
57
|
-
const {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// Add CDP auth if keys are available
|
|
62
|
-
if (process.env.CDP_API_KEY_ID && process.env.CDP_API_KEY_SECRET) {
|
|
63
|
-
facilitatorConfig.createAuthHeaders = async () => {
|
|
64
|
-
const { getAuthHeaders } = await import("@coinbase/cdp-sdk/auth");
|
|
65
|
-
const endpoints = ["verify", "settle", "supported"];
|
|
66
|
-
const result = {};
|
|
67
|
-
for (const ep of endpoints) {
|
|
68
|
-
const headers = await getAuthHeaders({
|
|
69
|
-
apiKeyId: process.env.CDP_API_KEY_ID,
|
|
70
|
-
apiKeySecret: process.env.CDP_API_KEY_SECRET,
|
|
71
|
-
requestMethod: "POST",
|
|
72
|
-
requestHost: `https://${new URL(_facilitatorUrl).host}`,
|
|
73
|
-
requestPath: `${new URL(_facilitatorUrl).pathname}/${ep}`,
|
|
74
|
-
});
|
|
75
|
-
result[ep] = headers;
|
|
76
|
-
}
|
|
77
|
-
return result;
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const client = new HTTPFacilitatorClient(facilitatorConfig);
|
|
82
|
-
_x402Server = new x402ResourceServer(client);
|
|
83
|
-
_x402Server.register("eip155:*", new ExactEvmScheme());
|
|
84
|
-
console.log(` 💰 x402: SDK initialized (ExactEvmScheme)`);
|
|
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)`);
|
|
85
52
|
} catch (err) {
|
|
86
|
-
console.log(` ⚠️ x402:
|
|
53
|
+
console.log(` ⚠️ x402: facilitator init failed (${err.message}) — install @coinbase/x402 x402`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (_payTo) {
|
|
57
|
+
console.log(` 💰 x402: payments to ${_payTo.slice(0, 6)}...${_payTo.slice(-4)} on ${_network}`);
|
|
87
58
|
}
|
|
88
59
|
|
|
89
60
|
// ── Route-level x402 hook ──
|
|
@@ -94,17 +65,18 @@ async function x402Plugin(fastify, opts) {
|
|
|
94
65
|
reply.code(500).send({ error: "x402 not configured — no wallet address" });
|
|
95
66
|
return;
|
|
96
67
|
}
|
|
68
|
+
if (!_facilitatorClient) {
|
|
69
|
+
reply.code(500).send({ error: "x402 facilitator not loaded — install @coinbase/x402 x402" });
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
97
72
|
|
|
98
|
-
// Determine
|
|
73
|
+
// Determine dollar amount
|
|
99
74
|
let dollarAmount;
|
|
100
75
|
if (routeConfig.variable) {
|
|
101
76
|
const field = routeConfig.priceField || "dollars";
|
|
102
77
|
const raw = request.body?.[field] || request.query?.[field];
|
|
103
78
|
if (!raw) {
|
|
104
|
-
reply.code(400).send({
|
|
105
|
-
error: `${field} required`,
|
|
106
|
-
x402: { variable: true, min: routeConfig.min || "$1", max: routeConfig.max || "$10000" },
|
|
107
|
-
});
|
|
79
|
+
reply.code(400).send({ error: `${field} required`, x402: { variable: true, min: routeConfig.min || "$1", max: routeConfig.max || "$10000" } });
|
|
108
80
|
return;
|
|
109
81
|
}
|
|
110
82
|
dollarAmount = parseFloat(String(raw).replace("$", ""));
|
|
@@ -119,113 +91,128 @@ async function x402Plugin(fastify, opts) {
|
|
|
119
91
|
if (!dollarAmount) { reply.code(500).send({ error: "x402 route missing price config" }); return; }
|
|
120
92
|
}
|
|
121
93
|
|
|
122
|
-
const
|
|
94
|
+
const usdcAtomicAmount = String(Math.round(dollarAmount * 1e6));
|
|
123
95
|
const price = "$" + dollarAmount.toFixed(2);
|
|
124
|
-
const paymentHeader = request.headers["payment-signature"] || request.headers["x-payment"];
|
|
125
96
|
|
|
126
|
-
//
|
|
97
|
+
// Check for payment header (X-PAYMENT for v1 compat, payment-signature for v2)
|
|
98
|
+
const paymentHeader = request.headers["x-payment"] || request.headers["payment-signature"];
|
|
99
|
+
|
|
100
|
+
// Build payment requirements (v1 format matching @coinbase/x402)
|
|
101
|
+
const { getAddress } = await import("viem");
|
|
102
|
+
const paymentRequirements = {
|
|
103
|
+
scheme: "exact",
|
|
104
|
+
network: _network,
|
|
105
|
+
maxAmountRequired: usdcAtomicAmount,
|
|
106
|
+
resource: `${request.method} ${request.url}`,
|
|
107
|
+
description: routeConfig.description || `Payment for ${request.method} ${request.url}`,
|
|
108
|
+
mimeType: "application/json",
|
|
109
|
+
payTo: getAddress(_payTo),
|
|
110
|
+
maxTimeoutSeconds: 300,
|
|
111
|
+
asset: getAddress(USDC_ADDRESS),
|
|
112
|
+
extra: USDC_EIP712,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// No payment — return 402
|
|
127
116
|
if (!paymentHeader) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
network: _network,
|
|
131
|
-
amount: usdcAmount,
|
|
132
|
-
asset: USDC_BASE,
|
|
133
|
-
payTo: _payTo,
|
|
134
|
-
maxTimeoutSeconds: 300,
|
|
135
|
-
extra: { name: "USD Coin", version: "2" },
|
|
136
|
-
};
|
|
137
|
-
const paymentRequired = {
|
|
138
|
-
x402Version: 2,
|
|
117
|
+
reply.code(402).send({
|
|
118
|
+
x402Version: 1,
|
|
139
119
|
error: "Payment Required",
|
|
140
|
-
resource: {
|
|
141
|
-
url: `${request.method} ${request.url}`,
|
|
142
|
-
description: routeConfig.description || `Payment for ${request.method} ${request.url}`,
|
|
143
|
-
mimeType: "application/json",
|
|
144
|
-
},
|
|
145
120
|
accepts: [paymentRequirements],
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
.
|
|
151
|
-
|
|
152
|
-
.send({
|
|
153
|
-
error: "Payment Required",
|
|
154
|
-
price,
|
|
155
|
-
network: _network,
|
|
156
|
-
payTo: _payTo,
|
|
157
|
-
protocol: "x402",
|
|
158
|
-
x402Version: 2,
|
|
159
|
-
...(routeConfig.variable ? { variable: true, min: routeConfig.min, max: routeConfig.max, priceField: routeConfig.priceField || "dollars" } : {}),
|
|
160
|
-
});
|
|
121
|
+
price,
|
|
122
|
+
network: _network,
|
|
123
|
+
payTo: _payTo,
|
|
124
|
+
protocol: "x402",
|
|
125
|
+
...(routeConfig.variable ? { variable: true, min: routeConfig.min, max: routeConfig.max, priceField: routeConfig.priceField || "dollars" } : {}),
|
|
126
|
+
});
|
|
161
127
|
return;
|
|
162
128
|
}
|
|
163
129
|
|
|
164
|
-
//
|
|
165
|
-
let
|
|
130
|
+
// Decode payment
|
|
131
|
+
let decodedPayment;
|
|
166
132
|
try {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
133
|
+
const { exact } = await import("x402/schemes");
|
|
134
|
+
const libraryDecoded = exact.evm.decodePayment(paymentHeader);
|
|
135
|
+
|
|
136
|
+
// Parse raw payload for metadata
|
|
137
|
+
const raw = JSON.parse(Buffer.from(paymentHeader, "base64").toString("utf-8"));
|
|
138
|
+
|
|
139
|
+
decodedPayment = {
|
|
140
|
+
x402Version: raw.x402Version || 1,
|
|
141
|
+
scheme: raw.scheme || "exact",
|
|
142
|
+
network: raw.network || _network,
|
|
143
|
+
payload: libraryDecoded.payload,
|
|
144
|
+
};
|
|
145
|
+
} catch (err) {
|
|
146
|
+
reply.code(402).send({ error: "Invalid payment format: " + err.message, accepts: [paymentRequirements] });
|
|
170
147
|
return;
|
|
171
148
|
}
|
|
172
149
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
scheme: "exact",
|
|
177
|
-
network: _network,
|
|
178
|
-
amount: usdcAmount,
|
|
179
|
-
asset: USDC_BASE,
|
|
180
|
-
payTo: _payTo,
|
|
181
|
-
maxTimeoutSeconds: 300,
|
|
182
|
-
extra: { name: "USD Coin", version: "2" },
|
|
183
|
-
};
|
|
150
|
+
// For variable pricing, use user's actual payment value as maxAmountRequired
|
|
151
|
+
const userValue = decodedPayment.payload.authorization.value;
|
|
152
|
+
const actualRequirements = { ...paymentRequirements, maxAmountRequired: userValue };
|
|
184
153
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const verifyResult = await _x402Server.verifyPayment(paymentPayload, paymentRequirements);
|
|
192
|
-
if (!verifyResult.isValid) {
|
|
193
|
-
reply.code(402).send({ error: "Payment verification failed", reason: verifyResult.invalidReason, price, payTo: _payTo });
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Settle via facilitator (executes on-chain transfer)
|
|
198
|
-
const settleResult = await _x402Server.settlePayment(paymentPayload, paymentRequirements);
|
|
199
|
-
if (!settleResult.success) {
|
|
200
|
-
reply.code(402).send({ error: "Payment settlement failed", reason: settleResult.errorReason, price, payTo: _payTo });
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const txHash = settleResult.transaction || null;
|
|
205
|
-
const payer = settleResult.payer || verifyResult.payer || paymentPayload.payload?.authorization?.from || null;
|
|
206
|
-
|
|
207
|
-
request.x402 = { paid: true, amount: price, receipt: settleResult, txHash, from: payer };
|
|
208
|
-
_logPayment({ route: request.url, method: request.method, amount: price, from: payer, txHash, verified: true, settled: "facilitator", timestamp: Date.now() });
|
|
209
|
-
return;
|
|
210
|
-
} catch (err) {
|
|
211
|
-
console.log(` ⚠️ x402 SDK error: ${err.message}`);
|
|
212
|
-
reply.code(402).send({ error: "Payment processing failed", reason: err.message, price, payTo: _payTo });
|
|
154
|
+
// Verify via facilitator
|
|
155
|
+
try {
|
|
156
|
+
const verifyResult = await _facilitatorClient.verify(decodedPayment, actualRequirements);
|
|
157
|
+
if (!verifyResult.isValid) {
|
|
158
|
+
console.log(` ⚠️ x402 verify failed: ${verifyResult.invalidReason}`);
|
|
159
|
+
reply.code(402).send({ error: verifyResult.invalidReason || "Payment verification failed", accepts: [paymentRequirements], payer: verifyResult.payer });
|
|
213
160
|
return;
|
|
214
161
|
}
|
|
162
|
+
} catch (err) {
|
|
163
|
+
console.log(` ⚠️ x402 verify error: ${err.message}`);
|
|
164
|
+
reply.code(402).send({ error: "Payment verification failed: " + err.message, accepts: [paymentRequirements] });
|
|
165
|
+
return;
|
|
215
166
|
}
|
|
216
167
|
|
|
217
|
-
//
|
|
218
|
-
|
|
168
|
+
// Payment verified — attach info to request
|
|
169
|
+
const payer = decodedPayment.payload.authorization.from;
|
|
170
|
+
request.x402 = { paid: true, amount: price, from: payer, value: userValue, verified: true };
|
|
171
|
+
|
|
172
|
+
// Log payment
|
|
173
|
+
_logPayment({ route: request.url, method: request.method, amount: price, from: payer, verified: true, timestamp: Date.now() });
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Settlement hook — settle AFTER successful handler response
|
|
177
|
+
fastify.addHook("onSend", async (request, reply, payload) => {
|
|
178
|
+
if (!request.x402?.paid || !_facilitatorClient) return payload;
|
|
179
|
+
if (reply.statusCode >= 400) return payload;
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
const paymentHeader = request.headers["x-payment"] || request.headers["payment-signature"];
|
|
183
|
+
const { exact } = require("x402/schemes");
|
|
184
|
+
const raw = JSON.parse(Buffer.from(paymentHeader, "base64").toString("utf-8"));
|
|
185
|
+
const libraryDecoded = exact.evm.decodePayment(paymentHeader);
|
|
186
|
+
const decodedPayment = { x402Version: raw.x402Version || 1, scheme: raw.scheme || "exact", network: raw.network || _network, payload: libraryDecoded.payload };
|
|
187
|
+
|
|
188
|
+
const userValue = decodedPayment.payload.authorization.value;
|
|
189
|
+
const { getAddress } = await import("viem");
|
|
190
|
+
const requirements = {
|
|
191
|
+
scheme: "exact", network: _network, maxAmountRequired: userValue,
|
|
192
|
+
resource: `${request.method} ${request.url}`, description: "", mimeType: "application/json",
|
|
193
|
+
payTo: getAddress(_payTo), maxTimeoutSeconds: 300, asset: getAddress(USDC_ADDRESS), extra: USDC_EIP712,
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const settleResult = await _facilitatorClient.settle(decodedPayment, requirements);
|
|
197
|
+
if (settleResult.success) {
|
|
198
|
+
request.x402.txHash = settleResult.transaction;
|
|
199
|
+
request.x402.settled = true;
|
|
200
|
+
console.log(` 💰 x402 settled: ${settleResult.transaction || "confirmed"} (${request.x402.amount} from ${request.x402.from?.slice(0, 10)})`);
|
|
201
|
+
// Update payment log
|
|
202
|
+
_logPayment({ route: request.url, method: request.method, amount: request.x402.amount, from: request.x402.from, txHash: settleResult.transaction, verified: true, settled: true, timestamp: Date.now() });
|
|
203
|
+
} else {
|
|
204
|
+
console.log(` ⚠️ x402 settle failed: ${settleResult.errorReason || "unknown"}`);
|
|
205
|
+
}
|
|
206
|
+
} catch (err) {
|
|
207
|
+
console.log(` ⚠️ x402 settle error: ${err.message}`);
|
|
208
|
+
}
|
|
209
|
+
return payload;
|
|
219
210
|
});
|
|
220
211
|
|
|
221
|
-
//
|
|
212
|
+
// Public info endpoint
|
|
222
213
|
fastify.get("/x402/info", async () => ({
|
|
223
|
-
payTo: _payTo,
|
|
224
|
-
|
|
225
|
-
facilitator: _facilitatorUrl,
|
|
226
|
-
protocol: "x402",
|
|
227
|
-
x402Version: 2,
|
|
228
|
-
sdkLoaded: !!_x402Server,
|
|
214
|
+
payTo: _payTo, network: _network, protocol: "x402", x402Version: 1,
|
|
215
|
+
facilitatorLoaded: !!_facilitatorClient,
|
|
229
216
|
docs: "https://docs.cdp.coinbase.com/x402/welcome",
|
|
230
217
|
}));
|
|
231
218
|
}
|