x402z-facilitator 0.0.3 → 0.0.5

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
@@ -17,6 +17,8 @@ Set env vars:
17
17
  - `FACILITATOR_EVM_CHAIN_ID` (default 11155111)
18
18
  - `FACILITATOR_NETWORKS` (default `eip155:11155111`)
19
19
  - `FACILITATOR_PORT` (default 8040)
20
+ - `FACILITATOR_BATCHER_ADDRESS` (optional FHETokenBatcher address)
21
+ - `FACILITATOR_GAS_MULTIPLIER` (optional, e.g. `2` to double estimated gas)
20
22
 
21
23
  Then run:
22
24
 
@@ -29,3 +31,4 @@ node packages/x402z-facilitator/dist/bootstrap.js
29
31
 
30
32
  - This service receives `/verify` and `/settle` HTTP calls from the server.
31
33
  - It submits `confidentialTransferWithAuthorization` transactions on-chain.
34
+ - When `FACILITATOR_BATCHER_ADDRESS` is set, it uses the batcher contract and validates the per-item result from batch events.
package/dist/bootstrap.js CHANGED
@@ -83,6 +83,63 @@ function createFacilitatorService(config) {
83
83
  // src/scheme.ts
84
84
  var import_viem = require("viem");
85
85
  var import_x402z_shared = require("x402z-shared");
86
+ var batcherAbi = [
87
+ {
88
+ inputs: [
89
+ { internalType: "address", name: "token", type: "address" },
90
+ {
91
+ components: [
92
+ {
93
+ components: [
94
+ { internalType: "address", name: "holder", type: "address" },
95
+ { internalType: "address", name: "payee", type: "address" },
96
+ { internalType: "uint256", name: "maxClearAmount", type: "uint256" },
97
+ { internalType: "bytes32", name: "resourceHash", type: "bytes32" },
98
+ { internalType: "uint48", name: "validAfter", type: "uint48" },
99
+ { internalType: "uint48", name: "validBefore", type: "uint48" },
100
+ { internalType: "bytes32", name: "nonce", type: "bytes32" },
101
+ { internalType: "bytes32", name: "encryptedAmountHash", type: "bytes32" }
102
+ ],
103
+ internalType: "struct IFHEToken.ConfidentialPayment",
104
+ name: "p",
105
+ type: "tuple"
106
+ },
107
+ { internalType: "externalEuint64", name: "encryptedAmountInput", type: "bytes32" },
108
+ { internalType: "bytes", name: "inputProof", type: "bytes" },
109
+ { internalType: "bytes", name: "sig", type: "bytes" }
110
+ ],
111
+ internalType: "struct FHETokenBatcher.Request[]",
112
+ name: "requests",
113
+ type: "tuple[]"
114
+ }
115
+ ],
116
+ name: "batchConfidentialTransferWithAuthorization",
117
+ outputs: [
118
+ { internalType: "bool[]", name: "successes", type: "bool[]" },
119
+ { internalType: "bytes32[]", name: "transferredHandles", type: "bytes32[]" }
120
+ ],
121
+ stateMutability: "nonpayable",
122
+ type: "function"
123
+ },
124
+ {
125
+ anonymous: false,
126
+ inputs: [
127
+ { indexed: true, internalType: "uint256", name: "index", type: "uint256" },
128
+ { indexed: false, internalType: "bytes32", name: "transferredHandle", type: "bytes32" }
129
+ ],
130
+ name: "BatchItemSuccess",
131
+ type: "event"
132
+ },
133
+ {
134
+ anonymous: false,
135
+ inputs: [
136
+ { indexed: true, internalType: "uint256", name: "index", type: "uint256" },
137
+ { indexed: false, internalType: "bytes", name: "reason", type: "bytes" }
138
+ ],
139
+ name: "BatchItemFailure",
140
+ type: "event"
141
+ }
142
+ ];
86
143
  var ConfidentialEvmFacilitator = class {
87
144
  constructor(config) {
88
145
  this.config = config;
@@ -219,8 +276,25 @@ var ConfidentialEvmFacilitator = class {
219
276
  network: requirements.network
220
277
  };
221
278
  }
222
- const txHash = await this.config.signer.writeContract({
223
- address: (0, import_viem.getAddress)(requirements.asset),
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,
283
+ abi: batcherAbi,
284
+ 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,
224
298
  abi: import_x402z_shared.confidentialTokenAbi,
225
299
  functionName: "confidentialTransferWithAuthorization",
226
300
  args: [
@@ -229,10 +303,20 @@ var ConfidentialEvmFacilitator = class {
229
303
  confidentialPayload.inputProof,
230
304
  confidentialPayload.signature
231
305
  ]
232
- });
306
+ };
307
+ if (process.env.X402Z_DEBUG === "1") {
308
+ console.debug("[x402z-facilitator] settle tx", {
309
+ batcherAddress,
310
+ tokenAddress,
311
+ functionName: txRequest.functionName,
312
+ to: txRequest.address
313
+ });
314
+ }
315
+ const txHash = await this.config.signer.writeContract(txRequest);
233
316
  if (process.env.X402Z_DEBUG === "1") {
234
317
  console.debug("[x402z-facilitator] tx submitted", txHash);
235
318
  }
319
+ let batchResult;
236
320
  if (this.waitForReceipt) {
237
321
  const receipt = await this.config.signer.waitForTransactionReceipt({
238
322
  hash: txHash
@@ -249,12 +333,90 @@ var ConfidentialEvmFacilitator = class {
249
333
  network: requirements.network
250
334
  };
251
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
350
+ });
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
+ }
402
+ if (!batchSucceeded) {
403
+ return {
404
+ success: false,
405
+ errorReason: "settlement_failed",
406
+ payer: confidentialPayload.authorization.holder,
407
+ transaction: txHash,
408
+ network: requirements.network,
409
+ ...batchResult ? { batch: batchResult } : {}
410
+ };
411
+ }
412
+ }
252
413
  }
