x402z-facilitator 0.0.11 → 0.0.12
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/README.md +1 -0
- package/dist/chunk-TDT6WJI7.mjs +711 -0
- package/dist/index.js +83 -5
- package/dist/index.mjs +1 -1
- package/dist/service/bootstrap.js +83 -5
- package/dist/service/bootstrap.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,6 +26,7 @@ Set env vars:
|
|
|
26
26
|
- `FACILITATOR_BATCHER_ADDRESS` (required FHETokenBatcher address)
|
|
27
27
|
- `FACILITATOR_BATCH_INTERVAL_MS` (default 15000)
|
|
28
28
|
- `FACILITATOR_GAS_MULTIPLIER` (optional, e.g. `2` to double estimated gas)
|
|
29
|
+
- `FACILITATOR_FEE_MULTIPLIER` (optional, scales estimated max fee + priority fee)
|
|
29
30
|
|
|
30
31
|
Then run:
|
|
31
32
|
|
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/service/bootstrap.ts
|
|
9
|
+
import { x402Facilitator } from "@x402/core/facilitator";
|
|
10
|
+
import { toFacilitatorEvmSigner } from "@x402/evm";
|
|
11
|
+
import { createWalletClient, http, publicActions } from "viem";
|
|
12
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
13
|
+
|
|
14
|
+
// src/service/service.ts
|
|
15
|
+
import { createServer } from "http";
|
|
16
|
+
async function readJson(req) {
|
|
17
|
+
const chunks = [];
|
|
18
|
+
for await (const chunk of req) {
|
|
19
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
20
|
+
}
|
|
21
|
+
const body = Buffer.concat(chunks).toString("utf8");
|
|
22
|
+
return JSON.parse(body);
|
|
23
|
+
}
|
|
24
|
+
function sendJson(res, status, payload) {
|
|
25
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
26
|
+
res.end(JSON.stringify(payload));
|
|
27
|
+
}
|
|
28
|
+
function createFacilitatorService(config) {
|
|
29
|
+
const server = createServer(async (req, res) => {
|
|
30
|
+
try {
|
|
31
|
+
const method = req.method ?? "GET";
|
|
32
|
+
const url = req.url ?? "/";
|
|
33
|
+
if (process.env.X402Z_DEBUG === "1") {
|
|
34
|
+
console.debug(`[x402z-facilitator] ${method} ${url}`);
|
|
35
|
+
}
|
|
36
|
+
if (!req.url) {
|
|
37
|
+
sendJson(res, 404, { error: "not_found" });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (req.method === "GET" && req.url === "/supported") {
|
|
41
|
+
sendJson(res, 200, config.facilitator.getSupported());
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (req.method === "POST" && req.url === "/verify") {
|
|
45
|
+
const body = await readJson(req);
|
|
46
|
+
const result = await config.facilitator.verify(body.paymentPayload, body.paymentRequirements);
|
|
47
|
+
sendJson(res, 200, result);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (req.method === "POST" && req.url === "/settle") {
|
|
51
|
+
const body = await readJson(req);
|
|
52
|
+
const result = await config.facilitator.settle(body.paymentPayload, body.paymentRequirements);
|
|
53
|
+
sendJson(res, 200, result);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
sendJson(res, 404, { error: "not_found" });
|
|
57
|
+
} catch (error) {
|
|
58
|
+
const message = error instanceof Error ? error.message : "internal_error";
|
|
59
|
+
sendJson(res, 500, { error: message });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
return server;
|
|
63
|
+
}
|
|
64
|
+
function startFacilitatorService(config) {
|
|
65
|
+
const port = config.port ?? 8040;
|
|
66
|
+
const server = createFacilitatorService(config);
|
|
67
|
+
server.listen(port);
|
|
68
|
+
return { port };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/scheme/scheme.ts
|
|
72
|
+
import { decodeEventLog, getAddress, isAddressEqual } from "viem";
|
|
73
|
+
import {
|
|
74
|
+
confidentialPaymentTypes,
|
|
75
|
+
confidentialTokenAbi,
|
|
76
|
+
hashEncryptedAmountInput
|
|
77
|
+
} from "x402z-shared";
|
|
78
|
+
var batcherAbi = [
|
|
79
|
+
{
|
|
80
|
+
inputs: [
|
|
81
|
+
{ internalType: "address", name: "token", type: "address" },
|
|
82
|
+
{
|
|
83
|
+
components: [
|
|
84
|
+
{
|
|
85
|
+
components: [
|
|
86
|
+
{ internalType: "address", name: "holder", type: "address" },
|
|
87
|
+
{ internalType: "address", name: "payee", type: "address" },
|
|
88
|
+
{ internalType: "uint256", name: "maxClearAmount", type: "uint256" },
|
|
89
|
+
{ internalType: "bytes32", name: "resourceHash", type: "bytes32" },
|
|
90
|
+
{ internalType: "uint48", name: "validAfter", type: "uint48" },
|
|
91
|
+
{ internalType: "uint48", name: "validBefore", type: "uint48" },
|
|
92
|
+
{ internalType: "bytes32", name: "nonce", type: "bytes32" },
|
|
93
|
+
{ internalType: "bytes32", name: "encryptedAmountHash", type: "bytes32" }
|
|
94
|
+
],
|
|
95
|
+
internalType: "struct IFHEToken.ConfidentialPayment",
|
|
96
|
+
name: "p",
|
|
97
|
+
type: "tuple"
|
|
98
|
+
},
|
|
99
|
+
{ internalType: "externalEuint64", name: "encryptedAmountInput", type: "bytes32" },
|
|
100
|
+
{ internalType: "bytes", name: "inputProof", type: "bytes" },
|
|
101
|
+
{ internalType: "bytes", name: "sig", type: "bytes" }
|
|
102
|
+
],
|
|
103
|
+
internalType: "struct FHETokenBatcher.Request[]",
|
|
104
|
+
name: "requests",
|
|
105
|
+
type: "tuple[]"
|
|
106
|
+
}
|
|
107
|
+
],
|
|
108
|
+
name: "batchConfidentialTransferWithAuthorization",
|
|
109
|
+
outputs: [
|
|
110
|
+
{ internalType: "bool[]", name: "successes", type: "bool[]" },
|
|
111
|
+
{ internalType: "bytes32[]", name: "transferredHandles", type: "bytes32[]" }
|
|
112
|
+
],
|
|
113
|
+
stateMutability: "nonpayable",
|
|
114
|
+
type: "function"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
anonymous: false,
|
|
118
|
+
inputs: [
|
|
119
|
+
{ indexed: true, internalType: "uint256", name: "index", type: "uint256" },
|
|
120
|
+
{ indexed: false, internalType: "bytes32", name: "transferredHandle", type: "bytes32" }
|
|
121
|
+
],
|
|
122
|
+
name: "BatchItemSuccess",
|
|
123
|
+
type: "event"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
anonymous: false,
|
|
127
|
+
inputs: [
|
|
128
|
+
{ indexed: true, internalType: "uint256", name: "index", type: "uint256" },
|
|
129
|
+
{ indexed: false, internalType: "bytes", name: "reason", type: "bytes" }
|
|
130
|
+
],
|
|
131
|
+
name: "BatchItemFailure",
|
|
132
|
+
type: "event"
|
|
133
|
+
}
|
|
134
|
+
];
|
|
135
|
+
var X402zEvmFacilitator = class {
|
|
136
|
+
constructor(config) {
|
|
137
|
+
this.config = config;
|
|
138
|
+
this.scheme = "erc7984-mind-v1";
|
|
139
|
+
this.caipFamily = "eip155:*";
|
|
140
|
+
this.queue = [];
|
|
141
|
+
this.hashFn = config.hashEncryptedAmountInput ?? hashEncryptedAmountInput;
|
|
142
|
+
this.clock = config.clock ?? (() => Math.floor(Date.now() / 1e3));
|
|
143
|
+
this.checkUsedNonces = config.checkUsedNonces ?? true;
|
|
144
|
+
this.waitForReceipt = config.waitForReceipt ?? true;
|
|
145
|
+
this.batchIntervalMs = Math.max(0, config.batchIntervalMs ?? 15e3);
|
|
146
|
+
this.receiptOptions = config.receipt;
|
|
147
|
+
this.batcherAddress = getAddress(config.batcherAddress);
|
|
148
|
+
}
|
|
149
|
+
getExtra(_) {
|
|
150
|
+
return void 0;
|
|
151
|
+
}
|
|
152
|
+
getSigners(_) {
|
|
153
|
+
return [...this.config.signer.getAddresses()];
|
|
154
|
+
}
|
|
155
|
+
async verify(payload, requirements) {
|
|
156
|
+
const confidentialPayload = payload.payload;
|
|
157
|
+
if (payload.accepted.scheme !== this.scheme || requirements.scheme !== this.scheme) {
|
|
158
|
+
return {
|
|
159
|
+
isValid: false,
|
|
160
|
+
invalidReason: "unsupported_scheme",
|
|
161
|
+
payer: confidentialPayload?.authorization?.holder
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (payload.accepted.network !== requirements.network) {
|
|
165
|
+
return {
|
|
166
|
+
isValid: false,
|
|
167
|
+
invalidReason: "network_mismatch",
|
|
168
|
+
payer: confidentialPayload.authorization.holder
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
const extra = requirements.extra;
|
|
172
|
+
const eip712 = extra?.eip712;
|
|
173
|
+
if (!eip712?.name || !eip712?.version) {
|
|
174
|
+
return {
|
|
175
|
+
isValid: false,
|
|
176
|
+
invalidReason: "missing_eip712_domain",
|
|
177
|
+
payer: confidentialPayload.authorization.holder
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const now = this.clock();
|
|
181
|
+
const validAfter = Number(confidentialPayload.authorization.validAfter);
|
|
182
|
+
const validBefore = Number(confidentialPayload.authorization.validBefore);
|
|
183
|
+
if (Number.isNaN(validAfter) || Number.isNaN(validBefore)) {
|
|
184
|
+
return {
|
|
185
|
+
isValid: false,
|
|
186
|
+
invalidReason: "invalid_validity_window",
|
|
187
|
+
payer: confidentialPayload.authorization.holder
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if (now < validAfter || now > validBefore) {
|
|
191
|
+
return {
|
|
192
|
+
isValid: false,
|
|
193
|
+
invalidReason: "authorization_expired",
|
|
194
|
+
payer: confidentialPayload.authorization.holder
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (!isAddressEqual(getAddress(confidentialPayload.authorization.payee), getAddress(requirements.payTo))) {
|
|
198
|
+
return {
|
|
199
|
+
isValid: false,
|
|
200
|
+
invalidReason: "recipient_mismatch",
|
|
201
|
+
payer: confidentialPayload.authorization.holder
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
const computedHash = this.hashFn(confidentialPayload.encryptedAmountInput);
|
|
205
|
+
if (computedHash !== confidentialPayload.authorization.encryptedAmountHash) {
|
|
206
|
+
return {
|
|
207
|
+
isValid: false,
|
|
208
|
+
invalidReason: "encrypted_amount_mismatch",
|
|
209
|
+
payer: confidentialPayload.authorization.holder
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const chainId = parseInt(requirements.network.split(":")[1]);
|
|
213
|
+
const isValidSignature = await this.config.signer.verifyTypedData({
|
|
214
|
+
address: confidentialPayload.authorization.holder,
|
|
215
|
+
domain: {
|
|
216
|
+
name: eip712.name,
|
|
217
|
+
version: eip712.version,
|
|
218
|
+
chainId,
|
|
219
|
+
verifyingContract: getAddress(requirements.asset)
|
|
220
|
+
},
|
|
221
|
+
types: confidentialPaymentTypes,
|
|
222
|
+
primaryType: "ConfidentialPayment",
|
|
223
|
+
message: {
|
|
224
|
+
holder: getAddress(confidentialPayload.authorization.holder),
|
|
225
|
+
payee: getAddress(confidentialPayload.authorization.payee),
|
|
226
|
+
maxClearAmount: BigInt(confidentialPayload.authorization.maxClearAmount),
|
|
227
|
+
resourceHash: confidentialPayload.authorization.resourceHash,
|
|
228
|
+
validAfter: BigInt(confidentialPayload.authorization.validAfter),
|
|
229
|
+
validBefore: BigInt(confidentialPayload.authorization.validBefore),
|
|
230
|
+
nonce: confidentialPayload.authorization.nonce,
|
|
231
|
+
encryptedAmountHash: confidentialPayload.authorization.encryptedAmountHash
|
|
232
|
+
},
|
|
233
|
+
signature: confidentialPayload.signature
|
|
234
|
+
});
|
|
235
|
+
if (!isValidSignature) {
|
|
236
|
+
return {
|
|
237
|
+
isValid: false,
|
|
238
|
+
invalidReason: "invalid_signature",
|
|
239
|
+
payer: confidentialPayload.authorization.holder
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
if (this.checkUsedNonces) {
|
|
243
|
+
const used = await this.config.signer.readContract({
|
|
244
|
+
address: getAddress(requirements.asset),
|
|
245
|
+
abi: confidentialTokenAbi,
|
|
246
|
+
functionName: "usedNonces",
|
|
247
|
+
args: [confidentialPayload.authorization.holder, confidentialPayload.authorization.nonce]
|
|
248
|
+
});
|
|
249
|
+
if (used) {
|
|
250
|
+
return {
|
|
251
|
+
isValid: false,
|
|
252
|
+
invalidReason: "nonce_already_used",
|
|
253
|
+
payer: confidentialPayload.authorization.holder
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
const txRequest = {
|
|
259
|
+
address: this.batcherAddress,
|
|
260
|
+
abi: batcherAbi,
|
|
261
|
+
functionName: "batchConfidentialTransferWithAuthorization",
|
|
262
|
+
args: [
|
|
263
|
+
getAddress(requirements.asset),
|
|
264
|
+
[
|
|
265
|
+
{
|
|
266
|
+
p: confidentialPayload.authorization,
|
|
267
|
+
encryptedAmountInput: confidentialPayload.encryptedAmountInput,
|
|
268
|
+
inputProof: confidentialPayload.inputProof,
|
|
269
|
+
sig: confidentialPayload.signature
|
|
270
|
+
}
|
|
271
|
+
]
|
|
272
|
+
]
|
|
273
|
+
};
|
|
274
|
+
const [successes] = await this.config.signer.simulateContract(txRequest);
|
|
275
|
+
if (!successes[0]) {
|
|
276
|
+
return {
|
|
277
|
+
isValid: false,
|
|
278
|
+
invalidReason: "preflight_failed",
|
|
279
|
+
payer: confidentialPayload.authorization.holder
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
} catch {
|
|
283
|
+
return {
|
|
284
|
+
isValid: false,
|
|
285
|
+
invalidReason: "preflight_failed",
|
|
286
|
+
payer: confidentialPayload.authorization.holder
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
isValid: true,
|
|
291
|
+
payer: confidentialPayload.authorization.holder
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
async settle(payload, requirements) {
|
|
295
|
+
const valid = await this.verify(payload, requirements);
|
|
296
|
+
const confidentialPayload = payload.payload;
|
|
297
|
+
if (!valid.isValid) {
|
|
298
|
+
return {
|
|
299
|
+
success: false,
|
|
300
|
+
errorReason: valid.invalidReason ?? "invalid_payment",
|
|
301
|
+
payer: confidentialPayload.authorization.holder,
|
|
302
|
+
transaction: "",
|
|
303
|
+
network: requirements.network
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
return new Promise((resolve) => {
|
|
307
|
+
this.queue.push({ payload, requirements, resolve });
|
|
308
|
+
this.scheduleFlush();
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
scheduleFlush() {
|
|
312
|
+
if (this.flushTimer) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const delay = this.batchIntervalMs;
|
|
316
|
+
this.flushTimer = setTimeout(() => {
|
|
317
|
+
this.flushTimer = void 0;
|
|
318
|
+
void this.flushQueue();
|
|
319
|
+
}, delay);
|
|
320
|
+
}
|
|
321
|
+
async flushQueue() {
|
|
322
|
+
if (this.queue.length === 0) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const queued = this.queue;
|
|
326
|
+
this.queue = [];
|
|
327
|
+
const byToken = /* @__PURE__ */ new Map();
|
|
328
|
+
for (const entry of queued) {
|
|
329
|
+
const tokenKey = getAddress(entry.requirements.asset);
|
|
330
|
+
const group = byToken.get(tokenKey) ?? [];
|
|
331
|
+
group.push(entry);
|
|
332
|
+
byToken.set(tokenKey, group);
|
|
333
|
+
}
|
|
334
|
+
for (const [tokenAddress, entries] of byToken.entries()) {
|
|
335
|
+
await this.flushBatch(tokenAddress, entries);
|
|
336
|
+
}
|
|
337
|
+
if (this.queue.length > 0) {
|
|
338
|
+
this.scheduleFlush();
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
async flushBatch(tokenAddress, entries) {
|
|
342
|
+
const buildRequests = (batchEntries2) => batchEntries2.map(({ entry }) => {
|
|
343
|
+
const confidentialPayload = entry.payload.payload;
|
|
344
|
+
return {
|
|
345
|
+
p: confidentialPayload.authorization,
|
|
346
|
+
encryptedAmountInput: confidentialPayload.encryptedAmountInput,
|
|
347
|
+
inputProof: confidentialPayload.inputProof,
|
|
348
|
+
sig: confidentialPayload.signature
|
|
349
|
+
};
|
|
350
|
+
});
|
|
351
|
+
const originalEntries = entries.map((entry, index) => ({ entry, index }));
|
|
352
|
+
let batchEntries = originalEntries;
|
|
353
|
+
let requests = buildRequests(batchEntries);
|
|
354
|
+
let txRequest = {
|
|
355
|
+
address: this.batcherAddress,
|
|
356
|
+
abi: batcherAbi,
|
|
357
|
+
functionName: "batchConfidentialTransferWithAuthorization",
|
|
358
|
+
args: [tokenAddress, requests]
|
|
359
|
+
};
|
|
360
|
+
if (process.env.X402Z_DEBUG === "1") {
|
|
361
|
+
console.debug("[x402z-facilitator] settle tx", {
|
|
362
|
+
batcherAddress: this.batcherAddress,
|
|
363
|
+
tokenAddress,
|
|
364
|
+
functionName: txRequest.functionName,
|
|
365
|
+
to: txRequest.address,
|
|
366
|
+
size: requests.length
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
try {
|
|
370
|
+
const [successes, transferredHandles] = await this.config.signer.simulateContract(txRequest);
|
|
371
|
+
if (successes.length === batchEntries.length && transferredHandles.length === batchEntries.length) {
|
|
372
|
+
const filtered = [];
|
|
373
|
+
batchEntries.forEach((item, index) => {
|
|
374
|
+
if (successes[index]) {
|
|
375
|
+
filtered.push(item);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const confidentialPayload = item.entry.payload.payload;
|
|
379
|
+
item.entry.resolve({
|
|
380
|
+
success: false,
|
|
381
|
+
errorReason: "preflight_failed",
|
|
382
|
+
payer: confidentialPayload.authorization.holder,
|
|
383
|
+
transaction: "",
|
|
384
|
+
network: item.entry.requirements.network,
|
|
385
|
+
batch: { index: item.index, success: false, transferredHandle: transferredHandles[index] }
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
if (filtered.length === 0) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
batchEntries = filtered;
|
|
392
|
+
requests = buildRequests(batchEntries);
|
|
393
|
+
txRequest = {
|
|
394
|
+
...txRequest,
|
|
395
|
+
args: [tokenAddress, requests]
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
} catch (error) {
|
|
399
|
+
if (process.env.X402Z_DEBUG === "1") {
|
|
400
|
+
console.error("[x402z-facilitator] settle tx error", error);
|
|
401
|
+
}
|
|
402
|
+
for (const entry of entries) {
|
|
403
|
+
const confidentialPayload = entry.payload.payload;
|
|
404
|
+
entry.resolve({
|
|
405
|
+
success: false,
|
|
406
|
+
errorReason: "preflight_failed",
|
|
407
|
+
payer: confidentialPayload.authorization.holder,
|
|
408
|
+
transaction: "",
|
|
409
|
+
network: entry.requirements.network
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const settleEntries = batchEntries.map((item) => item.entry);
|
|
415
|
+
let txHash;
|
|
416
|
+
try {
|
|
417
|
+
txHash = await this.config.signer.writeContract(txRequest);
|
|
418
|
+
} catch (error) {
|
|
419
|
+
for (const entry of settleEntries) {
|
|
420
|
+
const confidentialPayload = entry.payload.payload;
|
|
421
|
+
entry.resolve({
|
|
422
|
+
success: false,
|
|
423
|
+
errorReason: "settlement_failed",
|
|
424
|
+
payer: confidentialPayload.authorization.holder,
|
|
425
|
+
transaction: "",
|
|
426
|
+
network: entry.requirements.network
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (process.env.X402Z_DEBUG === "1") {
|
|
432
|
+
console.debug("[x402z-facilitator] tx submitted", txHash);
|
|
433
|
+
}
|
|
434
|
+
let receipt;
|
|
435
|
+
if (this.waitForReceipt) {
|
|
436
|
+
receipt = await this.config.signer.waitForTransactionReceipt({ hash: txHash });
|
|
437
|
+
if (process.env.X402Z_DEBUG === "1") {
|
|
438
|
+
console.debug("[x402z-facilitator] tx receipt", receipt);
|
|
439
|
+
}
|
|
440
|
+
if (receipt.status !== "success") {
|
|
441
|
+
for (const entry of settleEntries) {
|
|
442
|
+
const confidentialPayload = entry.payload.payload;
|
|
443
|
+
entry.resolve({
|
|
444
|
+
success: false,
|
|
445
|
+
errorReason: "settlement_failed",
|
|
446
|
+
payer: confidentialPayload.authorization.holder,
|
|
447
|
+
transaction: txHash,
|
|
448
|
+
network: entry.requirements.network
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (!this.waitForReceipt) {
|
|
455
|
+
settleEntries.forEach((entry) => {
|
|
456
|
+
const confidentialPayload = entry.payload.payload;
|
|
457
|
+
entry.resolve({
|
|
458
|
+
success: true,
|
|
459
|
+
payer: confidentialPayload.authorization.holder,
|
|
460
|
+
transaction: txHash,
|
|
461
|
+
network: entry.requirements.network
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const batchResults = /* @__PURE__ */ new Map();
|
|
467
|
+
if (receipt?.logs) {
|
|
468
|
+
for (const log of receipt.logs) {
|
|
469
|
+
if (!log?.address) {
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
if (!isAddressEqual(getAddress(log.address), this.batcherAddress)) {
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
const decoded = decodeEventLog({
|
|
476
|
+
abi: batcherAbi,
|
|
477
|
+
data: log.data,
|
|
478
|
+
topics: log.topics
|
|
479
|
+
});
|
|
480
|
+
if (process.env.X402Z_DEBUG === "1") {
|
|
481
|
+
console.debug("[x402z-facilitator] batch log", decoded);
|
|
482
|
+
}
|
|
483
|
+
if (decoded.eventName === "BatchItemSuccess") {
|
|
484
|
+
const args = decoded.args;
|
|
485
|
+
batchResults.set(Number(args.index), {
|
|
486
|
+
success: true,
|
|
487
|
+
transferredHandle: args.transferredHandle
|
|
488
|
+
});
|
|
489
|
+
} else if (decoded.eventName === "BatchItemFailure") {
|
|
490
|
+
const args = decoded.args;
|
|
491
|
+
batchResults.set(Number(args.index), {
|
|
492
|
+
success: false,
|
|
493
|
+
failureReason: args.reason
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
settleEntries.forEach((entry, index) => {
|
|
499
|
+
const confidentialPayload = entry.payload.payload;
|
|
500
|
+
const batchResult = batchResults.get(index);
|
|
501
|
+
if (!batchResult || !batchResult.success) {
|
|
502
|
+
entry.resolve({
|
|
503
|
+
success: false,
|
|
504
|
+
errorReason: "settlement_failed",
|
|
505
|
+
payer: confidentialPayload.authorization.holder,
|
|
506
|
+
transaction: txHash,
|
|
507
|
+
network: entry.requirements.network,
|
|
508
|
+
...batchResult ? { batch: { index, ...batchResult } } : {}
|
|
509
|
+
});
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
entry.resolve({
|
|
513
|
+
success: true,
|
|
514
|
+
payer: confidentialPayload.authorization.holder,
|
|
515
|
+
transaction: txHash,
|
|
516
|
+
network: entry.requirements.network,
|
|
517
|
+
batch: { index, ...batchResult }
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
// src/service/bootstrap.ts
|
|
524
|
+
function requireEnv(key) {
|
|
525
|
+
const value = process.env[key];
|
|
526
|
+
if (!value) {
|
|
527
|
+
throw new Error(`Missing required env var: ${key}`);
|
|
528
|
+
}
|
|
529
|
+
return value;
|
|
530
|
+
}
|
|
531
|
+
function createFacilitatorFromEnv() {
|
|
532
|
+
const privateKey = requireEnv("FACILITATOR_EVM_PRIVATE_KEY");
|
|
533
|
+
const chainId = Number(process.env.FACILITATOR_EVM_CHAIN_ID ?? "11155111");
|
|
534
|
+
const rpcUrl = requireEnv("FACILITATOR_EVM_RPC_URL");
|
|
535
|
+
const networks = (process.env.FACILITATOR_NETWORKS ?? "eip155:11155111").split(",").map((network) => network.trim()).filter(Boolean);
|
|
536
|
+
const waitForReceipt = (process.env.FACILITATOR_WAIT_FOR_RECEIPT ?? "true") === "true";
|
|
537
|
+
const batcherAddress = process.env.FACILITATOR_BATCHER_ADDRESS;
|
|
538
|
+
if (!batcherAddress) {
|
|
539
|
+
throw new Error("FACILITATOR_BATCHER_ADDRESS is required");
|
|
540
|
+
}
|
|
541
|
+
const batchIntervalMs = process.env.FACILITATOR_BATCH_INTERVAL_MS ? Number(process.env.FACILITATOR_BATCH_INTERVAL_MS) : void 0;
|
|
542
|
+
const receiptTimeoutMs = process.env.FACILITATOR_RECEIPT_TIMEOUT_MS ? Number(process.env.FACILITATOR_RECEIPT_TIMEOUT_MS) : void 0;
|
|
543
|
+
const receiptConfirmations = process.env.FACILITATOR_RECEIPT_CONFIRMATIONS ? Number(process.env.FACILITATOR_RECEIPT_CONFIRMATIONS) : void 0;
|
|
544
|
+
const receiptPollingIntervalMs = process.env.FACILITATOR_RECEIPT_POLLING_INTERVAL_MS ? Number(process.env.FACILITATOR_RECEIPT_POLLING_INTERVAL_MS) : void 0;
|
|
545
|
+
const gasMultiplier = process.env.FACILITATOR_GAS_MULTIPLIER ? Number(process.env.FACILITATOR_GAS_MULTIPLIER) : void 0;
|
|
546
|
+
const feeMultiplier = process.env.FACILITATOR_FEE_MULTIPLIER ? Number(process.env.FACILITATOR_FEE_MULTIPLIER) : void 0;
|
|
547
|
+
const debugEnabled = process.env.X402Z_DEBUG === "1";
|
|
548
|
+
const account = privateKeyToAccount(privateKey);
|
|
549
|
+
if (debugEnabled) {
|
|
550
|
+
console.debug("[x402z-facilitator] config", {
|
|
551
|
+
chainId,
|
|
552
|
+
networks,
|
|
553
|
+
rpcUrl,
|
|
554
|
+
waitForReceipt,
|
|
555
|
+
batcherAddress,
|
|
556
|
+
gasMultiplier,
|
|
557
|
+
feeMultiplier,
|
|
558
|
+
batchIntervalMs,
|
|
559
|
+
receipt: {
|
|
560
|
+
confirmations: receiptConfirmations,
|
|
561
|
+
timeoutMs: receiptTimeoutMs,
|
|
562
|
+
pollingIntervalMs: receiptPollingIntervalMs
|
|
563
|
+
},
|
|
564
|
+
address: account.address
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
const client = createWalletClient({
|
|
568
|
+
account,
|
|
569
|
+
chain: {
|
|
570
|
+
id: chainId,
|
|
571
|
+
name: "custom",
|
|
572
|
+
nativeCurrency: { name: "native", symbol: "NATIVE", decimals: 18 },
|
|
573
|
+
rpcUrls: { default: { http: [rpcUrl] } }
|
|
574
|
+
},
|
|
575
|
+
transport: http(rpcUrl)
|
|
576
|
+
}).extend(publicActions);
|
|
577
|
+
const baseSigner = toFacilitatorEvmSigner({
|
|
578
|
+
address: account.address,
|
|
579
|
+
readContract: (args) => client.readContract({
|
|
580
|
+
...args,
|
|
581
|
+
args: args.args || []
|
|
582
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
583
|
+
}),
|
|
584
|
+
verifyTypedData: (args) => client.verifyTypedData(args),
|
|
585
|
+
writeContract: async (args) => {
|
|
586
|
+
let gas;
|
|
587
|
+
let maxFeePerGas;
|
|
588
|
+
let maxPriorityFeePerGas;
|
|
589
|
+
if (gasMultiplier && gasMultiplier > 0) {
|
|
590
|
+
try {
|
|
591
|
+
const estimated = await client.estimateContractGas({
|
|
592
|
+
...args,
|
|
593
|
+
args: args.args || [],
|
|
594
|
+
account
|
|
595
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
596
|
+
});
|
|
597
|
+
const scale = BigInt(Math.round(gasMultiplier * 1e3));
|
|
598
|
+
const scaled = estimated * scale / 1000n;
|
|
599
|
+
gas = scaled > estimated ? scaled : estimated;
|
|
600
|
+
} catch (error) {
|
|
601
|
+
if (debugEnabled) {
|
|
602
|
+
console.debug("[x402z-facilitator] gas estimate failed", error);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
if (feeMultiplier && feeMultiplier > 0) {
|
|
607
|
+
try {
|
|
608
|
+
const fees = await client.estimateFeesPerGas();
|
|
609
|
+
if (fees.maxFeePerGas) {
|
|
610
|
+
const scale = BigInt(Math.round(feeMultiplier * 1e3));
|
|
611
|
+
maxFeePerGas = fees.maxFeePerGas * scale / 1000n;
|
|
612
|
+
}
|
|
613
|
+
if (fees.maxPriorityFeePerGas) {
|
|
614
|
+
const scale = BigInt(Math.round(feeMultiplier * 1e3));
|
|
615
|
+
maxPriorityFeePerGas = fees.maxPriorityFeePerGas * scale / 1000n;
|
|
616
|
+
}
|
|
617
|
+
} catch (error) {
|
|
618
|
+
if (debugEnabled) {
|
|
619
|
+
console.debug("[x402z-facilitator] fee estimate failed", error);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return client.writeContract({
|
|
624
|
+
...args,
|
|
625
|
+
args: args.args || [],
|
|
626
|
+
...gas ? { gas } : {},
|
|
627
|
+
...maxFeePerGas ? { maxFeePerGas } : {},
|
|
628
|
+
...maxPriorityFeePerGas ? { maxPriorityFeePerGas } : {}
|
|
629
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
630
|
+
});
|
|
631
|
+
},
|
|
632
|
+
sendTransaction: async (args) => {
|
|
633
|
+
let maxFeePerGas;
|
|
634
|
+
let maxPriorityFeePerGas;
|
|
635
|
+
if (feeMultiplier && feeMultiplier > 0) {
|
|
636
|
+
try {
|
|
637
|
+
const fees = await client.estimateFeesPerGas();
|
|
638
|
+
if (fees.maxFeePerGas) {
|
|
639
|
+
const scale = BigInt(Math.round(feeMultiplier * 1e3));
|
|
640
|
+
maxFeePerGas = fees.maxFeePerGas * scale / 1000n;
|
|
641
|
+
}
|
|
642
|
+
if (fees.maxPriorityFeePerGas) {
|
|
643
|
+
const scale = BigInt(Math.round(feeMultiplier * 1e3));
|
|
644
|
+
maxPriorityFeePerGas = fees.maxPriorityFeePerGas * scale / 1000n;
|
|
645
|
+
}
|
|
646
|
+
} catch (error) {
|
|
647
|
+
if (debugEnabled) {
|
|
648
|
+
console.debug("[x402z-facilitator] fee estimate failed", error);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return client.sendTransaction({
|
|
653
|
+
to: args.to,
|
|
654
|
+
data: args.data,
|
|
655
|
+
...maxFeePerGas ? { maxFeePerGas } : {},
|
|
656
|
+
...maxPriorityFeePerGas ? { maxPriorityFeePerGas } : {}
|
|
657
|
+
});
|
|
658
|
+
},
|
|
659
|
+
waitForTransactionReceipt: (args) => client.waitForTransactionReceipt(args),
|
|
660
|
+
getCode: (args) => client.getCode(args)
|
|
661
|
+
});
|
|
662
|
+
const signer = {
|
|
663
|
+
...baseSigner,
|
|
664
|
+
simulateContract: async (args) => {
|
|
665
|
+
const result = await client.simulateContract({
|
|
666
|
+
...args,
|
|
667
|
+
args: args.args || [],
|
|
668
|
+
account
|
|
669
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
670
|
+
});
|
|
671
|
+
return result.result;
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
const facilitator = new x402Facilitator();
|
|
675
|
+
for (const network of networks) {
|
|
676
|
+
facilitator.register(
|
|
677
|
+
network,
|
|
678
|
+
new X402zEvmFacilitator({
|
|
679
|
+
signer,
|
|
680
|
+
waitForReceipt,
|
|
681
|
+
batcherAddress,
|
|
682
|
+
batchIntervalMs,
|
|
683
|
+
receipt: {
|
|
684
|
+
confirmations: receiptConfirmations,
|
|
685
|
+
timeoutMs: receiptTimeoutMs,
|
|
686
|
+
pollingIntervalMs: receiptPollingIntervalMs
|
|
687
|
+
}
|
|
688
|
+
})
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
return facilitator;
|
|
692
|
+
}
|
|
693
|
+
function startFacilitator() {
|
|
694
|
+
const facilitator = createFacilitatorFromEnv();
|
|
695
|
+
const port = Number(process.env.FACILITATOR_PORT ?? "8040");
|
|
696
|
+
const server = createFacilitatorService({ facilitator, port });
|
|
697
|
+
server.listen(port);
|
|
698
|
+
return { port };
|
|
699
|
+
}
|
|
700
|
+
if (__require.main === module) {
|
|
701
|
+
const { port } = startFacilitator();
|
|
702
|
+
console.log(`Confidential facilitator listening on :${port}`);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
export {
|
|
706
|
+
X402zEvmFacilitator,
|
|
707
|
+
createFacilitatorService,
|
|
708
|
+
startFacilitatorService,
|
|
709
|
+
createFacilitatorFromEnv,
|
|
710
|
+
startFacilitator
|
|
711
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -210,6 +210,38 @@ var X402zEvmFacilitator = class {
|
|
|
210
210
|
};
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
|
+
try {
|
|
214
|
+
const txRequest = {
|
|
215
|
+
address: this.batcherAddress,
|
|
216
|
+
abi: batcherAbi,
|
|
217
|
+
functionName: "batchConfidentialTransferWithAuthorization",
|
|
218
|
+
args: [
|
|
219
|
+
(0, import_viem.getAddress)(requirements.asset),
|
|
220
|
+
[
|
|
221
|
+
{
|
|
222
|
+
p: confidentialPayload.authorization,
|
|
223
|
+
encryptedAmountInput: confidentialPayload.encryptedAmountInput,
|
|
224
|
+
inputProof: confidentialPayload.inputProof,
|
|
225
|
+
sig: confidentialPayload.signature
|
|
226
|
+
}
|
|
227
|
+
]
|
|
228
|
+
]
|
|
229
|
+
};
|
|
230
|
+
const [successes] = await this.config.signer.simulateContract(txRequest);
|
|
231
|
+
if (!successes[0]) {
|
|
232
|
+
return {
|
|
233
|
+
isValid: false,
|
|
234
|
+
invalidReason: "preflight_failed",
|
|
235
|
+
payer: confidentialPayload.authorization.holder
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
} catch {
|
|
239
|
+
return {
|
|
240
|
+
isValid: false,
|
|
241
|
+
invalidReason: "preflight_failed",
|
|
242
|
+
payer: confidentialPayload.authorization.holder
|
|
243
|
+
};
|
|
244
|
+
}
|
|
213
245
|
return {
|
|
214
246
|
isValid: true,
|
|
215
247
|
payer: confidentialPayload.authorization.holder
|
|
@@ -538,6 +570,7 @@ function createFacilitatorFromEnv() {
|
|
|
538
570
|
const receiptConfirmations = process.env.FACILITATOR_RECEIPT_CONFIRMATIONS ? Number(process.env.FACILITATOR_RECEIPT_CONFIRMATIONS) : void 0;
|
|
539
571
|
const receiptPollingIntervalMs = process.env.FACILITATOR_RECEIPT_POLLING_INTERVAL_MS ? Number(process.env.FACILITATOR_RECEIPT_POLLING_INTERVAL_MS) : void 0;
|
|
540
572
|
const gasMultiplier = process.env.FACILITATOR_GAS_MULTIPLIER ? Number(process.env.FACILITATOR_GAS_MULTIPLIER) : void 0;
|
|
573
|
+
const feeMultiplier = process.env.FACILITATOR_FEE_MULTIPLIER ? Number(process.env.FACILITATOR_FEE_MULTIPLIER) : void 0;
|
|
541
574
|
const debugEnabled = process.env.X402Z_DEBUG === "1";
|
|
542
575
|
const account = (0, import_accounts.privateKeyToAccount)(privateKey);
|
|
543
576
|
if (debugEnabled) {
|
|
@@ -548,6 +581,7 @@ function createFacilitatorFromEnv() {
|
|
|
548
581
|
waitForReceipt,
|
|
549
582
|
batcherAddress,
|
|
550
583
|
gasMultiplier,
|
|
584
|
+
feeMultiplier,
|
|
551
585
|
batchIntervalMs,
|
|
552
586
|
receipt: {
|
|
553
587
|
confirmations: receiptConfirmations,
|
|
@@ -577,6 +611,8 @@ function createFacilitatorFromEnv() {
|
|
|
577
611
|
verifyTypedData: (args) => client.verifyTypedData(args),
|
|
578
612
|
writeContract: async (args) => {
|
|
579
613
|
let gas;
|
|
614
|
+
let maxFeePerGas;
|
|
615
|
+
let maxPriorityFeePerGas;
|
|
580
616
|
if (gasMultiplier && gasMultiplier > 0) {
|
|
581
617
|
try {
|
|
582
618
|
const estimated = await client.estimateContractGas({
|
|
@@ -594,17 +630,59 @@ function createFacilitatorFromEnv() {
|
|
|
594
630
|
}
|
|
595
631
|
}
|
|
596
632
|
}
|
|
633
|
+
if (feeMultiplier && feeMultiplier > 0) {
|
|
634
|
+
try {
|
|
635
|
+
const fees = await client.estimateFeesPerGas();
|
|
636
|
+
if (fees.maxFeePerGas) {
|
|
637
|
+
const scale = BigInt(Math.round(feeMultiplier * 1e3));
|
|
638
|
+
maxFeePerGas = fees.maxFeePerGas * scale / 1000n;
|
|
639
|
+
}
|
|
640
|
+
if (fees.maxPriorityFeePerGas) {
|
|
641
|
+
const scale = BigInt(Math.round(feeMultiplier * 1e3));
|
|
642
|
+
maxPriorityFeePerGas = fees.maxPriorityFeePerGas * scale / 1000n;
|
|
643
|
+
}
|
|
644
|
+
} catch (error) {
|
|
645
|
+
if (debugEnabled) {
|
|
646
|
+
console.debug("[x402z-facilitator] fee estimate failed", error);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
597
650
|
return client.writeContract({
|
|
598
651
|
...args,
|
|
599
652
|
args: args.args || [],
|
|
600
|
-
...gas ? { gas } : {}
|
|
653
|
+
...gas ? { gas } : {},
|
|
654
|
+
...maxFeePerGas ? { maxFeePerGas } : {},
|
|
655
|
+
...maxPriorityFeePerGas ? { maxPriorityFeePerGas } : {}
|
|
601
656
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
602
657
|
});
|
|
603
658
|
},
|
|
604
|
-
sendTransaction: (args) =>
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
659
|
+
sendTransaction: async (args) => {
|
|
660
|
+
let maxFeePerGas;
|
|
661
|
+
let maxPriorityFeePerGas;
|
|
662
|
+
if (feeMultiplier && feeMultiplier > 0) {
|
|
663
|
+
try {
|
|
664
|
+
const fees = await client.estimateFeesPerGas();
|
|
665
|
+
if (fees.maxFeePerGas) {
|
|
666
|
+
const scale = BigInt(Math.round(feeMultiplier * 1e3));
|
|
667
|
+
maxFeePerGas = fees.maxFeePerGas * scale / 1000n;
|
|
668
|
+
}
|
|
669
|
+
if (fees.maxPriorityFeePerGas) {
|
|
670
|
+
const scale = BigInt(Math.round(feeMultiplier * 1e3));
|
|
671
|
+
maxPriorityFeePerGas = fees.maxPriorityFeePerGas * scale / 1000n;
|
|
672
|
+
}
|
|
673
|
+
} catch (error) {
|
|
674
|
+
if (debugEnabled) {
|
|
675
|
+
console.debug("[x402z-facilitator] fee estimate failed", error);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
return client.sendTransaction({
|
|
680
|
+
to: args.to,
|
|
681
|
+
data: args.data,
|
|
682
|
+
...maxFeePerGas ? { maxFeePerGas } : {},
|
|
683
|
+
...maxPriorityFeePerGas ? { maxPriorityFeePerGas } : {}
|
|
684
|
+
});
|
|
685
|
+
},
|
|
608
686
|
waitForTransactionReceipt: (args) => client.waitForTransactionReceipt(args),
|
|
609
687
|
getCode: (args) => client.getCode(args)
|
|
610
688
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -262,6 +262,38 @@ var X402zEvmFacilitator = class {
|
|
|
262
262
|
};
|
|
263
263
|
}
|
|
264
264
|
}
|
|
265
|
+
try {
|
|
266
|
+
const txRequest = {
|
|
267
|
+
address: this.batcherAddress,
|
|
268
|
+
abi: batcherAbi,
|
|
269
|
+
functionName: "batchConfidentialTransferWithAuthorization",
|
|
270
|
+
args: [
|
|
271
|
+
(0, import_viem.getAddress)(requirements.asset),
|
|
272
|
+
[
|
|
273
|
+
{
|
|
274
|
+
p: confidentialPayload.authorization,
|
|
275
|
+
encryptedAmountInput: confidentialPayload.encryptedAmountInput,
|
|
276
|
+
inputProof: confidentialPayload.inputProof,
|
|
277
|
+
sig: confidentialPayload.signature
|
|
278
|
+
}
|
|
279
|
+
]
|
|
280
|
+
]
|
|
281
|
+
};
|
|
282
|
+
const [successes] = await this.config.signer.simulateContract(txRequest);
|
|
283
|
+
if (!successes[0]) {
|
|
284
|
+
return {
|
|
285
|
+
isValid: false,
|
|
286
|
+
invalidReason: "preflight_failed",
|
|
287
|
+
payer: confidentialPayload.authorization.holder
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
} catch {
|
|
291
|
+
return {
|
|
292
|
+
isValid: false,
|
|
293
|
+
invalidReason: "preflight_failed",
|
|
294
|
+
payer: confidentialPayload.authorization.holder
|
|
295
|
+
};
|
|
296
|
+
}
|
|
265
297
|
return {
|
|
266
298
|
isValid: true,
|
|
267
299
|
payer: confidentialPayload.authorization.holder
|
|
@@ -519,6 +551,7 @@ function createFacilitatorFromEnv() {
|
|
|
519
551
|
const receiptConfirmations = process.env.FACILITATOR_RECEIPT_CONFIRMATIONS ? Number(process.env.FACILITATOR_RECEIPT_CONFIRMATIONS) : void 0;
|
|
520
552
|
const receiptPollingIntervalMs = process.env.FACILITATOR_RECEIPT_POLLING_INTERVAL_MS ? Number(process.env.FACILITATOR_RECEIPT_POLLING_INTERVAL_MS) : void 0;
|
|
521
553
|
const gasMultiplier = process.env.FACILITATOR_GAS_MULTIPLIER ? Number(process.env.FACILITATOR_GAS_MULTIPLIER) : void 0;
|
|
554
|
+
const feeMultiplier = process.env.FACILITATOR_FEE_MULTIPLIER ? Number(process.env.FACILITATOR_FEE_MULTIPLIER) : void 0;
|
|
522
555
|
const debugEnabled = process.env.X402Z_DEBUG === "1";
|
|
523
556
|
const account = (0, import_accounts.privateKeyToAccount)(privateKey);
|
|
524
557
|
if (debugEnabled) {
|
|
@@ -529,6 +562,7 @@ function createFacilitatorFromEnv() {
|
|
|
529
562
|
waitForReceipt,
|
|
530
563
|
batcherAddress,
|
|
531
564
|
gasMultiplier,
|
|
565
|
+
feeMultiplier,
|
|
532
566
|
batchIntervalMs,
|
|
533
567
|
receipt: {
|
|
534
568
|
confirmations: receiptConfirmations,
|
|
@@ -558,6 +592,8 @@ function createFacilitatorFromEnv() {
|
|
|
558
592
|
verifyTypedData: (args) => client.verifyTypedData(args),
|
|
559
593
|
writeContract: async (args) => {
|
|
560
594
|
let gas;
|
|
595
|
+
let maxFeePerGas;
|
|
596
|
+
let maxPriorityFeePerGas;
|
|
561
597
|
if (gasMultiplier && gasMultiplier > 0) {
|
|
562
598
|
try {
|
|
563
599
|
const estimated = await client.estimateContractGas({
|
|
@@ -575,17 +611,59 @@ function createFacilitatorFromEnv() {
|
|
|
575
611
|
}
|
|
576
612
|
}
|
|
577
613
|
}
|
|
614
|
+
if (feeMultiplier && feeMultiplier > 0) {
|
|
615
|
+
try {
|
|
616
|
+
const fees = await client.estimateFeesPerGas();
|
|
617
|
+
if (fees.maxFeePerGas) {
|
|
618
|
+
const scale = BigInt(Math.round(feeMultiplier * 1e3));
|
|
619
|
+
maxFeePerGas = fees.maxFeePerGas * scale / 1000n;
|
|
620
|
+
}
|
|
621
|
+
if (fees.maxPriorityFeePerGas) {
|
|
622
|
+
const scale = BigInt(Math.round(feeMultiplier * 1e3));
|
|
623
|
+
maxPriorityFeePerGas = fees.maxPriorityFeePerGas * scale / 1000n;
|
|
624
|
+
}
|
|
625
|
+
} catch (error) {
|
|
626
|
+
if (debugEnabled) {
|
|
627
|
+
console.debug("[x402z-facilitator] fee estimate failed", error);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
578
631
|
return client.writeContract({
|
|
579
632
|
...args,
|
|
580
633
|
args: args.args || [],
|
|
581
|
-
...gas ? { gas } : {}
|
|
634
|
+
...gas ? { gas } : {},
|
|
635
|
+
...maxFeePerGas ? { maxFeePerGas } : {},
|
|
636
|
+
...maxPriorityFeePerGas ? { maxPriorityFeePerGas } : {}
|
|
582
637
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
583
638
|
});
|
|
584
639
|
},
|
|
585
|
-
sendTransaction: (args) =>
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
640
|
+
sendTransaction: async (args) => {
|
|
641
|
+
let maxFeePerGas;
|
|
642
|
+
let maxPriorityFeePerGas;
|
|
643
|
+
if (feeMultiplier && feeMultiplier > 0) {
|
|
644
|
+
try {
|
|
645
|
+
const fees = await client.estimateFeesPerGas();
|
|
646
|
+
if (fees.maxFeePerGas) {
|
|
647
|
+
const scale = BigInt(Math.round(feeMultiplier * 1e3));
|
|
648
|
+
maxFeePerGas = fees.maxFeePerGas * scale / 1000n;
|
|
649
|
+
}
|
|
650
|
+
if (fees.maxPriorityFeePerGas) {
|
|
651
|
+
const scale = BigInt(Math.round(feeMultiplier * 1e3));
|
|
652
|
+
maxPriorityFeePerGas = fees.maxPriorityFeePerGas * scale / 1000n;
|
|
653
|
+
}
|
|
654
|
+
} catch (error) {
|
|
655
|
+
if (debugEnabled) {
|
|
656
|
+
console.debug("[x402z-facilitator] fee estimate failed", error);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return client.sendTransaction({
|
|
661
|
+
to: args.to,
|
|
662
|
+
data: args.data,
|
|
663
|
+
...maxFeePerGas ? { maxFeePerGas } : {},
|
|
664
|
+
...maxPriorityFeePerGas ? { maxPriorityFeePerGas } : {}
|
|
665
|
+
});
|
|
666
|
+
},
|
|
589
667
|
waitForTransactionReceipt: (args) => client.waitForTransactionReceipt(args),
|
|
590
668
|
getCode: (args) => client.getCode(args)
|
|
591
669
|
});
|