x402z-facilitator 0.0.4 → 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 +3 -0
- package/dist/bootstrap.js +197 -9
- package/dist/bootstrap.mjs +1 -1
- package/dist/chunk-4UDK7NYX.mjs +540 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +197 -9
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
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
|
|
223
|
-
|
|
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) =>
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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,
|
package/dist/bootstrap.mjs
CHANGED
|
@@ -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
|
@@ -7,6 +7,7 @@ export { startConfidentialFacilitator } from './bootstrap.mjs';
|
|
|
7
7
|
|
|
8
8
|
type ConfidentialFacilitatorConfig = {
|
|
9
9
|
signer: FacilitatorEvmSigner;
|
|
10
|
+
batcherAddress?: `0x${string}`;
|
|
10
11
|
hashEncryptedAmountInput?: (encryptedAmountInput: `0x${string}`) => `0x${string}`;
|
|
11
12
|
checkUsedNonces?: boolean;
|
|
12
13
|
clock?: () => number;
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export { startConfidentialFacilitator } from './bootstrap.js';
|
|
|
7
7
|
|
|
8
8
|
type ConfidentialFacilitatorConfig = {
|
|
9
9
|
signer: FacilitatorEvmSigner;
|
|
10
|
+
batcherAddress?: `0x${string}`;
|
|
10
11
|
hashEncryptedAmountInput?: (encryptedAmountInput: `0x${string}`) => `0x${string}`;
|
|
11
12
|
checkUsedNonces?: boolean;
|
|
12
13
|
clock?: () => number;
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,63 @@ module.exports = __toCommonJS(index_exports);
|
|
|
31
31
|
// src/scheme.ts
|
|
32
32
|
var import_viem = require("viem");
|
|
33
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
|
+
];
|
|
34
91
|
var ConfidentialEvmFacilitator = class {
|
|
35
92
|
constructor(config) {
|
|
36
93
|
this.config = config;
|
|
@@ -167,8 +224,25 @@ var ConfidentialEvmFacilitator = class {
|
|
|
167
224
|
network: requirements.network
|
|
168
225
|
};
|
|
169
226
|
}
|
|
170
|
-
const
|
|
171
|
-
|
|
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,
|
|
172
246
|
abi: import_x402z_shared.confidentialTokenAbi,
|
|
173
247
|
functionName: "confidentialTransferWithAuthorization",
|
|
174
248
|
args: [
|
|
@@ -177,10 +251,20 @@ var ConfidentialEvmFacilitator = class {
|
|
|
177
251
|
confidentialPayload.inputProof,
|
|
178
252
|
confidentialPayload.signature
|
|
179
253
|
]
|
|
180
|
-
}
|
|
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);
|
|
181
264
|
if (process.env.X402Z_DEBUG === "1") {
|
|
182
265
|
console.debug("[x402z-facilitator] tx submitted", txHash);
|
|
183
266
|
}
|
|
267
|
+
let batchResult;
|
|
184
268
|
if (this.waitForReceipt) {
|
|
185
269
|
const receipt = await this.config.signer.waitForTransactionReceipt({
|
|
186
270
|
hash: txHash
|
|
@@ -197,12 +281,90 @@ var ConfidentialEvmFacilitator = class {
|
|
|
197
281
|
network: requirements.network
|
|
198
282
|
};
|
|
199
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
|
+
}
|
|
200
361
|
}
|
|
201
362
|
return {
|
|
202
363
|
success: true,
|
|
203
364
|
payer: confidentialPayload.authorization.holder,
|
|
204
365
|
transaction: txHash,
|
|
205
|
-
network: requirements.network
|
|
366
|
+
network: requirements.network,
|
|
367
|
+
...batchResult ? { batch: batchResult } : {}
|
|
206
368
|
};
|
|
207
369
|
}
|
|
208
370
|
};
|
|
@@ -292,9 +454,11 @@ function createConfidentialFacilitatorFromEnv() {
|
|
|
292
454
|
const rpcUrl = requireEnv("FACILITATOR_EVM_RPC_URL");
|
|
293
455
|
const networks = (process.env.FACILITATOR_NETWORKS ?? "eip155:11155111").split(",").map((network) => network.trim()).filter(Boolean);
|
|
294
456
|
const waitForReceipt = (process.env.FACILITATOR_WAIT_FOR_RECEIPT ?? "true") === "true";
|
|
457
|
+
const batcherAddress = process.env.FACILITATOR_BATCHER_ADDRESS;
|
|
295
458
|
const receiptTimeoutMs = process.env.FACILITATOR_RECEIPT_TIMEOUT_MS ? Number(process.env.FACILITATOR_RECEIPT_TIMEOUT_MS) : void 0;
|
|
296
459
|
const receiptConfirmations = process.env.FACILITATOR_RECEIPT_CONFIRMATIONS ? Number(process.env.FACILITATOR_RECEIPT_CONFIRMATIONS) : void 0;
|
|
297
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;
|
|
298
462
|
const debugEnabled = process.env.X402Z_DEBUG === "1";
|
|
299
463
|
const account = (0, import_accounts.privateKeyToAccount)(privateKey);
|
|
300
464
|
if (debugEnabled) {
|
|
@@ -303,6 +467,8 @@ function createConfidentialFacilitatorFromEnv() {
|
|
|
303
467
|
networks,
|
|
304
468
|
rpcUrl,
|
|
305
469
|
waitForReceipt,
|
|
470
|
+
batcherAddress,
|
|
471
|
+
gasMultiplier,
|
|
306
472
|
receipt: {
|
|
307
473
|
confirmations: receiptConfirmations,
|
|
308
474
|
timeoutMs: receiptTimeoutMs,
|
|
@@ -329,11 +495,32 @@ function createConfidentialFacilitatorFromEnv() {
|
|
|
329
495
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
330
496
|
}),
|
|
331
497
|
verifyTypedData: (args) => client.verifyTypedData(args),
|
|
332
|
-
writeContract: (args) =>
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
+
},
|
|
337
524
|
sendTransaction: (args) => (
|
|
338
525
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
339
526
|
client.sendTransaction({ to: args.to, data: args.data })
|
|
@@ -348,6 +535,7 @@ function createConfidentialFacilitatorFromEnv() {
|
|
|
348
535
|
new ConfidentialEvmFacilitator({
|
|
349
536
|
signer,
|
|
350
537
|
waitForReceipt,
|
|
538
|
+
batcherAddress,
|
|
351
539
|
receipt: {
|
|
352
540
|
confirmations: receiptConfirmations,
|
|
353
541
|
timeoutMs: receiptTimeoutMs,
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "x402z-facilitator",
|
|
3
|
-
"version": "0.0.
|
|
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.
|
|
14
|
+
"x402z-shared": "0.0.4"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"jest": "^29.7.0",
|