wolverine-ai 5.2.2 → 5.2.4
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 +1 -1
- package/src/middleware/x402-fastify.js +128 -229
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wolverine-ai",
|
|
3
|
-
"version": "5.2.
|
|
3
|
+
"version": "5.2.4",
|
|
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,49 +4,21 @@ const path = require("path");
|
|
|
4
4
|
/**
|
|
5
5
|
* x402 Fastify Plugin — add crypto payments to any route with one flag.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* handles the 402 → payment → verification → settlement flow.
|
|
10
|
-
*
|
|
11
|
-
* Settlement is handled by the x402 facilitator (Coinbase CDP),
|
|
12
|
-
* which verifies the payment signature AND executes the on-chain
|
|
13
|
-
* USDC transfer. Compatible with x402 Bazaar for API discovery.
|
|
7
|
+
* Implements the x402 v2 protocol with CDP facilitator for on-chain settlement.
|
|
8
|
+
* Compatible with @x402/fetch, @x402/evm client SDKs and x402 Bazaar.
|
|
14
9
|
*
|
|
15
10
|
* Two modes:
|
|
16
11
|
* Fixed price: { x402: { price: "$0.01" } }
|
|
17
12
|
* Variable price: { x402: { variable: true, min: "$1", max: "$10000" } }
|
|
18
|
-
*
|
|
19
|
-
* Example — fixed price route:
|
|
20
|
-
* fastify.get("/premium-data", { config: { x402: { price: "$0.10" } } }, handler)
|
|
21
|
-
*
|
|
22
|
-
* Example — variable price (credit purchase):
|
|
23
|
-
* fastify.post("/buy-credits", {
|
|
24
|
-
* config: {
|
|
25
|
-
* x402: {
|
|
26
|
-
* variable: true,
|
|
27
|
-
* min: "$1",
|
|
28
|
-
* max: "$10000",
|
|
29
|
-
* priceField: "dollars", // reads amount from request body
|
|
30
|
-
* }
|
|
31
|
-
* }
|
|
32
|
-
* }, async (req, reply) => {
|
|
33
|
-
* // req.x402.paid === true, req.x402.amount === "$5.00"
|
|
34
|
-
* addCredits(req.x402.amount);
|
|
35
|
-
* return { credits: newBalance };
|
|
36
|
-
* })
|
|
37
|
-
*
|
|
38
|
-
* The payment receipt is in req.x402 after verification:
|
|
39
|
-
* { paid: true, amount: "$5.00", receipt: {...}, txHash: "0x..." }
|
|
40
13
|
*/
|
|
41
14
|
|
|
15
|
+
const USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
16
|
+
|
|
42
17
|
let _payTo = null;
|
|
43
18
|
let _network = "eip155:8453";
|
|
44
|
-
|
|
45
|
-
let _facilitatorUrl = "https://api.cdp.coinbase.com/platform/v2/x402";
|
|
46
|
-
// Testnet facilitator: "https://x402.org/facilitator"
|
|
19
|
+
let _facilitatorUrl = "https://x402.org/facilitator";
|
|
47
20
|
|
|
48
21
|
async function x402Plugin(fastify, opts) {
|
|
49
|
-
// Config
|
|
50
22
|
_network = opts.network || _network;
|
|
51
23
|
_facilitatorUrl = opts.facilitator || _facilitatorUrl;
|
|
52
24
|
_payTo = opts.payTo || null;
|
|
@@ -73,7 +45,7 @@ async function x402Plugin(fastify, opts) {
|
|
|
73
45
|
const isTestnet = _network.includes("84532") || _network.includes("11155");
|
|
74
46
|
_facilitatorUrl = isTestnet
|
|
75
47
|
? "https://x402.org/facilitator"
|
|
76
|
-
: "https://
|
|
48
|
+
: "https://x402.org/facilitator"; // CDP production requires auth — use x402.org for both
|
|
77
49
|
}
|
|
78
50
|
|
|
79
51
|
if (_payTo) {
|
|
@@ -81,20 +53,18 @@ async function x402Plugin(fastify, opts) {
|
|
|
81
53
|
console.log(` 💰 x402: facilitator ${_facilitatorUrl}`);
|
|
82
54
|
}
|
|
83
55
|
|
|
84
|
-
// ── Route-level x402 hook
|
|
56
|
+
// ── Route-level x402 hook ──
|
|
85
57
|
fastify.addHook("preHandler", async (request, reply) => {
|
|
86
|
-
// Check if this route has x402 config (Fastify v4: context.config, v5: routeOptions.config)
|
|
87
58
|
const routeConfig = request.routeOptions?.config?.x402 || request.routeConfig?.x402 || request.context?.config?.x402;
|
|
88
|
-
if (!routeConfig) return;
|
|
59
|
+
if (!routeConfig) return;
|
|
89
60
|
if (!_payTo) {
|
|
90
61
|
reply.code(500).send({ error: "x402 not configured — no wallet address" });
|
|
91
62
|
return;
|
|
92
63
|
}
|
|
93
64
|
|
|
94
65
|
// Determine the price
|
|
95
|
-
let
|
|
66
|
+
let dollarAmount;
|
|
96
67
|
if (routeConfig.variable) {
|
|
97
|
-
// Variable pricing — read amount from request body or query
|
|
98
68
|
const field = routeConfig.priceField || "dollars";
|
|
99
69
|
const raw = request.body?.[field] || request.query?.[field];
|
|
100
70
|
if (!raw) {
|
|
@@ -104,76 +74,124 @@ async function x402Plugin(fastify, opts) {
|
|
|
104
74
|
});
|
|
105
75
|
return;
|
|
106
76
|
}
|
|
107
|
-
|
|
77
|
+
dollarAmount = parseFloat(String(raw).replace("$", ""));
|
|
108
78
|
const min = parseFloat((routeConfig.min || "$1").replace("$", ""));
|
|
109
79
|
const max = parseFloat((routeConfig.max || "$10000").replace("$", ""));
|
|
110
|
-
if (isNaN(
|
|
80
|
+
if (isNaN(dollarAmount) || dollarAmount < min || dollarAmount > max) {
|
|
111
81
|
reply.code(400).send({ error: `Amount must be $${min}-$${max}` });
|
|
112
82
|
return;
|
|
113
83
|
}
|
|
114
|
-
price = "$" + amount.toFixed(2);
|
|
115
84
|
} else {
|
|
116
|
-
|
|
117
|
-
if (!
|
|
85
|
+
dollarAmount = parseFloat((routeConfig.price || "0").replace("$", ""));
|
|
86
|
+
if (!dollarAmount) { reply.code(500).send({ error: "x402 route missing price config" }); return; }
|
|
118
87
|
}
|
|
119
88
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
89
|
+
// USDC amount in atomic units (6 decimals)
|
|
90
|
+
const usdcAmount = String(Math.round(dollarAmount * 1e6));
|
|
91
|
+
const price = "$" + dollarAmount.toFixed(2);
|
|
92
|
+
|
|
93
|
+
const paymentHeader = request.headers["payment-signature"] || request.headers["x-payment"];
|
|
94
|
+
|
|
95
|
+
// No payment — return 402 with x402 v2 payment requirements
|
|
96
|
+
if (!paymentHeader) {
|
|
97
|
+
const paymentRequirements = {
|
|
98
|
+
scheme: "exact",
|
|
99
|
+
network: _network,
|
|
100
|
+
amount: usdcAmount,
|
|
101
|
+
asset: USDC_BASE,
|
|
102
|
+
payTo: _payTo,
|
|
103
|
+
maxTimeoutSeconds: 300,
|
|
104
|
+
extra: { name: "USD Coin", version: "2" }, // EIP-712 domain params for USDC
|
|
105
|
+
};
|
|
106
|
+
const paymentRequired = {
|
|
107
|
+
x402Version: 2,
|
|
108
|
+
error: "Payment Required",
|
|
109
|
+
resource: {
|
|
110
|
+
url: `${request.method} ${request.url}`,
|
|
111
|
+
description: routeConfig.description || `Payment for ${request.method} ${request.url}`,
|
|
112
|
+
mimeType: "application/json",
|
|
113
|
+
},
|
|
114
|
+
accepts: [paymentRequirements],
|
|
133
115
|
};
|
|
116
|
+
|
|
117
|
+
const encoded = Buffer.from(JSON.stringify(paymentRequired)).toString("base64");
|
|
134
118
|
reply
|
|
135
119
|
.code(402)
|
|
136
|
-
.header("Payment-Required",
|
|
120
|
+
.header("Payment-Required", encoded)
|
|
137
121
|
.send({
|
|
138
122
|
error: "Payment Required",
|
|
139
123
|
price,
|
|
140
124
|
network: _network,
|
|
141
125
|
payTo: _payTo,
|
|
142
126
|
protocol: "x402",
|
|
127
|
+
x402Version: 2,
|
|
143
128
|
...(routeConfig.variable ? { variable: true, min: routeConfig.min, max: routeConfig.max, priceField: routeConfig.priceField || "dollars" } : {}),
|
|
144
129
|
});
|
|
145
130
|
return;
|
|
146
131
|
}
|
|
147
132
|
|
|
148
|
-
// Payment present — verify
|
|
149
|
-
|
|
150
|
-
const routeAccepts = [{
|
|
133
|
+
// Payment present — decode, verify via facilitator, then settle
|
|
134
|
+
const paymentRequirements = {
|
|
151
135
|
scheme: "exact",
|
|
152
|
-
price,
|
|
153
136
|
network: _network,
|
|
137
|
+
amount: usdcAmount,
|
|
138
|
+
asset: USDC_BASE,
|
|
154
139
|
payTo: _payTo,
|
|
155
|
-
|
|
140
|
+
maxTimeoutSeconds: 300,
|
|
141
|
+
extra: { name: "USD Coin", version: "2" },
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// Decode the payment payload
|
|
145
|
+
let paymentPayload;
|
|
146
|
+
try {
|
|
147
|
+
const decoded = Buffer.from(paymentHeader, "base64").toString();
|
|
148
|
+
paymentPayload = JSON.parse(decoded);
|
|
149
|
+
} catch {
|
|
150
|
+
reply.code(400).send({ error: "Invalid payment encoding" });
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Ensure x402Version is set
|
|
155
|
+
if (!paymentPayload.x402Version) {
|
|
156
|
+
paymentPayload.x402Version = 2;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Ensure the accepted requirements are included (x402 v2 spec)
|
|
160
|
+
if (!paymentPayload.accepted) {
|
|
161
|
+
paymentPayload.accepted = paymentRequirements;
|
|
162
|
+
}
|
|
156
163
|
|
|
157
|
-
// Step 1:
|
|
158
|
-
const
|
|
159
|
-
if (
|
|
160
|
-
reply.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
+
// Step 1: Verify via facilitator
|
|
165
|
+
const verifyResult = await _facilitatorCall("/verify", paymentPayload, paymentRequirements);
|
|
166
|
+
if (!verifyResult.ok) {
|
|
167
|
+
reply.code(402).send({
|
|
168
|
+
error: "Payment verification failed",
|
|
169
|
+
reason: verifyResult.reason,
|
|
170
|
+
price,
|
|
171
|
+
payTo: _payTo,
|
|
172
|
+
});
|
|
173
|
+
return;
|
|
164
174
|
}
|
|
165
175
|
|
|
166
|
-
// Step 2:
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
176
|
+
// Step 2: Settle via facilitator (executes on-chain transfer)
|
|
177
|
+
const settleResult = await _facilitatorCall("/settle", paymentPayload, paymentRequirements);
|
|
178
|
+
if (!settleResult.ok) {
|
|
179
|
+
reply.code(402).send({
|
|
180
|
+
error: "Payment settlement failed",
|
|
181
|
+
reason: settleResult.reason,
|
|
182
|
+
price,
|
|
183
|
+
payTo: _payTo,
|
|
184
|
+
});
|
|
173
185
|
return;
|
|
174
186
|
}
|
|
175
187
|
|
|
176
|
-
|
|
188
|
+
// Payment verified AND settled on-chain
|
|
189
|
+
const txHash = settleResult.data?.transaction || settleResult.data?.txHash || null;
|
|
190
|
+
const payer = settleResult.data?.payer || verifyResult.data?.payer || paymentPayload.payload?.authorization?.from || null;
|
|
191
|
+
|
|
192
|
+
reply.header("Payment-Response", JSON.stringify(settleResult.data || {}));
|
|
193
|
+
request.x402 = { paid: true, amount: price, receipt: settleResult.data, txHash, from: payer };
|
|
194
|
+
_logPayment({ route: request.url, method: request.method, amount: price, from: payer, txHash, verified: true, settled: "facilitator", timestamp: Date.now() });
|
|
177
195
|
});
|
|
178
196
|
|
|
179
197
|
// ── Public pricing endpoint ──
|
|
@@ -182,28 +200,28 @@ async function x402Plugin(fastify, opts) {
|
|
|
182
200
|
network: _network,
|
|
183
201
|
facilitator: _facilitatorUrl,
|
|
184
202
|
protocol: "x402",
|
|
203
|
+
x402Version: 2,
|
|
185
204
|
docs: "https://docs.cdp.coinbase.com/x402/welcome",
|
|
186
205
|
}));
|
|
187
206
|
}
|
|
188
207
|
|
|
189
208
|
/**
|
|
190
|
-
*
|
|
191
|
-
* The facilitator:
|
|
192
|
-
* 1. Verifies the EIP-3009 signature
|
|
193
|
-
* 2. Calls transferWithAuthorization() on the USDC contract
|
|
194
|
-
* 3. Returns the transaction hash
|
|
209
|
+
* Call the x402 facilitator — matches the exact format from @x402/core HTTPFacilitatorClient.
|
|
195
210
|
*
|
|
196
|
-
*
|
|
211
|
+
* POST {facilitatorUrl}/verify or /settle
|
|
212
|
+
* Body: { x402Version, paymentPayload, paymentRequirements }
|
|
197
213
|
*/
|
|
198
|
-
async function
|
|
214
|
+
async function _facilitatorCall(endpoint, paymentPayload, paymentRequirements) {
|
|
199
215
|
try {
|
|
200
216
|
const https = require("https");
|
|
201
217
|
const http = require("http");
|
|
202
|
-
const url = new (require("url").URL)(_facilitatorUrl +
|
|
218
|
+
const url = new (require("url").URL)(_facilitatorUrl + endpoint);
|
|
203
219
|
const body = JSON.stringify({
|
|
204
|
-
|
|
205
|
-
|
|
220
|
+
x402Version: paymentPayload.x402Version || 2,
|
|
221
|
+
paymentPayload,
|
|
222
|
+
paymentRequirements,
|
|
206
223
|
});
|
|
224
|
+
|
|
207
225
|
return new Promise((resolve) => {
|
|
208
226
|
const client = url.protocol === "https:" ? https : http;
|
|
209
227
|
const req = client.request({
|
|
@@ -212,161 +230,43 @@ async function _settleViaFacilitator(paymentSig, routeAccepts) {
|
|
|
212
230
|
path: url.pathname,
|
|
213
231
|
method: "POST",
|
|
214
232
|
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) },
|
|
215
|
-
timeout: 30000,
|
|
233
|
+
timeout: 30000,
|
|
216
234
|
}, (res) => {
|
|
217
235
|
let data = "";
|
|
218
236
|
res.on("data", (c) => data += c);
|
|
219
237
|
res.on("end", () => {
|
|
220
238
|
try {
|
|
221
|
-
const
|
|
222
|
-
if (
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
239
|
+
const parsed = JSON.parse(data);
|
|
240
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
241
|
+
// Verify: check isValid. Settle: check success.
|
|
242
|
+
if (endpoint === "/verify" && parsed.isValid === false) {
|
|
243
|
+
resolve({ ok: false, reason: parsed.invalidReason || "verification_rejected", data: parsed });
|
|
244
|
+
} else if (endpoint === "/settle" && parsed.success === false) {
|
|
245
|
+
resolve({ ok: false, reason: parsed.errorReason || "settlement_rejected", data: parsed });
|
|
246
|
+
} else {
|
|
247
|
+
resolve({ ok: true, data: parsed });
|
|
248
|
+
}
|
|
229
249
|
} else {
|
|
230
|
-
|
|
250
|
+
// Error response
|
|
251
|
+
const reason = parsed.invalidReason || parsed.errorReason || parsed.error || `facilitator_${res.statusCode}`;
|
|
252
|
+
console.log(` ⚠️ x402 facilitator ${endpoint} ${res.statusCode}: ${reason}`);
|
|
253
|
+
resolve({ ok: false, reason, data: parsed });
|
|
231
254
|
}
|
|
232
255
|
} catch {
|
|
233
|
-
resolve({
|
|
256
|
+
resolve({ ok: false, reason: `facilitator_parse_error_${res.statusCode}` });
|
|
234
257
|
}
|
|
235
258
|
});
|
|
236
259
|
});
|
|
237
|
-
req.on("error", () =>
|
|
238
|
-
|
|
260
|
+
req.on("error", (err) => {
|
|
261
|
+
console.log(` ⚠️ x402 facilitator ${endpoint} error: ${err.message}`);
|
|
262
|
+
resolve({ ok: false, reason: "facilitator_unavailable: " + err.message });
|
|
263
|
+
});
|
|
264
|
+
req.on("timeout", () => { req.destroy(); resolve({ ok: false, reason: "facilitator_timeout" }); });
|
|
239
265
|
req.write(body);
|
|
240
266
|
req.end();
|
|
241
267
|
});
|
|
242
|
-
} catch {
|
|
243
|
-
return { valid: false, reason: "facilitator_unavailable" };
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Self-settlement fallback — verify the signature AND execute the
|
|
249
|
-
* on-chain transferWithAuthorization() using the server's vault wallet.
|
|
250
|
-
*
|
|
251
|
-
* Only used when the facilitator is unavailable. Requires ethers + RPC.
|
|
252
|
-
* The server pays gas to execute the transfer.
|
|
253
|
-
*/
|
|
254
|
-
async function _selfSettle(paymentSig, price) {
|
|
255
|
-
let payload;
|
|
256
|
-
try {
|
|
257
|
-
payload = JSON.parse(Buffer.from(paymentSig, "base64").toString());
|
|
258
|
-
} catch {
|
|
259
|
-
return { valid: false, reason: "invalid_payment_format" };
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (!payload.payload?.authorization || !payload.payload?.signature) {
|
|
263
|
-
return { valid: false, reason: "missing_authorization" };
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const auth = payload.payload.authorization;
|
|
267
|
-
const sig = payload.payload.signature;
|
|
268
|
-
|
|
269
|
-
// Verify amount
|
|
270
|
-
const expectedUsdc = Math.round(parseFloat(price.replace("$", "")) * 1e6);
|
|
271
|
-
// Value comes as decimal string (e.g. "1000000" for $1) or hex (legacy "0xf4240")
|
|
272
|
-
const valStr = String(auth.value);
|
|
273
|
-
const actualUsdc = valStr.startsWith("0x") ? parseInt(valStr, 16) : parseInt(valStr, 10) || 0;
|
|
274
|
-
if (actualUsdc < expectedUsdc * 0.99) {
|
|
275
|
-
return { valid: false, reason: "amount_mismatch" };
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Verify payTo matches
|
|
279
|
-
if (auth.to?.toLowerCase() !== _payTo?.toLowerCase()) {
|
|
280
|
-
return { valid: false, reason: "wrong_recipient" };
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Verify not expired
|
|
284
|
-
if (auth.validBefore && auth.validBefore < Math.floor(Date.now() / 1000)) {
|
|
285
|
-
return { valid: false, reason: "payment_expired" };
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
try {
|
|
289
|
-
const { ethers } = require("ethers");
|
|
290
|
-
const fromAddr = ethers.getAddress(auth.from.toLowerCase());
|
|
291
|
-
const toAddr = ethers.getAddress(auth.to.toLowerCase());
|
|
292
|
-
|
|
293
|
-
// Step 1: Verify the signature
|
|
294
|
-
const domain = {
|
|
295
|
-
name: "USD Coin",
|
|
296
|
-
version: "2",
|
|
297
|
-
chainId: 8453,
|
|
298
|
-
verifyingContract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
299
|
-
};
|
|
300
|
-
const types = {
|
|
301
|
-
TransferWithAuthorization: [
|
|
302
|
-
{ name: "from", type: "address" },
|
|
303
|
-
{ name: "to", type: "address" },
|
|
304
|
-
{ name: "value", type: "uint256" },
|
|
305
|
-
{ name: "validAfter", type: "uint256" },
|
|
306
|
-
{ name: "validBefore", type: "uint256" },
|
|
307
|
-
{ name: "nonce", type: "bytes32" },
|
|
308
|
-
],
|
|
309
|
-
};
|
|
310
|
-
const message = {
|
|
311
|
-
from: fromAddr,
|
|
312
|
-
to: toAddr,
|
|
313
|
-
value: auth.value,
|
|
314
|
-
validAfter: auth.validAfter,
|
|
315
|
-
validBefore: auth.validBefore,
|
|
316
|
-
nonce: auth.nonce,
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
const recoveredAddress = ethers.verifyTypedData(domain, types, message, sig);
|
|
320
|
-
if (recoveredAddress.toLowerCase() !== fromAddr.toLowerCase()) {
|
|
321
|
-
return { valid: false, reason: "signature_mismatch" };
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// Step 2: Execute transferWithAuthorization on-chain
|
|
325
|
-
// Use the vault wallet to submit the transaction (server pays gas)
|
|
326
|
-
const { decryptPrivateKey } = require("../vault/vault-manager");
|
|
327
|
-
let keyBuf = null;
|
|
328
|
-
try {
|
|
329
|
-
keyBuf = decryptPrivateKey();
|
|
330
|
-
const provider = new ethers.JsonRpcProvider("https://mainnet.base.org");
|
|
331
|
-
const wallet = new ethers.Wallet(keyBuf, provider);
|
|
332
|
-
|
|
333
|
-
const usdcContract = new ethers.Contract(
|
|
334
|
-
"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
335
|
-
[
|
|
336
|
-
"function transferWithAuthorization(address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s) external",
|
|
337
|
-
],
|
|
338
|
-
wallet
|
|
339
|
-
);
|
|
340
|
-
|
|
341
|
-
// Parse the signature into v, r, s
|
|
342
|
-
const sigBytes = ethers.getBytes(sig);
|
|
343
|
-
const r = ethers.hexlify(sigBytes.slice(0, 32));
|
|
344
|
-
const s = ethers.hexlify(sigBytes.slice(32, 64));
|
|
345
|
-
const v = sigBytes[64];
|
|
346
|
-
|
|
347
|
-
const tx = await usdcContract.transferWithAuthorization(
|
|
348
|
-
fromAddr, toAddr, auth.value,
|
|
349
|
-
auth.validAfter, auth.validBefore, auth.nonce,
|
|
350
|
-
v, r, s,
|
|
351
|
-
{ gasLimit: 100000 }
|
|
352
|
-
);
|
|
353
|
-
|
|
354
|
-
// Wait for confirmation (1 block)
|
|
355
|
-
const receipt = await tx.wait(1);
|
|
356
|
-
console.log(` 💰 x402 self-settled: ${receipt.hash} (${price} USDC)`);
|
|
357
|
-
|
|
358
|
-
return {
|
|
359
|
-
valid: true,
|
|
360
|
-
from: fromAddr,
|
|
361
|
-
receipt: { authorization: auth, settled: "self", blockNumber: receipt.blockNumber },
|
|
362
|
-
txHash: receipt.hash,
|
|
363
|
-
};
|
|
364
|
-
} finally {
|
|
365
|
-
if (keyBuf) keyBuf.fill(0);
|
|
366
|
-
}
|
|
367
268
|
} catch (err) {
|
|
368
|
-
|
|
369
|
-
return { valid: false, reason: "settlement_failed: " + err.message };
|
|
269
|
+
return { ok: false, reason: "facilitator_error: " + err.message };
|
|
370
270
|
}
|
|
371
271
|
}
|
|
372
272
|
|
|
@@ -376,7 +276,6 @@ function _logPayment(entry) {
|
|
|
376
276
|
let payments = [];
|
|
377
277
|
try { payments = JSON.parse(fs.readFileSync(logPath, "utf-8")); } catch {}
|
|
378
278
|
payments.push(entry);
|
|
379
|
-
// Keep last 1000 payments
|
|
380
279
|
if (payments.length > 1000) payments = payments.slice(-1000);
|
|
381
280
|
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
382
281
|
fs.writeFileSync(logPath, JSON.stringify(payments, null, 2));
|