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