wolverine-ai 5.2.1 → 5.2.3
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 +126 -231
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wolverine-ai",
|
|
3
|
-
"version": "5.2.
|
|
3
|
+
"version": "5.2.3",
|
|
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,78 +74,122 @@ 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
|
+
};
|
|
105
|
+
const paymentRequired = {
|
|
106
|
+
x402Version: 2,
|
|
107
|
+
error: "Payment Required",
|
|
108
|
+
resource: {
|
|
109
|
+
url: `${request.method} ${request.url}`,
|
|
110
|
+
description: routeConfig.description || `Payment for ${request.method} ${request.url}`,
|
|
111
|
+
mimeType: "application/json",
|
|
112
|
+
},
|
|
113
|
+
accepts: [paymentRequirements],
|
|
133
114
|
};
|
|
115
|
+
|
|
116
|
+
const encoded = Buffer.from(JSON.stringify(paymentRequired)).toString("base64");
|
|
134
117
|
reply
|
|
135
118
|
.code(402)
|
|
136
|
-
.header("Payment-Required",
|
|
119
|
+
.header("Payment-Required", encoded)
|
|
137
120
|
.send({
|
|
138
121
|
error: "Payment Required",
|
|
139
122
|
price,
|
|
140
123
|
network: _network,
|
|
141
124
|
payTo: _payTo,
|
|
142
125
|
protocol: "x402",
|
|
126
|
+
x402Version: 2,
|
|
143
127
|
...(routeConfig.variable ? { variable: true, min: routeConfig.min, max: routeConfig.max, priceField: routeConfig.priceField || "dollars" } : {}),
|
|
144
128
|
});
|
|
145
129
|
return;
|
|
146
130
|
}
|
|
147
131
|
|
|
148
|
-
// Payment present —
|
|
149
|
-
const
|
|
132
|
+
// Payment present — decode, verify via facilitator, then settle
|
|
133
|
+
const paymentRequirements = {
|
|
150
134
|
scheme: "exact",
|
|
151
|
-
price,
|
|
152
135
|
network: _network,
|
|
136
|
+
amount: usdcAmount,
|
|
137
|
+
asset: USDC_BASE,
|
|
153
138
|
payTo: _payTo,
|
|
154
|
-
|
|
139
|
+
maxTimeoutSeconds: 300,
|
|
140
|
+
};
|
|
155
141
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
142
|
+
// Decode the payment payload
|
|
143
|
+
let paymentPayload;
|
|
144
|
+
try {
|
|
145
|
+
const decoded = Buffer.from(paymentHeader, "base64").toString();
|
|
146
|
+
paymentPayload = JSON.parse(decoded);
|
|
147
|
+
} catch {
|
|
148
|
+
reply.code(400).send({ error: "Invalid payment encoding" });
|
|
149
|
+
return;
|
|
163
150
|
}
|
|
164
151
|
|
|
165
|
-
//
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
152
|
+
// Ensure x402Version is set
|
|
153
|
+
if (!paymentPayload.x402Version) {
|
|
154
|
+
paymentPayload.x402Version = 2;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Ensure the accepted requirements are included (x402 v2 spec)
|
|
158
|
+
if (!paymentPayload.accepted) {
|
|
159
|
+
paymentPayload.accepted = paymentRequirements;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Step 1: Verify via facilitator
|
|
163
|
+
const verifyResult = await _facilitatorCall("/verify", paymentPayload, paymentRequirements);
|
|
164
|
+
if (!verifyResult.ok) {
|
|
165
|
+
reply.code(402).send({
|
|
166
|
+
error: "Payment verification failed",
|
|
167
|
+
reason: verifyResult.reason,
|
|
168
|
+
price,
|
|
169
|
+
payTo: _payTo,
|
|
170
|
+
});
|
|
175
171
|
return;
|
|
176
172
|
}
|
|
177
173
|
|
|
178
|
-
|
|
174
|
+
// Step 2: Settle via facilitator (executes on-chain transfer)
|
|
175
|
+
const settleResult = await _facilitatorCall("/settle", paymentPayload, paymentRequirements);
|
|
176
|
+
if (!settleResult.ok) {
|
|
177
|
+
reply.code(402).send({
|
|
178
|
+
error: "Payment settlement failed",
|
|
179
|
+
reason: settleResult.reason,
|
|
180
|
+
price,
|
|
181
|
+
payTo: _payTo,
|
|
182
|
+
});
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Payment verified AND settled on-chain
|
|
187
|
+
const txHash = settleResult.data?.transaction || settleResult.data?.txHash || null;
|
|
188
|
+
const payer = settleResult.data?.payer || verifyResult.data?.payer || paymentPayload.payload?.authorization?.from || null;
|
|
189
|
+
|
|
190
|
+
reply.header("Payment-Response", JSON.stringify(settleResult.data || {}));
|
|
191
|
+
request.x402 = { paid: true, amount: price, receipt: settleResult.data, txHash, from: payer };
|
|
192
|
+
_logPayment({ route: request.url, method: request.method, amount: price, from: payer, txHash, verified: true, settled: "facilitator", timestamp: Date.now() });
|
|
179
193
|
});
|
|
180
194
|
|
|
181
195
|
// ── Public pricing endpoint ──
|
|
@@ -184,28 +198,28 @@ async function x402Plugin(fastify, opts) {
|
|
|
184
198
|
network: _network,
|
|
185
199
|
facilitator: _facilitatorUrl,
|
|
186
200
|
protocol: "x402",
|
|
201
|
+
x402Version: 2,
|
|
187
202
|
docs: "https://docs.cdp.coinbase.com/x402/welcome",
|
|
188
203
|
}));
|
|
189
204
|
}
|
|
190
205
|
|
|
191
206
|
/**
|
|
192
|
-
*
|
|
193
|
-
* The facilitator:
|
|
194
|
-
* 1. Verifies the EIP-3009 signature
|
|
195
|
-
* 2. Calls transferWithAuthorization() on the USDC contract
|
|
196
|
-
* 3. Returns the transaction hash
|
|
207
|
+
* Call the x402 facilitator — matches the exact format from @x402/core HTTPFacilitatorClient.
|
|
197
208
|
*
|
|
198
|
-
*
|
|
209
|
+
* POST {facilitatorUrl}/verify or /settle
|
|
210
|
+
* Body: { x402Version, paymentPayload, paymentRequirements }
|
|
199
211
|
*/
|
|
200
|
-
async function
|
|
212
|
+
async function _facilitatorCall(endpoint, paymentPayload, paymentRequirements) {
|
|
201
213
|
try {
|
|
202
214
|
const https = require("https");
|
|
203
215
|
const http = require("http");
|
|
204
|
-
const url = new (require("url").URL)(_facilitatorUrl +
|
|
216
|
+
const url = new (require("url").URL)(_facilitatorUrl + endpoint);
|
|
205
217
|
const body = JSON.stringify({
|
|
206
|
-
|
|
207
|
-
|
|
218
|
+
x402Version: paymentPayload.x402Version || 2,
|
|
219
|
+
paymentPayload,
|
|
220
|
+
paymentRequirements,
|
|
208
221
|
});
|
|
222
|
+
|
|
209
223
|
return new Promise((resolve) => {
|
|
210
224
|
const client = url.protocol === "https:" ? https : http;
|
|
211
225
|
const req = client.request({
|
|
@@ -214,161 +228,43 @@ async function _settleViaFacilitator(paymentSig, routeAccepts) {
|
|
|
214
228
|
path: url.pathname,
|
|
215
229
|
method: "POST",
|
|
216
230
|
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) },
|
|
217
|
-
timeout: 30000,
|
|
231
|
+
timeout: 30000,
|
|
218
232
|
}, (res) => {
|
|
219
233
|
let data = "";
|
|
220
234
|
res.on("data", (c) => data += c);
|
|
221
235
|
res.on("end", () => {
|
|
222
236
|
try {
|
|
223
|
-
const
|
|
224
|
-
if (
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
237
|
+
const parsed = JSON.parse(data);
|
|
238
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
239
|
+
// Verify: check isValid. Settle: check success.
|
|
240
|
+
if (endpoint === "/verify" && parsed.isValid === false) {
|
|
241
|
+
resolve({ ok: false, reason: parsed.invalidReason || "verification_rejected", data: parsed });
|
|
242
|
+
} else if (endpoint === "/settle" && parsed.success === false) {
|
|
243
|
+
resolve({ ok: false, reason: parsed.errorReason || "settlement_rejected", data: parsed });
|
|
244
|
+
} else {
|
|
245
|
+
resolve({ ok: true, data: parsed });
|
|
246
|
+
}
|
|
231
247
|
} else {
|
|
232
|
-
|
|
248
|
+
// Error response
|
|
249
|
+
const reason = parsed.invalidReason || parsed.errorReason || parsed.error || `facilitator_${res.statusCode}`;
|
|
250
|
+
console.log(` ⚠️ x402 facilitator ${endpoint} ${res.statusCode}: ${reason}`);
|
|
251
|
+
resolve({ ok: false, reason, data: parsed });
|
|
233
252
|
}
|
|
234
253
|
} catch {
|
|
235
|
-
resolve({
|
|
254
|
+
resolve({ ok: false, reason: `facilitator_parse_error_${res.statusCode}` });
|
|
236
255
|
}
|
|
237
256
|
});
|
|
238
257
|
});
|
|
239
|
-
req.on("error", () =>
|
|
240
|
-
|
|
258
|
+
req.on("error", (err) => {
|
|
259
|
+
console.log(` ⚠️ x402 facilitator ${endpoint} error: ${err.message}`);
|
|
260
|
+
resolve({ ok: false, reason: "facilitator_unavailable: " + err.message });
|
|
261
|
+
});
|
|
262
|
+
req.on("timeout", () => { req.destroy(); resolve({ ok: false, reason: "facilitator_timeout" }); });
|
|
241
263
|
req.write(body);
|
|
242
264
|
req.end();
|
|
243
265
|
});
|
|
244
|
-
} catch {
|
|
245
|
-
return { valid: false, reason: "facilitator_unavailable" };
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Self-settlement fallback — verify the signature AND execute the
|
|
251
|
-
* on-chain transferWithAuthorization() using the server's vault wallet.
|
|
252
|
-
*
|
|
253
|
-
* Only used when the facilitator is unavailable. Requires ethers + RPC.
|
|
254
|
-
* The server pays gas to execute the transfer.
|
|
255
|
-
*/
|
|
256
|
-
async function _selfSettle(paymentSig, price) {
|
|
257
|
-
let payload;
|
|
258
|
-
try {
|
|
259
|
-
payload = JSON.parse(Buffer.from(paymentSig, "base64").toString());
|
|
260
|
-
} catch {
|
|
261
|
-
return { valid: false, reason: "invalid_payment_format" };
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if (!payload.payload?.authorization || !payload.payload?.signature) {
|
|
265
|
-
return { valid: false, reason: "missing_authorization" };
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
const auth = payload.payload.authorization;
|
|
269
|
-
const sig = payload.payload.signature;
|
|
270
|
-
|
|
271
|
-
// Verify amount
|
|
272
|
-
const expectedUsdc = Math.round(parseFloat(price.replace("$", "")) * 1e6);
|
|
273
|
-
// Value comes as decimal string (e.g. "1000000" for $1) or hex (legacy "0xf4240")
|
|
274
|
-
const valStr = String(auth.value);
|
|
275
|
-
const actualUsdc = valStr.startsWith("0x") ? parseInt(valStr, 16) : parseInt(valStr, 10) || 0;
|
|
276
|
-
if (actualUsdc < expectedUsdc * 0.99) {
|
|
277
|
-
return { valid: false, reason: "amount_mismatch" };
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Verify payTo matches
|
|
281
|
-
if (auth.to?.toLowerCase() !== _payTo?.toLowerCase()) {
|
|
282
|
-
return { valid: false, reason: "wrong_recipient" };
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Verify not expired
|
|
286
|
-
if (auth.validBefore && auth.validBefore < Math.floor(Date.now() / 1000)) {
|
|
287
|
-
return { valid: false, reason: "payment_expired" };
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
try {
|
|
291
|
-
const { ethers } = require("ethers");
|
|
292
|
-
const fromAddr = ethers.getAddress(auth.from.toLowerCase());
|
|
293
|
-
const toAddr = ethers.getAddress(auth.to.toLowerCase());
|
|
294
|
-
|
|
295
|
-
// Step 1: Verify the signature
|
|
296
|
-
const domain = {
|
|
297
|
-
name: "USD Coin",
|
|
298
|
-
version: "2",
|
|
299
|
-
chainId: 8453,
|
|
300
|
-
verifyingContract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
301
|
-
};
|
|
302
|
-
const types = {
|
|
303
|
-
TransferWithAuthorization: [
|
|
304
|
-
{ name: "from", type: "address" },
|
|
305
|
-
{ name: "to", type: "address" },
|
|
306
|
-
{ name: "value", type: "uint256" },
|
|
307
|
-
{ name: "validAfter", type: "uint256" },
|
|
308
|
-
{ name: "validBefore", type: "uint256" },
|
|
309
|
-
{ name: "nonce", type: "bytes32" },
|
|
310
|
-
],
|
|
311
|
-
};
|
|
312
|
-
const message = {
|
|
313
|
-
from: fromAddr,
|
|
314
|
-
to: toAddr,
|
|
315
|
-
value: auth.value,
|
|
316
|
-
validAfter: auth.validAfter,
|
|
317
|
-
validBefore: auth.validBefore,
|
|
318
|
-
nonce: auth.nonce,
|
|
319
|
-
};
|
|
320
|
-
|
|
321
|
-
const recoveredAddress = ethers.verifyTypedData(domain, types, message, sig);
|
|
322
|
-
if (recoveredAddress.toLowerCase() !== fromAddr.toLowerCase()) {
|
|
323
|
-
return { valid: false, reason: "signature_mismatch" };
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// Step 2: Execute transferWithAuthorization on-chain
|
|
327
|
-
// Use the vault wallet to submit the transaction (server pays gas)
|
|
328
|
-
const { decryptPrivateKey } = require("../vault/vault-manager");
|
|
329
|
-
let keyBuf = null;
|
|
330
|
-
try {
|
|
331
|
-
keyBuf = decryptPrivateKey();
|
|
332
|
-
const provider = new ethers.JsonRpcProvider("https://mainnet.base.org");
|
|
333
|
-
const wallet = new ethers.Wallet(keyBuf, provider);
|
|
334
|
-
|
|
335
|
-
const usdcContract = new ethers.Contract(
|
|
336
|
-
"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
337
|
-
[
|
|
338
|
-
"function transferWithAuthorization(address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s) external",
|
|
339
|
-
],
|
|
340
|
-
wallet
|
|
341
|
-
);
|
|
342
|
-
|
|
343
|
-
// Parse the signature into v, r, s
|
|
344
|
-
const sigBytes = ethers.getBytes(sig);
|
|
345
|
-
const r = ethers.hexlify(sigBytes.slice(0, 32));
|
|
346
|
-
const s = ethers.hexlify(sigBytes.slice(32, 64));
|
|
347
|
-
const v = sigBytes[64];
|
|
348
|
-
|
|
349
|
-
const tx = await usdcContract.transferWithAuthorization(
|
|
350
|
-
fromAddr, toAddr, auth.value,
|
|
351
|
-
auth.validAfter, auth.validBefore, auth.nonce,
|
|
352
|
-
v, r, s,
|
|
353
|
-
{ gasLimit: 100000 }
|
|
354
|
-
);
|
|
355
|
-
|
|
356
|
-
// Wait for confirmation (1 block)
|
|
357
|
-
const receipt = await tx.wait(1);
|
|
358
|
-
console.log(` 💰 x402 self-settled: ${receipt.hash} (${price} USDC)`);
|
|
359
|
-
|
|
360
|
-
return {
|
|
361
|
-
valid: true,
|
|
362
|
-
from: fromAddr,
|
|
363
|
-
receipt: { authorization: auth, settled: "self", blockNumber: receipt.blockNumber },
|
|
364
|
-
txHash: receipt.hash,
|
|
365
|
-
};
|
|
366
|
-
} finally {
|
|
367
|
-
if (keyBuf) keyBuf.fill(0);
|
|
368
|
-
}
|
|
369
266
|
} catch (err) {
|
|
370
|
-
|
|
371
|
-
return { valid: false, reason: "settlement_failed: " + err.message };
|
|
267
|
+
return { ok: false, reason: "facilitator_error: " + err.message };
|
|
372
268
|
}
|
|
373
269
|
}
|
|
374
270
|
|
|
@@ -378,7 +274,6 @@ function _logPayment(entry) {
|
|
|
378
274
|
let payments = [];
|
|
379
275
|
try { payments = JSON.parse(fs.readFileSync(logPath, "utf-8")); } catch {}
|
|
380
276
|
payments.push(entry);
|
|
381
|
-
// Keep last 1000 payments
|
|
382
277
|
if (payments.length > 1000) payments = payments.slice(-1000);
|
|
383
278
|
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
384
279
|
fs.writeFileSync(logPath, JSON.stringify(payments, null, 2));
|