253
414
  return {
254
415
  success: true,
255
416
  payer: confidentialPayload.authorization.holder,
256
417
  transaction: txHash,
257
- network: requirements.network
418
+ network: requirements.network,
419
+ ...batchResult ? { batch: batchResult } : {}
258
420
  };
259
421
  }
260
422
  };
@@ -273,9 +435,11 @@ function createConfidentialFacilitatorFromEnv() {
273
435
  const rpcUrl = requireEnv("FACILITATOR_EVM_RPC_URL");
274
436
  const networks = (process.env.FACILITATOR_NETWORKS ?? "eip155:11155111").split(",").map((network) => network.trim()).filter(Boolean);
275
437
  const waitForReceipt = (process.env.FACILITATOR_WAIT_FOR_RECEIPT ?? "true") === "true";
438
+ const batcherAddress = process.env.FACILITATOR_BATCHER_ADDRESS;
276
439
  const receiptTimeoutMs = process.env.FACILITATOR_RECEIPT_TIMEOUT_MS ? Number(process.env.FACILITATOR_RECEIPT_TIMEOUT_MS) : void 0;
277
440
  const receiptConfirmations = process.env.FACILITATOR_RECEIPT_CONFIRMATIONS ? Number(process.env.FACILITATOR_RECEIPT_CONFIRMATIONS) : void 0;
278
441
  const receiptPollingIntervalMs = process.env.FACILITATOR_RECEIPT_POLLING_INTERVAL_MS ? Number(process.env.FACILITATOR_RECEIPT_POLLING_INTERVAL_MS) : void 0;
442
+ const gasMultiplier = process.env.FACILITATOR_GAS_MULTIPLIER ? Number(process.env.FACILITATOR_GAS_MULTIPLIER) : void 0;
279
443
  const debugEnabled = process.env.X402Z_DEBUG === "1";
280
444
  const account = (0, import_accounts.privateKeyToAccount)(privateKey);
281
445
  if (debugEnabled) {
@@ -284,6 +448,8 @@ function createConfidentialFacilitatorFromEnv() {
284
448
  networks,
285
449
  rpcUrl,
286
450
  waitForReceipt,
451
+ batcherAddress,
452
+ gasMultiplier,
287
453
  receipt: {
288
454
  confirmations: receiptConfirmations,
289
455
  timeoutMs: receiptTimeoutMs,
@@ -310,11 +476,32 @@ function createConfidentialFacilitatorFromEnv() {
310
476
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
311
477
  }),
312
478
  verifyTypedData: (args) => client.verifyTypedData(args),
313
- writeContract: (args) => client.writeContract({
314
- ...args,
315
- args: args.args || []
316
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
317
- }),
479
+ writeContract: async (args) => {
480
+ let gas;
481
+ if (gasMultiplier && gasMultiplier > 0) {
482
+ try {
483
+ const estimated = await client.estimateContractGas({
484
+ ...args,
485
+ args: args.args || [],
486
+ account
487
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
488
+ });
489
+ const scale = BigInt(Math.round(gasMultiplier * 1e3));
490
+ const scaled = estimated * scale / 1000n;
491
+ gas = scaled > estimated ? scaled : estimated;
492
+ } catch (error) {
493
+ if (debugEnabled) {
494
+ console.debug("[x402z-facilitator] gas estimate failed", error);
495
+ }
496
+ }
497
+ }
498
+ return client.writeContract({
499
+ ...args,
500
+ args: args.args || [],
501
+ ...gas ? { gas } : {}
502
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
503
+ });
504
+ },
318
505
  sendTransaction: (args) => (
319
506
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
320
507
  client.sendTransaction({ to: args.to, data: args.data })
@@ -329,6 +516,7 @@ function createConfidentialFacilitatorFromEnv() {
329
516
  new ConfidentialEvmFacilitator({
330
517
  signer,
331
518
  waitForReceipt,
519
+ batcherAddress,
332
520
  receipt: {
333
521
  confirmations: receiptConfirmations,
334
522
  timeoutMs: receiptTimeoutMs,
@@ -1,104 +1,7 @@
1
1
  import {
2
- ConfidentialEvmFacilitator,
3
- __require,
4
- createFacilitatorService
5
- } from "./chunk-4LCQ4MN3.mjs";
6
-
7
- // src/bootstrap.ts
8
- import { x402Facilitator } from "@x402/core/facilitator";
9
- import { toFacilitatorEvmSigner } from "@x402/evm";
10
- import { createWalletClient, http, publicActions } from "viem";
11
- import { privateKeyToAccount } from "viem/accounts";
12
- function requireEnv(key) {
13
- const value = process.env[key];
14
- if (!value) {
15
- throw new Error(`Missing required env var: ${key}`);
16
- }
17
- return value;
18
- }
19
- function createConfidentialFacilitatorFromEnv() {
20
- const privateKey = requireEnv("FACILITATOR_EVM_PRIVATE_KEY");
21
- const chainId = Number(process.env.FACILITATOR_EVM_CHAIN_ID ?? "11155111");
22
- const rpcUrl = requireEnv("FACILITATOR_EVM_RPC_URL");
23
- const networks = (process.env.FACILITATOR_NETWORKS ?? "eip155:11155111").split(",").map((network) => network.trim()).filter(Boolean);
24
- const waitForReceipt = (process.env.FACILITATOR_WAIT_FOR_RECEIPT ?? "true") === "true";
25
- const receiptTimeoutMs = process.env.FACILITATOR_RECEIPT_TIMEOUT_MS ? Number(process.env.FACILITATOR_RECEIPT_TIMEOUT_MS) : void 0;
26
- const receiptConfirmations = process.env.FACILITATOR_RECEIPT_CONFIRMATIONS ? Number(process.env.FACILITATOR_RECEIPT_CONFIRMATIONS) : void 0;
27
- const receiptPollingIntervalMs = process.env.FACILITATOR_RECEIPT_POLLING_INTERVAL_MS ? Number(process.env.FACILITATOR_RECEIPT_POLLING_INTERVAL_MS) : void 0;
28
- const debugEnabled = process.env.X402Z_DEBUG === "1";
29
- const account = privateKeyToAccount(privateKey);
30
- if (debugEnabled) {
31
- console.debug("[x402z-facilitator] config", {
32
- chainId,
33
- networks,
34
- rpcUrl,
35
- waitForReceipt,
36
- receipt: {
37
- confirmations: receiptConfirmations,
38
- timeoutMs: receiptTimeoutMs,
39
- pollingIntervalMs: receiptPollingIntervalMs
40
- },
41
- address: account.address
42
- });
43
- }
44
- const client = createWalletClient({
45
- account,
46
- chain: {
47
- id: chainId,
48
- name: "custom",
49
- nativeCurrency: { name: "native", symbol: "NATIVE", decimals: 18 },
50
- rpcUrls: { default: { http: [rpcUrl] } }
51
- },
52
- transport: http(rpcUrl)
53
- }).extend(publicActions);
54
- const signer = toFacilitatorEvmSigner({
55
- address: account.address,
56
- readContract: (args) => client.readContract({
57
- ...args,
58
- args: args.args || []
59
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
- }),
61
- verifyTypedData: (args) => client.verifyTypedData(args),
62
- writeContract: (args) => client.writeContract({
63
- ...args,
64
- args: args.args || []
65
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
- }),
67
- sendTransaction: (args) => (
68
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
69
- client.sendTransaction({ to: args.to, data: args.data })
70
- ),
71
- waitForTransactionReceipt: (args) => client.waitForTransactionReceipt(args),
72
- getCode: (args) => client.getCode(args)
73
- });
74
- const facilitator = new x402Facilitator();
75
- for (const network of networks) {
76
- facilitator.register(
77
- network,
78
- new ConfidentialEvmFacilitator({
79
- signer,
80
- waitForReceipt,
81
- receipt: {
82
- confirmations: receiptConfirmations,
83
- timeoutMs: receiptTimeoutMs,
84
- pollingIntervalMs: receiptPollingIntervalMs
85
- }
86
- })
87
- );
88
- }
89
- return facilitator;
90
- }
91
- function startConfidentialFacilitator() {
92
- const facilitator = createConfidentialFacilitatorFromEnv();
93
- const port = Number(process.env.FACILITATOR_PORT ?? "8040");
94
- const server = createFacilitatorService({ facilitator, port });
95
- server.listen(port);
96
- return { port };
97
- }
98
- if (__require.main === module) {
99
- const { port } = startConfidentialFacilitator();
100
- console.log(`Confidential facilitator listening on :${port}`);
101
- }
2
+ createConfidentialFacilitatorFromEnv,
3
+ startConfidentialFacilitator
4
+ } from "./chunk-4UDK7NYX.mjs";
102
5
  export {
103
6
  createConfidentialFacilitatorFromEnv,
104
7
  startConfidentialFacilitator
@@ -0,0 +1,540 @@
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.hashFn = config.hashEncryptedAmountInput ?? hashEncryptedAmountInput;
141
+ this.clock = config.clock ?? (() => Math.floor(Date.now() / 1e3));
142
+ this.checkUsedNonces = config.checkUsedNonces ?? true;
143
+ this.waitForReceipt = config.waitForReceipt ?? true;
144
+ this.receiptOptions = config.receipt;
145
+ }
146
+ getExtra(_) {
147
+ return void 0;
148
+ }
149
+ getSigners(_) {
150
+ return [...this.config.signer.getAddresses()];
151
+ }
152
+ async verify(payload, requirements) {
153
+ const confidentialPayload = payload.payload;
154
+ if (payload.accepted.scheme !== this.scheme || requirements.scheme !== this.scheme) {
155
+ return {
156
+ isValid: false,
157
+ invalidReason: "unsupported_scheme",
158
+ payer: confidentialPayload?.authorization?.holder
159
+ };
160
+ }
161
+ if (payload.accepted.network !== requirements.network) {
162
+ return {
163
+ isValid: false,
164
+ invalidReason: "network_mismatch",
165
+ payer: confidentialPayload.authorization.holder
166
+ };
167
+ }
168
+ const extra = requirements.extra;
169
+ const eip712 = extra?.eip712;
170
+ if (!eip712?.name || !eip712?.version) {
171
+ return {
172
+ isValid: false,
173
+ invalidReason: "missing_eip712_domain",
174
+ payer: confidentialPayload.authorization.holder
175
+ };
176
+ }
177
+ const now = this.clock();
178
+ const validAfter = Number(confidentialPayload.authorization.validAfter);
179
+ const validBefore = Number(confidentialPayload.authorization.validBefore);
180
+ if (Number.isNaN(validAfter) || Number.isNaN(validBefore)) {
181
+ return {
182
+ isValid: false,
183
+ invalidReason: "invalid_validity_window",
184
+ payer: confidentialPayload.authorization.holder
185
+ };
186
+ }
187
+ if (now < validAfter || now > validBefore) {
188
+ return {
189
+ isValid: false,
190
+ invalidReason: "authorization_expired",
191
+ payer: confidentialPayload.authorization.holder
192
+ };
193
+ }
194
+ if (!isAddressEqual(getAddress(confidentialPayload.authorization.payee), getAddress(requirements.payTo))) {
195
+ return {
196
+ isValid: false,
197
+ invalidReason: "recipient_mismatch",
198
+ payer: confidentialPayload.authorization.holder
199
+ };
200
+ }
201
+ const computedHash = this.hashFn(confidentialPayload.encryptedAmountInput);
202
+ if (computedHash !== confidentialPayload.authorization.encryptedAmountHash) {
203
+ return {
204
+ isValid: false,
205
+ invalidReason: "encrypted_amount_mismatch",
206
+ payer: confidentialPayload.authorization.holder
207
+ };
208
+ }
209
+ const chainId = parseInt(requirements.network.split(":")[1]);
210
+ const isValidSignature = await this.config.signer.verifyTypedData({
211
+ address: confidentialPayload.authorization.holder,
212
+ domain: {
213
+ name: eip712.name,
214
+ version: eip712.version,
215
+ chainId,
216
+ verifyingContract: getAddress(requirements.asset)
217
+ },
218
+ types: confidentialPaymentTypes,
219
+ primaryType: "ConfidentialPayment",
220
+ message: {
221
+ holder: getAddress(confidentialPayload.authorization.holder),
222
+ payee: getAddress(confidentialPayload.authorization.payee),
223
+ maxClearAmount: BigInt(confidentialPayload.authorization.maxClearAmount),
224
+ resourceHash: confidentialPayload.authorization.resourceHash,
225
+ validAfter: BigInt(confidentialPayload.authorization.validAfter),
226
+ validBefore: BigInt(confidentialPayload.authorization.validBefore),
227
+ nonce: confidentialPayload.authorization.nonce,
228
+ encryptedAmountHash: confidentialPayload.authorization.encryptedAmountHash
229
+ },
230
+ signature: confidentialPayload.signature
231
+ });
232
+ if (!isValidSignature) {
233
+ return {
234
+ isValid: false,
235
+ invalidReason: "invalid_signature",
236
+ payer: confidentialPayload.authorization.holder
237
+ };
238
+ }
239
+ if (this.checkUsedNonces) {
240
+ const used = await this.config.signer.readContract({
241
+ address: getAddress(requirements.asset),
242
+ abi: confidentialTokenAbi,
243
+ functionName: "usedNonces",
244
+ args: [confidentialPayload.authorization.holder, confidentialPayload.authorization.nonce]
245
+ });
246
+ if (used) {
247
+ return {
248
+ isValid: false,
249
+ invalidReason: "nonce_already_used",
250
+ payer: confidentialPayload.authorization.holder
251
+ };
252
+ }
253
+ }
254
+ return {
255
+ isValid: true,
256
+ payer: confidentialPayload.authorization.holder
257
+ };
258
+ }
259
+ async settle(payload, requirements) {
260
+ const valid = await this.verify(payload, requirements);
261
+ const confidentialPayload = payload.payload;
262
+ if (!valid.isValid) {
263
+ return {
264
+ success: false,
265
+ errorReason: valid.invalidReason ?? "invalid_payment",
266
+ payer: confidentialPayload.authorization.holder,
267
+ transaction: "",
268
+ network: requirements.network
269
+ };
270
+ }
271
+ const tokenAddress = getAddress(requirements.asset);
272
+ const batcherAddress = this.config.batcherAddress ? getAddress(this.config.batcherAddress) : void 0;
273
+ const txRequest = batcherAddress ? {
274
+ address: batcherAddress,
275
+ abi: batcherAbi,
276
+ functionName: "batchConfidentialTransferWithAuthorization",
277
+ args: [
278
+ tokenAddress,
279
+ [
280
+ {
281
+ p: confidentialPayload.authorization,
282
+ encryptedAmountInput: confidentialPayload.encryptedAmountInput,
283
+ inputProof: confidentialPayload.inputProof,
284
+ sig: confidentialPayload.signature
285
+ }
286
+ ]
287
+ ]
288
+ } : {
289
+ address: tokenAddress,
290
+ abi: confidentialTokenAbi,
291
+ functionName: "confidentialTransferWithAuthorization",
292
+ args: [
293
+ confidentialPayload.authorization,
294
+ confidentialPayload.encryptedAmountInput,
295
+ confidentialPayload.inputProof,
296
+ confidentialPayload.signature
297
+ ]
298
+ };
299
+ if (process.env.X402Z_DEBUG === "1") {
300
+ console.debug("[x402z-facilitator] settle tx", {
301
+ batcherAddress,
302
+ tokenAddress,
303
+ functionName: txRequest.functionName,
304
+ to: txRequest.address
305
+ });
306
+ }
307
+ const txHash = await this.config.signer.writeContract(txRequest);
308
+ if (process.env.X402Z_DEBUG === "1") {
309
+ console.debug("[x402z-facilitator] tx submitted", txHash);
310
+ }
311
+ let batchResult;
312
+ if (this.waitForReceipt) {
313
+ const receipt = await this.config.signer.waitForTransactionReceipt({
314
+ hash: txHash
315
+ });
316
+ if (process.env.X402Z_DEBUG === "1") {
317
+ console.debug("[x402z-facilitator] tx receipt", receipt);
318
+ }
319
+ if (receipt.status !== "success") {
320
+ return {
321
+ success: false,
322
+ errorReason: "settlement_failed",
323
+ payer: confidentialPayload.authorization.holder,
324
+ transaction: txHash,
325
+ network: requirements.network
326
+ };
327
+ }
328
+ if (batcherAddress) {
329
+ let batchSucceeded = false;
330
+ const logs = "logs" in receipt ? receipt.logs ?? [] : [];
331
+ for (const log of logs) {
332
+ if (!log || !("address" in log) || !log.address) {
333
+ continue;
334
+ }
335
+ if (!isAddressEqual(getAddress(log.address), batcherAddress)) {
336
+ continue;
337
+ }
338
+ const decoded = decodeEventLog({
339
+ abi: batcherAbi,
340
+ data: log.data,
341
+ topics: log.topics
342
+ });
343
+ if (process.env.X402Z_DEBUG === "1") {
344
+ console.debug("[x402z-facilitator] batch log", decoded);
345
+ }
346
+ if (decoded.eventName === "BatchItemSuccess") {
347
+ const args = decoded.args;
348
+ if (Number(args.index) === 0) {
349
+ batchSucceeded = true;
350
+ batchResult = {
351
+ index: Number(args.index),
352
+ success: true,
353
+ transferredHandle: args.transferredHandle
354
+ };
355
+ break;
356
+ }
357
+ }
358
+ if (decoded.eventName === "BatchItemFailure") {
359
+ const args = decoded.args;
360
+ if (Number(args.index) === 0) {
361
+ batchResult = {
362
+ index: Number(args.index),
363
+ success: false,
364
+ failureReason: args.reason
365
+ };
366
+ if (process.env.X402Z_DEBUG === "1") {
367
+ try {
368
+ await this.config.signer.readContract({
369
+ address: tokenAddress,
370
+ abi: confidentialTokenAbi,
371
+ functionName: "confidentialTransferWithAuthorization",
372
+ args: [
373
+ confidentialPayload.authorization,
374
+ confidentialPayload.encryptedAmountInput,
375
+ confidentialPayload.inputProof,
376
+ confidentialPayload.signature
377
+ ]
378
+ });
379
+ } catch (innerError) {
380
+ console.debug("[x402z-facilitator] batch fallback call failed", innerError);
381
+ }
382
+ }
383
+ return {
384
+ success: false,
385
+ errorReason: "settlement_failed",
386
+ payer: confidentialPayload.authorization.holder,
387
+ transaction: txHash,
388
+ network: requirements.network,
389
+ ...batchResult ? { batch: batchResult } : {}
390
+ };
391
+ }
392
+ }
393
+ }
394
+ if (!batchSucceeded) {
395
+ return {
396
+ success: false,
397
+ errorReason: "settlement_failed",
398
+ payer: confidentialPayload.authorization.holder,
399
+ transaction: txHash,
400
+ network: requirements.network,
401
+ ...batchResult ? { batch: batchResult } : {}
402
+ };
403
+ }
404
+ }
405
+ }
406
+ return {
407
+ success: true,
408
+ payer: confidentialPayload.authorization.holder,
409
+ transaction: txHash,
410
+ network: requirements.network,
411
+ ...batchResult ? { batch: batchResult } : {}
412
+ };
413
+ }
414
+ };
415
+
416
+ // src/bootstrap.ts
417
+ function requireEnv(key) {
418
+ const value = process.env[key];
419
+ if (!value) {
420
+ throw new Error(`Missing required env var: ${key}`);
421
+ }
422
+ return value;
423
+ }
424
+ function createConfidentialFacilitatorFromEnv() {
425
+ const privateKey = requireEnv("FACILITATOR_EVM_PRIVATE_KEY");
426
+ const chainId = Number(process.env.FACILITATOR_EVM_CHAIN_ID ?? "11155111");
427
+ const rpcUrl = requireEnv("FACILITATOR_EVM_RPC_URL");
428
+ const networks = (process.env.FACILITATOR_NETWORKS ?? "eip155:11155111").split(",").map((network) => network.trim()).filter(Boolean);
429
+ const waitForReceipt = (process.env.FACILITATOR_WAIT_FOR_RECEIPT ?? "true") === "true";
430
+ const batcherAddress = process.env.FACILITATOR_BATCHER_ADDRESS;
431
+ const receiptTimeoutMs = process.env.FACILITATOR_RECEIPT_TIMEOUT_MS ? Number(process.env.FACILITATOR_RECEIPT_TIMEOUT_MS) : void 0;
432
+ const receiptConfirmations = process.env.FACILITATOR_RECEIPT_CONFIRMATIONS ? Number(process.env.FACILITATOR_RECEIPT_CONFIRMATIONS) : void 0;
433
+ const receiptPollingIntervalMs = process.env.FACILITATOR_RECEIPT_POLLING_INTERVAL_MS ? Number(process.env.FACILITATOR_RECEIPT_POLLING_INTERVAL_MS) : void 0;
434
+ const gasMultiplier = process.env.FACILITATOR_GAS_MULTIPLIER ? Number(process.env.FACILITATOR_GAS_MULTIPLIER) : void 0;
435
+ const debugEnabled = process.env.X402Z_DEBUG === "1";
436
+ const account = privateKeyToAccount(privateKey);
437
+ if (debugEnabled) {
438
+ console.debug("[x402z-facilitator] config", {
439
+ chainId,
440
+ networks,
441
+ rpcUrl,
442
+ waitForReceipt,
443
+ batcherAddress,
444
+ gasMultiplier,
445
+ receipt: {
446
+ confirmations: receiptConfirmations,
447
+ timeoutMs: receiptTimeoutMs,
448
+ pollingIntervalMs: receiptPollingIntervalMs
449
+ },
450
+ address: account.address
451
+ });
452
+ }
453
+ const client = createWalletClient({
454
+ account,
455
+ chain: {
456
+ id: chainId,
457
+ name: "custom",
458
+ nativeCurrency: { name: "native", symbol: "NATIVE", decimals: 18 },
459
+ rpcUrls: { default: { http: [rpcUrl] } }
460
+ },
461
+ transport: http(rpcUrl)
462
+ }).extend(publicActions);
463
+ const signer = toFacilitatorEvmSigner({
464
+ address: account.address,
465
+ readContract: (args) => client.readContract({
466
+ ...args,
467
+ args: args.args || []
468
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
469
+ }),
470
+ verifyTypedData: (args) => client.verifyTypedData(args),
471
+ writeContract: async (args) => {
472
+ let gas;
473
+ if (gasMultiplier && gasMultiplier > 0) {
474
+ try {
475
+ const estimated = await client.estimateContractGas({
476
+ ...args,
477
+ args: args.args || [],
478
+ account
479
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
480
+ });
481
+ const scale = BigInt(Math.round(gasMultiplier * 1e3));
482
+ const scaled = estimated * scale / 1000n;
483
+ gas = scaled > estimated ? scaled : estimated;
484
+ } catch (error) {
485
+ if (debugEnabled) {
486
+ console.debug("[x402z-facilitator] gas estimate failed", error);
487
+ }
488
+ }
489
+ }
490
+ return client.writeContract({
491
+ ...args,
492
+ args: args.args || [],
493
+ ...gas ? { gas } : {}
494
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
495
+ });
496
+ },
497
+ sendTransaction: (args) => (
498
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
499
+ client.sendTransaction({ to: args.to, data: args.data })
500
+ ),
501
+ waitForTransactionReceipt: (args) => client.waitForTransactionReceipt(args),
502
+ getCode: (args) => client.getCode(args)
503
+ });
504
+ const facilitator = new x402Facilitator();
505
+ for (const network of networks) {
506
+ facilitator.register(
507
+ network,
508
+ new ConfidentialEvmFacilitator({
509
+ signer,
510
+ waitForReceipt,
511
+ batcherAddress,
512
+ receipt: {
513
+ confirmations: receiptConfirmations,
514
+ timeoutMs: receiptTimeoutMs,
515
+ pollingIntervalMs: receiptPollingIntervalMs
516
+ }
517
+ })
518
+ );
519
+ }
520
+ return facilitator;
521
+ }
522
+ function startConfidentialFacilitator() {
523
+ const facilitator = createConfidentialFacilitatorFromEnv();
524
+ const port = Number(process.env.FACILITATOR_PORT ?? "8040");
525
+ const server = createFacilitatorService({ facilitator, port });
526
+ server.listen(port);
527
+ return { port };
528
+ }
529
+ if (__require.main === module) {
530
+ const { port } = startConfidentialFacilitator();
531
+ console.log(`Confidential facilitator listening on :${port}`);
532
+ }
533
+
534
+ export {
535
+ createFacilitatorService,
536
+ startFacilitatorService,
537
+ ConfidentialEvmFacilitator,
538
+ createConfidentialFacilitatorFromEnv,
539
+ startConfidentialFacilitator
540
+ };
package/dist/index.d.mts CHANGED
@@ -3,9 +3,11 @@ import { FacilitatorEvmSigner } from '@x402/evm';
3
3
  import { x402Facilitator } from '@x402/core/facilitator';
4
4
  import * as http from 'http';
5
5
  import { IncomingMessage, ServerResponse } from 'node:http';
6
+ export { startConfidentialFacilitator } from './bootstrap.mjs';
6
7
 
7
8
  type ConfidentialFacilitatorConfig = {
8
9
  signer: FacilitatorEvmSigner;
10
+ batcherAddress?: `0x${string}`;
9
11
  hashEncryptedAmountInput?: (encryptedAmountInput: `0x${string}`) => `0x${string}`;
10
12
  checkUsedNonces?: boolean;
11
13
  clock?: () => number;
package/dist/index.d.ts CHANGED
@@ -3,9 +3,11 @@ import { FacilitatorEvmSigner } from '@x402/evm';
3
3
  import { x402Facilitator } from '@x402/core/facilitator';
4
4
  import * as http from 'http';
5
5
  import { IncomingMessage, ServerResponse } from 'node:http';
6
+ export { startConfidentialFacilitator } from './bootstrap.js';
6
7
 
7
8
  type ConfidentialFacilitatorConfig = {
8
9
  signer: FacilitatorEvmSigner;
10
+ batcherAddress?: `0x${string}`;
9
11
  hashEncryptedAmountInput?: (encryptedAmountInput: `0x${string}`) => `0x${string}`;
10
12
  checkUsedNonces?: boolean;
11
13
  clock?: () => number;
package/dist/index.js CHANGED
@@ -23,6 +23,7 @@ __export(index_exports, {
23
23
  ConfidentialEvmFacilitator: () => ConfidentialEvmFacilitator,
24
24
  createFacilitatorService: () => createFacilitatorService,
25
25
  registerConfidentialEvmScheme: () => registerConfidentialEvmScheme,
26
+ startConfidentialFacilitator: () => startConfidentialFacilitator,
26
27
  startFacilitatorService: () => startFacilitatorService
27
28
  });
28
29
  module.exports = __toCommonJS(index_exports);
@@ -30,6 +31,63 @@ module.exports = __toCommonJS(index_exports);
30
31
  // src/scheme.ts
31
32
  var import_viem = require("viem");
32
33
  var import_x402z_shared = require("x402z-shared");
34
+ var batcherAbi = [
35
+ {
36
+ inputs: [
37
+ { internalType: "address", name: "token", type: "address" },
38
+ {
39
+ components: [
40
+ {
41
+ components: [
42
+ { internalType: "address", name: "holder", type: "address" },
43
+ { internalType: "address", name: "payee", type: "address" },
44
+ { internalType: "uint256", name: "maxClearAmount", type: "uint256" },
45
+ { internalType: "bytes32", name: "resourceHash", type: "bytes32" },
46
+ { internalType: "uint48", name: "validAfter", type: "uint48" },
47
+ { internalType: "uint48", name: "validBefore", type: "uint48" },
48
+ { internalType: "bytes32", name: "nonce", type: "bytes32" },
49
+ { internalType: "bytes32", name: "encryptedAmountHash", type: "bytes32" }
50
+ ],
51
+ internalType: "struct IFHEToken.ConfidentialPayment",
52
+ name: "p",
53
+ type: "tuple"
54
+ },
55
+ { internalType: "externalEuint64", name: "encryptedAmountInput", type: "bytes32" },
56
+ { internalType: "bytes", name: "inputProof", type: "bytes" },
57
+ { internalType: "bytes", name: "sig", type: "bytes" }
58
+ ],
59
+ internalType: "struct FHETokenBatcher.Request[]",
60
+ name: "requests",
61
+ type: "tuple[]"
62
+ }
63
+ ],
64
+ name: "batchConfidentialTransferWithAuthorization",
65
+ outputs: [
66
+ { internalType: "bool[]", name: "successes", type: "bool[]" },
67
+ { internalType: "bytes32[]", name: "transferredHandles", type: "bytes32[]" }
68
+ ],
69
+ stateMutability: "nonpayable",
70
+ type: "function"
71
+ },
72
+ {
73
+ anonymous: false,
74
+ inputs: [
75
+ { indexed: true, internalType: "uint256", name: "index", type: "uint256" },
76
+ { indexed: false, internalType: "bytes32", name: "transferredHandle", type: "bytes32" }
77
+ ],
78
+ name: "BatchItemSuccess",
79
+ type: "event"
80
+ },
81
+ {
82
+ anonymous: false,
83
+ inputs: [
84
+ { indexed: true, internalType: "uint256", name: "index", type: "uint256" },
85
+ { indexed: false, internalType: "bytes", name: "reason", type: "bytes" }
86
+ ],
87
+ name: "BatchItemFailure",
88
+ type: "event"
89
+ }
90
+ ];
33
91
  var ConfidentialEvmFacilitator = class {
34
92
  constructor(config) {
35
93
  this.config = config;
@@ -166,8 +224,25 @@ var ConfidentialEvmFacilitator = class {
166
224
  network: requirements.network
167
225
  };
168
226
  }
169
- const txHash = await this.config.signer.writeContract({
170
- address: (0, import_viem.getAddress)(requirements.asset),
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,
231
+ abi: batcherAbi,
232
+ 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,
171
246
  abi: import_x402z_shared.confidentialTokenAbi,
172
247
  functionName: "confidentialTransferWithAuthorization",
173
248
  args: [
@@ -176,10 +251,20 @@ var ConfidentialEvmFacilitator = class {
176
251
  confidentialPayload.inputProof,
177
252
  confidentialPayload.signature
178
253
  ]
179
- });
254
+ };
255
+ if (process.env.X402Z_DEBUG === "1") {
256
+ console.debug("[x402z-facilitator] settle tx", {
257
+ batcherAddress,
258
+ tokenAddress,
259
+ functionName: txRequest.functionName,
260
+ to: txRequest.address
261
+ });
262
+ }
263
+ const txHash = await this.config.signer.writeContract(txRequest);
180
264
  if (process.env.X402Z_DEBUG === "1") {
181
265
  console.debug("[x402z-facilitator] tx submitted", txHash);
182
266
  }
267
+ let batchResult;
183
268
  if (this.waitForReceipt) {
184
269
  const receipt = await this.config.signer.waitForTransactionReceipt({
185
270
  hash: txHash
@@ -196,12 +281,90 @@ var ConfidentialEvmFacilitator = class {
196
281
  network: requirements.network
197
282
  };
198
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
298
+ });
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
+ }
350
+ if (!batchSucceeded) {
351
+ return {
352
+ success: false,
353
+ errorReason: "settlement_failed",
354
+ payer: confidentialPayload.authorization.holder,
355
+ transaction: txHash,
356
+ network: requirements.network,
357
+ ...batchResult ? { batch: batchResult } : {}
358
+ };
359
+ }
360
+ }
199
361
  }
200
362
  return {
201
363
  success: true,
202
364
  payer: confidentialPayload.authorization.holder,
203
365
  transaction: txHash,
204
- network: requirements.network
366
+ network: requirements.network,
367
+ ...batchResult ? { batch: batchResult } : {}
205
368
  };
206
369
  }
207
370
  };
@@ -272,10 +435,133 @@ function startFacilitatorService(config) {
272
435
  server.listen(port);
273
436
  return { port };
274
437
  }
438
+
439
+ // src/bootstrap.ts
440
+ var import_facilitator = require("@x402/core/facilitator");
441
+ var import_evm = require("@x402/evm");
442
+ var import_viem2 = require("viem");
443
+ var import_accounts = require("viem/accounts");
444
+ function requireEnv(key) {
445
+ const value = process.env[key];
446
+ if (!value) {
447
+ throw new Error(`Missing required env var: ${key}`);
448
+ }
449
+ return value;
450
+ }
451
+ function createConfidentialFacilitatorFromEnv() {
452
+ const privateKey = requireEnv("FACILITATOR_EVM_PRIVATE_KEY");
453
+ const chainId = Number(process.env.FACILITATOR_EVM_CHAIN_ID ?? "11155111");
454
+ const rpcUrl = requireEnv("FACILITATOR_EVM_RPC_URL");
455
+ const networks = (process.env.FACILITATOR_NETWORKS ?? "eip155:11155111").split(",").map((network) => network.trim()).filter(Boolean);
456
+ const waitForReceipt = (process.env.FACILITATOR_WAIT_FOR_RECEIPT ?? "true") === "true";
457
+ const batcherAddress = process.env.FACILITATOR_BATCHER_ADDRESS;
458
+ const receiptTimeoutMs = process.env.FACILITATOR_RECEIPT_TIMEOUT_MS ? Number(process.env.FACILITATOR_RECEIPT_TIMEOUT_MS) : void 0;
459
+ const receiptConfirmations = process.env.FACILITATOR_RECEIPT_CONFIRMATIONS ? Number(process.env.FACILITATOR_RECEIPT_CONFIRMATIONS) : void 0;
460
+ const receiptPollingIntervalMs = process.env.FACILITATOR_RECEIPT_POLLING_INTERVAL_MS ? Number(process.env.FACILITATOR_RECEIPT_POLLING_INTERVAL_MS) : void 0;
461
+ const gasMultiplier = process.env.FACILITATOR_GAS_MULTIPLIER ? Number(process.env.FACILITATOR_GAS_MULTIPLIER) : void 0;
462
+ const debugEnabled = process.env.X402Z_DEBUG === "1";
463
+ const account = (0, import_accounts.privateKeyToAccount)(privateKey);
464
+ if (debugEnabled) {
465
+ console.debug("[x402z-facilitator] config", {
466
+ chainId,
467
+ networks,
468
+ rpcUrl,
469
+ waitForReceipt,
470
+ batcherAddress,
471
+ gasMultiplier,
472
+ receipt: {
473
+ confirmations: receiptConfirmations,
474
+ timeoutMs: receiptTimeoutMs,
475
+ pollingIntervalMs: receiptPollingIntervalMs
476
+ },
477
+ address: account.address
478
+ });
479
+ }
480
+ const client = (0, import_viem2.createWalletClient)({
481
+ account,
482
+ chain: {
483
+ id: chainId,
484
+ name: "custom",
485
+ nativeCurrency: { name: "native", symbol: "NATIVE", decimals: 18 },
486
+ rpcUrls: { default: { http: [rpcUrl] } }
487
+ },
488
+ transport: (0, import_viem2.http)(rpcUrl)
489
+ }).extend(import_viem2.publicActions);
490
+ const signer = (0, import_evm.toFacilitatorEvmSigner)({
491
+ address: account.address,
492
+ readContract: (args) => client.readContract({
493
+ ...args,
494
+ args: args.args || []
495
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
496
+ }),
497
+ verifyTypedData: (args) => client.verifyTypedData(args),
498
+ writeContract: async (args) => {
499
+ let gas;
500
+ if (gasMultiplier && gasMultiplier > 0) {
501
+ try {
502
+ const estimated = await client.estimateContractGas({
503
+ ...args,
504
+ args: args.args || [],
505
+ account
506
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
507
+ });
508
+ const scale = BigInt(Math.round(gasMultiplier * 1e3));
509
+ const scaled = estimated * scale / 1000n;
510
+ gas = scaled > estimated ? scaled : estimated;
511
+ } catch (error) {
512
+ if (debugEnabled) {
513
+ console.debug("[x402z-facilitator] gas estimate failed", error);
514
+ }
515
+ }
516
+ }
517
+ return client.writeContract({
518
+ ...args,
519
+ args: args.args || [],
520
+ ...gas ? { gas } : {}
521
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
522
+ });
523
+ },
524
+ sendTransaction: (args) => (
525
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
526
+ client.sendTransaction({ to: args.to, data: args.data })
527
+ ),
528
+ waitForTransactionReceipt: (args) => client.waitForTransactionReceipt(args),
529
+ getCode: (args) => client.getCode(args)
530
+ });
531
+ const facilitator = new import_facilitator.x402Facilitator();
532
+ for (const network of networks) {
533
+ facilitator.register(
534
+ network,
535
+ new ConfidentialEvmFacilitator({
536
+ signer,
537
+ waitForReceipt,
538
+ batcherAddress,
539
+ receipt: {
540
+ confirmations: receiptConfirmations,
541
+ timeoutMs: receiptTimeoutMs,
542
+ pollingIntervalMs: receiptPollingIntervalMs
543
+ }
544
+ })
545
+ );
546
+ }
547
+ return facilitator;
548
+ }
549
+ function startConfidentialFacilitator() {
550
+ const facilitator = createConfidentialFacilitatorFromEnv();
551
+ const port = Number(process.env.FACILITATOR_PORT ?? "8040");
552
+ const server = createFacilitatorService({ facilitator, port });
553
+ server.listen(port);
554
+ return { port };
555
+ }
556
+ if (require.main === module) {
557
+ const { port } = startConfidentialFacilitator();
558
+ console.log(`Confidential facilitator listening on :${port}`);
559
+ }
275
560
  // Annotate the CommonJS export names for ESM import in node:
276
561
  0 && (module.exports = {
277
562
  ConfidentialEvmFacilitator,
278
563
  createFacilitatorService,
279
564
  registerConfidentialEvmScheme,
565
+ startConfidentialFacilitator,
280
566
  startFacilitatorService
281
567
  });
package/dist/index.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  ConfidentialEvmFacilitator,
3
3
  createFacilitatorService,
4
+ startConfidentialFacilitator,
4
5
  startFacilitatorService
5
- } from "./chunk-4LCQ4MN3.mjs";
6
+ } from "./chunk-4UDK7NYX.mjs";
6
7
 
7
8
  // src/register.ts
8
9
  function registerConfidentialEvmScheme(facilitator, config) {
@@ -17,5 +18,6 @@ export {
17
18
  ConfidentialEvmFacilitator,
18
19
  createFacilitatorService,
19
20
  registerConfidentialEvmScheme,
21
+ startConfidentialFacilitator,
20
22
  startFacilitatorService
21
23
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x402z-facilitator",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
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.2"
14
+ "x402z-shared": "0.0.4"
15
15
  },
16
16
  "devDependencies": {
17
17
  "jest": "^29.7.0",