x402z-facilitator 0.0.5 → 0.0.7

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/dist/bootstrap.js CHANGED
@@ -145,11 +145,14 @@ var ConfidentialEvmFacilitator = class {
145
145
  this.config = config;
146
146
  this.scheme = "erc7984-mind-v1";
147
147
  this.caipFamily = "eip155:*";
148
+ this.queue = [];
148
149
  this.hashFn = config.hashEncryptedAmountInput ?? import_x402z_shared.hashEncryptedAmountInput;
149
150
  this.clock = config.clock ?? (() => Math.floor(Date.now() / 1e3));
150
151
  this.checkUsedNonces = config.checkUsedNonces ?? true;
151
152
  this.waitForReceipt = config.waitForReceipt ?? true;
153
+ this.batchIntervalMs = Math.max(0, config.batchIntervalMs ?? 15e3);
152
154
  this.receiptOptions = config.receipt;
155
+ this.batcherAddress = (0, import_viem.getAddress)(config.batcherAddress);
153
156
  }
154
157
  getExtra(_) {
155
158
  return void 0;
@@ -276,148 +279,175 @@ var ConfidentialEvmFacilitator = class {
276
279
  network: requirements.network
277
280
  };
278
281
  }
279
- const tokenAddress = (0, import_viem.getAddress)(requirements.asset);
280
- const batcherAddress = this.config.batcherAddress ? (0, import_viem.getAddress)(this.config.batcherAddress) : void 0;
281
- const txRequest = batcherAddress ? {
282
- address: batcherAddress,
282
+ return new Promise((resolve) => {
283
+ this.queue.push({ payload, requirements, resolve });
284
+ this.scheduleFlush();
285
+ });
286
+ }
287
+ scheduleFlush() {
288
+ if (this.flushTimer) {
289
+ return;
290
+ }
291
+ const delay = this.batchIntervalMs;
292
+ this.flushTimer = setTimeout(() => {
293
+ this.flushTimer = void 0;
294
+ void this.flushQueue();
295
+ }, delay);
296
+ }
297
+ async flushQueue() {
298
+ if (this.queue.length === 0) {
299
+ return;
300
+ }
301
+ const queued = this.queue;
302
+ this.queue = [];
303
+ const byToken = /* @__PURE__ */ new Map();
304
+ for (const entry of queued) {
305
+ const tokenKey = (0, import_viem.getAddress)(entry.requirements.asset);
306
+ const group = byToken.get(tokenKey) ?? [];
307
+ group.push(entry);
308
+ byToken.set(tokenKey, group);
309
+ }
310
+ for (const [tokenAddress, entries] of byToken.entries()) {
311
+ await this.flushBatch(tokenAddress, entries);
312
+ }
313
+ if (this.queue.length > 0) {
314
+ this.scheduleFlush();
315
+ }
316
+ }
317
+ async flushBatch(tokenAddress, entries) {
318
+ const requests = entries.map((entry) => {
319
+ const confidentialPayload = entry.payload.payload;
320
+ return {
321
+ p: confidentialPayload.authorization,
322
+ encryptedAmountInput: confidentialPayload.encryptedAmountInput,
323
+ inputProof: confidentialPayload.inputProof,
324
+ sig: confidentialPayload.signature
325
+ };
326
+ });
327
+ const txRequest = {
328
+ address: this.batcherAddress,
283
329
  abi: batcherAbi,
284
330
  functionName: "batchConfidentialTransferWithAuthorization",
285
- args: [
286
- tokenAddress,
287
- [
288
- {
289
- p: confidentialPayload.authorization,
290
- encryptedAmountInput: confidentialPayload.encryptedAmountInput,
291
- inputProof: confidentialPayload.inputProof,
292
- sig: confidentialPayload.signature
293
- }
294
- ]
295
- ]
296
- } : {
297
- address: tokenAddress,
298
- abi: import_x402z_shared.confidentialTokenAbi,
299
- functionName: "confidentialTransferWithAuthorization",
300
- args: [
301
- confidentialPayload.authorization,
302
- confidentialPayload.encryptedAmountInput,
303
- confidentialPayload.inputProof,
304
- confidentialPayload.signature
305
- ]
331
+ args: [tokenAddress, requests]
306
332
  };
307
333
  if (process.env.X402Z_DEBUG === "1") {
308
334
  console.debug("[x402z-facilitator] settle tx", {
309
- batcherAddress,
335
+ batcherAddress: this.batcherAddress,
310
336
  tokenAddress,
311
337
  functionName: txRequest.functionName,
312
- to: txRequest.address
338
+ to: txRequest.address,
339
+ size: requests.length
313
340
  });
314
341
  }
315
- const txHash = await this.config.signer.writeContract(txRequest);
342
+ let txHash;
343
+ try {
344
+ txHash = await this.config.signer.writeContract(txRequest);
345
+ } catch (error) {
346
+ for (const entry of entries) {
347
+ const confidentialPayload = entry.payload.payload;
348
+ entry.resolve({
349
+ success: false,
350
+ errorReason: "settlement_failed",
351
+ payer: confidentialPayload.authorization.holder,
352
+ transaction: "",
353
+ network: entry.requirements.network
354
+ });
355
+ }
356
+ return;
357
+ }
316
358
  if (process.env.X402Z_DEBUG === "1") {
317
359
  console.debug("[x402z-facilitator] tx submitted", txHash);
318
360
  }
319
- let batchResult;
361
+ let receipt;
320
362
  if (this.waitForReceipt) {
321
- const receipt = await this.config.signer.waitForTransactionReceipt({
322
- hash: txHash
323
- });
363
+ receipt = await this.config.signer.waitForTransactionReceipt({ hash: txHash });
324
364
  if (process.env.X402Z_DEBUG === "1") {
325
365
  console.debug("[x402z-facilitator] tx receipt", receipt);
326
366
  }
327
367
  if (receipt.status !== "success") {
328
- return {
329
- success: false,
330
- errorReason: "settlement_failed",
368
+ for (const entry of entries) {
369
+ const confidentialPayload = entry.payload.payload;
370
+ entry.resolve({
371
+ success: false,
372
+ errorReason: "settlement_failed",
373
+ payer: confidentialPayload.authorization.holder,
374
+ transaction: txHash,
375
+ network: entry.requirements.network
376
+ });
377
+ }
378
+ return;
379
+ }
380
+ }
381
+ if (!this.waitForReceipt) {
382
+ entries.forEach((entry) => {
383
+ const confidentialPayload = entry.payload.payload;
384
+ entry.resolve({
385
+ success: true,
331
386
  payer: confidentialPayload.authorization.holder,
332
387
  transaction: txHash,
333
- network: requirements.network
334
- };
335
- }
336
- if (batcherAddress) {
337
- let batchSucceeded = false;
338
- const logs = "logs" in receipt ? receipt.logs ?? [] : [];
339
- for (const log of logs) {
340
- if (!log || !("address" in log) || !log.address) {
341
- continue;
342
- }
343
- if (!(0, import_viem.isAddressEqual)((0, import_viem.getAddress)(log.address), batcherAddress)) {
344
- continue;
345
- }
346
- const decoded = (0, import_viem.decodeEventLog)({
347
- abi: batcherAbi,
348
- data: log.data,
349
- topics: log.topics
388
+ network: entry.requirements.network
389
+ });
390
+ });
391
+ return;
392
+ }
393
+ const batchResults = /* @__PURE__ */ new Map();
394
+ if (receipt?.logs) {
395
+ for (const log of receipt.logs) {
396
+ if (!log?.address) {
397
+ continue;
398
+ }
399
+ if (!(0, import_viem.isAddressEqual)((0, import_viem.getAddress)(log.address), this.batcherAddress)) {
400
+ continue;
401
+ }
402
+ const decoded = (0, import_viem.decodeEventLog)({
403
+ abi: batcherAbi,
404
+ data: log.data,
405
+ topics: log.topics
406
+ });
407
+ if (process.env.X402Z_DEBUG === "1") {
408
+ console.debug("[x402z-facilitator] batch log", decoded);
409
+ }
410
+ if (decoded.eventName === "BatchItemSuccess") {
411
+ const args = decoded.args;
412
+ batchResults.set(Number(args.index), {
413
+ success: true,
414
+ transferredHandle: args.transferredHandle
415
+ });
416
+ } else if (decoded.eventName === "BatchItemFailure") {
417
+ const args = decoded.args;
418
+ batchResults.set(Number(args.index), {
419
+ success: false,
420
+ failureReason: args.reason
350
421
  });
351
- if (process.env.X402Z_DEBUG === "1") {
352
- console.debug("[x402z-facilitator] batch log", decoded);
353
- }
354
- if (decoded.eventName === "BatchItemSuccess") {
355
- const args = decoded.args;
356
- if (Number(args.index) === 0) {
357
- batchSucceeded = true;
358
- batchResult = {
359
- index: Number(args.index),
360
- success: true,
361
- transferredHandle: args.transferredHandle
362
- };
363
- break;
364
- }
365
- }
366
- if (decoded.eventName === "BatchItemFailure") {
367
- const args = decoded.args;
368
- if (Number(args.index) === 0) {
369
- batchResult = {
370
- index: Number(args.index),
371
- success: false,
372
- failureReason: args.reason
373
- };
374
- if (process.env.X402Z_DEBUG === "1") {
375
- try {
376
- await this.config.signer.readContract({
377
- address: tokenAddress,
378
- abi: import_x402z_shared.confidentialTokenAbi,
379
- functionName: "confidentialTransferWithAuthorization",
380
- args: [
381
- confidentialPayload.authorization,
382
- confidentialPayload.encryptedAmountInput,
383
- confidentialPayload.inputProof,
384
- confidentialPayload.signature
385
- ]
386
- });
387
- } catch (innerError) {
388
- console.debug("[x402z-facilitator] batch fallback call failed", innerError);
389
- }
390
- }
391
- return {
392
- success: false,
393
- errorReason: "settlement_failed",
394
- payer: confidentialPayload.authorization.holder,
395
- transaction: txHash,
396
- network: requirements.network,
397
- ...batchResult ? { batch: batchResult } : {}
398
- };
399
- }
400
- }
401
422
  }
402
- if (!batchSucceeded) {
403
- return {
423
+ }
424
+ }
425
+ entries.forEach((entry, index) => {
426
+ const confidentialPayload = entry.payload.payload;
427
+ const batchResult = batchResults.get(index);
428
+ if (!batchResult || !batchResult.success) {
429
+ entry.resolve(
430
+ {
404
431
  success: false,
405
432
  errorReason: "settlement_failed",
406
433
  payer: confidentialPayload.authorization.holder,
407
434
  transaction: txHash,
408
- network: requirements.network,
409
- ...batchResult ? { batch: batchResult } : {}
410
- };
411
- }
435
+ network: entry.requirements.network,
436
+ ...batchResult ? { batch: { index, ...batchResult } } : {}
437
+ }
438
+ );
439
+ return;
412
440
  }
413
- }
414
- return {
415
- success: true,
416
- payer: confidentialPayload.authorization.holder,
417
- transaction: txHash,
418
- network: requirements.network,
419
- ...batchResult ? { batch: batchResult } : {}
420
- };
441
+ entry.resolve(
442
+ {
443
+ success: true,
444
+ payer: confidentialPayload.authorization.holder,
445
+ transaction: txHash,
446
+ network: entry.requirements.network,
447
+ batch: { index, ...batchResult }
448
+ }
449
+ );
450
+ });
421
451
  }
422
452
  };
423
453
 
@@ -436,6 +466,10 @@ function createConfidentialFacilitatorFromEnv() {
436
466
  const networks = (process.env.FACILITATOR_NETWORKS ?? "eip155:11155111").split(",").map((network) => network.trim()).filter(Boolean);
437
467
  const waitForReceipt = (process.env.FACILITATOR_WAIT_FOR_RECEIPT ?? "true") === "true";
438
468
  const batcherAddress = process.env.FACILITATOR_BATCHER_ADDRESS;
469
+ if (!batcherAddress) {
470
+ throw new Error("FACILITATOR_BATCHER_ADDRESS is required");
471
+ }
472
+ const batchIntervalMs = process.env.FACILITATOR_BATCH_INTERVAL_MS ? Number(process.env.FACILITATOR_BATCH_INTERVAL_MS) : void 0;
439
473
  const receiptTimeoutMs = process.env.FACILITATOR_RECEIPT_TIMEOUT_MS ? Number(process.env.FACILITATOR_RECEIPT_TIMEOUT_MS) : void 0;
440
474
  const receiptConfirmations = process.env.FACILITATOR_RECEIPT_CONFIRMATIONS ? Number(process.env.FACILITATOR_RECEIPT_CONFIRMATIONS) : void 0;
441
475
  const receiptPollingIntervalMs = process.env.FACILITATOR_RECEIPT_POLLING_INTERVAL_MS ? Number(process.env.FACILITATOR_RECEIPT_POLLING_INTERVAL_MS) : void 0;
@@ -450,6 +484,7 @@ function createConfidentialFacilitatorFromEnv() {
450
484
  waitForReceipt,
451
485
  batcherAddress,
452
486
  gasMultiplier,
487
+ batchIntervalMs,
453
488
  receipt: {
454
489
  confirmations: receiptConfirmations,
455
490
  timeoutMs: receiptTimeoutMs,
@@ -517,6 +552,7 @@ function createConfidentialFacilitatorFromEnv() {
517
552
  signer,
518
553
  waitForReceipt,
519
554
  batcherAddress,
555
+ batchIntervalMs,
520
556
  receipt: {
521
557
  confirmations: receiptConfirmations,
522
558
  timeoutMs: receiptTimeoutMs,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createConfidentialFacilitatorFromEnv,
3
3
  startConfidentialFacilitator
4
- } from "./chunk-4UDK7NYX.mjs";
4
+ } from "./chunk-FABCSL7D.mjs";
5
5
  export {
6
6
  createConfidentialFacilitatorFromEnv,
7
7
  startConfidentialFacilitator
@@ -0,0 +1,576 @@
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/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.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.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 ConfidentialEvmFacilitator = 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
+ return {
258
+ isValid: true,
259
+ payer: confidentialPayload.authorization.holder
260
+ };
261
+ }
262
+ async settle(payload, requirements) {
263
+ const valid = await this.verify(payload, requirements);
264
+ const confidentialPayload = payload.payload;
265
+ if (!valid.isValid) {
266
+ return {
267
+ success: false,
268
+ errorReason: valid.invalidReason ?? "invalid_payment",
269
+ payer: confidentialPayload.authorization.holder,
270
+ transaction: "",
271
+ network: requirements.network
272
+ };
273
+ }
274
+ return new Promise((resolve) => {
275
+ this.queue.push({ payload, requirements, resolve });
276
+ this.scheduleFlush();
277
+ });
278
+ }
279
+ scheduleFlush() {
280
+ if (this.flushTimer) {
281
+ return;
282
+ }
283
+ const delay = this.batchIntervalMs;
284
+ this.flushTimer = setTimeout(() => {
285
+ this.flushTimer = void 0;
286
+ void this.flushQueue();
287
+ }, delay);
288
+ }
289
+ async flushQueue() {
290
+ if (this.queue.length === 0) {
291
+ return;
292
+ }
293
+ const queued = this.queue;
294
+ this.queue = [];
295
+ const byToken = /* @__PURE__ */ new Map();
296
+ for (const entry of queued) {
297
+ const tokenKey = getAddress(entry.requirements.asset);
298
+ const group = byToken.get(tokenKey) ?? [];
299
+ group.push(entry);
300
+ byToken.set(tokenKey, group);
301
+ }
302
+ for (const [tokenAddress, entries] of byToken.entries()) {
303
+ await this.flushBatch(tokenAddress, entries);
304
+ }
305
+ if (this.queue.length > 0) {
306
+ this.scheduleFlush();
307
+ }
308
+ }
309
+ async flushBatch(tokenAddress, entries) {
310
+ const requests = entries.map((entry) => {
311
+ const confidentialPayload = entry.payload.payload;
312
+ return {
313
+ p: confidentialPayload.authorization,
314
+ encryptedAmountInput: confidentialPayload.encryptedAmountInput,
315
+ inputProof: confidentialPayload.inputProof,
316
+ sig: confidentialPayload.signature
317
+ };
318
+ });
319
+ const txRequest = {
320
+ address: this.batcherAddress,
321
+ abi: batcherAbi,
322
+ functionName: "batchConfidentialTransferWithAuthorization",
323
+ args: [tokenAddress, requests]
324
+ };
325
+ if (process.env.X402Z_DEBUG === "1") {
326
+ console.debug("[x402z-facilitator] settle tx", {
327
+ batcherAddress: this.batcherAddress,
328
+ tokenAddress,
329
+ functionName: txRequest.functionName,
330
+ to: txRequest.address,
331
+ size: requests.length
332
+ });
333
+ }
334
+ let txHash;
335
+ try {
336
+ txHash = await this.config.signer.writeContract(txRequest);
337
+ } catch (error) {
338
+ for (const entry of entries) {
339
+ const confidentialPayload = entry.payload.payload;
340
+ entry.resolve({
341
+ success: false,
342
+ errorReason: "settlement_failed",
343
+ payer: confidentialPayload.authorization.holder,
344
+ transaction: "",
345
+ network: entry.requirements.network
346
+ });
347
+ }
348
+ return;
349
+ }
350
+ if (process.env.X402Z_DEBUG === "1") {
351
+ console.debug("[x402z-facilitator] tx submitted", txHash);
352
+ }
353
+ let receipt;
354
+ if (this.waitForReceipt) {
355
+ receipt = await this.config.signer.waitForTransactionReceipt({ hash: txHash });
356
+ if (process.env.X402Z_DEBUG === "1") {
357
+ console.debug("[x402z-facilitator] tx receipt", receipt);
358
+ }
359
+ if (receipt.status !== "success") {
360
+ for (const entry of entries) {
361
+ const confidentialPayload = entry.payload.payload;
362
+ entry.resolve({
363
+ success: false,
364
+ errorReason: "settlement_failed",
365
+ payer: confidentialPayload.authorization.holder,
366
+ transaction: txHash,
367
+ network: entry.requirements.network
368
+ });
369
+ }
370
+ return;
371
+ }
372
+ }
373
+ if (!this.waitForReceipt) {
374
+ entries.forEach((entry) => {
375
+ const confidentialPayload = entry.payload.payload;
376
+ entry.resolve({
377
+ success: true,
378
+ payer: confidentialPayload.authorization.holder,
379
+ transaction: txHash,
380
+ network: entry.requirements.network
381
+ });
382
+ });
383
+ return;
384
+ }
385
+ const batchResults = /* @__PURE__ */ new Map();
386
+ if (receipt?.logs) {
387
+ for (const log of receipt.logs) {
388
+ if (!log?.address) {
389
+ continue;
390
+ }
391
+ if (!isAddressEqual(getAddress(log.address), this.batcherAddress)) {
392
+ continue;
393
+ }
394
+ const decoded = decodeEventLog({
395
+ abi: batcherAbi,
396
+ data: log.data,
397
+ topics: log.topics
398
+ });
399
+ if (process.env.X402Z_DEBUG === "1") {
400
+ console.debug("[x402z-facilitator] batch log", decoded);
401
+ }
402
+ if (decoded.eventName === "BatchItemSuccess") {
403
+ const args = decoded.args;
404
+ batchResults.set(Number(args.index), {
405
+ success: true,
406
+ transferredHandle: args.transferredHandle
407
+ });
408
+ } else if (decoded.eventName === "BatchItemFailure") {
409
+ const args = decoded.args;
410
+ batchResults.set(Number(args.index), {
411
+ success: false,
412
+ failureReason: args.reason
413
+ });
414
+ }
415
+ }
416
+ }
417
+ entries.forEach((entry, index) => {
418
+ const confidentialPayload = entry.payload.payload;
419
+ const batchResult = batchResults.get(index);
420
+ if (!batchResult || !batchResult.success) {
421
+ entry.resolve(
422
+ {
423
+ success: false,
424
+ errorReason: "settlement_failed",
425
+ payer: confidentialPayload.authorization.holder,
426
+ transaction: txHash,
427
+ network: entry.requirements.network,
428
+ ...batchResult ? { batch: { index, ...batchResult } } : {}
429
+ }
430
+ );
431
+ return;
432
+ }
433
+ entry.resolve(
434
+ {
435
+ success: true,
436
+ payer: confidentialPayload.authorization.holder,
437
+ transaction: txHash,
438
+ network: entry.requirements.network,
439
+ batch: { index, ...batchResult }
440
+ }
441
+ );
442
+ });
443
+ }
444
+ };
445
+
446
+ // src/bootstrap.ts
447
+ function requireEnv(key) {
448
+ const value = process.env[key];
449
+ if (!value) {
450
+ throw new Error(`Missing required env var: ${key}`);
451
+ }
452
+ return value;
453
+ }
454
+ function createConfidentialFacilitatorFromEnv() {
455
+ const privateKey = requireEnv("FACILITATOR_EVM_PRIVATE_KEY");
456
+ const chainId = Number(process.env.FACILITATOR_EVM_CHAIN_ID ?? "11155111");
457
+ const rpcUrl = requireEnv("FACILITATOR_EVM_RPC_URL");
458
+ const networks = (process.env.FACILITATOR_NETWORKS ?? "eip155:11155111").split(",").map((network) => network.trim()).filter(Boolean);
459
+ const waitForReceipt = (process.env.FACILITATOR_WAIT_FOR_RECEIPT ?? "true") === "true";
460
+ const batcherAddress = process.env.FACILITATOR_BATCHER_ADDRESS;
461
+ if (!batcherAddress) {
462
+ throw new Error("FACILITATOR_BATCHER_ADDRESS is required");
463
+ }
464
+ const batchIntervalMs = process.env.FACILITATOR_BATCH_INTERVAL_MS ? Number(process.env.FACILITATOR_BATCH_INTERVAL_MS) : void 0;
465
+ const receiptTimeoutMs = process.env.FACILITATOR_RECEIPT_TIMEOUT_MS ? Number(process.env.FACILITATOR_RECEIPT_TIMEOUT_MS) : void 0;
466
+ const receiptConfirmations = process.env.FACILITATOR_RECEIPT_CONFIRMATIONS ? Number(process.env.FACILITATOR_RECEIPT_CONFIRMATIONS) : void 0;
467
+ const receiptPollingIntervalMs = process.env.FACILITATOR_RECEIPT_POLLING_INTERVAL_MS ? Number(process.env.FACILITATOR_RECEIPT_POLLING_INTERVAL_MS) : void 0;
468
+ const gasMultiplier = process.env.FACILITATOR_GAS_MULTIPLIER ? Number(process.env.FACILITATOR_GAS_MULTIPLIER) : void 0;
469
+ const debugEnabled = process.env.X402Z_DEBUG === "1";
470
+ const account = privateKeyToAccount(privateKey);
471
+ if (debugEnabled) {
472
+ console.debug("[x402z-facilitator] config", {
473
+ chainId,
474
+ networks,
475
+ rpcUrl,
476
+ waitForReceipt,
477
+ batcherAddress,
478
+ gasMultiplier,
479
+ batchIntervalMs,
480
+ receipt: {
481
+ confirmations: receiptConfirmations,
482
+ timeoutMs: receiptTimeoutMs,
483
+ pollingIntervalMs: receiptPollingIntervalMs
484
+ },
485
+ address: account.address
486
+ });
487
+ }
488
+ const client = createWalletClient({
489
+ account,
490
+ chain: {
491
+ id: chainId,
492
+ name: "custom",
493
+ nativeCurrency: { name: "native", symbol: "NATIVE", decimals: 18 },
494
+ rpcUrls: { default: { http: [rpcUrl] } }
495
+ },
496
+ transport: http(rpcUrl)
497
+ }).extend(publicActions);
498
+ const signer = toFacilitatorEvmSigner({
499
+ address: account.address,
500
+ readContract: (args) => client.readContract({
501
+ ...args,
502
+ args: args.args || []
503
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
504
+ }),
505
+ verifyTypedData: (args) => client.verifyTypedData(args),
506
+ writeContract: async (args) => {
507
+ let gas;
508
+ if (gasMultiplier && gasMultiplier > 0) {
509
+ try {
510
+ const estimated = await client.estimateContractGas({
511
+ ...args,
512
+ args: args.args || [],
513
+ account
514
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
515
+ });
516
+ const scale = BigInt(Math.round(gasMultiplier * 1e3));
517
+ const scaled = estimated * scale / 1000n;
518
+ gas = scaled > estimated ? scaled : estimated;
519
+ } catch (error) {
520
+ if (debugEnabled) {
521
+ console.debug("[x402z-facilitator] gas estimate failed", error);
522
+ }
523
+ }
524
+ }
525
+ return client.writeContract({
526
+ ...args,
527
+ args: args.args || [],
528
+ ...gas ? { gas } : {}
529
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
530
+ });
531
+ },
532
+ sendTransaction: (args) => (
533
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
534
+ client.sendTransaction({ to: args.to, data: args.data })
535
+ ),
536
+ waitForTransactionReceipt: (args) => client.waitForTransactionReceipt(args),
537
+ getCode: (args) => client.getCode(args)
538
+ });
539
+ const facilitator = new x402Facilitator();
540
+ for (const network of networks) {
541
+ facilitator.register(
542
+ network,
543
+ new ConfidentialEvmFacilitator({
544
+ signer,
545
+ waitForReceipt,
546
+ batcherAddress,
547
+ batchIntervalMs,
548
+ receipt: {
549
+ confirmations: receiptConfirmations,
550
+ timeoutMs: receiptTimeoutMs,
551
+ pollingIntervalMs: receiptPollingIntervalMs
552
+ }
553
+ })
554
+ );
555
+ }
556
+ return facilitator;
557
+ }
558
+ function startConfidentialFacilitator() {
559
+ const facilitator = createConfidentialFacilitatorFromEnv();
560
+ const port = Number(process.env.FACILITATOR_PORT ?? "8040");
561
+ const server = createFacilitatorService({ facilitator, port });
562
+ server.listen(port);
563
+ return { port };
564
+ }
565
+ if (__require.main === module) {
566
+ const { port } = startConfidentialFacilitator();
567
+ console.log(`Confidential facilitator listening on :${port}`);
568
+ }
569
+
570
+ export {
571
+ createFacilitatorService,
572
+ startFacilitatorService,
573
+ ConfidentialEvmFacilitator,
574
+ createConfidentialFacilitatorFromEnv,
575
+ startConfidentialFacilitator
576
+ };
package/dist/index.d.mts CHANGED
@@ -7,10 +7,11 @@ export { startConfidentialFacilitator } from './bootstrap.mjs';
7
7
 
8
8
  type ConfidentialFacilitatorConfig = {
9
9
  signer: FacilitatorEvmSigner;
10
- batcherAddress?: `0x${string}`;
10
+ batcherAddress: `0x${string}`;
11
11
  hashEncryptedAmountInput?: (encryptedAmountInput: `0x${string}`) => `0x${string}`;
12
12
  checkUsedNonces?: boolean;
13
13
  clock?: () => number;
14
+ batchIntervalMs?: number;
14
15
  waitForReceipt?: boolean;
15
16
  receipt?: {
16
17
  confirmations?: number;
@@ -26,12 +27,19 @@ declare class ConfidentialEvmFacilitator implements SchemeNetworkFacilitator {
26
27
  private readonly clock;
27
28
  private readonly checkUsedNonces;
28
29
  private readonly waitForReceipt;
30
+ private readonly batchIntervalMs;
31
+ private readonly batcherAddress;
29
32
  private readonly receiptOptions?;
33
+ private flushTimer?;
34
+ private queue;
30
35
  constructor(config: ConfidentialFacilitatorConfig);
31
36
  getExtra(_: string): Record<string, unknown> | undefined;
32
37
  getSigners(_: string): string[];
33
38
  verify(payload: PaymentPayload, requirements: PaymentRequirements): Promise<VerifyResponse>;
34
39
  settle(payload: PaymentPayload, requirements: PaymentRequirements): Promise<SettleResponse>;
40
+ private scheduleFlush;
41
+ private flushQueue;
42
+ private flushBatch;
35
43
  }
36
44
 
37
45
  type ConfidentialFacilitatorRegisterConfig = ConfidentialFacilitatorConfig & {
package/dist/index.d.ts CHANGED
@@ -7,10 +7,11 @@ export { startConfidentialFacilitator } from './bootstrap.js';
7
7
 
8
8
  type ConfidentialFacilitatorConfig = {
9
9
  signer: FacilitatorEvmSigner;
10
- batcherAddress?: `0x${string}`;
10
+ batcherAddress: `0x${string}`;
11
11
  hashEncryptedAmountInput?: (encryptedAmountInput: `0x${string}`) => `0x${string}`;
12
12
  checkUsedNonces?: boolean;
13
13
  clock?: () => number;
14
+ batchIntervalMs?: number;
14
15
  waitForReceipt?: boolean;
15
16
  receipt?: {
16
17
  confirmations?: number;
@@ -26,12 +27,19 @@ declare class ConfidentialEvmFacilitator implements SchemeNetworkFacilitator {
26
27
  private readonly clock;
27
28
  private readonly checkUsedNonces;
28
29
  private readonly waitForReceipt;
30
+ private readonly batchIntervalMs;
31
+ private readonly batcherAddress;
29
32
  private readonly receiptOptions?;
33
+ private flushTimer?;
34
+ private queue;
30
35
  constructor(config: ConfidentialFacilitatorConfig);
31
36
  getExtra(_: string): Record<string, unknown> | undefined;
32
37
  getSigners(_: string): string[];
33
38
  verify(payload: PaymentPayload, requirements: PaymentRequirements): Promise<VerifyResponse>;
34
39
  settle(payload: PaymentPayload, requirements: PaymentRequirements): Promise<SettleResponse>;
40
+ private scheduleFlush;
41
+ private flushQueue;
42
+ private flushBatch;
35
43
  }
36
44
 
37
45
  type ConfidentialFacilitatorRegisterConfig = ConfidentialFacilitatorConfig & {
package/dist/index.js CHANGED
@@ -93,11 +93,14 @@ var ConfidentialEvmFacilitator = class {
93
93
  this.config = config;
94
94
  this.scheme = "erc7984-mind-v1";
95
95
  this.caipFamily = "eip155:*";
96
+ this.queue = [];
96
97
  this.hashFn = config.hashEncryptedAmountInput ?? import_x402z_shared.hashEncryptedAmountInput;
97
98
  this.clock = config.clock ?? (() => Math.floor(Date.now() / 1e3));
98
99
  this.checkUsedNonces = config.checkUsedNonces ?? true;
99
100
  this.waitForReceipt = config.waitForReceipt ?? true;
101
+ this.batchIntervalMs = Math.max(0, config.batchIntervalMs ?? 15e3);
100
102
  this.receiptOptions = config.receipt;
103
+ this.batcherAddress = (0, import_viem.getAddress)(config.batcherAddress);
101
104
  }
102
105
  getExtra(_) {
103
106
  return void 0;
@@ -224,148 +227,175 @@ var ConfidentialEvmFacilitator = class {
224
227
  network: requirements.network
225
228
  };
226
229
  }
227
- const tokenAddress = (0, import_viem.getAddress)(requirements.asset);
228
- const batcherAddress = this.config.batcherAddress ? (0, import_viem.getAddress)(this.config.batcherAddress) : void 0;
229
- const txRequest = batcherAddress ? {
230
- address: batcherAddress,
230
+ return new Promise((resolve) => {
231
+ this.queue.push({ payload, requirements, resolve });
232
+ this.scheduleFlush();
233
+ });
234
+ }
235
+ scheduleFlush() {
236
+ if (this.flushTimer) {
237
+ return;
238
+ }
239
+ const delay = this.batchIntervalMs;
240
+ this.flushTimer = setTimeout(() => {
241
+ this.flushTimer = void 0;
242
+ void this.flushQueue();
243
+ }, delay);
244
+ }
245
+ async flushQueue() {
246
+ if (this.queue.length === 0) {
247
+ return;
248
+ }
249
+ const queued = this.queue;
250
+ this.queue = [];
251
+ const byToken = /* @__PURE__ */ new Map();
252
+ for (const entry of queued) {
253
+ const tokenKey = (0, import_viem.getAddress)(entry.requirements.asset);
254
+ const group = byToken.get(tokenKey) ?? [];
255
+ group.push(entry);
256
+ byToken.set(tokenKey, group);
257
+ }
258
+ for (const [tokenAddress, entries] of byToken.entries()) {
259
+ await this.flushBatch(tokenAddress, entries);
260
+ }
261
+ if (this.queue.length > 0) {
262
+ this.scheduleFlush();
263
+ }
264
+ }
265
+ async flushBatch(tokenAddress, entries) {
266
+ const requests = entries.map((entry) => {
267
+ const confidentialPayload = entry.payload.payload;
268
+ return {
269
+ p: confidentialPayload.authorization,
270
+ encryptedAmountInput: confidentialPayload.encryptedAmountInput,
271
+ inputProof: confidentialPayload.inputProof,
272
+ sig: confidentialPayload.signature
273
+ };
274
+ });
275
+ const txRequest = {
276
+ address: this.batcherAddress,
231
277
  abi: batcherAbi,
232
278
  functionName: "batchConfidentialTransferWithAuthorization",
233
- args: [
234
- tokenAddress,
235
- [
236
- {
237
- p: confidentialPayload.authorization,
238
- encryptedAmountInput: confidentialPayload.encryptedAmountInput,
239
- inputProof: confidentialPayload.inputProof,
240
- sig: confidentialPayload.signature
241
- }
242
- ]
243
- ]
244
- } : {
245
- address: tokenAddress,
246
- abi: import_x402z_shared.confidentialTokenAbi,
247
- functionName: "confidentialTransferWithAuthorization",
248
- args: [
249
- confidentialPayload.authorization,
250
- confidentialPayload.encryptedAmountInput,
251
- confidentialPayload.inputProof,
252
- confidentialPayload.signature
253
- ]
279
+ args: [tokenAddress, requests]
254
280
  };
255
281
  if (process.env.X402Z_DEBUG === "1") {
256
282
  console.debug("[x402z-facilitator] settle tx", {
257
- batcherAddress,
283
+ batcherAddress: this.batcherAddress,
258
284
  tokenAddress,
259
285
  functionName: txRequest.functionName,
260
- to: txRequest.address
286
+ to: txRequest.address,
287
+ size: requests.length
261
288
  });
262
289
  }
263
- const txHash = await this.config.signer.writeContract(txRequest);
290
+ let txHash;
291
+ try {
292
+ txHash = await this.config.signer.writeContract(txRequest);
293
+ } catch (error) {
294
+ for (const entry of entries) {
295
+ const confidentialPayload = entry.payload.payload;
296
+ entry.resolve({
297
+ success: false,
298
+ errorReason: "settlement_failed",
299
+ payer: confidentialPayload.authorization.holder,
300
+ transaction: "",
301
+ network: entry.requirements.network
302
+ });
303
+ }
304
+ return;
305
+ }
264
306
  if (process.env.X402Z_DEBUG === "1") {
265
307
  console.debug("[x402z-facilitator] tx submitted", txHash);
266
308
  }
267
- let batchResult;
309
+ let receipt;
268
310
  if (this.waitForReceipt) {
269
- const receipt = await this.config.signer.waitForTransactionReceipt({
270
- hash: txHash
271
- });
311
+ receipt = await this.config.signer.waitForTransactionReceipt({ hash: txHash });
272
312
  if (process.env.X402Z_DEBUG === "1") {
273
313
  console.debug("[x402z-facilitator] tx receipt", receipt);
274
314
  }
275
315
  if (receipt.status !== "success") {
276
- return {
277
- success: false,
278
- errorReason: "settlement_failed",
316
+ for (const entry of entries) {
317
+ const confidentialPayload = entry.payload.payload;
318
+ entry.resolve({
319
+ success: false,
320
+ errorReason: "settlement_failed",
321
+ payer: confidentialPayload.authorization.holder,
322
+ transaction: txHash,
323
+ network: entry.requirements.network
324
+ });
325
+ }
326
+ return;
327
+ }
328
+ }
329
+ if (!this.waitForReceipt) {
330
+ entries.forEach((entry) => {
331
+ const confidentialPayload = entry.payload.payload;
332
+ entry.resolve({
333
+ success: true,
279
334
  payer: confidentialPayload.authorization.holder,
280
335
  transaction: txHash,
281
- network: requirements.network
282
- };
283
- }
284
- if (batcherAddress) {
285
- let batchSucceeded = false;
286
- const logs = "logs" in receipt ? receipt.logs ?? [] : [];
287
- for (const log of logs) {
288
- if (!log || !("address" in log) || !log.address) {
289
- continue;
290
- }
291
- if (!(0, import_viem.isAddressEqual)((0, import_viem.getAddress)(log.address), batcherAddress)) {
292
- continue;
293
- }
294
- const decoded = (0, import_viem.decodeEventLog)({
295
- abi: batcherAbi,
296
- data: log.data,
297
- topics: log.topics
336
+ network: entry.requirements.network
337
+ });
338
+ });
339
+ return;
340
+ }
341
+ const batchResults = /* @__PURE__ */ new Map();
342
+ if (receipt?.logs) {
343
+ for (const log of receipt.logs) {
344
+ if (!log?.address) {
345
+ continue;
346
+ }
347
+ if (!(0, import_viem.isAddressEqual)((0, import_viem.getAddress)(log.address), this.batcherAddress)) {
348
+ continue;
349
+ }
350
+ const decoded = (0, import_viem.decodeEventLog)({
351
+ abi: batcherAbi,
352
+ data: log.data,
353
+ topics: log.topics
354
+ });
355
+ if (process.env.X402Z_DEBUG === "1") {
356
+ console.debug("[x402z-facilitator] batch log", decoded);
357
+ }
358
+ if (decoded.eventName === "BatchItemSuccess") {
359
+ const args = decoded.args;
360
+ batchResults.set(Number(args.index), {
361
+ success: true,
362
+ transferredHandle: args.transferredHandle
363
+ });
364
+ } else if (decoded.eventName === "BatchItemFailure") {
365
+ const args = decoded.args;
366
+ batchResults.set(Number(args.index), {
367
+ success: false,
368
+ failureReason: args.reason
298
369
  });
299
- if (process.env.X402Z_DEBUG === "1") {
300
- console.debug("[x402z-facilitator] batch log", decoded);
301
- }
302
- if (decoded.eventName === "BatchItemSuccess") {
303
- const args = decoded.args;
304
- if (Number(args.index) === 0) {
305
- batchSucceeded = true;
306
- batchResult = {
307
- index: Number(args.index),
308
- success: true,
309
- transferredHandle: args.transferredHandle
310
- };
311
- break;
312
- }
313
- }
314
- if (decoded.eventName === "BatchItemFailure") {
315
- const args = decoded.args;
316
- if (Number(args.index) === 0) {
317
- batchResult = {
318
- index: Number(args.index),
319
- success: false,
320
- failureReason: args.reason
321
- };
322
- if (process.env.X402Z_DEBUG === "1") {
323
- try {
324
- await this.config.signer.readContract({
325
- address: tokenAddress,
326
- abi: import_x402z_shared.confidentialTokenAbi,
327
- functionName: "confidentialTransferWithAuthorization",
328
- args: [
329
- confidentialPayload.authorization,
330
- confidentialPayload.encryptedAmountInput,
331
- confidentialPayload.inputProof,
332
- confidentialPayload.signature
333
- ]
334
- });
335
- } catch (innerError) {
336
- console.debug("[x402z-facilitator] batch fallback call failed", innerError);
337
- }
338
- }
339
- return {
340
- success: false,
341
- errorReason: "settlement_failed",
342
- payer: confidentialPayload.authorization.holder,
343
- transaction: txHash,
344
- network: requirements.network,
345
- ...batchResult ? { batch: batchResult } : {}
346
- };
347
- }
348
- }
349
370
  }
350
- if (!batchSucceeded) {
351
- return {
371
+ }
372
+ }
373
+ entries.forEach((entry, index) => {
374
+ const confidentialPayload = entry.payload.payload;
375
+ const batchResult = batchResults.get(index);
376
+ if (!batchResult || !batchResult.success) {
377
+ entry.resolve(
378
+ {
352
379
  success: false,
353
380
  errorReason: "settlement_failed",
354
381
  payer: confidentialPayload.authorization.holder,
355
382
  transaction: txHash,
356
- network: requirements.network,
357
- ...batchResult ? { batch: batchResult } : {}
358
- };
359
- }
383
+ network: entry.requirements.network,
384
+ ...batchResult ? { batch: { index, ...batchResult } } : {}
385
+ }
386
+ );
387
+ return;
360
388
  }
361
- }
362
- return {
363
- success: true,
364
- payer: confidentialPayload.authorization.holder,
365
- transaction: txHash,
366
- network: requirements.network,
367
- ...batchResult ? { batch: batchResult } : {}
368
- };
389
+ entry.resolve(
390
+ {
391
+ success: true,
392
+ payer: confidentialPayload.authorization.holder,
393
+ transaction: txHash,
394
+ network: entry.requirements.network,
395
+ batch: { index, ...batchResult }
396
+ }
397
+ );
398
+ });
369
399
  }
370
400
  };
371
401
 
@@ -455,6 +485,10 @@ function createConfidentialFacilitatorFromEnv() {
455
485
  const networks = (process.env.FACILITATOR_NETWORKS ?? "eip155:11155111").split(",").map((network) => network.trim()).filter(Boolean);
456
486
  const waitForReceipt = (process.env.FACILITATOR_WAIT_FOR_RECEIPT ?? "true") === "true";
457
487
  const batcherAddress = process.env.FACILITATOR_BATCHER_ADDRESS;
488
+ if (!batcherAddress) {
489
+ throw new Error("FACILITATOR_BATCHER_ADDRESS is required");
490
+ }
491
+ const batchIntervalMs = process.env.FACILITATOR_BATCH_INTERVAL_MS ? Number(process.env.FACILITATOR_BATCH_INTERVAL_MS) : void 0;
458
492
  const receiptTimeoutMs = process.env.FACILITATOR_RECEIPT_TIMEOUT_MS ? Number(process.env.FACILITATOR_RECEIPT_TIMEOUT_MS) : void 0;
459
493
  const receiptConfirmations = process.env.FACILITATOR_RECEIPT_CONFIRMATIONS ? Number(process.env.FACILITATOR_RECEIPT_CONFIRMATIONS) : void 0;
460
494
  const receiptPollingIntervalMs = process.env.FACILITATOR_RECEIPT_POLLING_INTERVAL_MS ? Number(process.env.FACILITATOR_RECEIPT_POLLING_INTERVAL_MS) : void 0;
@@ -469,6 +503,7 @@ function createConfidentialFacilitatorFromEnv() {
469
503
  waitForReceipt,
470
504
  batcherAddress,
471
505
  gasMultiplier,
506
+ batchIntervalMs,
472
507
  receipt: {
473
508
  confirmations: receiptConfirmations,
474
509
  timeoutMs: receiptTimeoutMs,
@@ -536,6 +571,7 @@ function createConfidentialFacilitatorFromEnv() {
536
571
  signer,
537
572
  waitForReceipt,
538
573
  batcherAddress,
574
+ batchIntervalMs,
539
575
  receipt: {
540
576
  confirmations: receiptConfirmations,
541
577
  timeoutMs: receiptTimeoutMs,
package/dist/index.mjs CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  createFacilitatorService,
4
4
  startConfidentialFacilitator,
5
5
  startFacilitatorService
6
- } from "./chunk-4UDK7NYX.mjs";
6
+ } from "./chunk-FABCSL7D.mjs";
7
7
 
8
8
  // src/register.ts
9
9
  function registerConfidentialEvmScheme(facilitator, config) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x402z-facilitator",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
@@ -11,7 +11,7 @@
11
11
  "@x402/core": "^2.0.0",
12
12
  "@x402/evm": "^2.0.0",
13
13
  "viem": "^2.39.3",
14
- "x402z-shared": "0.0.4"
14
+ "x402z-shared": "0.0.7"
15
15
  },
16
16
  "devDependencies": {
17
17
  "jest": "^29.7.0",