x402z-facilitator 0.0.8 → 0.0.9
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 +6 -0
- package/dist/chunk-3XCMVK47.mjs +576 -0
- package/dist/index.d.mts +10 -10
- package/dist/index.d.ts +10 -10
- package/dist/index.js +19 -19
- package/dist/index.mjs +10 -10
- package/dist/service/bootstrap.d.mts +8 -0
- package/dist/service/bootstrap.d.ts +8 -0
- package/dist/service/bootstrap.js +581 -0
- package/dist/service/bootstrap.mjs +8 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -8,6 +8,12 @@ Facilitator service for the erc7984-mind-v1 x402 scheme.
|
|
|
8
8
|
pnpm add x402z-facilitator
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
## Folder map
|
|
12
|
+
|
|
13
|
+
- `src/service/`: HTTP service + bootstrap
|
|
14
|
+
- `src/scheme/`: scheme implementation + registration
|
|
15
|
+
- `src/index.ts`: public exports
|
|
16
|
+
|
|
11
17
|
## Run (service)
|
|
12
18
|
|
|
13
19
|
Set env vars:
|
|
@@ -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/service/bootstrap.ts
|
|
9
|
+
import { x402Facilitator } from "@x402/core/facilitator";
|
|
10
|
+
import { toFacilitatorEvmSigner } from "@x402/evm";
|
|
11
|
+
import { createWalletClient, http, publicActions } from "viem";
|
|
12
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
13
|
+
|
|
14
|
+
// src/service/service.ts
|
|
15
|
+
import { createServer } from "http";
|
|
16
|
+
async function readJson(req) {
|
|
17
|
+
const chunks = [];
|
|
18
|
+
for await (const chunk of req) {
|
|
19
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
20
|
+
}
|
|
21
|
+
const body = Buffer.concat(chunks).toString("utf8");
|
|
22
|
+
return JSON.parse(body);
|
|
23
|
+
}
|
|
24
|
+
function sendJson(res, status, payload) {
|
|
25
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
26
|
+
res.end(JSON.stringify(payload));
|
|
27
|
+
}
|
|
28
|
+
function createFacilitatorService(config) {
|
|
29
|
+
const server = createServer(async (req, res) => {
|
|
30
|
+
try {
|
|
31
|
+
const method = req.method ?? "GET";
|
|
32
|
+
const url = req.url ?? "/";
|
|
33
|
+
if (process.env.X402Z_DEBUG === "1") {
|
|
34
|
+
console.debug(`[x402z-facilitator] ${method} ${url}`);
|
|
35
|
+
}
|
|
36
|
+
if (!req.url) {
|
|
37
|
+
sendJson(res, 404, { error: "not_found" });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (req.method === "GET" && req.url === "/supported") {
|
|
41
|
+
sendJson(res, 200, config.facilitator.getSupported());
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (req.method === "POST" && req.url === "/verify") {
|
|
45
|
+
const body = await readJson(req);
|
|
46
|
+
const result = await config.facilitator.verify(body.paymentPayload, body.paymentRequirements);
|
|
47
|
+
sendJson(res, 200, result);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (req.method === "POST" && req.url === "/settle") {
|
|
51
|
+
const body = await readJson(req);
|
|
52
|
+
const result = await config.facilitator.settle(body.paymentPayload, body.paymentRequirements);
|
|
53
|
+
sendJson(res, 200, result);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
sendJson(res, 404, { error: "not_found" });
|
|
57
|
+
} catch (error) {
|
|
58
|
+
const message = error instanceof Error ? error.message : "internal_error";
|
|
59
|
+
sendJson(res, 500, { error: message });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
return server;
|
|
63
|
+
}
|
|
64
|
+
function startFacilitatorService(config) {
|
|
65
|
+
const port = config.port ?? 8040;
|
|
66
|
+
const server = createFacilitatorService(config);
|
|
67
|
+
server.listen(port);
|
|
68
|
+
return { port };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/scheme/scheme.ts
|
|
72
|
+
import { decodeEventLog, getAddress, isAddressEqual } from "viem";
|
|
73
|
+
import {
|
|
74
|
+
confidentialPaymentTypes,
|
|
75
|
+
confidentialTokenAbi,
|
|
76
|
+
hashEncryptedAmountInput
|
|
77
|
+
} from "x402z-shared";
|
|
78
|
+
var batcherAbi = [
|
|
79
|
+
{
|
|
80
|
+
inputs: [
|
|
81
|
+
{ internalType: "address", name: "token", type: "address" },
|
|
82
|
+
{
|
|
83
|
+
components: [
|
|
84
|
+
{
|
|
85
|
+
components: [
|
|
86
|
+
{ internalType: "address", name: "holder", type: "address" },
|
|
87
|
+
{ internalType: "address", name: "payee", type: "address" },
|
|
88
|
+
{ internalType: "uint256", name: "maxClearAmount", type: "uint256" },
|
|
89
|
+
{ internalType: "bytes32", name: "resourceHash", type: "bytes32" },
|
|
90
|
+
{ internalType: "uint48", name: "validAfter", type: "uint48" },
|
|
91
|
+
{ internalType: "uint48", name: "validBefore", type: "uint48" },
|
|
92
|
+
{ internalType: "bytes32", name: "nonce", type: "bytes32" },
|
|
93
|
+
{ internalType: "bytes32", name: "encryptedAmountHash", type: "bytes32" }
|
|
94
|
+
],
|
|
95
|
+
internalType: "struct IFHEToken.ConfidentialPayment",
|
|
96
|
+
name: "p",
|
|
97
|
+
type: "tuple"
|
|
98
|
+
},
|
|
99
|
+
{ internalType: "externalEuint64", name: "encryptedAmountInput", type: "bytes32" },
|
|
100
|
+
{ internalType: "bytes", name: "inputProof", type: "bytes" },
|
|
101
|
+
{ internalType: "bytes", name: "sig", type: "bytes" }
|
|
102
|
+
],
|
|
103
|
+
internalType: "struct FHETokenBatcher.Request[]",
|
|
104
|
+
name: "requests",
|
|
105
|
+
type: "tuple[]"
|
|
106
|
+
}
|
|
107
|
+
],
|
|
108
|
+
name: "batchConfidentialTransferWithAuthorization",
|
|
109
|
+
outputs: [
|
|
110
|
+
{ internalType: "bool[]", name: "successes", type: "bool[]" },
|
|
111
|
+
{ internalType: "bytes32[]", name: "transferredHandles", type: "bytes32[]" }
|
|
112
|
+
],
|
|
113
|
+
stateMutability: "nonpayable",
|
|
114
|
+
type: "function"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
anonymous: false,
|
|
118
|
+
inputs: [
|
|
119
|
+
{ indexed: true, internalType: "uint256", name: "index", type: "uint256" },
|
|
120
|
+
{ indexed: false, internalType: "bytes32", name: "transferredHandle", type: "bytes32" }
|
|
121
|
+
],
|
|
122
|
+
name: "BatchItemSuccess",
|
|
123
|
+
type: "event"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
anonymous: false,
|
|
127
|
+
inputs: [
|
|
128
|
+
{ indexed: true, internalType: "uint256", name: "index", type: "uint256" },
|
|
129
|
+
{ indexed: false, internalType: "bytes", name: "reason", type: "bytes" }
|
|
130
|
+
],
|
|
131
|
+
name: "BatchItemFailure",
|
|
132
|
+
type: "event"
|
|
133
|
+
}
|
|
134
|
+
];
|
|
135
|
+
var X402zEvmFacilitator = class {
|
|
136
|
+
constructor(config) {
|
|
137
|
+
this.config = config;
|
|
138
|
+
this.scheme = "erc7984-mind-v1";
|
|
139
|
+
this.caipFamily = "eip155:*";
|
|
140
|
+
this.queue = [];
|
|
141
|
+
this.hashFn = config.hashEncryptedAmountInput ?? hashEncryptedAmountInput;
|
|
142
|
+
this.clock = config.clock ?? (() => Math.floor(Date.now() / 1e3));
|
|
143
|
+
this.checkUsedNonces = config.checkUsedNonces ?? true;
|
|
144
|
+
this.waitForReceipt = config.waitForReceipt ?? true;
|
|
145
|
+
this.batchIntervalMs = Math.max(0, config.batchIntervalMs ?? 15e3);
|
|
146
|
+
this.receiptOptions = config.receipt;
|
|
147
|
+
this.batcherAddress = getAddress(config.batcherAddress);
|
|
148
|
+
}
|
|
149
|
+
getExtra(_) {
|
|
150
|
+
return void 0;
|
|
151
|
+
}
|
|
152
|
+
getSigners(_) {
|
|
153
|
+
return [...this.config.signer.getAddresses()];
|
|
154
|
+
}
|
|
155
|
+
async verify(payload, requirements) {
|
|
156
|
+
const confidentialPayload = payload.payload;
|
|
157
|
+
if (payload.accepted.scheme !== this.scheme || requirements.scheme !== this.scheme) {
|
|
158
|
+
return {
|
|
159
|
+
isValid: false,
|
|
160
|
+
invalidReason: "unsupported_scheme",
|
|
161
|
+
payer: confidentialPayload?.authorization?.holder
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (payload.accepted.network !== requirements.network) {
|
|
165
|
+
return {
|
|
166
|
+
isValid: false,
|
|
167
|
+
invalidReason: "network_mismatch",
|
|
168
|
+
payer: confidentialPayload.authorization.holder
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
const extra = requirements.extra;
|
|
172
|
+
const eip712 = extra?.eip712;
|
|
173
|
+
if (!eip712?.name || !eip712?.version) {
|
|
174
|
+
return {
|
|
175
|
+
isValid: false,
|
|
176
|
+
invalidReason: "missing_eip712_domain",
|
|
177
|
+
payer: confidentialPayload.authorization.holder
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const now = this.clock();
|
|
181
|
+
const validAfter = Number(confidentialPayload.authorization.validAfter);
|
|
182
|
+
const validBefore = Number(confidentialPayload.authorization.validBefore);
|
|
183
|
+
if (Number.isNaN(validAfter) || Number.isNaN(validBefore)) {
|
|
184
|
+
return {
|
|
185
|
+
isValid: false,
|
|
186
|
+
invalidReason: "invalid_validity_window",
|
|
187
|
+
payer: confidentialPayload.authorization.holder
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if (now < validAfter || now > validBefore) {
|
|
191
|
+
return {
|
|
192
|
+
isValid: false,
|
|
193
|
+
invalidReason: "authorization_expired",
|
|
194
|
+
payer: confidentialPayload.authorization.holder
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (!isAddressEqual(getAddress(confidentialPayload.authorization.payee), getAddress(requirements.payTo))) {
|
|
198
|
+
return {
|
|
199
|
+
isValid: false,
|
|
200
|
+
invalidReason: "recipient_mismatch",
|
|
201
|
+
payer: confidentialPayload.authorization.holder
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
const computedHash = this.hashFn(confidentialPayload.encryptedAmountInput);
|
|
205
|
+
if (computedHash !== confidentialPayload.authorization.encryptedAmountHash) {
|
|
206
|
+
return {
|
|
207
|
+
isValid: false,
|
|
208
|
+
invalidReason: "encrypted_amount_mismatch",
|
|
209
|
+
payer: confidentialPayload.authorization.holder
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const chainId = parseInt(requirements.network.split(":")[1]);
|
|
213
|
+
const isValidSignature = await this.config.signer.verifyTypedData({
|
|
214
|
+
address: confidentialPayload.authorization.holder,
|
|
215
|
+
domain: {
|
|
216
|
+
name: eip712.name,
|
|
217
|
+
version: eip712.version,
|
|
218
|
+
chainId,
|
|
219
|
+
verifyingContract: getAddress(requirements.asset)
|
|
220
|
+
},
|
|
221
|
+
types: confidentialPaymentTypes,
|
|
222
|
+
primaryType: "ConfidentialPayment",
|
|
223
|
+
message: {
|
|
224
|
+
holder: getAddress(confidentialPayload.authorization.holder),
|
|
225
|
+
payee: getAddress(confidentialPayload.authorization.payee),
|
|
226
|
+
maxClearAmount: BigInt(confidentialPayload.authorization.maxClearAmount),
|
|
227
|
+
resourceHash: confidentialPayload.authorization.resourceHash,
|
|
228
|
+
validAfter: BigInt(confidentialPayload.authorization.validAfter),
|
|
229
|
+
validBefore: BigInt(confidentialPayload.authorization.validBefore),
|
|
230
|
+
nonce: confidentialPayload.authorization.nonce,
|
|
231
|
+
encryptedAmountHash: confidentialPayload.authorization.encryptedAmountHash
|
|
232
|
+
},
|
|
233
|
+
signature: confidentialPayload.signature
|
|
234
|
+
});
|
|
235
|
+
if (!isValidSignature) {
|
|
236
|
+
return {
|
|
237
|
+
isValid: false,
|
|
238
|
+
invalidReason: "invalid_signature",
|
|
239
|
+
payer: confidentialPayload.authorization.holder
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
if (this.checkUsedNonces) {
|
|
243
|
+
const used = await this.config.signer.readContract({
|
|
244
|
+
address: getAddress(requirements.asset),
|
|
245
|
+
abi: confidentialTokenAbi,
|
|
246
|
+
functionName: "usedNonces",
|
|
247
|
+
args: [confidentialPayload.authorization.holder, confidentialPayload.authorization.nonce]
|
|
248
|
+
});
|
|
249
|
+
if (used) {
|
|
250
|
+
return {
|
|
251
|
+
isValid: false,
|
|
252
|
+
invalidReason: "nonce_already_used",
|
|
253
|
+
payer: confidentialPayload.authorization.holder
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
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/service/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 createFacilitatorFromEnv() {
|
|
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 X402zEvmFacilitator({
|
|
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 startFacilitator() {
|
|
559
|
+
const facilitator = createFacilitatorFromEnv();
|
|
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 } = startFacilitator();
|
|
567
|
+
console.log(`Confidential facilitator listening on :${port}`);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
export {
|
|
571
|
+
X402zEvmFacilitator,
|
|
572
|
+
createFacilitatorService,
|
|
573
|
+
startFacilitatorService,
|
|
574
|
+
createFacilitatorFromEnv,
|
|
575
|
+
startFacilitator
|
|
576
|
+
};
|
package/dist/index.d.mts
CHANGED
|
@@ -3,9 +3,9 @@ import { FacilitatorEvmSigner } from '@x402/evm';
|
|
|
3
3
|
import { x402Facilitator } from '@x402/core/facilitator';
|
|
4
4
|
import * as http from 'http';
|
|
5
5
|
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
6
|
-
export {
|
|
6
|
+
export { startFacilitator } from './service/bootstrap.mjs';
|
|
7
7
|
|
|
8
|
-
type
|
|
8
|
+
type X402zFacilitatorSchemeOptions = {
|
|
9
9
|
signer: FacilitatorEvmSigner;
|
|
10
10
|
batcherAddress: `0x${string}`;
|
|
11
11
|
hashEncryptedAmountInput?: (encryptedAmountInput: `0x${string}`) => `0x${string}`;
|
|
@@ -19,7 +19,7 @@ type ConfidentialFacilitatorConfig = {
|
|
|
19
19
|
pollingIntervalMs?: number;
|
|
20
20
|
};
|
|
21
21
|
};
|
|
22
|
-
declare class
|
|
22
|
+
declare class X402zEvmFacilitator implements SchemeNetworkFacilitator {
|
|
23
23
|
private readonly config;
|
|
24
24
|
readonly scheme = "erc7984-mind-v1";
|
|
25
25
|
readonly caipFamily = "eip155:*";
|
|
@@ -32,7 +32,7 @@ declare class ConfidentialEvmFacilitator implements SchemeNetworkFacilitator {
|
|
|
32
32
|
private readonly receiptOptions?;
|
|
33
33
|
private flushTimer?;
|
|
34
34
|
private queue;
|
|
35
|
-
constructor(config:
|
|
35
|
+
constructor(config: X402zFacilitatorSchemeOptions);
|
|
36
36
|
getExtra(_: string): Record<string, unknown> | undefined;
|
|
37
37
|
getSigners(_: string): string[];
|
|
38
38
|
verify(payload: PaymentPayload, requirements: PaymentRequirements): Promise<VerifyResponse>;
|
|
@@ -42,18 +42,18 @@ declare class ConfidentialEvmFacilitator implements SchemeNetworkFacilitator {
|
|
|
42
42
|
private flushBatch;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
type
|
|
45
|
+
type X402zFacilitatorRegistrationOptions = X402zFacilitatorSchemeOptions & {
|
|
46
46
|
networks?: Network[];
|
|
47
47
|
};
|
|
48
|
-
declare function
|
|
48
|
+
declare function registerX402zEvmFacilitatorScheme(facilitator: x402Facilitator, config: X402zFacilitatorRegistrationOptions): x402Facilitator;
|
|
49
49
|
|
|
50
|
-
type
|
|
50
|
+
type FacilitatorServiceOptions = {
|
|
51
51
|
facilitator: x402Facilitator;
|
|
52
52
|
port?: number;
|
|
53
53
|
};
|
|
54
|
-
declare function createFacilitatorService(config:
|
|
55
|
-
declare function startFacilitatorService(config:
|
|
54
|
+
declare function createFacilitatorService(config: FacilitatorServiceOptions): http.Server<typeof IncomingMessage, typeof ServerResponse>;
|
|
55
|
+
declare function startFacilitatorService(config: FacilitatorServiceOptions): {
|
|
56
56
|
port: number;
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
export {
|
|
59
|
+
export { type FacilitatorServiceOptions, X402zEvmFacilitator, type X402zFacilitatorRegistrationOptions, type X402zFacilitatorSchemeOptions, createFacilitatorService, registerX402zEvmFacilitatorScheme, startFacilitatorService };
|
package/dist/index.d.ts
CHANGED
|
@@ -3,9 +3,9 @@ import { FacilitatorEvmSigner } from '@x402/evm';
|
|
|
3
3
|
import { x402Facilitator } from '@x402/core/facilitator';
|
|
4
4
|
import * as http from 'http';
|
|
5
5
|
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
6
|
-
export {
|
|
6
|
+
export { startFacilitator } from './service/bootstrap.js';
|
|
7
7
|
|
|
8
|
-
type
|
|
8
|
+
type X402zFacilitatorSchemeOptions = {
|
|
9
9
|
signer: FacilitatorEvmSigner;
|
|
10
10
|
batcherAddress: `0x${string}`;
|
|
11
11
|
hashEncryptedAmountInput?: (encryptedAmountInput: `0x${string}`) => `0x${string}`;
|
|
@@ -19,7 +19,7 @@ type ConfidentialFacilitatorConfig = {
|
|
|
19
19
|
pollingIntervalMs?: number;
|
|
20
20
|
};
|
|
21
21
|
};
|
|
22
|
-
declare class
|
|
22
|
+
declare class X402zEvmFacilitator implements SchemeNetworkFacilitator {
|
|
23
23
|
private readonly config;
|
|
24
24
|
readonly scheme = "erc7984-mind-v1";
|
|
25
25
|
readonly caipFamily = "eip155:*";
|
|
@@ -32,7 +32,7 @@ declare class ConfidentialEvmFacilitator implements SchemeNetworkFacilitator {
|
|
|
32
32
|
private readonly receiptOptions?;
|
|
33
33
|
private flushTimer?;
|
|
34
34
|
private queue;
|
|
35
|
-
constructor(config:
|
|
35
|
+
constructor(config: X402zFacilitatorSchemeOptions);
|
|
36
36
|
getExtra(_: string): Record<string, unknown> | undefined;
|
|
37
37
|
getSigners(_: string): string[];
|
|
38
38
|
verify(payload: PaymentPayload, requirements: PaymentRequirements): Promise<VerifyResponse>;
|
|
@@ -42,18 +42,18 @@ declare class ConfidentialEvmFacilitator implements SchemeNetworkFacilitator {
|
|
|
42
42
|
private flushBatch;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
type
|
|
45
|
+
type X402zFacilitatorRegistrationOptions = X402zFacilitatorSchemeOptions & {
|
|
46
46
|
networks?: Network[];
|
|
47
47
|
};
|
|
48
|
-
declare function
|
|
48
|
+
declare function registerX402zEvmFacilitatorScheme(facilitator: x402Facilitator, config: X402zFacilitatorRegistrationOptions): x402Facilitator;
|
|
49
49
|
|
|
50
|
-
type
|
|
50
|
+
type FacilitatorServiceOptions = {
|
|
51
51
|
facilitator: x402Facilitator;
|
|
52
52
|
port?: number;
|
|
53
53
|
};
|
|
54
|
-
declare function createFacilitatorService(config:
|
|
55
|
-
declare function startFacilitatorService(config:
|
|
54
|
+
declare function createFacilitatorService(config: FacilitatorServiceOptions): http.Server<typeof IncomingMessage, typeof ServerResponse>;
|
|
55
|
+
declare function startFacilitatorService(config: FacilitatorServiceOptions): {
|
|
56
56
|
port: number;
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
export {
|
|
59
|
+
export { type FacilitatorServiceOptions, X402zEvmFacilitator, type X402zFacilitatorRegistrationOptions, type X402zFacilitatorSchemeOptions, createFacilitatorService, registerX402zEvmFacilitatorScheme, startFacilitatorService };
|