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 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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
606
- client.sendTransaction({ to: args.to, data: args.data })
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
@@ -3,7 +3,7 @@ import {
3
3
  createFacilitatorService,
4
4
  startFacilitator,
5
5
  startFacilitatorService
6
- } from "./chunk-3E55KK5J.mjs";
6
+ } from "./chunk-TDT6WJI7.mjs";
7
7
 
8
8
  // src/scheme/register.ts
9
9
  function registerX402zEvmFacilitatorScheme(facilitator, config) {
@@ -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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
587
- client.sendTransaction({ to: args.to, data: args.data })
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
  });
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createFacilitatorFromEnv,
3
3
  startFacilitator
4
- } from "../chunk-3E55KK5J.mjs";
4
+ } from "../chunk-TDT6WJI7.mjs";
5
5
  export {
6
6
  createFacilitatorFromEnv,
7
7
  startFacilitator
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x402z-facilitator",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",